// Fenrir console screen reader. // Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . // Package presentation names screen text and characters for reader output. package presentation import ( "strconv" "strings" "unicode" "git.stormux.org/storm/fenrir/internal/i18n" "git.stormux.org/storm/fenrir/internal/screen" ) // Line describes a visible row, including blank rows. func Line(s screen.Snapshot, row int) string { text := s.Text(row) if strings.TrimSpace(text) == "" { return i18n.T("blank") } return text } // Line_message describes a row with Python's blank-line sound alternative. func Line_message(s screen.Snapshot, row int) Message { message := Message{Text: Line(s, row)} if strings.TrimSpace(s.Text(row)) == "" { message.Cue = Blank } return message } func Character_message(char rune) Message { return Message{Text: Character(char), Character: &char} } // Character describes punctuation and whitespace. Capital cues are applied by // the shared speech presenter using the original character. func Character(char rune) string { names := map[rune]string{ ' ': "space", '\t': "tab", '.': "dot", ',': "comma", ':': "colon", ';': "semicolon", '!': "bang", '?': "question?", '-': "dash", '_': "line", '/': "slash", '\\': "backslash", '(': "left paren", ')': "right paren", '[': "left bracket", ']': "right bracket", '{': "left brace", '}': "right brace", '<': "less", '>': "greater", '\'': "apostrophe", '"': "quote", '@': "at", '#': "hash", '$': "dollar", '%': "percent", '^': "carrot", '&': "and", '*': "star", '+': "plus", '=': "equals", '|': "bar", '~': "tilde", '`': "grave", } if name, ok := names[char]; ok { return i18n.T(name) } if unicode.IsControl(char) { return i18n.Format("character {0}", strconv.Itoa(int(char))) } return string(char) }