@zombie-mermaid/ascii-renderer 2.2.1

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 (232) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +8 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +146 -0
  5. package/dist/index.d.ts +146 -0
  6. package/dist/index.js +5392 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +36 -0
  9. package/src/__tests__/ascii-arrowhead-direction-1083.test.ts +51 -0
  10. package/src/__tests__/ascii-canvas-first-claim-wins-1093.test.ts +89 -0
  11. package/src/__tests__/ascii-canvas-size-offset-1093.test.ts +95 -0
  12. package/src/__tests__/ascii-canvas-write.test.ts +132 -0
  13. package/src/__tests__/ascii-chain-edge-overlap-1067.test.ts +100 -0
  14. package/src/__tests__/ascii-charset-border-junctions.test.ts +63 -0
  15. package/src/__tests__/ascii-cjk-width.test.ts +150 -0
  16. package/src/__tests__/ascii-class-box-occupancy.test.ts +462 -0
  17. package/src/__tests__/ascii-class-column-width-488-489.test.ts +639 -0
  18. package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
  19. package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +114 -0
  20. package/src/__tests__/ascii-class-diagram-compartments.test.ts +70 -0
  21. package/src/__tests__/ascii-class-label-box-collision.test.ts +60 -0
  22. package/src/__tests__/ascii-class-label-row-collision-531.test.ts +135 -0
  23. package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +57 -0
  24. package/src/__tests__/ascii-class-padding.test.ts +104 -0
  25. package/src/__tests__/ascii-class-parent-alignment-971.test.ts +139 -0
  26. package/src/__tests__/ascii-class-parent-alignment-972.test.ts +208 -0
  27. package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +169 -0
  28. package/src/__tests__/ascii-combining-mark-width.test.ts +70 -0
  29. package/src/__tests__/ascii-coords-overlay.test.ts +64 -0
  30. package/src/__tests__/ascii-decision-lr-box-start.test.ts +106 -0
  31. package/src/__tests__/ascii-display-width-unit.test.ts +154 -0
  32. package/src/__tests__/ascii-draw-arrows-coverage.test.ts +224 -0
  33. package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +105 -0
  34. package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +72 -0
  35. package/src/__tests__/ascii-edge-ending-glyphs.test.ts +206 -0
  36. package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +95 -0
  37. package/src/__tests__/ascii-edge-routing-fixes.test.ts +178 -0
  38. package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +103 -0
  39. package/src/__tests__/ascii-edge-style-consistency-1067.test.ts +89 -0
  40. package/src/__tests__/ascii-edge-styles.test.ts +149 -0
  41. package/src/__tests__/ascii-emoji-cluster-width.test.ts +101 -0
  42. package/src/__tests__/ascii-er-box-occupancy.test.ts +251 -0
  43. package/src/__tests__/ascii-er-cardinality.test.ts +40 -0
  44. package/src/__tests__/ascii-er-corner-glyphs.test.ts +249 -0
  45. package/src/__tests__/ascii-er-jog-stray-line.test.ts +111 -0
  46. package/src/__tests__/ascii-er-label-padding.test.ts +118 -0
  47. package/src/__tests__/ascii-er-padding.test.ts +114 -0
  48. package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +458 -0
  49. package/src/__tests__/ascii-er-relationship-overwrite.test.ts +325 -0
  50. package/src/__tests__/ascii-er-stray-connectors.test.ts +344 -0
  51. package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +98 -0
  52. package/src/__tests__/ascii-er-vertical-one-marker.test.ts +110 -0
  53. package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +168 -0
  54. package/src/__tests__/ascii-lane-search.test.ts +207 -0
  55. package/src/__tests__/ascii-multibox-cjk-width.test.ts +184 -0
  56. package/src/__tests__/ascii-multiline.test.ts +288 -0
  57. package/src/__tests__/ascii-padding-edge-cases.test.ts +154 -0
  58. package/src/__tests__/ascii-pathfinder-route-edge.test.ts +184 -0
  59. package/src/__tests__/ascii-sequence-alt-else-label.test.ts +236 -0
  60. package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +217 -0
  61. package/src/__tests__/ascii-sequence-box-group.test.ts +159 -0
  62. package/src/__tests__/ascii-sequence-cjk-width.test.ts +235 -0
  63. package/src/__tests__/ascii-sequence-create-destroy.test.ts +114 -0
  64. package/src/__tests__/ascii-sequence-form-invariants.test.ts +432 -0
  65. package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +219 -0
  66. package/src/__tests__/ascii-sequence-notes.test.ts +61 -0
  67. package/src/__tests__/ascii-sequence-padding.test.ts +119 -0
  68. package/src/__tests__/ascii-sequence-self-arrow.test.ts +206 -0
  69. package/src/__tests__/ascii-shape-diamond.test.ts +38 -0
  70. package/src/__tests__/ascii-shape-rectangle.test.ts +258 -0
  71. package/src/__tests__/ascii-shape-rounded.test.ts +36 -0
  72. package/src/__tests__/ascii-shapes-circle.test.ts +41 -0
  73. package/src/__tests__/ascii-shapes-hexagon.test.ts +42 -0
  74. package/src/__tests__/ascii-shapes-special.test.ts +344 -0
  75. package/src/__tests__/ascii-shapes-stadium.test.ts +217 -0
  76. package/src/__tests__/ascii-shapes-state.test.ts +224 -0
  77. package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +130 -0
  78. package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +90 -0
  79. package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +152 -0
  80. package/src/__tests__/ascii-subgraph-title-padding.test.ts +77 -0
  81. package/src/__tests__/ascii-territory-unit.test.ts +219 -0
  82. package/src/__tests__/ascii-validate.test.ts +220 -0
  83. package/src/__tests__/ascii.test.ts +325 -0
  84. package/src/__tests__/class-arrow-directions.test.ts +505 -0
  85. package/src/__tests__/draw-lines.test.ts +93 -0
  86. package/src/__tests__/edge-cell-styles.test.ts +278 -0
  87. package/src/__tests__/grid-occupancy.test.ts +240 -0
  88. package/src/__tests__/helpers/ascii-form.ts +142 -0
  89. package/src/__tests__/helpers/terminal-display-width.ts +74 -0
  90. package/src/__tests__/pathfinder.test.ts +239 -0
  91. package/src/__tests__/testdata/ascii/ampersand_lhs.txt +18 -0
  92. package/src/__tests__/testdata/ascii/ampersand_lhs_and_rhs.txt +18 -0
  93. package/src/__tests__/testdata/ascii/ampersand_rhs.txt +18 -0
  94. package/src/__tests__/testdata/ascii/ampersand_td_fanin.txt +18 -0
  95. package/src/__tests__/testdata/ascii/ampersand_td_fanout.txt +18 -0
  96. package/src/__tests__/testdata/ascii/ampersand_without_edge.txt +18 -0
  97. package/src/__tests__/testdata/ascii/back_reference_from_child.txt +10 -0
  98. package/src/__tests__/testdata/ascii/backlink_from_bottom.txt +22 -0
  99. package/src/__tests__/testdata/ascii/backlink_from_top.txt +22 -0
  100. package/src/__tests__/testdata/ascii/backlink_with_short_y_padding.txt +20 -0
  101. package/src/__tests__/testdata/ascii/cls_all_relationships.txt +19 -0
  102. package/src/__tests__/testdata/ascii/cls_annotation.txt +29 -0
  103. package/src/__tests__/testdata/ascii/cls_association.txt +14 -0
  104. package/src/__tests__/testdata/ascii/cls_basic.txt +15 -0
  105. package/src/__tests__/testdata/ascii/cls_dependency.txt +14 -0
  106. package/src/__tests__/testdata/ascii/cls_inheritance.txt +20 -0
  107. package/src/__tests__/testdata/ascii/cls_methods.txt +21 -0
  108. package/src/__tests__/testdata/ascii/comments.txt +23 -0
  109. package/src/__tests__/testdata/ascii/custom_padding.txt +10 -0
  110. package/src/__tests__/testdata/ascii/duplicate_labels.txt +19 -0
  111. package/src/__tests__/testdata/ascii/er_attributes.txt +21 -0
  112. package/src/__tests__/testdata/ascii/er_basic.txt +8 -0
  113. package/src/__tests__/testdata/ascii/er_identifying.txt +18 -0
  114. package/src/__tests__/testdata/ascii/flowchart_tb_simple.txt +29 -0
  115. package/src/__tests__/testdata/ascii/graph_bt_direction.txt +28 -0
  116. package/src/__tests__/testdata/ascii/graph_tb_direction.txt +26 -0
  117. package/src/__tests__/testdata/ascii/nested_subgraphs_with_labels.txt +36 -0
  118. package/src/__tests__/testdata/ascii/preserve_order_of_definition.txt +23 -0
  119. package/src/__tests__/testdata/ascii/self_reference.txt +10 -0
  120. package/src/__tests__/testdata/ascii/self_reference_with_edge.txt +10 -0
  121. package/src/__tests__/testdata/ascii/seq_basic.txt +17 -0
  122. package/src/__tests__/testdata/ascii/seq_multiple_messages.txt +25 -0
  123. package/src/__tests__/testdata/ascii/seq_self_message.txt +18 -0
  124. package/src/__tests__/testdata/ascii/single_node.txt +8 -0
  125. package/src/__tests__/testdata/ascii/single_node_longer_name.txt +8 -0
  126. package/src/__tests__/testdata/ascii/subgraph_complex_mixed.txt +38 -0
  127. package/src/__tests__/testdata/ascii/subgraph_complex_nested.txt +49 -0
  128. package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +47 -0
  129. package/src/__tests__/testdata/ascii/subgraph_empty.txt +10 -0
  130. package/src/__tests__/testdata/ascii/subgraph_mixed_nodes.txt +20 -0
  131. package/src/__tests__/testdata/ascii/subgraph_mixed_nodes_td.txt +48 -0
  132. package/src/__tests__/testdata/ascii/subgraph_multiple_edges.txt +32 -0
  133. package/src/__tests__/testdata/ascii/subgraph_multiple_nodes.txt +16 -0
  134. package/src/__tests__/testdata/ascii/subgraph_nested.txt +24 -0
  135. package/src/__tests__/testdata/ascii/subgraph_nested_with_external.txt +30 -0
  136. package/src/__tests__/testdata/ascii/subgraph_node_outside_lr.txt +17 -0
  137. package/src/__tests__/testdata/ascii/subgraph_single_node.txt +16 -0
  138. package/src/__tests__/testdata/ascii/subgraph_td_direction.txt +26 -0
  139. package/src/__tests__/testdata/ascii/subgraph_td_multiple.txt +44 -0
  140. package/src/__tests__/testdata/ascii/subgraph_td_multiple_paddingy.txt +42 -0
  141. package/src/__tests__/testdata/ascii/subgraph_three_levels_nested.txt +32 -0
  142. package/src/__tests__/testdata/ascii/subgraph_three_separate.txt +24 -0
  143. package/src/__tests__/testdata/ascii/subgraph_two_separate.txt +20 -0
  144. package/src/__tests__/testdata/ascii/subgraph_with_labels.txt +20 -0
  145. package/src/__tests__/testdata/ascii/three_nodes.txt +9 -0
  146. package/src/__tests__/testdata/ascii/three_nodes_single_line.txt +8 -0
  147. package/src/__tests__/testdata/ascii/two_layer_single_graph.txt +19 -0
  148. package/src/__tests__/testdata/ascii/two_layer_single_graph_longer_names.txt +19 -0
  149. package/src/__tests__/testdata/ascii/two_nodes_linked.txt +8 -0
  150. package/src/__tests__/testdata/ascii/two_nodes_longer_names.txt +8 -0
  151. package/src/__tests__/testdata/ascii/two_root_nodes.txt +19 -0
  152. package/src/__tests__/testdata/ascii/two_root_nodes_longer_names.txt +19 -0
  153. package/src/__tests__/testdata/ascii/two_single_root_nodes.txt +19 -0
  154. package/src/__tests__/testdata/unicode/ampersand_lhs.txt +18 -0
  155. package/src/__tests__/testdata/unicode/ampersand_lhs_and_rhs.txt +18 -0
  156. package/src/__tests__/testdata/unicode/ampersand_rhs.txt +18 -0
  157. package/src/__tests__/testdata/unicode/ampersand_without_edge.txt +18 -0
  158. package/src/__tests__/testdata/unicode/back_reference_from_child.txt +10 -0
  159. package/src/__tests__/testdata/unicode/backlink_from_bottom.txt +22 -0
  160. package/src/__tests__/testdata/unicode/backlink_from_top.txt +22 -0
  161. package/src/__tests__/testdata/unicode/cls_all_relationships.txt +19 -0
  162. package/src/__tests__/testdata/unicode/cls_annotation.txt +29 -0
  163. package/src/__tests__/testdata/unicode/cls_association.txt +14 -0
  164. package/src/__tests__/testdata/unicode/cls_basic.txt +15 -0
  165. package/src/__tests__/testdata/unicode/cls_dependency.txt +14 -0
  166. package/src/__tests__/testdata/unicode/cls_inheritance.txt +20 -0
  167. package/src/__tests__/testdata/unicode/cls_methods.txt +21 -0
  168. package/src/__tests__/testdata/unicode/comments.txt +23 -0
  169. package/src/__tests__/testdata/unicode/duplicate_labels.txt +19 -0
  170. package/src/__tests__/testdata/unicode/er_attributes.txt +21 -0
  171. package/src/__tests__/testdata/unicode/er_basic.txt +8 -0
  172. package/src/__tests__/testdata/unicode/er_identifying.txt +18 -0
  173. package/src/__tests__/testdata/unicode/graph_bt_direction.txt +28 -0
  174. package/src/__tests__/testdata/unicode/preserve_order_of_definition.txt +23 -0
  175. package/src/__tests__/testdata/unicode/self_reference.txt +10 -0
  176. package/src/__tests__/testdata/unicode/self_reference_with_edge.txt +10 -0
  177. package/src/__tests__/testdata/unicode/seq_basic.txt +17 -0
  178. package/src/__tests__/testdata/unicode/seq_multiple_messages.txt +25 -0
  179. package/src/__tests__/testdata/unicode/seq_self_message.txt +18 -0
  180. package/src/__tests__/testdata/unicode/single_node.txt +8 -0
  181. package/src/__tests__/testdata/unicode/single_node_longer_name.txt +8 -0
  182. package/src/__tests__/testdata/unicode/three_nodes.txt +9 -0
  183. package/src/__tests__/testdata/unicode/three_nodes_single_line.txt +8 -0
  184. package/src/__tests__/testdata/unicode/two_layer_single_graph.txt +19 -0
  185. package/src/__tests__/testdata/unicode/two_layer_single_graph_longer_names.txt +19 -0
  186. package/src/__tests__/testdata/unicode/two_nodes_linked.txt +8 -0
  187. package/src/__tests__/testdata/unicode/two_nodes_longer_names.txt +8 -0
  188. package/src/__tests__/testdata/unicode/two_root_nodes.txt +19 -0
  189. package/src/__tests__/testdata/unicode/two_root_nodes_longer_names.txt +19 -0
  190. package/src/__tests__/testdata/unicode/two_single_root_nodes.txt +19 -0
  191. package/src/__tests__/xychart-ascii.test.ts +376 -0
  192. package/src/ansi.ts +490 -0
  193. package/src/canvas.ts +757 -0
  194. package/src/class-diagram.ts +2001 -0
  195. package/src/converter.ts +446 -0
  196. package/src/coords.ts +58 -0
  197. package/src/display-width.ts +151 -0
  198. package/src/draw-arrows.ts +593 -0
  199. package/src/draw-boxes.ts +267 -0
  200. package/src/draw-bundles.ts +611 -0
  201. package/src/draw-lines.ts +174 -0
  202. package/src/draw-subgraphs.ts +108 -0
  203. package/src/draw.ts +350 -0
  204. package/src/edge-bundling.ts +435 -0
  205. package/src/edge-cell-styles.ts +209 -0
  206. package/src/edge-routing.ts +1070 -0
  207. package/src/er-diagram.ts +1488 -0
  208. package/src/flowchart.ts +94 -0
  209. package/src/grid-occupancy.ts +234 -0
  210. package/src/grid.ts +1309 -0
  211. package/src/hyperlinks.ts +248 -0
  212. package/src/index.ts +163 -0
  213. package/src/lane-search.ts +68 -0
  214. package/src/multiline-utils.ts +82 -0
  215. package/src/pathfinder.ts +448 -0
  216. package/src/registry.ts +88 -0
  217. package/src/sequence.ts +1318 -0
  218. package/src/shapes/circle.ts +31 -0
  219. package/src/shapes/corners.ts +273 -0
  220. package/src/shapes/diamond.ts +31 -0
  221. package/src/shapes/hexagon.ts +35 -0
  222. package/src/shapes/index.ts +123 -0
  223. package/src/shapes/rectangle.ts +199 -0
  224. package/src/shapes/rounded.ts +31 -0
  225. package/src/shapes/special.ts +360 -0
  226. package/src/shapes/stadium.ts +122 -0
  227. package/src/shapes/state.ts +204 -0
  228. package/src/shapes/types.ts +78 -0
  229. package/src/territory.ts +136 -0
  230. package/src/types.ts +454 -0
  231. package/src/validate.ts +189 -0
  232. package/src/xychart.ts +1085 -0
@@ -0,0 +1,31 @@
1
+ // ============================================================================
2
+ // Circle shape renderer — uses corner decorators instead of curves
3
+ // ============================================================================
4
+
5
+ import type { ShapeRenderer } from './types.ts'
6
+ import {
7
+ getBoxDimensions,
8
+ renderBox,
9
+ getBoxAttachmentPoint,
10
+ } from './rectangle.ts'
11
+ import { getCorners } from './corners.ts'
12
+
13
+ /**
14
+ * Circle shape renderer.
15
+ * Uses circle markers (◯) at corners to indicate circular shape semantics.
16
+ *
17
+ * Renders as:
18
+ * ◯─────────◯
19
+ * │ Label │
20
+ * ◯─────────◯
21
+ */
22
+ export const circleRenderer: ShapeRenderer = {
23
+ getDimensions: getBoxDimensions,
24
+
25
+ render(label, dimensions, options) {
26
+ const corners = getCorners('circle', options.useAscii)
27
+ return renderBox(label, dimensions, corners, options.useAscii)
28
+ },
29
+
30
+ getAttachmentPoint: getBoxAttachmentPoint,
31
+ }
@@ -0,0 +1,273 @@
1
+ // ============================================================================
2
+ // Corner character lookup table for shape rendering
3
+ // ============================================================================
4
+ //
5
+ // All shapes are rendered as rectangles with distinctive corner characters
6
+ // to indicate shape type. This eliminates diagonal characters while keeping
7
+ // shapes visually distinguishable.
8
+
9
+ import type { AsciiNodeShape } from '../types.ts'
10
+
11
+ /**
12
+ * Corner characters for a shape in both Unicode and ASCII modes.
13
+ */
14
+ export interface CornerChars {
15
+ /** Top-left corner */
16
+ tl: string
17
+ /** Top-right corner */
18
+ tr: string
19
+ /** Bottom-left corner */
20
+ bl: string
21
+ /** Bottom-right corner */
22
+ br: string
23
+ }
24
+
25
+ /**
26
+ * Shape corner configuration with both Unicode and ASCII variants.
27
+ */
28
+ export interface ShapeCorners {
29
+ unicode: CornerChars
30
+ ascii: CornerChars
31
+ }
32
+
33
+ /**
34
+ * Corner character lookup table for all shape types.
35
+ *
36
+ * Design principles:
37
+ * - All shapes use orthogonal box structure (no diagonals)
38
+ * - Corner characters indicate shape semantics
39
+ * - ASCII fallbacks use available punctuation
40
+ */
41
+ export const SHAPE_CORNERS: Record<AsciiNodeShape, ShapeCorners> = {
42
+ // Standard rectangular shapes
43
+ rectangle: {
44
+ unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },
45
+ ascii: { tl: '+', tr: '+', bl: '+', br: '+' },
46
+ },
47
+ rounded: {
48
+ unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },
49
+ ascii: { tl: '.', tr: '.', bl: "'", br: "'" },
50
+ },
51
+
52
+ // Circular shapes - use circle markers at corners
53
+ circle: {
54
+ unicode: { tl: '◯', tr: '◯', bl: '◯', br: '◯' },
55
+ ascii: { tl: 'o', tr: 'o', bl: 'o', br: 'o' },
56
+ },
57
+ doublecircle: {
58
+ unicode: { tl: '◎', tr: '◎', bl: '◎', br: '◎' },
59
+ ascii: { tl: '@', tr: '@', bl: '@', br: '@' },
60
+ },
61
+
62
+ // Diamond - decision nodes
63
+ diamond: {
64
+ unicode: { tl: '◇', tr: '◇', bl: '◇', br: '◇' },
65
+ ascii: { tl: '<', tr: '>', bl: '<', br: '>' },
66
+ },
67
+
68
+ // Hexagon - process nodes (crop corners — monospace-safe, distinct from rectangle)
69
+ hexagon: {
70
+ unicode: { tl: '⌜', tr: '⌝', bl: '⌞', br: '⌟' },
71
+ ascii: { tl: '*', tr: '*', bl: '*', br: '*' },
72
+ },
73
+
74
+ // Stadium/pill shape
75
+ stadium: {
76
+ unicode: { tl: '(', tr: ')', bl: '(', br: ')' },
77
+ ascii: { tl: '(', tr: ')', bl: '(', br: ')' },
78
+ },
79
+
80
+ // Subroutine - double vertical bars
81
+ subroutine: {
82
+ unicode: { tl: '╟', tr: '╢', bl: '╟', br: '╢' },
83
+ ascii: { tl: '|', tr: '|', bl: '|', br: '|' },
84
+ },
85
+
86
+ // Cylinder/database
87
+ cylinder: {
88
+ unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },
89
+ ascii: { tl: '.', tr: '.', bl: "'", br: "'" },
90
+ },
91
+
92
+ // Asymmetric/flag - pointer on left side
93
+ asymmetric: {
94
+ unicode: { tl: '▷', tr: '┐', bl: '▷', br: '┘' },
95
+ ascii: { tl: '>', tr: '+', bl: '>', br: '+' },
96
+ },
97
+
98
+ // Trapezoid - wider at bottom (top corners slope inward)
99
+ trapezoid: {
100
+ unicode: { tl: '/', tr: '\\', bl: '└', br: '┘' },
101
+ ascii: { tl: '/', tr: '\\', bl: '+', br: '+' },
102
+ },
103
+
104
+ // Trapezoid-alt - wider at top (bottom corners slope inward)
105
+ 'trapezoid-alt': {
106
+ unicode: { tl: '┌', tr: '┐', bl: '\\', br: '/' },
107
+ ascii: { tl: '+', tr: '+', bl: '\\', br: '/' },
108
+ },
109
+
110
+ // Parallelogram - leans right (both sides slope the same way)
111
+ parallelogram: {
112
+ unicode: { tl: '/', tr: '/', bl: '/', br: '/' },
113
+ ascii: { tl: '/', tr: '/', bl: '/', br: '/' },
114
+ },
115
+
116
+ // Parallelogram-alt - leans left
117
+ 'parallelogram-alt': {
118
+ unicode: { tl: '\\', tr: '\\', bl: '\\', br: '\\' },
119
+ ascii: { tl: '\\', tr: '\\', bl: '\\', br: '\\' },
120
+ },
121
+
122
+ // State diagram pseudostates (special handling, not corner-based)
123
+ 'state-start': {
124
+ unicode: { tl: '●', tr: '●', bl: '●', br: '●' },
125
+ ascii: { tl: '*', tr: '*', bl: '*', br: '*' },
126
+ },
127
+ 'state-end': {
128
+ unicode: { tl: '◉', tr: '◉', bl: '◉', br: '◉' },
129
+ ascii: { tl: '@', tr: '@', bl: '@', br: '@' },
130
+ },
131
+
132
+ // --------------------------------------------------------------------
133
+ // Expanded-syntax shapes (`A@{ shape: ... }`).
134
+ //
135
+ // The ASCII grid has no diagonals and one glyph per corner, so these are
136
+ // distinguished by corner character alone. Shapes whose defining feature
137
+ // is interior (a rule, a cross, a notch) or whose outline the grid cannot
138
+ // express keep box corners and rely on their label — the alternative is a
139
+ // misleading outline, and docs/diagrams.md records which shapes render
140
+ // distinctly in ASCII.
141
+ // --------------------------------------------------------------------
142
+
143
+ // Document family — wavy bottom edge suggested by curve glyphs
144
+ document: {
145
+ unicode: { tl: '┌', tr: '┐', bl: '╰', br: '╮' },
146
+ ascii: { tl: '+', tr: '+', bl: "'", br: '~' },
147
+ },
148
+ 'stacked-document': {
149
+ unicode: { tl: '┌', tr: '╗', bl: '╰', br: '╮' },
150
+ ascii: { tl: '+', tr: '#', bl: "'", br: '~' },
151
+ },
152
+ 'stacked-process': {
153
+ unicode: { tl: '┌', tr: '╗', bl: '└', br: '╝' },
154
+ ascii: { tl: '+', tr: '#', bl: '+', br: '#' },
155
+ },
156
+
157
+ // Card — notched top-left corner
158
+ card: {
159
+ unicode: { tl: '╱', tr: '┐', bl: '└', br: '┘' },
160
+ ascii: { tl: '/', tr: '+', bl: '+', br: '+' },
161
+ },
162
+
163
+ // Interior-feature rectangles — box corners, feature not expressible
164
+ 'lined-process': {
165
+ unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },
166
+ ascii: { tl: '+', tr: '+', bl: '+', br: '+' },
167
+ },
168
+ 'divided-process': {
169
+ unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },
170
+ ascii: { tl: '+', tr: '+', bl: '+', br: '+' },
171
+ },
172
+ 'window-pane': {
173
+ unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },
174
+ ascii: { tl: '+', tr: '+', bl: '+', br: '+' },
175
+ },
176
+
177
+ // Triangles
178
+ triangle: {
179
+ unicode: { tl: '╱', tr: '╲', bl: '└', br: '┘' },
180
+ ascii: { tl: '/', tr: '\\', bl: '+', br: '+' },
181
+ },
182
+ 'flipped-triangle': {
183
+ unicode: { tl: '┌', tr: '┐', bl: '╲', br: '╱' },
184
+ ascii: { tl: '+', tr: '+', bl: '\\', br: '/' },
185
+ },
186
+
187
+ // Circles
188
+ 'filled-circle': {
189
+ unicode: { tl: '●', tr: '●', bl: '●', br: '●' },
190
+ ascii: { tl: '*', tr: '*', bl: '*', br: '*' },
191
+ },
192
+ 'crossed-circle': {
193
+ unicode: { tl: '╳', tr: '╳', bl: '╳', br: '╳' },
194
+ ascii: { tl: 'X', tr: 'X', bl: 'X', br: 'X' },
195
+ },
196
+
197
+ // Fork/join bar
198
+ 'fork-join': {
199
+ unicode: { tl: '━', tr: '━', bl: '━', br: '━' },
200
+ ascii: { tl: '=', tr: '=', bl: '=', br: '=' },
201
+ },
202
+
203
+ // Notched pentagon — clipped top corners
204
+ 'notched-pentagon': {
205
+ unicode: { tl: '╱', tr: '╲', bl: '└', br: '┘' },
206
+ ascii: { tl: '/', tr: '\\', bl: '+', br: '+' },
207
+ },
208
+
209
+ // Sloped rectangle (manual input) — sloped top edge
210
+ 'sloped-rectangle': {
211
+ unicode: { tl: '╱', tr: '┐', bl: '└', br: '┘' },
212
+ ascii: { tl: '/', tr: '+', bl: '+', br: '+' },
213
+ },
214
+
215
+ // Flag / paper tape — wavy top and bottom
216
+ flag: {
217
+ unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },
218
+ ascii: { tl: '~', tr: '~', bl: '~', br: '~' },
219
+ },
220
+
221
+ // Bow-tie rectangle (stored data) — concave sides
222
+ 'bow-tie-rectangle': {
223
+ unicode: { tl: '╲', tr: '╱', bl: '╱', br: '╲' },
224
+ ascii: { tl: '\\', tr: '/', bl: '/', br: '\\' },
225
+ },
226
+
227
+ // Delay — one rounded end
228
+ 'half-rounded-rectangle': {
229
+ unicode: { tl: '┌', tr: '╮', bl: '└', br: '╯' },
230
+ ascii: { tl: '+', tr: '.', bl: '+', br: "'" },
231
+ },
232
+
233
+ // Braces
234
+ brace: {
235
+ unicode: { tl: '╭', tr: '┐', bl: '╰', br: '┘' },
236
+ ascii: { tl: '{', tr: '+', bl: '{', br: '+' },
237
+ },
238
+ 'brace-right': {
239
+ unicode: { tl: '┌', tr: '╮', bl: '└', br: '╯' },
240
+ ascii: { tl: '+', tr: '}', bl: '+', br: '}' },
241
+ },
242
+ braces: {
243
+ unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },
244
+ ascii: { tl: '{', tr: '}', bl: '{', br: '}' },
245
+ },
246
+
247
+ // Lightning bolt (communication link)
248
+ bolt: {
249
+ unicode: { tl: '╱', tr: '╲', bl: '╲', br: '╱' },
250
+ ascii: { tl: '/', tr: '\\', bl: '\\', br: '/' },
251
+ },
252
+
253
+ // Bare text and anchor — no visible outline
254
+ text: {
255
+ unicode: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },
256
+ ascii: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },
257
+ },
258
+ anchor: {
259
+ unicode: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },
260
+ ascii: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },
261
+ },
262
+ }
263
+
264
+ /**
265
+ * Get corner characters for a shape type.
266
+ */
267
+ export function getCorners(
268
+ shape: AsciiNodeShape,
269
+ useAscii: boolean,
270
+ ): CornerChars {
271
+ const corners = SHAPE_CORNERS[shape] ?? SHAPE_CORNERS.rectangle
272
+ return useAscii ? corners.ascii : corners.unicode
273
+ }
@@ -0,0 +1,31 @@
1
+ // ============================================================================
2
+ // Diamond shape renderer — uses corner decorators instead of diagonals
3
+ // ============================================================================
4
+
5
+ import type { ShapeRenderer } from './types.ts'
6
+ import {
7
+ getBoxDimensions,
8
+ renderBox,
9
+ getBoxAttachmentPoint,
10
+ } from './rectangle.ts'
11
+ import { getCorners } from './corners.ts'
12
+
13
+ /**
14
+ * Diamond shape renderer.
15
+ * Uses diamond markers (◇) at corners to indicate decision node semantics.
16
+ *
17
+ * Renders as:
18
+ * ◇─────────◇
19
+ * │ Label │
20
+ * ◇─────────◇
21
+ */
22
+ export const diamondRenderer: ShapeRenderer = {
23
+ getDimensions: getBoxDimensions,
24
+
25
+ render(label, dimensions, options) {
26
+ const corners = getCorners('diamond', options.useAscii)
27
+ return renderBox(label, dimensions, corners, options.useAscii)
28
+ },
29
+
30
+ getAttachmentPoint: getBoxAttachmentPoint,
31
+ }
@@ -0,0 +1,35 @@
1
+ // ============================================================================
2
+ // Hexagon shape renderer — uses corner decorators instead of diagonals
3
+ // ============================================================================
4
+
5
+ import type { ShapeRenderer } from './types.ts'
6
+ import {
7
+ getBoxDimensions,
8
+ renderBox,
9
+ getBoxAttachmentPoint,
10
+ } from './rectangle.ts'
11
+ import { getCorners } from './corners.ts'
12
+
13
+ /**
14
+ * Hexagon shape renderer.
15
+ * Uses crop-corner markers (⌜⌝⌞⌟, see corners.ts's `SHAPE_CORNERS.hexagon`)
16
+ * to indicate process node semantics — not the hexagon glyph ⬡ (U+2B21)
17
+ * this comment used to claim: JetBrains Mono NL has no glyph for ⬡ at all,
18
+ * and the renderer hasn't actually emitted it since corners.ts moved to
19
+ * the monospace-safe crop-corner style. See issue #1062.
20
+ *
21
+ * Renders as:
22
+ * ⌜─────────⌝
23
+ * │ Label │
24
+ * ⌞─────────⌟
25
+ */
26
+ export const hexagonRenderer: ShapeRenderer = {
27
+ getDimensions: getBoxDimensions,
28
+
29
+ render(label, dimensions, options) {
30
+ const corners = getCorners('hexagon', options.useAscii)
31
+ return renderBox(label, dimensions, corners, options.useAscii)
32
+ },
33
+
34
+ getAttachmentPoint: getBoxAttachmentPoint,
35
+ }
@@ -0,0 +1,123 @@
1
+ // ============================================================================
2
+ // Shape registry — pluggable ASCII shape renderers
3
+ // ============================================================================
4
+
5
+ import type {
6
+ AsciiNodeShape,
7
+ Canvas,
8
+ DrawingCoord,
9
+ Direction,
10
+ } from '../types.ts'
11
+ import type {
12
+ ShapeRenderer,
13
+ ShapeDimensions,
14
+ ShapeRenderOptions,
15
+ ShapeRegistry,
16
+ } from './types.ts'
17
+
18
+ // Import all shape renderers
19
+ import { rectangleRenderer } from './rectangle.ts'
20
+ import { diamondRenderer } from './diamond.ts'
21
+ import { circleRenderer } from './circle.ts'
22
+ import { stateStartRenderer, stateEndRenderer } from './state.ts'
23
+ import { roundedRenderer } from './rounded.ts'
24
+ import { stadiumRenderer } from './stadium.ts'
25
+ import { hexagonRenderer } from './hexagon.ts'
26
+ import {
27
+ subroutineRenderer,
28
+ doublecircleRenderer,
29
+ cylinderRenderer,
30
+ asymmetricRenderer,
31
+ trapezoidRenderer,
32
+ trapezoidAltRenderer,
33
+ parallelogramRenderer,
34
+ parallelogramAltRenderer,
35
+ } from './special.ts'
36
+
37
+ // Re-export types
38
+ export type {
39
+ ShapeRenderer,
40
+ ShapeDimensions,
41
+ ShapeRenderOptions,
42
+ ShapeRegistry,
43
+ }
44
+
45
+ /**
46
+ * Global shape registry — maps shape types to their renderers.
47
+ * Rectangle is the default fallback for unregistered shapes.
48
+ */
49
+ export const shapeRegistry: ShapeRegistry = new Map<
50
+ AsciiNodeShape,
51
+ ShapeRenderer
52
+ >([
53
+ // Core shapes
54
+ ['rectangle', rectangleRenderer],
55
+ ['rounded', roundedRenderer],
56
+ ['diamond', diamondRenderer],
57
+ ['stadium', stadiumRenderer],
58
+ ['circle', circleRenderer],
59
+
60
+ // Batch 1 additions
61
+ ['subroutine', subroutineRenderer],
62
+ ['doublecircle', doublecircleRenderer],
63
+ ['hexagon', hexagonRenderer],
64
+
65
+ // Batch 2 additions
66
+ ['cylinder', cylinderRenderer],
67
+ ['asymmetric', asymmetricRenderer],
68
+ ['trapezoid', trapezoidRenderer],
69
+ ['trapezoid-alt', trapezoidAltRenderer],
70
+ ['parallelogram', parallelogramRenderer],
71
+ ['parallelogram-alt', parallelogramAltRenderer],
72
+
73
+ // State diagram pseudo-states
74
+ ['state-start', stateStartRenderer],
75
+ ['state-end', stateEndRenderer],
76
+ ])
77
+
78
+ /**
79
+ * Get the renderer for a shape type, falling back to rectangle.
80
+ */
81
+ export function getShapeRenderer(shape: AsciiNodeShape): ShapeRenderer {
82
+ return shapeRegistry.get(shape) ?? rectangleRenderer
83
+ }
84
+
85
+ /**
86
+ * Render a node shape to a canvas.
87
+ * This is the main entry point for shape rendering.
88
+ */
89
+ export function renderShape(
90
+ shape: AsciiNodeShape,
91
+ label: string,
92
+ options: ShapeRenderOptions,
93
+ ): Canvas {
94
+ const renderer = getShapeRenderer(shape)
95
+ const dimensions = renderer.getDimensions(label, options)
96
+ return renderer.render(label, dimensions, options)
97
+ }
98
+
99
+ /**
100
+ * Get dimensions for a shape given a label.
101
+ * Used during layout to determine node size.
102
+ */
103
+ export function getShapeDimensions(
104
+ shape: AsciiNodeShape,
105
+ label: string,
106
+ options: ShapeRenderOptions,
107
+ ): ShapeDimensions {
108
+ const renderer = getShapeRenderer(shape)
109
+ return renderer.getDimensions(label, options)
110
+ }
111
+
112
+ /**
113
+ * Get edge attachment point for a shape.
114
+ */
115
+ export function getShapeAttachmentPoint(
116
+ shape: AsciiNodeShape,
117
+ dir: Direction,
118
+ dimensions: ShapeDimensions,
119
+ baseCoord: DrawingCoord,
120
+ ): DrawingCoord {
121
+ const renderer = getShapeRenderer(shape)
122
+ return renderer.getAttachmentPoint(dir, dimensions, baseCoord)
123
+ }
@@ -0,0 +1,199 @@
1
+ // ============================================================================
2
+ // Rectangle shape renderer — standard box with corners
3
+ // ============================================================================
4
+ //
5
+ // This module provides the base box rendering used by all rectangular shapes.
6
+ // The renderBox() function accepts custom corner characters, allowing different
7
+ // shapes to reuse the same rendering logic with different visual markers.
8
+
9
+ import type { Canvas, DrawingCoord, Direction } from '../types.ts'
10
+ import {
11
+ Up,
12
+ Down,
13
+ Left,
14
+ Right,
15
+ UpperLeft,
16
+ UpperRight,
17
+ LowerLeft,
18
+ LowerRight,
19
+ } from '../types.ts'
20
+ import { mkCanvas } from '../canvas.ts'
21
+ import { splitLines } from '../multiline-utils.ts'
22
+ import { displayWidth, toDisplayCells } from '../display-width.ts'
23
+ import type {
24
+ ShapeRenderer,
25
+ ShapeDimensions,
26
+ ShapeRenderOptions,
27
+ } from './types.ts'
28
+ import { dirEquals } from '../edge-routing.ts'
29
+ import { type CornerChars, getCorners } from './corners.ts'
30
+
31
+ // ============================================================================
32
+ // Shared dimension calculation
33
+ // ============================================================================
34
+
35
+ /**
36
+ * Calculate standard box dimensions for any rectangular shape.
37
+ * Used by rectangle, circle, diamond, hexagon, etc.
38
+ */
39
+ export function getBoxDimensions(
40
+ label: string,
41
+ options: ShapeRenderOptions,
42
+ ): ShapeDimensions {
43
+ const lines = splitLines(label)
44
+ const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)
45
+ const lineCount = lines.length
46
+
47
+ // Width: 2*padding + maxLineWidth + 2 border chars
48
+ const innerWidth = 2 * options.padding + maxLineWidth
49
+ const width = innerWidth + 2
50
+
51
+ // Height: lineCount + 2*padding + 2 border chars
52
+ // Ensure innerHeight is odd for symmetric vertical centering
53
+ const rawInnerHeight = lineCount + 2 * options.padding
54
+ const innerHeight =
55
+ rawInnerHeight % 2 === 0 ? rawInnerHeight + 1 : rawInnerHeight
56
+ const height = innerHeight + 2
57
+
58
+ return {
59
+ width,
60
+ height,
61
+ labelArea: {
62
+ x: 1 + options.padding,
63
+ y: 1 + options.padding,
64
+ width: maxLineWidth,
65
+ height: lineCount,
66
+ },
67
+ // Grid layout: [border=1, content, border=1]
68
+ gridColumns: [1, innerWidth, 1],
69
+ gridRows: [1, innerHeight, 1],
70
+ }
71
+ }
72
+
73
+ // ============================================================================
74
+ // Shared box rendering
75
+ // ============================================================================
76
+
77
+ /**
78
+ * Render a box with custom corner characters.
79
+ * This is the core rendering function used by all rectangular shapes.
80
+ *
81
+ * @param label - Text to display in the box
82
+ * @param dimensions - Pre-calculated dimensions
83
+ * @param corners - Corner characters (tl, tr, bl, br)
84
+ * @param useAscii - Whether to use ASCII or Unicode for lines
85
+ */
86
+ export function renderBox(
87
+ label: string,
88
+ dimensions: ShapeDimensions,
89
+ corners: CornerChars,
90
+ useAscii: boolean,
91
+ ): Canvas {
92
+ const { width, height } = dimensions
93
+ const canvas = mkCanvas(width - 1, height - 1)
94
+
95
+ const from = { x: 0, y: 0 }
96
+ const to = { x: width - 1, y: height - 1 }
97
+
98
+ // Line characters
99
+ const hLine = useAscii ? '-' : '─'
100
+ const vLine = useAscii ? '|' : '│'
101
+
102
+ // Draw horizontal lines (top and bottom)
103
+ for (let x = from.x + 1; x < to.x; x++) {
104
+ canvas[x]![from.y] = hLine
105
+ canvas[x]![to.y] = hLine
106
+ }
107
+
108
+ // Draw vertical lines (left and right)
109
+ for (let y = from.y + 1; y < to.y; y++) {
110
+ canvas[from.x]![y] = vLine
111
+ canvas[to.x]![y] = vLine
112
+ }
113
+
114
+ // Draw corners
115
+ canvas[from.x]![from.y] = corners.tl
116
+ canvas[to.x]![from.y] = corners.tr
117
+ canvas[from.x]![to.y] = corners.bl
118
+ canvas[to.x]![to.y] = corners.br
119
+
120
+ // Center the multi-line label
121
+ const lines = splitLines(label)
122
+ const w = width - 1 // Match original grid-based width calculation
123
+ const h = height - 1
124
+ const centerY = Math.floor(h / 2)
125
+ const startY = centerY - Math.floor((lines.length - 1) / 2)
126
+
127
+ for (let i = 0; i < lines.length; i++) {
128
+ const line = lines[i]!
129
+ const textX = Math.floor(w / 2) - Math.ceil(displayWidth(line) / 2) + 1
130
+ const cells = toDisplayCells(line)
131
+ for (let j = 0; j < cells.length; j++) {
132
+ const x = textX + j
133
+ const y = startY + i
134
+ if (x >= 0 && x < canvas.length && y >= 0 && y < canvas[0]!.length) {
135
+ canvas[x]![y] = cells[j]!
136
+ }
137
+ }
138
+ }
139
+
140
+ return canvas
141
+ }
142
+
143
+ // ============================================================================
144
+ // Shared attachment point calculation
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Calculate edge attachment point for rectangular shapes.
149
+ * All box-based shapes use the same attachment logic.
150
+ */
151
+ export function getBoxAttachmentPoint(
152
+ dir: Direction,
153
+ dimensions: ShapeDimensions,
154
+ baseCoord: DrawingCoord,
155
+ ): DrawingCoord {
156
+ const { width, height } = dimensions
157
+ const centerX = baseCoord.x + Math.floor(width / 2)
158
+ const centerY = baseCoord.y + Math.floor(height / 2)
159
+
160
+ if (dirEquals(dir, Up)) return { x: centerX, y: baseCoord.y }
161
+ if (dirEquals(dir, Down)) return { x: centerX, y: baseCoord.y + height - 1 }
162
+ if (dirEquals(dir, Left)) return { x: baseCoord.x, y: centerY }
163
+ if (dirEquals(dir, Right)) return { x: baseCoord.x + width - 1, y: centerY }
164
+ if (dirEquals(dir, UpperLeft)) return { x: baseCoord.x, y: baseCoord.y }
165
+ if (dirEquals(dir, UpperRight))
166
+ return { x: baseCoord.x + width - 1, y: baseCoord.y }
167
+ if (dirEquals(dir, LowerLeft))
168
+ return { x: baseCoord.x, y: baseCoord.y + height - 1 }
169
+ if (dirEquals(dir, LowerRight))
170
+ return { x: baseCoord.x + width - 1, y: baseCoord.y + height - 1 }
171
+ // Middle
172
+ return { x: centerX, y: centerY }
173
+ }
174
+
175
+ // ============================================================================
176
+ // Rectangle renderer
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Rectangle shape renderer — the default box shape.
181
+ * Renders as:
182
+ * ┌─────────┐
183
+ * │ Label │
184
+ * └─────────┘
185
+ */
186
+ export const rectangleRenderer: ShapeRenderer = {
187
+ getDimensions: getBoxDimensions,
188
+
189
+ render(
190
+ label: string,
191
+ dimensions: ShapeDimensions,
192
+ options: ShapeRenderOptions,
193
+ ): Canvas {
194
+ const corners = getCorners('rectangle', options.useAscii)
195
+ return renderBox(label, dimensions, corners, options.useAscii)
196
+ },
197
+
198
+ getAttachmentPoint: getBoxAttachmentPoint,
199
+ }