105 lines
3.0 KiB
Go
105 lines
3.0 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package punctuation
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/i18n"
|
|
)
|
|
|
|
type TextOptions struct {
|
|
Emoticons bool
|
|
RepeatedSymbols bool
|
|
RepeatedSymbolsMin int
|
|
}
|
|
|
|
// Process_text substitutes complete tokens before ordinary punctuation. Each
|
|
// generated name is emitted once, without processing punctuation inside it.
|
|
// Whitespace belongs to neither neighboring token, so adjacent emoticons work.
|
|
func (p *Processor) Process_text(text, level string, options TextOptions) string {
|
|
if !options.Emoticons && !options.RepeatedSymbols {
|
|
return p.Text(text, level)
|
|
}
|
|
var result strings.Builder
|
|
tokenStart := 0
|
|
for index, char := range text {
|
|
if !unicode.IsSpace(char) {
|
|
continue
|
|
}
|
|
result.WriteString(p.process_token(text[tokenStart:index], level, options))
|
|
result.WriteString(p.Text(string(char), level))
|
|
tokenStart = index + utf8.RuneLen(char)
|
|
}
|
|
result.WriteString(p.process_token(text[tokenStart:], level, options))
|
|
return result.String()
|
|
}
|
|
|
|
func (p *Processor) process_token(token, level string, options TextOptions) string {
|
|
if options.Emoticons {
|
|
if name, exists := emoticonNames[token]; exists {
|
|
return i18n.T(name)
|
|
}
|
|
}
|
|
if options.RepeatedSymbols && options.RepeatedSymbolsMin >= 2 && token != "" {
|
|
repeated := token
|
|
prefix, suffix := "", ""
|
|
// Progress bars commonly enclose repeated runs in brackets. Keep
|
|
// those delimiters under ordinary punctuation handling. Symbol-only
|
|
// tokens, including diffstats such as +++++-----, summarize each run.
|
|
if len(token) >= 2 {
|
|
for _, pair := range []string{"[]", "()", "{}", "<>"} {
|
|
if token[0] == pair[0] && token[len(token)-1] == pair[1] {
|
|
prefix, suffix = token[:1], token[len(token)-1:]
|
|
repeated = token[1 : len(token)-1]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
chars := []rune(repeated)
|
|
for _, char := range chars {
|
|
if !(unicode.IsPunct(char) || unicode.IsSymbol(char)) {
|
|
return p.Text(token, level)
|
|
}
|
|
}
|
|
var result strings.Builder
|
|
summarized := false
|
|
for start := 0; start < len(chars); {
|
|
char := chars[start]
|
|
end := start + 1
|
|
for end < len(chars) && chars[end] == char {
|
|
end++
|
|
}
|
|
count := end - start
|
|
if start > 0 {
|
|
result.WriteByte(' ')
|
|
}
|
|
if p.levels[level][char] && count >= options.RepeatedSymbolsMin {
|
|
name := p.Character(char)
|
|
// G13's requested English summary is "5 number". Keep ordinary
|
|
// hash feedback, custom names and localized profile names intact.
|
|
if char == '#' && p.symbols[char] == "hash" {
|
|
name = "number"
|
|
if p.translate {
|
|
name = i18n.T(name)
|
|
}
|
|
}
|
|
result.WriteString(i18n.Format("{0} {1}", strconv.Itoa(count), name))
|
|
summarized = true
|
|
} else {
|
|
result.WriteString(p.Text(string(chars[start:end]), level))
|
|
}
|
|
start = end
|
|
}
|
|
if summarized {
|
|
return p.Text(prefix, level) + result.String() + p.Text(suffix, level)
|
|
}
|
|
}
|
|
return p.Text(token, level)
|
|
}
|