132 lines
3.0 KiB
Go
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
|
|
}
|