Files

132 lines
3.0 KiB
Go

package atspi
import (
"context"
"errors"
"math"
)
var ErrNoDocumentCaret = errors.New("document caret line unavailable")
type DocumentContent struct {
Reference ObjectReference
Snapshot Snapshot
Start int
End int
Text string
RawText string
// Separator is display spacing before this object, outside its native range.
Separator string
Bounds Rectangle
}
type DocumentLine struct {
Context []Snapshot
Document ObjectReference
Contents []DocumentContent
}
func (tree *AccessibleTree) DocumentLineAtOffset(ctx context.Context, reference ObjectReference, offset int) (DocumentLine, error) {
document, err := tree.Document(ctx, reference)
if err != nil {
return DocumentLine{}, err
}
caretIndex := -1
if reference.Valid() {
for index, node := range document.Nodes {
if node.Reference == reference {
caretIndex = index
break
}
}
}
if caretIndex < 0 {
for index, node := range document.Nodes {
if node.Text.CaretKnown {
caretIndex = index
break
}
}
}
if caretIndex < 0 {
return DocumentLine{}, ErrNoDocumentCaret
}
caretNode := document.Nodes[caretIndex]
if offset < 0 {
offset = caretNode.Text.CaretOffset
}
if offset < 0 {
return DocumentLine{}, ErrNoDocumentCaret
}
caretRange, found := textRangeAtOffset(caretNode.Text, offset)
if !found || !caretRange.Bounds.Valid() {
return DocumentLine{}, ErrNoDocumentCaret
}
ancestors := document.Context.Ancestors
if len(ancestors) > 0 && ancestors[0].ID() == reference.ID() {
ancestors = ancestors[1:]
}
line := DocumentLine{Document: document.Context.TopLevel, Context: ancestors}
for _, node := range document.Nodes {
if node.Hidden {
continue
}
for _, textRange := range node.Text.Ranges {
if !boundsAreOnSameLine(caretRange.Bounds, textRange.Bounds) {
continue
}
if textRange.Text == "" {
continue
}
line.Contents = append(line.Contents, DocumentContent{
Reference: node.Reference,
Snapshot: node.Snapshot,
Start: textRange.Start,
End: textRange.End,
Text: textRange.Text,
RawText: textRange.Text,
Bounds: textRange.Bounds,
})
}
}
if len(line.Contents) == 0 {
return DocumentLine{}, ErrNoDocumentCaret
}
return line, nil
}
func textRangeAtOffset(text VisibleText, offset int) (TextRange, bool) {
for _, textRange := range text.Ranges {
if textRange.Start <= offset && offset < textRange.End {
return textRange, true
}
}
for _, textRange := range text.Ranges {
if textRange.Start <= offset && offset <= textRange.End {
return textRange, true
}
}
return TextRange{}, false
}
func boundsAreOnSameLine(first, second Rectangle) bool {
if first == second {
return true
}
if !first.Valid() || !second.Valid() {
return false
}
top := max(first.Y, second.Y)
bottom := min(first.Y+first.Height, second.Y+second.Height)
if bottom <= top {
return false
}
firstCenter := first.Y + first.Height/2
secondCenter := second.Y + second.Height/2
return math.Abs(float64(firstCenter-secondCenter)) <= 5
}