Files

128 lines
3.7 KiB
Go

package presentation
import (
"fmt"
"strings"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/sound"
)
// DocumentLineText mirrors the web script's content presentation: join the
// displayed text for each same-line object and speak common role/state details.
func DocumentLineText(line atspi.DocumentLine) string {
return DocumentLineTextWithOptions(line, DefaultOptions())
}
func DocumentLineTextWithOptions(line atspi.DocumentLine, options Options) string {
return DocumentLineSpeechWithOptions(line, options).Text
}
func DocumentLineSpeechWithOptions(line atspi.DocumentLine, options Options) Speech {
reading := reading_options(options)
var parts []string
seen := make(map[atspi.ObjectID]bool)
type rowKey struct {
bus, path string
row int
}
rows := make(map[rowKey]bool)
for _, content := range line.Contents {
text := strings.TrimSpace(content.Text)
if content.Snapshot.Role == "table cell" && content.Snapshot.TableRowText != "" && FullRowEnabled(content.Snapshot, options) {
key := rowKey{content.Snapshot.BusName, content.Snapshot.TablePath, content.Snapshot.TableRowIndex}
if key.path != "" {
if rows[key] {
continue
}
rows[key] = true
}
text = content.Snapshot.TableRowText
}
if text == "" {
text = strings.TrimSpace(content.Snapshot.Name)
}
appendPart(&parts, text)
if reading.OnlySpeakDisplayedText {
continue
}
id := content.Snapshot.ID()
if seen[id] {
continue
}
_, roleCue := roleSoundCue(content.Snapshot, options)
_, stateCue := stateSoundCue(content.Snapshot, options)
role := lineRole(content.Snapshot)
if options.SpeechVerbosityLevel == 0 && (role == "icon" || role == "canvas") {
role = ""
}
if role != "" && !((roleCue || stateCue) && options.RoleSoundPresentation == "sound_only") {
appendPart(&parts, role)
}
states := controlStates(content.Snapshot)
if stateCue && options.RoleSoundPresentation == "sound_only" {
states = removeReplacedState(states)
}
for _, state := range states {
appendPart(&parts, state)
}
appendPart(&parts, controlValue(content.Snapshot))
if reading.SpeakMisspelledIndicator && content.Snapshot.Misspelled {
appendPart(&parts, "misspelled")
}
seen[id] = true
}
if len(parts) == 0 {
if !options.SpeakBlankLines {
return Speech{}
}
return Speech{Text: "blank"}
}
return SpeechWithOptions(parts, options)
}
// A text object can contribute several ranges to a visual line. Present its
// role and state sounds once, in document order.
func DocumentLineSoundCues(line atspi.DocumentLine, options Options) []sound.Cue {
var cues []sound.Cue
seen := make(map[atspi.ObjectID]bool)
for _, content := range line.Contents {
id := content.Snapshot.ID()
if !seen[id] {
cues = append(cues, SoundCues(content.Snapshot, options)...)
seen[id] = true
}
}
return cues
}
func appendPart(parts *[]string, part string) {
if part == "" {
return
}
*parts = append(*parts, part)
}
// Keep this deliberately small: line reading presents displayed text plus
// the role/state details Python's web generator keeps for common content.
func lineRole(snapshot atspi.Snapshot) string {
switch snapshot.Role {
case "heading":
if snapshot.HeadingLevel > 0 {
return fmt.Sprintf("heading level %d", snapshot.HeadingLevel)
}
return "heading"
case "link":
if snapshot.Visited {
return "visited link"
}
return "link"
case "footer", "form", "label", "menu item", "paragraph", "section",
"redundant object", "unknown", "table cell", "text", "static",
"content suggestion", "list item", "list", "document frame",
"document web", "alert", "row header", "toggle button":
return ""
}
return snapshot.Role
}