@zombie-mermaid/ascii-renderer 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.cjs +7 -7
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +1804 -1428
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -3
  6. package/src/__tests__/ascii-class-box-occupancy.test.ts +30 -30
  7. package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +19 -19
  8. package/src/__tests__/ascii-class-namespace-1196.test.ts +109 -0
  9. package/src/__tests__/ascii-class-parent-alignment-971.test.ts +3 -3
  10. package/src/__tests__/ascii-cluster-entry-1283.test.ts +258 -0
  11. package/src/__tests__/ascii-cluster-exit-anchoring-1148.test.ts +6 -2
  12. package/src/__tests__/ascii-multibox-cjk-width.test.ts +1 -1
  13. package/src/__tests__/ascii-rl-direction-1231.test.ts +156 -0
  14. package/src/__tests__/ascii-subgraph-back-edge-1197.test.ts +10 -18
  15. package/src/__tests__/ascii-subgraph-frames-1252-1254.test.ts +135 -0
  16. package/src/__tests__/ascii-subgraph-title-edge-1222.test.ts +51 -42
  17. package/src/__tests__/ascii-subgraph-title-padding.test.ts +7 -4
  18. package/src/__tests__/ascii-subgraph-title-room-1278.test.ts +101 -0
  19. package/src/__tests__/ascii-subgraph-wall-clears-node-1288.test.ts +55 -0
  20. package/src/__tests__/testdata/ascii/cls_all_relationships.txt +1 -1
  21. package/src/__tests__/testdata/ascii/cls_annotation.txt +1 -1
  22. package/src/__tests__/testdata/ascii/cls_basic.txt +9 -9
  23. package/src/__tests__/testdata/ascii/cls_inheritance.txt +7 -7
  24. package/src/__tests__/testdata/ascii/cls_methods.txt +2 -2
  25. package/src/__tests__/testdata/ascii/graph_rl_direction.txt +8 -0
  26. package/src/__tests__/testdata/ascii/nested_subgraphs_with_labels.txt +35 -25
  27. package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +21 -21
  28. package/src/__tests__/testdata/ascii/subgraph_mixed_nodes_td.txt +38 -38
  29. package/src/__tests__/testdata/unicode/cls_all_relationships.txt +1 -1
  30. package/src/__tests__/testdata/unicode/cls_annotation.txt +1 -1
  31. package/src/__tests__/testdata/unicode/cls_basic.txt +9 -9
  32. package/src/__tests__/testdata/unicode/cls_inheritance.txt +7 -7
  33. package/src/__tests__/testdata/unicode/cls_methods.txt +2 -2
  34. package/src/__tests__/testdata/unicode/graph_rl_direction.txt +8 -0
  35. package/src/canvas.ts +152 -1
  36. package/src/class-diagram.ts +174 -2
  37. package/src/cluster-boundary.ts +56 -0
  38. package/src/converter.ts +9 -1
  39. package/src/draw-arrows.ts +81 -8
  40. package/src/draw-subgraphs.ts +33 -22
  41. package/src/draw.ts +9 -0
  42. package/src/edge-routing.ts +75 -1
  43. package/src/flowchart.ts +22 -2
  44. package/src/frame-avoidance.ts +31 -19
  45. package/src/grid.ts +261 -6
  46. package/src/hyperlinks.ts +9 -0
  47. package/src/types.ts +14 -0
@@ -16,4 +16,4 @@ classDiagram
16
16
  | B | | D | | F | | H | | J | | L |
17
17
  +---+ +---+ +---+ +---+ +---+ +---+
18
18
 
19
-
19
+
@@ -21,7 +21,7 @@ classDiagram
21
21
  +----------------+
22
22
  | Circle |
23
23
  +----------------+
24
- | + int: radius |
24
+ | + radius int |
25
25
  +----------------+
26
26
  | + draw(): void |
27
27
  +----------------+
@@ -4,12 +4,12 @@ classDiagram
4
4
  +eat() void
5
5
  }
6
6
  ---
7
- +----------------+
8
- | Animal |
9
- +----------------+
10
- | + name: String |
11
- +----------------+
12
- | + eat(): void |
13
- +----------------+
14
-
15
-
7
+ +---------------+
8
+ | Animal |
9
+ +---------------+
10
+ | + String name |
11
+ +---------------+
12
+ | + eat(): void |
13
+ +---------------+
14
+
15
+
@@ -3,13 +3,13 @@ classDiagram
3
3
  Animal : +String name
4
4
  Dog : +bark() void
5
5
  ---
6
- +----------------+
7
- | Animal |
8
- +----------------+
9
- | + name: String |
10
- +----------------+
11
- ^
12
- |
6
+ +---------------+
7
+ | Animal |
8
+ +---------------+
9
+ | + String name |
10
+ +---------------+
11
+ ^
12
+ --
13
13
  |
14
14
  +----------------+
15
15
  | Dog |
@@ -10,8 +10,8 @@ classDiagram
10
10
  +------------------------------+
11
11
  | BankAccount |
12
12
  +------------------------------+
13
- | + owner: String |
14
- | - balance: int |
13
+ | + String owner |
14
+ | - int balance |
15
15
  +------------------------------+
16
16
  | + deposit(int amount): void |
17
17
  | + withdraw(int amount): bool |
@@ -0,0 +1,8 @@
1
+ graph RL
2
+ A --> B --> C
3
+ ---
4
+ +---+ +---+ +---+
5
+ | | | | | |
6
+ | C |<----+ B |<----+ A |
7
+ | | | | | |
8
+ +---+ +---+ +---+
@@ -9,28 +9,38 @@ B
9
9
  C
10
10
  end
11
11
  ---
12
- +-------------+
13
- | |
14
- | Web Server |
15
- | |
16
- +------+------+
17
- |
18
- |
19
- |
20
- |
21
- v
22
- +-------------+
23
- | |
24
- | API Gateway |
25
- | |
26
- +------+------+
27
- |
28
- |
29
- |
30
- |
31
- v
32
- +-------------+
33
- | |
34
- | Web Server |
35
- | |
36
- +-------------+
12
+ +-----------------+
13
+ | Frontend |
14
+ | |
15
+ | |
16
+ | +-------------+ |
17
+ | | | |
18
+ | | Web Server | |
19
+ | | | |
20
+ | +------+------+ |
21
+ | | |
22
+ +--------|--------+
23
+ |
24
+ |
25
+ |
26
+ +--------|--------+
27
+ |Backend | |
28
+ | | |
29
+ | v |
30
+ | +-------------+ |
31
+ | | | |
32
+ | | API Gateway | |
33
+ | | | |
34
+ | +------+------+ |
35
+ | | |
36
+ | | |
37
+ | | |
38
+ | | |
39
+ | v |
40
+ | +-------------+ |
41
+ | | | |
42
+ | | Web Server | |
43
+ | | | |
44
+ | +-------------+ |
45
+ | |
46
+ +-----------------+
@@ -16,27 +16,27 @@ B --> Y
16
16
  |
17
17
  |
18
18
  |
19
- +---|-----+
20
- | LR|Group|
21
- | | |
22
- | v |
23
- | +---+ |
24
- | | | |
25
- | | A | |
26
- | | | |
27
- | +-+-+ |
28
- | | |
29
- | | |
30
- | | |
31
- | | |
32
- | v |
33
- | +---+ |
34
- | | | |
35
- | | B | |
36
- | | | |
37
- | +-+-+ |
38
- | | |
39
- +---|-----+
19
+ +---|----------+
20
+ | | LR Group |
21
+ | | |
22
+ | v |
23
+ | +---+ |
24
+ | | | |
25
+ | | A | |
26
+ | | | |
27
+ | +-+-+ |
28
+ | | |
29
+ | | |
30
+ | | |
31
+ | | |
32
+ | v |
33
+ | +---+ |
34
+ | | | |
35
+ | | B | |
36
+ | | | |
37
+ | +-+-+ |
38
+ | | |
39
+ +---|----------+
40
40
  |
41
41
  |
42
42
  v
@@ -7,42 +7,42 @@ Y
7
7
  X --> A
8
8
  B --> Y
9
9
  ---
10
- +---+
11
- | |
12
- | X |
13
- | |
10
+ +---+
11
+ | |
12
+ | X |
13
+ | |
14
14
  +-+-+
15
- |
16
- |
17
- |
18
- |
19
- |
20
- +---|---+
21
- |one| |
22
- | | |
23
- | v |
24
- | +---+ |
25
- | | | |
26
- | | A | |
27
- | | | |
28
- | +-+-+ |
29
- | | |
30
- | | |
31
- | | |
32
- | | |
33
- | v |
34
- | +---+ |
35
- | | | |
36
- | | B | |
37
- | | | |
38
- | +-+-+ |
39
- | | |
40
- +---|---+
41
- |
42
- |
43
- v
44
- +---+
45
- | |
46
- | Y |
47
- | |
48
- +---+
15
+ |
16
+ |
17
+ |
18
+ |
19
+ |
20
+ +---|-----+
21
+ | | one |
22
+ | | |
23
+ | v |
24
+ | +---+ |
25
+ | | | |
26
+ | | A | |
27
+ | | | |
28
+ | +-+-+ |
29
+ | | |
30
+ | | |
31
+ | | |
32
+ | | |
33
+ | v |
34
+ | +---+ |
35
+ | | | |
36
+ | | B | |
37
+ | | | |
38
+ | +-+-+ |
39
+ | | |
40
+ +---|-----+
41
+ |
42
+ |
43
+ v
44
+ +---+
45
+ | |
46
+ | Y |
47
+ | |
48
+ +---+
@@ -16,4 +16,4 @@ classDiagram
16
16
  │ B │ │ D │ │ F │ │ H │ │ J │ │ L │
17
17
  └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
18
18
 
19
-
19
+
@@ -21,7 +21,7 @@ classDiagram
21
21
  ┌────────────────┐
22
22
  │ Circle │
23
23
  ├────────────────┤
24
- │ + int: radius │
24
+ │ + radius int │
25
25
  ├────────────────┤
26
26
  │ + draw(): void │
27
27
  └────────────────┘
@@ -4,12 +4,12 @@ classDiagram
4
4
  +eat() void
5
5
  }
6
6
  ---
7
- ┌────────────────┐
8
- │ Animal │
9
- ├────────────────┤
10
- │ + name: String │
11
- ├────────────────┤
12
- │ + eat(): void │
13
- └────────────────┘
14
-
15
-
7
+ ┌───────────────┐
8
+ │ Animal │
9
+ ├───────────────┤
10
+ │ + String name │
11
+ ├───────────────┤
12
+ │ + eat(): void │
13
+ └───────────────┘
14
+
15
+
@@ -3,13 +3,13 @@ classDiagram
3
3
  Animal : +String name
4
4
  Dog : +bark() void
5
5
  ---
6
- ┌────────────────┐
7
- │ Animal │
8
- ├────────────────┤
9
- │ + name: String │
10
- └────────────────┘
11
- △
12
- │
6
+ ┌───────────────┐
7
+ │ Animal │
8
+ ├───────────────┤
9
+ │ + String name │
10
+ └───────────────┘
11
+ △
12
+ └┐
13
13
  │
14
14
  ┌────────────────┐
15
15
  │ Dog │
@@ -10,8 +10,8 @@ classDiagram
10
10
  ┌──────────────────────────────┐
11
11
  │ BankAccount │
12
12
  ├──────────────────────────────┤
13
- │ + owner: String │
14
- │ - balance: int │
13
+ │ + String owner │
14
+ │ - int balance │
15
15
  ├──────────────────────────────┤
16
16
  │ + deposit(int amount): void │
17
17
  │ + withdraw(int amount): bool │
@@ -0,0 +1,8 @@
1
+ graph RL
2
+ A --> B --> C
3
+ ---
4
+ ┌───┐ ┌───┐ ┌───┐
5
+ │ │ │ │ │ │
6
+ │ C │◄────┤ B │◄────┤ A │
7
+ │ │ │ │ │ │
8
+ └───┘ └───┘ └───┘
package/src/canvas.ts CHANGED
@@ -16,7 +16,7 @@ import type {
16
16
  ColorMode,
17
17
  } from './types.ts'
18
18
  import { colorizeLine, DEFAULT_ASCII_THEME } from './ansi.ts'
19
- import { toDisplayCells } from './display-width.ts'
19
+ import { toDisplayCells, WIDE_CHAR_PLACEHOLDER } from './display-width.ts'
20
20
  import { joinWithLinks } from './hyperlinks.ts'
21
21
  import type { LinkCanvas } from './hyperlinks.ts'
22
22
 
@@ -753,6 +753,157 @@ export function flipRoleCanvasVertically(roleCanvas: RoleCanvas): RoleCanvas {
753
753
  return roleCanvas
754
754
  }
755
755
 
756
+ // ============================================================================
757
+ // Canvas horizontal flip — used for RL (right-to-left) direction support.
758
+ //
759
+ // RL is laid out as LR and the finished canvas is mirrored left-to-right,
760
+ // remapping glyphs whose meaning depends on the X-axis. The counterpart of the
761
+ // vertical flip above.
762
+ // ============================================================================
763
+
764
+ /**
765
+ * Characters that change meaning when the X-axis is flipped. Symmetric
766
+ * characters (─, │, ┬, ┴, ┼, ╵, ╷) are unchanged.
767
+ */
768
+ const HORIZONTAL_FLIP_MAP: Record<string, string> = {
769
+ // Arrowheads
770
+ '►': '◄',
771
+ '◄': '►',
772
+ '>': '<',
773
+ '<': '>',
774
+ // Diagonal arrowheads (U+2196-U+2199)
775
+ '↖': '↗',
776
+ '↗': '↖',
777
+ '↙': '↘',
778
+ '↘': '↙',
779
+ // Corners
780
+ '┌': '┐',
781
+ '┐': '┌',
782
+ '└': '┘',
783
+ '┘': '└',
784
+ // Junctions (T-pieces flip horizontally)
785
+ '├': '┤',
786
+ '┤': '├',
787
+ // Rounded corners
788
+ '╭': '╮',
789
+ '╮': '╭',
790
+ '╰': '╯',
791
+ '╯': '╰',
792
+ // Double-line corners and junctions
793
+ '╔': '╗',
794
+ '╗': '╔',
795
+ '╚': '╝',
796
+ '╝': '╚',
797
+ '╟': '╢',
798
+ '╢': '╟',
799
+ // Corner brackets
800
+ '⌜': '⌝',
801
+ '⌝': '⌜',
802
+ '⌞': '⌟',
803
+ '⌟': '⌞',
804
+ // Slanted strokes and triangles
805
+ '╱': '╲',
806
+ '╲': '╱',
807
+ '/': '\\',
808
+ '\\': '/',
809
+ '◢': '◣',
810
+ '◣': '◢',
811
+ '◤': '◥',
812
+ '◥': '◤',
813
+ '◸': '◹',
814
+ '◹': '◸',
815
+ '◺': '◿',
816
+ '◿': '◺',
817
+ }
818
+
819
+ /**
820
+ * Flip the canvas horizontally (mirror across the vertical center).
821
+ * Reverses column order and remaps directional characters so arrows point
822
+ * the other way and corners mirror correctly.
823
+ *
824
+ * Cells `roleCanvas` marks as `'text'` are never remapped: a label containing
825
+ * `<` or `/` is prose, not an arrowhead. Call `mirrorLabelColumns` first so
826
+ * labels keep reading left-to-right.
827
+ *
828
+ * Used to transform an LR-rendered canvas into RL output. Mutates the canvas
829
+ * in place and returns it. `roleCanvas` must still be unflipped when this runs.
830
+ */
831
+ export function flipCanvasHorizontally(
832
+ canvas: Canvas,
833
+ roleCanvas?: RoleCanvas,
834
+ ): Canvas {
835
+ for (const [x, col] of canvas.entries()) {
836
+ for (const [y, ch] of col.entries()) {
837
+ if (roleCanvas?.[x]?.[y] === 'text') continue
838
+ const flipped = HORIZONTAL_FLIP_MAP[ch]
839
+ if (flipped) col[y] = flipped
840
+ }
841
+ }
842
+ canvas.reverse()
843
+ return canvas
844
+ }
845
+
846
+ /**
847
+ * Pre-compensate labels for a following horizontal flip: within each label
848
+ * rectangle, reflect the `'text'` cells across the rectangle's vertical
849
+ * center, so that after `flipCanvasHorizontally` each label lands at its
850
+ * mirrored position but still reads left-to-right (wide-character placeholder
851
+ * cells keep their order too, since the two reflections cancel). `linkCanvas`
852
+ * is permuted identically. Mutates in place; call before the flips.
853
+ */
854
+ export function mirrorLabelColumns(
855
+ canvas: Canvas,
856
+ roleCanvas: RoleCanvas,
857
+ labelRects: LabelRect[],
858
+ linkCanvas?: (string | null)[][],
859
+ ): void {
860
+ const moves: { cells: [number, number][]; xSum: number }[] = []
861
+ const seen = new Set<string>()
862
+ for (const { x0, y0, x1, y1 } of labelRects) {
863
+ if (x0 === x1) continue
864
+ const cells: [number, number][] = []
865
+ for (let x = x0; x <= x1; x++) {
866
+ for (let y = y0; y <= y1; y++) {
867
+ const key = `${x},${y}`
868
+ // A wide glyph's placeholder cell is '' and carries no role of its
869
+ // own, but it must travel with the glyph it follows.
870
+ const isText =
871
+ roleCanvas[x]?.[y] === 'text' ||
872
+ canvas[x]?.[y] === WIDE_CHAR_PLACEHOLDER
873
+ if (!isText || seen.has(key)) continue
874
+ seen.add(key)
875
+ cells.push([x, y])
876
+ }
877
+ }
878
+ moves.push({ cells, xSum: x0 + x1 })
879
+ }
880
+
881
+ for (const { cells, xSum } of moves) {
882
+ moveCellsX(canvas, cells, xSum, ' ')
883
+ moveCellsX(roleCanvas, cells, xSum, null)
884
+ if (linkCanvas) moveCellsX(linkCanvas, cells, xSum, null)
885
+ }
886
+ }
887
+
888
+ function moveCellsX<T>(
889
+ layer: T[][],
890
+ cells: [number, number][],
891
+ xSum: number,
892
+ empty: T,
893
+ ): void {
894
+ const saved = cells.map(([cx, cy]) => layer[cx]![cy]!)
895
+ for (const [cx, cy] of cells) layer[cx]![cy] = empty
896
+ for (const [i, [cx, cy]] of cells.entries()) {
897
+ layer[xSum - cx]![cy] = saved[i]!
898
+ }
899
+ }
900
+
901
+ /** Flip the role canvas horizontally to match flipCanvasHorizontally. */
902
+ export function flipRoleCanvasHorizontally(roleCanvas: RoleCanvas): RoleCanvas {
903
+ roleCanvas.reverse()
904
+ return roleCanvas
905
+ }
906
+
756
907
  /**
757
908
  * Draw text string onto the canvas starting at the given coordinate.
758
909
  * By default, preserves existing non-space characters (labels don't overwrite each other).