Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package atspi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
||||
)
|
||||
|
||||
var ErrOCRGeometry = errors.New("OCR source bounds unavailable")
|
||||
|
||||
// OCRGeometry is an owned snapshot. ProviderBounds/WindowBounds use AT-SPI
|
||||
// coordinates; ScreenBounds uses physical X11 root pixels. It authorizes
|
||||
// capture only, never activation. Foreground must still match at capture time.
|
||||
type OCRGeometry struct {
|
||||
Object ObjectID
|
||||
ProviderBounds, WindowBounds Rectangle
|
||||
ScreenBounds image.Rectangle
|
||||
Foreground ForegroundState
|
||||
}
|
||||
|
||||
// Match the source's own fresh top-level window, allowing the same proportional
|
||||
// scaling supported by the capture contract. Origins must match either the
|
||||
// physical or uniformly scaled root. Unknown origins are rejected. Use measured
|
||||
// per-axis ratios, not DPI.
|
||||
func ocr_screen_bounds(bounds, window Rectangle, client xorg.Foreground) (image.Rectangle, error) {
|
||||
if !bounds.Valid() || !window.Valid() || !client.Known || client.Window == 0 {
|
||||
return image.Rectangle{}, ErrOCRGeometry
|
||||
}
|
||||
inner := Rectangle{int32(client.X), int32(client.Y), int32(client.Width), int32(client.Height)}
|
||||
outer := Rectangle{int32(client.OuterX), int32(client.OuterY), int32(client.OuterWidth), int32(client.OuterHeight)}
|
||||
matched := Rectangle{}
|
||||
// Exact matches take precedence over a proportional decoration match.
|
||||
for _, candidate := range []Rectangle{inner, outer} {
|
||||
if window == candidate {
|
||||
matched = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched.Valid() {
|
||||
for _, candidate := range []Rectangle{inner, outer} {
|
||||
if ocr_window_matches(window, candidate) {
|
||||
if matched.Valid() && matched != candidate {
|
||||
return image.Rectangle{}, ErrOCRGeometry
|
||||
}
|
||||
matched = candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matched.Valid() {
|
||||
return image.Rectangle{}, ErrOCRGeometry
|
||||
}
|
||||
clipped, ok := bounds.Intersection(window)
|
||||
if !ok {
|
||||
return image.Rectangle{}, ErrOCRGeometry
|
||||
}
|
||||
|
||||
// Use exact integer edge ratios. Provider windows can exceed the OCR image
|
||||
// pixel cap while the chosen object/zone remains a small valid capture.
|
||||
map_edge := func(value int64, origin, extent, screenOrigin, screenExtent int32, upper bool) int {
|
||||
numerator := (value - int64(origin)) * int64(screenExtent)
|
||||
if upper {
|
||||
numerator += int64(extent) - 1
|
||||
}
|
||||
return int(int64(screenOrigin) + numerator/int64(extent))
|
||||
}
|
||||
mapped := image.Rect(map_edge(int64(clipped.X), window.X, window.Width, matched.X, matched.Width, false),
|
||||
map_edge(int64(clipped.Y), window.Y, window.Height, matched.Y, matched.Height, false),
|
||||
map_edge(int64(clipped.X)+int64(clipped.Width), window.X, window.Width, matched.X, matched.Width, true),
|
||||
map_edge(int64(clipped.Y)+int64(clipped.Height), window.Y, window.Height, matched.Y, matched.Height, true))
|
||||
if mapped.Empty() {
|
||||
return image.Rectangle{}, ErrOCRGeometry
|
||||
}
|
||||
|
||||
return mapped, nil
|
||||
}
|
||||
|
||||
// GTK can scale the root origin as well as dimensions; Chromium may retain
|
||||
// a physical root origin. Admit these two measured contracts only. Other
|
||||
// monitor-dependent origin conventions need separate evidence.
|
||||
func ocr_window_matches(provider, native Rectangle) bool {
|
||||
if !provider.Valid() || !native.Valid() {
|
||||
return false
|
||||
}
|
||||
scaleX, scaleY := float64(native.Width)/float64(provider.Width), float64(native.Height)/float64(provider.Height)
|
||||
if scaleX < 0.5 || scaleX > 3 || scaleY < 0.5 || scaleY > 3 || math.Abs(scaleX-scaleY) > 0.01*math.Max(scaleX, scaleY) {
|
||||
return false
|
||||
}
|
||||
return provider.X == native.X && provider.Y == native.Y ||
|
||||
math.Abs(float64(provider.X)*scaleX-float64(native.X)) <= 1 &&
|
||||
math.Abs(float64(provider.Y)*scaleY-float64(native.Y)) <= 1
|
||||
}
|
||||
Reference in new issue
Block a user