76 lines
3.1 KiB
Go
76 lines
3.1 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package review
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
)
|
|
|
|
// readDocumentBraille reads the accepted line and selection ranges for its
|
|
// inline fragments. Optional display reads never fail or repeat navigation.
|
|
func (worker *Worker) readDocumentBraille(ctx context.Context, result Result) Result {
|
|
if !worker.documentBraille.Load() || !result.Moved || result.Err != nil || result.CaretErr != nil ||
|
|
!worker.CurrentDocumentResult(result) || worker.focus() != result.DocumentFocus ||
|
|
result.DocumentCaretCommand >= TableLeft || result.DocumentPosition.Snapshot.Role == "password text" {
|
|
return result
|
|
}
|
|
// Explicitly named links are atomic browse objects. Their native text may
|
|
// contain only a numeric badge; braille must use the same accessible label
|
|
// as speech rather than enriching it with that incomplete child text.
|
|
if snapshot := result.DocumentPosition.Snapshot; snapshot.Role == "link" && wholeCaretNode(snapshot) {
|
|
return result
|
|
}
|
|
reader, ok := worker.nativeCapturerFor(result.DocumentFocus).(DocumentCaretReader)
|
|
if !ok || !result.DocumentPosition.Object.Valid() {
|
|
return result
|
|
}
|
|
// Display enrichment must not hold ordinary speech/navigation for the
|
|
// longer whole-document capture timeout.
|
|
deadline, cancel := context.WithTimeout(ctx, min(worker.captureTimeout, 100*time.Millisecond))
|
|
defer cancel()
|
|
line, err := reader.ReadDocumentCaret(deadline, result.DocumentPosition.Object, result.DocumentPosition.Offset, atspi.DocumentCaretBrailleLine)
|
|
if err != nil {
|
|
result.DocumentBrailleErr = err
|
|
return result
|
|
}
|
|
if line.Start < 0 || result.DocumentPosition.Offset < line.Start ||
|
|
result.DocumentPosition.Offset-line.Start > utf8.RuneCountInString(line.Text) || line.Text == "" {
|
|
result.DocumentBrailleErr = atspi.ErrNoDocumentCaret
|
|
return result
|
|
}
|
|
result.DocumentBrailleLine = &line
|
|
if assembled := result.DocumentCaretLine; assembled != nil {
|
|
copyLine := *assembled
|
|
copyLine.Contents = append([]atspi.DocumentContent(nil), assembled.Contents...)
|
|
for index := range copyLine.Contents {
|
|
content := ©Line.Contents[index]
|
|
content.Snapshot.SelectionStart, content.Snapshot.SelectionEnd = 0, 0
|
|
selected := line
|
|
if content.Snapshot.ID() != result.DocumentPosition.Object.ID() {
|
|
if !content.Reference.Valid() {
|
|
continue
|
|
}
|
|
selected, err = reader.ReadDocumentCaret(deadline, content.Reference, content.Start, atspi.DocumentCaretBrailleLine)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
start, end := content.Start-selected.Start, content.Start-selected.Start+utf8.RuneCountInString(content.RawText)
|
|
runes := []rune(selected.Text)
|
|
if start < 0 || end > len(runes) || string(runes[start:end]) != content.RawText {
|
|
continue
|
|
}
|
|
content.Snapshot.SelectionStart, content.Snapshot.SelectionEnd = selected.SelectionStart, selected.SelectionEnd
|
|
content.Snapshot.TextAttributeRuns = selected.TextAttributeRuns
|
|
}
|
|
result.DocumentCaretLine = ©Line
|
|
}
|
|
return result
|
|
}
|