@@ -62,8 +62,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
62
62
}
63
63
}
64
64
65
- // TODO: preserve arrow head
66
- // canvas.ReScale(canvas.w, canvas.AutoHeight())
65
+ canvas .ReScale (canvas .w , canvas .AutoHeight ())
67
66
return canvas .TrimBytes (), nil
68
67
}
69
68
@@ -383,6 +382,10 @@ func (c *Canvas) AutoHeight() int {
383
382
return maxH
384
383
}
385
384
385
+ func isArrowHead (ch rune ) bool {
386
+ return ch == '>' || ch == '<' || ch == '^' || ch == 'v'
387
+ }
388
+
386
389
// ReScale reduces the size of ASCII art using a pixel-like sampling technique
387
390
func (c * Canvas ) ReScale (targetWidth , targetHeight int ) {
388
391
scaleX := float64 (targetWidth ) / float64 (c .w )
@@ -397,27 +400,67 @@ func (c *Canvas) ReScale(targetWidth, targetHeight int) {
397
400
}
398
401
}
399
402
400
- // First scale the borders and lines (source -> target mapping)
401
- for y := 0 ; y < c .h ; y ++ {
403
+ // First pass: scale borders and lines, but skip arrow heads
404
+ for y := range c .h {
402
405
targetY := int (float64 (y ) * scaleY )
403
406
if targetY >= targetHeight {
404
407
continue
405
408
}
406
409
407
- for x := 0 ; x < c .w ; x ++ {
410
+ for x := range c .w {
408
411
targetX := int (float64 (x ) * scaleX )
409
412
if targetX >= targetWidth {
410
413
continue
411
414
}
412
415
413
416
ch := c.grid [y ][x ]
414
- if ch == '+' || ch == '-' || ch == '|' || ch == '/' || ch == '\\' || ch == '.' {
417
+ if ! isArrowHead ( ch ) && ( ch == '+' || ch == '-' || ch == '|' || ch == '/' || ch == '\\' ) {
415
418
newGrid [targetY ][targetX ] = ch
416
419
}
417
420
}
418
421
}
419
422
420
- // Then redraw text at scaled positions
423
+ // Second pass: copy arrow heads with position adjustment
424
+ for y := range c .h {
425
+ targetY := int (float64 (y ) * scaleY )
426
+ if targetY >= targetHeight {
427
+ continue
428
+ }
429
+
430
+ for x := range c .w {
431
+ targetX := int (float64 (x ) * scaleX )
432
+ if targetX >= targetWidth {
433
+ continue
434
+ }
435
+
436
+ ch := c.grid [y ][x ]
437
+ if isArrowHead (ch ) {
438
+ // Determine offset based on arrow direction
439
+ var dx , dy int
440
+ switch ch {
441
+ case '>' :
442
+ dx = - 1
443
+ case '<' :
444
+ dx = 1
445
+ case 'v' :
446
+ dy = - 1
447
+ case '^' :
448
+ dy = 1
449
+ }
450
+
451
+ // Apply offset and ensure we stay within bounds
452
+ finalX := min (max (0 , targetX + dx ), targetWidth - 1 )
453
+ finalY := min (max (0 , targetY + dy ), targetHeight - 1 )
454
+
455
+ // Only place arrow if target position is empty or has a line character
456
+ if newGrid [finalY ][finalX ] == ' ' || newGrid [finalY ][finalX ] == '-' || newGrid [finalY ][finalX ] == '|' {
457
+ newGrid [finalY ][finalX ] = ch
458
+ }
459
+ }
460
+ }
461
+ }
462
+
463
+ // Third pass: redraw text at scaled positions
421
464
for _ , label := range c .textPositions {
422
465
// Get box dimensions in source coordinates first
423
466
srcBoxCenterY := label .y + label .h / 2
@@ -455,11 +498,10 @@ func (c *Canvas) ReScale(targetWidth, targetHeight int) {
455
498
continue
456
499
}
457
500
458
- // Only overwrite space or existing text
501
+ // Only overwrite if not an arrow head and not a border
459
502
existing := newGrid [targetY ][targetX ]
460
- if existing == ' ' || (existing != '+' && existing != '-' &&
461
- existing != '|' && existing != '/' && existing != '\\' &&
462
- existing != '.' ) {
503
+ if ! isArrowHead (existing ) && existing != '+' && existing != '-' &&
504
+ existing != '|' && existing != '/' && existing != '\\' {
463
505
newGrid [targetY ][targetX ] = ch
464
506
}
465
507
}
0 commit comments