Files
cthulhu/internal/review/document_braille.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 := &copyLine.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 = &copyLine
}
return result
}