summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-21 10:45:38 +0300
committerPaul Buetow <paul@buetow.org>2026-04-21 10:45:38 +0300
commit138695208e06a733d6b27673338fd3ca8de7fe62 (patch)
treee5f0cc88c1b9ec67bb11eb735926aba9d2f4d7cf /internal
parentc31e91ce42fc00079e0ee23f93e337846eea00ad (diff)
fix o7: keep comic section splitting UTF-8 safe
Diffstat (limited to 'internal')
-rw-r--r--internal/comic/artist.go5
-rw-r--r--internal/comic/comic_test.go29
-rw-r--r--internal/comic/helpers.go7
3 files changed, 36 insertions, 5 deletions
diff --git a/internal/comic/artist.go b/internal/comic/artist.go
index 248e8c4..aa1dd5f 100644
--- a/internal/comic/artist.go
+++ b/internal/comic/artist.go
@@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"time"
+ "unicode/utf8"
"codeberg.org/snonux/comicforge/internal/provider"
)
@@ -377,8 +378,8 @@ func buildPanelLayout(section string, pagePanels []string, panelCount int) strin
}
excerpt := strings.TrimSpace(section)
- if len(excerpt) > comicPromptMaxChars {
- excerpt = excerpt[:comicPromptMaxChars]
+ if utf8.RuneCountInString(excerpt) > comicPromptMaxChars {
+ excerpt = string([]rune(excerpt)[:comicPromptMaxChars])
if idx := strings.LastIndex(excerpt, " "); idx > 0 {
excerpt = excerpt[:idx]
}
diff --git a/internal/comic/comic_test.go b/internal/comic/comic_test.go
index 020fba5..0acccb0 100644
--- a/internal/comic/comic_test.go
+++ b/internal/comic/comic_test.go
@@ -8,6 +8,7 @@ import (
"strings"
"testing"
"time"
+ "unicode/utf8"
)
func TestSlugify(t *testing.T) {
@@ -55,6 +56,34 @@ func TestBuildPanelLayoutUsesFallbackExcerpt(t *testing.T) {
}
}
+func TestSplitIntoSectionsUsesRuneBoundaries(t *testing.T) {
+ t.Parallel()
+
+ text := strings.Repeat("Български текст за проверка на разделянето. ", 80)
+ sections := splitIntoSections(text, 4)
+ if len(sections) != 4 {
+ t.Fatalf("splitIntoSections() sections = %d, want 4", len(sections))
+ }
+ for i, section := range sections {
+ if !utf8.ValidString(section) {
+ t.Fatalf("splitIntoSections() section %d is not valid UTF-8: %q", i, section)
+ }
+ }
+}
+
+func TestBuildPanelLayoutTruncatesCyrillicOnRuneBoundaries(t *testing.T) {
+ t.Parallel()
+
+ text := strings.Repeat("абвгдежзийклмно", 70)
+ got := buildPanelLayout(text, nil, 2)
+ if !utf8.ValidString(got) {
+ t.Fatalf("buildPanelLayout() returned invalid UTF-8: %q", got)
+ }
+ if !strings.Contains(got, "…") {
+ t.Fatalf("buildPanelLayout() = %q, want truncation ellipsis", got)
+ }
+}
+
func TestParseGenerateResultUsesConfiguredDimensions(t *testing.T) {
t.Parallel()
diff --git a/internal/comic/helpers.go b/internal/comic/helpers.go
index 0458db5..6f5b5a8 100644
--- a/internal/comic/helpers.go
+++ b/internal/comic/helpers.go
@@ -64,15 +64,16 @@ func distributeParagraphs(paragraphs []string, n int) []string {
}
func splitByChars(text string, n int) []string {
- size := len(text) / n
+ runes := []rune(text)
+ size := len(runes) / n
sections := make([]string, n)
for i := range n {
start := i * size
end := start + size
if i == n-1 {
- end = len(text)
+ end = len(runes)
}
- sections[i] = strings.TrimSpace(text[start:end])
+ sections[i] = strings.TrimSpace(string(runes[start:end]))
}
return sections
}