Skip to content

Commit 876b5b4

Browse files
Merge pull request #67 from igor-chernikov/fix/cyrillic-rune-handling
2 parents fba8b40 + f3779f0 commit 876b5b4

5 files changed

Lines changed: 58 additions & 5 deletions

File tree

cmd/draw.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,21 @@ func (g *graph) drawEdge(e *edge) (*drawing, *drawing, *drawing, *drawing, *draw
4848
}
4949

5050
func (d *drawing) drawText(start drawingCoord, text string) {
51-
// Increase dimensions if necessary.
52-
d.increaseSize(start.x+len(text), start.y)
53-
log.Debug("Drawing '", text, "' from ", start, " to ", drawingCoord{x: start.x + len(text), y: start.y})
54-
for x := 0; x < len(text); x++ {
55-
(*d)[x+start.x][start.y] = string(text[x])
51+
// Increase dimensions if necessary. Use the visual width so multibyte
52+
// (e.g. Cyrillic, CJK) runes reserve the correct number of cells.
53+
textWidth := runewidth.StringWidth(text)
54+
d.increaseSize(start.x+textWidth, start.y)
55+
log.Debug("Drawing '", text, "' from ", start, " to ", drawingCoord{x: start.x + textWidth, y: start.y})
56+
// Iterate over runes (not bytes) so multibyte characters are placed in a
57+
// single cell instead of being split into invalid byte fragments.
58+
textX := start.x
59+
for _, r := range text {
60+
runeWidth := Max(runewidth.RuneWidth(r), 1)
61+
(*d)[textX][start.y] = string(r)
62+
for offset := 1; offset < runeWidth; offset++ {
63+
(*d)[textX+offset][start.y] = ""
64+
}
65+
textX += runeWidth
5666
}
5767
}
5868

cmd/graph_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ func TestExtendedChars(t *testing.T) {
6464
}
6565
}
6666

67+
// TestMultibyte verifies that multibyte UTF-8 labels (Cyrillic, Greek, accented
68+
// Latin, etc.) render correctly without splitting runes into invalid byte
69+
// fragments. Test cases are loaded from testdata/multibyte/*.txt.
70+
func TestMultibyte(t *testing.T) {
71+
dir := "testdata/multibyte"
72+
files, err := os.ReadDir(dir)
73+
if err != nil {
74+
t.Fatalf("Failed to read directory %s: %v", dir, err)
75+
}
76+
77+
for _, file := range files {
78+
if !file.IsDir() && strings.HasSuffix(file.Name(), ".txt") {
79+
t.Run(file.Name(), func(t *testing.T) {
80+
verifyMap(t, filepath.Join(dir, file.Name()), true)
81+
})
82+
}
83+
}
84+
}
85+
6786
// TestGraphUseAsciiConfig tests that RenderDiagram respects config.UseAscii for graphs
6887
func TestGraphUseAsciiConfig(t *testing.T) {
6988
mermaidInput := `graph LR
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
graph LR
2+
A[Café]-->|résumé|B[Über]
3+
---
4+
+------+ +------+
5+
| | | |
6+
| Café |résumé--->| Über |
7+
| | | |
8+
+------+ +------+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
graph LR
2+
A[Привет]-->|метка|B[Мир]
3+
---
4+
+--------+ +-----+
5+
| | | |
6+
| Привет |метка------>| Мир |
7+
| | | |
8+
+--------+ +-----+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
graph LR
2+
A[Γειά]-->|ετικέτα|B[Κόσμος]
3+
---
4+
+------+ +--------+
5+
| | | |
6+
| Γειά |ετικέτα-------->| Κόσμος |
7+
| | | |
8+
+------+ +--------+

0 commit comments

Comments
 (0)