@@ -19,6 +19,11 @@ const (
1919 labelBufferSpace = 10
2020)
2121
22+ // widthCondition is used for calculating display width.
23+ // EastAsianWidth is set to false so that box drawing characters
24+ // are treated as narrow (width=1) while CJK characters remain wide (width=2).
25+ var widthCondition = & runewidth.Condition {EastAsianWidth : false }
26+
2227type diagramLayout struct {
2328 participantWidths []int
2429 participantCenters []int
@@ -35,7 +40,7 @@ func calculateLayout(sd *SequenceDiagram, config *diagram.Config) *diagramLayout
3540
3641 widths := make ([]int , len (sd .Participants ))
3742 for i , p := range sd .Participants {
38- w := runewidth .StringWidth (p .Label ) + boxPaddingLeftRight
43+ w := widthCondition .StringWidth (p .Label ) + boxPaddingLeftRight
3944 if w < minBoxWidth {
4045 w = minBoxWidth
4146 }
@@ -99,7 +104,7 @@ func Render(sd *SequenceDiagram, config *diagram.Config) (string, error) {
99104
100105 lines = append (lines , buildLine (sd .Participants , layout , func (i int ) string {
101106 w := layout .participantWidths [i ]
102- labelLen := runewidth .StringWidth (sd .Participants [i ].Label )
107+ labelLen := widthCondition .StringWidth (sd .Participants [i ].Label )
103108 pad := (w - labelLen ) / 2
104109 return string (chars .Vertical ) + strings .Repeat (" " , pad ) + sd .Participants [i ].Label +
105110 strings .Repeat (" " , w - pad - labelLen ) + string (chars .Vertical )
@@ -134,7 +139,7 @@ func buildLine(participants []*Participant, layout *diagramLayout, draw func(int
134139 boxWidth := layout .participantWidths [i ] + boxBorderWidth
135140 left := layout .participantCenters [i ] - boxWidth / 2
136141
137- needed := left - len ([] rune ( sb .String () ))
142+ needed := left - widthCondition . StringWidth ( sb .String ())
138143 if needed > 0 {
139144 sb .WriteString (strings .Repeat (" " , needed ))
140145 }
@@ -169,25 +174,42 @@ func renderMessage(msg *Message, layout *diagramLayout, chars BoxChars) []string
169174 start := min (from , to ) + labelLeftMargin
170175 labelLines := strings .Split (label , "\n " )
171176 for _ , labelLine := range labelLines {
172- labelWidth := runewidth .StringWidth (labelLine )
173- w := max (layout .totalWidth , start + labelWidth ) + labelBufferSpace
174- line := []rune (buildLifeline (layout , chars ))
175- if len (line ) < w {
176- padding := make ([]rune , w - len (line ))
177- for k := range padding {
178- padding [k ] = ' '
177+ labelWidth := widthCondition .StringWidth (labelLine )
178+ totalW := max (layout .totalWidth , start + labelWidth ) + labelBufferSpace
179+
180+ // Build the line using display width positioning
181+ var sb strings.Builder
182+ pos := 0 // current display position
183+ labelRunes := []rune (labelLine )
184+ labelIdx := 0
185+
186+ for pos < totalW {
187+ // Label takes priority over lifelines (overwrites them)
188+ if pos >= start && labelIdx < len (labelRunes ) {
189+ r := labelRunes [labelIdx ]
190+ sb .WriteRune (r )
191+ pos += widthCondition .RuneWidth (r )
192+ labelIdx ++
193+ } else {
194+ // Check if current position is a lifeline
195+ isLifeline := false
196+ for _ , c := range layout .participantCenters {
197+ if pos == c {
198+ isLifeline = true
199+ break
200+ }
201+ }
202+
203+ if isLifeline {
204+ sb .WriteRune (chars .Vertical )
205+ } else {
206+ sb .WriteRune (' ' )
207+ }
208+ pos ++
179209 }
180- line = append (line , padding ... )
181210 }
182211
183- col := start
184- for _ , r := range labelLine {
185- if col < len (line ) {
186- line [col ] = r
187- col ++
188- }
189- }
190- lines = append (lines , strings .TrimRight (string (line ), " " ))
212+ lines = append (lines , strings .TrimRight (sb .String (), " " ))
191213 }
192214 }
193215
@@ -243,24 +265,42 @@ func renderSelfMessage(msg *Message, layout *diagramLayout, chars BoxChars) []st
243265 start := center + labelLeftMargin
244266 labelLines := strings .Split (label , "\n " )
245267 for _ , labelLine := range labelLines {
246- line := ensureWidth (buildLifeline (layout , chars ))
247- labelWidth := runewidth .StringWidth (labelLine )
248- needed := start + labelWidth + labelBufferSpace
249- if len (line ) < needed {
250- pad := make ([]rune , needed - len (line ))
251- for i := range pad {
252- pad [i ] = ' '
268+ labelWidth := widthCondition .StringWidth (labelLine )
269+ totalW := start + labelWidth + labelBufferSpace
270+
271+ // Build the line using display width positioning
272+ var sb strings.Builder
273+ pos := 0 // current display position
274+ labelRunes := []rune (labelLine )
275+ labelIdx := 0
276+
277+ for pos < totalW {
278+ // Label takes priority over lifelines
279+ if pos >= start && labelIdx < len (labelRunes ) {
280+ r := labelRunes [labelIdx ]
281+ sb .WriteRune (r )
282+ pos += widthCondition .RuneWidth (r )
283+ labelIdx ++
284+ } else {
285+ // Check if current position is a lifeline
286+ isLifeline := false
287+ for _ , c := range layout .participantCenters {
288+ if pos == c {
289+ isLifeline = true
290+ break
291+ }
292+ }
293+
294+ if isLifeline {
295+ sb .WriteRune (chars .Vertical )
296+ } else {
297+ sb .WriteRune (' ' )
298+ }
299+ pos ++
253300 }
254- line = append (line , pad ... )
255301 }
256- col := start
257- for _ , c := range labelLine {
258- if col < len (line ) {
259- line [col ] = c
260- col ++
261- }
262- }
263- lines = append (lines , strings .TrimRight (string (line ), " " ))
302+
303+ lines = append (lines , strings .TrimRight (sb .String (), " " ))
264304 }
265305 }
266306
0 commit comments