// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "strings" "unicode" "git.stormux.org/storm/cthulhu/internal/config" ) // Apply pronunciation choices to the final text handed to the speech backend. // Python Cthulhu splits lower-to-upper case boundaries and numeric words. func readingSpeechText(text string, settings config.ReadingSettings) string { if settings.SpeakMultiCaseStringsAsWords { var result strings.Builder var previous rune for _, current := range text { if unicode.IsLower(previous) && unicode.IsUpper(current) { result.WriteByte(' ') } result.WriteRune(current) previous = current } text = result.String() } if !settings.SpeakNumbersAsDigits { return text } var result strings.Builder var word []rune flush := func() { if len(word) == 0 { return } allNumbers := true for _, value := range word { if !unicode.IsNumber(value) { allNumbers = false break } } for index, value := range word { if allNumbers && index != 0 { result.WriteByte(' ') } result.WriteRune(value) } word = word[:0] } for _, value := range text { if unicode.IsLetter(value) || unicode.IsNumber(value) || value == '_' { word = append(word, value) } else { flush() result.WriteRune(value) } } flush() return result.String() } // Replace complete words once, without feeding replacements back into the // dictionary. This keeps punctuation and spacing from the accessible text. func pronunciationSpeechText(text string, entries []config.PronunciationEntry) string { if len(entries) == 0 { return text } dictionary := make(map[string]string, len(entries)) for _, entry := range entries { dictionary[strings.ToLower(entry.Word)] = entry.Replacement } var output strings.Builder var word []rune flush := func() { if len(word) == 0 { return } original := string(word) if replacement, ok := dictionary[strings.ToLower(original)]; ok { output.WriteString(replacement) } else { output.WriteString(original) } word = word[:0] } for _, current := range text { if unicode.IsLetter(current) || unicode.IsNumber(current) || current == '_' { word = append(word, current) } else { flush() output.WriteRune(current) } } flush() return output.String() }