@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
+ // Rounded rectangle shape renderer — uses rounded corner decorators
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
+ * Rounded rectangle shape renderer.
15
+ * Uses rounded corner markers (╭╮╰╯) to indicate soft edges.
16
+ *
17
+ * Renders as:
18
+ * ╭─────────╮
19
+ * │ Label │
20
+ * ╰─────────╯
21
+ */
22
+ export const roundedRenderer: ShapeRenderer = {
23
+ getDimensions: getBoxDimensions,
24
+
25
+ render(label, dimensions, options) {
26
+ const corners = getCorners('rounded', options.useAscii)
27
+ return renderBox(label, dimensions, corners, options.useAscii)
28
+ },
29
+
30
+ getAttachmentPoint: getBoxAttachmentPoint,
31
+ }
@@ -0,0 +1,360 @@
1
+ // ============================================================================
2
+ // Special shape renderers — subroutine, doublecircle, cylinder, etc.
3
+ // ============================================================================
4
+ //
5
+ // Some shapes have unique internal structure (subroutine, cylinder) and keep
6
+ // custom rendering. Others use the corner decorator pattern for simplicity.
7
+
8
+ import type { Canvas } from '../types.ts'
9
+ import { mkCanvas } from '../canvas.ts'
10
+ import { splitLines } from '../multiline-utils.ts'
11
+ import { displayWidth, toDisplayCells } from '../display-width.ts'
12
+ import type {
13
+ ShapeRenderer,
14
+ ShapeDimensions,
15
+ ShapeRenderOptions,
16
+ } from './types.ts'
17
+ import {
18
+ getBoxDimensions,
19
+ renderBox,
20
+ getBoxAttachmentPoint,
21
+ } from './rectangle.ts'
22
+ import { getCorners } from './corners.ts'
23
+
24
+ // ============================================================================
25
+ // Subroutine — keeps custom double-border rendering
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Subroutine shape renderer — double-bordered rectangle.
30
+ * Renders as:
31
+ * ┌┬─────────┬┐
32
+ * ││ Label ││
33
+ * └┴─────────┴┘
34
+ */
35
+ export const subroutineRenderer: ShapeRenderer = {
36
+ getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {
37
+ const lines = splitLines(label)
38
+ const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)
39
+ const lineCount = lines.length
40
+
41
+ const innerWidth = 2 * options.padding + maxLineWidth
42
+ const width = innerWidth + 4 // Double borders on each side
43
+ const innerHeight = lineCount + 2 * options.padding
44
+ const height = innerHeight + 2
45
+
46
+ return {
47
+ width,
48
+ height,
49
+ labelArea: {
50
+ x: 2 + options.padding,
51
+ y: 1 + options.padding,
52
+ width: maxLineWidth,
53
+ height: lineCount,
54
+ },
55
+ gridColumns: [2, innerWidth, 2],
56
+ gridRows: [1, innerHeight, 1],
57
+ }
58
+ },
59
+
60
+ render(
61
+ label: string,
62
+ dimensions: ShapeDimensions,
63
+ options: ShapeRenderOptions,
64
+ ): Canvas {
65
+ const { width, height } = dimensions
66
+ const canvas = mkCanvas(width - 1, height - 1)
67
+
68
+ const hChar = options.useAscii ? '-' : '─'
69
+ const vChar = options.useAscii ? '|' : '│'
70
+
71
+ // Top border
72
+ canvas[0]![0] = options.useAscii ? '+' : '┌'
73
+ canvas[1]![0] = options.useAscii ? '+' : '┬'
74
+ for (let x = 2; x < width - 2; x++) canvas[x]![0] = hChar
75
+ canvas[width - 2]![0] = options.useAscii ? '+' : '┬'
76
+ canvas[width - 1]![0] = options.useAscii ? '+' : '┐'
77
+
78
+ // Sides with double border
79
+ for (let y = 1; y < height - 1; y++) {
80
+ canvas[0]![y] = vChar
81
+ canvas[1]![y] = vChar
82
+ canvas[width - 2]![y] = vChar
83
+ canvas[width - 1]![y] = vChar
84
+ }
85
+
86
+ // Bottom border
87
+ canvas[0]![height - 1] = options.useAscii ? '+' : '└'
88
+ canvas[1]![height - 1] = options.useAscii ? '+' : '┴'
89
+ for (let x = 2; x < width - 2; x++) canvas[x]![height - 1] = hChar
90
+ canvas[width - 2]![height - 1] = options.useAscii ? '+' : '┴'
91
+ canvas[width - 1]![height - 1] = options.useAscii ? '+' : '┘'
92
+
93
+ // Center the label
94
+ const lines = splitLines(label)
95
+ const centerY = Math.floor(height / 2)
96
+ const startY = centerY - Math.floor((lines.length - 1) / 2)
97
+
98
+ for (let i = 0; i < lines.length; i++) {
99
+ const line = lines[i]!
100
+ const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2)
101
+ const cells = toDisplayCells(line)
102
+ for (let j = 0; j < cells.length; j++) {
103
+ const x = textX + j
104
+ const y = startY + i
105
+ if (x > 1 && x < width - 2 && y > 0 && y < height - 1) {
106
+ canvas[x]![y] = cells[j]!
107
+ }
108
+ }
109
+ }
110
+
111
+ return canvas
112
+ },
113
+
114
+ getAttachmentPoint: getBoxAttachmentPoint,
115
+ }
116
+
117
+ // ============================================================================
118
+ // Double circle — uses corner decorators
119
+ // ============================================================================
120
+
121
+ /**
122
+ * Double circle shape renderer.
123
+ * Uses double circle markers (◎) at corners.
124
+ *
125
+ * Renders as:
126
+ * ◎─────────◎
127
+ * │ Label │
128
+ * ◎─────────◎
129
+ */
130
+ export const doublecircleRenderer: ShapeRenderer = {
131
+ getDimensions: getBoxDimensions,
132
+
133
+ render(label, dimensions, options) {
134
+ const corners = getCorners('doublecircle', options.useAscii)
135
+ return renderBox(label, dimensions, corners, options.useAscii)
136
+ },
137
+
138
+ getAttachmentPoint: getBoxAttachmentPoint,
139
+ }
140
+
141
+ // ============================================================================
142
+ // Cylinder — keeps custom rendering for database appearance
143
+ // ============================================================================
144
+
145
+ /**
146
+ * Cylinder shape renderer — database symbol.
147
+ * Renders as:
148
+ * ╭─────╮
149
+ * │─────│
150
+ * │ DB │
151
+ * │─────│
152
+ * ╰─────╯
153
+ */
154
+ export const cylinderRenderer: ShapeRenderer = {
155
+ getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {
156
+ const lines = splitLines(label)
157
+ const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)
158
+ const lineCount = lines.length
159
+
160
+ const innerWidth = 2 * options.padding + maxLineWidth
161
+ const width = innerWidth + 2
162
+ const innerHeight = lineCount + 2 * options.padding + 2 // Extra for curved top/bottom
163
+ const height = innerHeight + 2
164
+
165
+ return {
166
+ width,
167
+ height,
168
+ labelArea: {
169
+ x: 1 + options.padding,
170
+ y: 2 + options.padding,
171
+ width: maxLineWidth,
172
+ height: lineCount,
173
+ },
174
+ gridColumns: [1, innerWidth, 1],
175
+ gridRows: [2, innerHeight - 2, 2],
176
+ }
177
+ },
178
+
179
+ render(
180
+ label: string,
181
+ dimensions: ShapeDimensions,
182
+ options: ShapeRenderOptions,
183
+ ): Canvas {
184
+ const { width, height } = dimensions
185
+ const canvas = mkCanvas(width - 1, height - 1)
186
+
187
+ const hChar = options.useAscii ? '-' : '─'
188
+ const vChar = options.useAscii ? '|' : '│'
189
+
190
+ // Top ellipse
191
+ canvas[0]![0] = options.useAscii ? '.' : '╭'
192
+ for (let x = 1; x < width - 1; x++) canvas[x]![0] = hChar
193
+ canvas[width - 1]![0] = options.useAscii ? '.' : '╮'
194
+
195
+ // Second row - bottom of top ellipse
196
+ canvas[0]![1] = vChar
197
+ for (let x = 1; x < width - 1; x++) canvas[x]![1] = hChar
198
+ canvas[width - 1]![1] = vChar
199
+
200
+ // Middle section
201
+ for (let y = 2; y < height - 2; y++) {
202
+ canvas[0]![y] = vChar
203
+ canvas[width - 1]![y] = vChar
204
+ }
205
+
206
+ // Second to last row - top of bottom ellipse
207
+ canvas[0]![height - 2] = vChar
208
+ for (let x = 1; x < width - 1; x++) canvas[x]![height - 2] = hChar
209
+ canvas[width - 1]![height - 2] = vChar
210
+
211
+ // Bottom ellipse
212
+ canvas[0]![height - 1] = options.useAscii ? "'" : '╰'
213
+ for (let x = 1; x < width - 1; x++) canvas[x]![height - 1] = hChar
214
+ canvas[width - 1]![height - 1] = options.useAscii ? "'" : '╯'
215
+
216
+ // Center the label
217
+ const lines = splitLines(label)
218
+ const centerY = Math.floor(height / 2)
219
+ const startY = centerY - Math.floor((lines.length - 1) / 2)
220
+
221
+ for (let i = 0; i < lines.length; i++) {
222
+ const line = lines[i]!
223
+ const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2)
224
+ const cells = toDisplayCells(line)
225
+ for (let j = 0; j < cells.length; j++) {
226
+ const x = textX + j
227
+ const y = startY + i
228
+ if (x > 0 && x < width - 1 && y > 1 && y < height - 2) {
229
+ canvas[x]![y] = cells[j]!
230
+ }
231
+ }
232
+ }
233
+
234
+ return canvas
235
+ },
236
+
237
+ getAttachmentPoint: getBoxAttachmentPoint,
238
+ }
239
+
240
+ // ============================================================================
241
+ // Asymmetric (flag) — uses corner decorators
242
+ // ============================================================================
243
+
244
+ /**
245
+ * Asymmetric (flag/banner) shape renderer.
246
+ * Uses arrow markers (▷) on left corners.
247
+ *
248
+ * Renders as:
249
+ * ▷─────────┐
250
+ * │ Label │
251
+ * ▷─────────┘
252
+ */
253
+ export const asymmetricRenderer: ShapeRenderer = {
254
+ getDimensions: getBoxDimensions,
255
+
256
+ render(label, dimensions, options) {
257
+ const corners = getCorners('asymmetric', options.useAscii)
258
+ return renderBox(label, dimensions, corners, options.useAscii)
259
+ },
260
+
261
+ getAttachmentPoint: getBoxAttachmentPoint,
262
+ }
263
+
264
+ // ============================================================================
265
+ // Trapezoid — uses corner decorators instead of diagonal sides
266
+ // ============================================================================
267
+
268
+ /**
269
+ * Trapezoid shape renderer — wider at bottom.
270
+ * Uses plain slash slope markers (/ \, see corners.ts's
271
+ * `SHAPE_CORNERS.trapezoid`) on top corners — not the ◸◹ corner-triangle
272
+ * glyphs this comment used to claim: JetBrains Mono NL has no glyph for
273
+ * those at all, and the renderer hasn't actually emitted them since
274
+ * corners.ts moved to the diagonal-slash style. See issue #1062.
275
+ *
276
+ * Renders as:
277
+ * /─────────\
278
+ * │ Label │
279
+ * └─────────┘
280
+ */
281
+ export const trapezoidRenderer: ShapeRenderer = {
282
+ getDimensions: getBoxDimensions,
283
+
284
+ render(label, dimensions, options) {
285
+ const corners = getCorners('trapezoid', options.useAscii)
286
+ return renderBox(label, dimensions, corners, options.useAscii)
287
+ },
288
+
289
+ getAttachmentPoint: getBoxAttachmentPoint,
290
+ }
291
+
292
+ // ============================================================================
293
+ // Trapezoid-alt — uses corner decorators instead of diagonal sides
294
+ // ============================================================================
295
+
296
+ /**
297
+ * Trapezoid-alt shape renderer — wider at top.
298
+ * Uses plain slash slope markers (\ /, see corners.ts's
299
+ * `SHAPE_CORNERS['trapezoid-alt']`) on bottom corners — not the ◺◿
300
+ * corner-triangle glyphs this comment used to claim: JetBrains Mono NL has
301
+ * no glyph for those at all, and the renderer hasn't actually emitted them
302
+ * since corners.ts moved to the diagonal-slash style. See issue #1062.
303
+ *
304
+ * Renders as:
305
+ * ┌─────────┐
306
+ * │ Label │
307
+ * \─────────/
308
+ */
309
+ export const trapezoidAltRenderer: ShapeRenderer = {
310
+ getDimensions: getBoxDimensions,
311
+
312
+ render(label, dimensions, options) {
313
+ const corners = getCorners('trapezoid-alt', options.useAscii)
314
+ return renderBox(label, dimensions, corners, options.useAscii)
315
+ },
316
+
317
+ getAttachmentPoint: getBoxAttachmentPoint,
318
+ }
319
+
320
+ // ============================================================================
321
+ // Parallelogram — both sides slope the same way
322
+ // ============================================================================
323
+
324
+ /**
325
+ * Parallelogram shape renderer — leans right.
326
+ *
327
+ * Renders as:
328
+ * /─────────/
329
+ * │ Label │
330
+ * /─────────/
331
+ */
332
+ export const parallelogramRenderer: ShapeRenderer = {
333
+ getDimensions: getBoxDimensions,
334
+
335
+ render(label, dimensions, options) {
336
+ const corners = getCorners('parallelogram', options.useAscii)
337
+ return renderBox(label, dimensions, corners, options.useAscii)
338
+ },
339
+
340
+ getAttachmentPoint: getBoxAttachmentPoint,
341
+ }
342
+
343
+ /**
344
+ * Parallelogram-alt shape renderer — leans left.
345
+ *
346
+ * Renders as:
347
+ * \─────────\
348
+ * │ Label │
349
+ * \─────────\
350
+ */
351
+ export const parallelogramAltRenderer: ShapeRenderer = {
352
+ getDimensions: getBoxDimensions,
353
+
354
+ render(label, dimensions, options) {
355
+ const corners = getCorners('parallelogram-alt', options.useAscii)
356
+ return renderBox(label, dimensions, corners, options.useAscii)
357
+ },
358
+
359
+ getAttachmentPoint: getBoxAttachmentPoint,
360
+ }
@@ -0,0 +1,122 @@
1
+ // ============================================================================
2
+ // Stadium (pill) shape renderer — special parentheses-based rendering
3
+ // ============================================================================
4
+ //
5
+ // Stadium has unique rendering: single-line is inline `(Label)`, multi-line
6
+ // uses parentheses or rounded corners. This differs from other shapes that
7
+ // use corner decorators with box lines.
8
+
9
+ import type { Canvas } from '../types.ts'
10
+ import { mkCanvas } from '../canvas.ts'
11
+ import { splitLines } from '../multiline-utils.ts'
12
+ import { displayWidth, toDisplayCells } from '../display-width.ts'
13
+ import type {
14
+ ShapeRenderer,
15
+ ShapeDimensions,
16
+ ShapeRenderOptions,
17
+ } from './types.ts'
18
+ import { getBoxAttachmentPoint } from './rectangle.ts'
19
+
20
+ /**
21
+ * Stadium (pill) shape renderer.
22
+ *
23
+ * Single-line: ( Label )
24
+ *
25
+ * Multi-line unicode:
26
+ * ╭──────────╮
27
+ * │ Label │
28
+ * ╰──────────╯
29
+ *
30
+ * Multi-line ASCII:
31
+ * (----------)
32
+ * ( Label )
33
+ * (----------)
34
+ */
35
+ export const stadiumRenderer: ShapeRenderer = {
36
+ getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {
37
+ const lines = splitLines(label)
38
+ const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)
39
+ const lineCount = lines.length
40
+
41
+ const innerWidth = 2 * options.padding + maxLineWidth
42
+ const width = innerWidth + 4 // Extra for rounded ends
43
+ const innerHeight = lineCount + 2 * options.padding
44
+ const height = Math.max(innerHeight + 2, 3)
45
+
46
+ return {
47
+ width,
48
+ height,
49
+ labelArea: {
50
+ x: 2 + options.padding,
51
+ y: 1 + options.padding,
52
+ width: maxLineWidth,
53
+ height: lineCount,
54
+ },
55
+ gridColumns: [2, innerWidth, 2],
56
+ gridRows: [1, innerHeight, 1],
57
+ }
58
+ },
59
+
60
+ render(
61
+ label: string,
62
+ dimensions: ShapeDimensions,
63
+ options: ShapeRenderOptions,
64
+ ): Canvas {
65
+ const { width, height } = dimensions
66
+ const canvas = mkCanvas(width - 1, height - 1)
67
+
68
+ const centerY = Math.floor(height / 2)
69
+ const hChar = options.useAscii ? '-' : '─'
70
+
71
+ if (height === 3) {
72
+ // Single row pill: ( Label )
73
+ canvas[0]![centerY] = '('
74
+ canvas[width - 1]![centerY] = ')'
75
+ } else if (!options.useAscii) {
76
+ // Multi-row stadium with rounded corners (unicode)
77
+ canvas[0]![0] = '╭'
78
+ for (let x = 1; x < width - 1; x++) canvas[x]![0] = hChar
79
+ canvas[width - 1]![0] = '╮'
80
+
81
+ for (let y = 1; y < height - 1; y++) {
82
+ canvas[0]![y] = '│'
83
+ canvas[width - 1]![y] = '│'
84
+ }
85
+
86
+ canvas[0]![height - 1] = '╰'
87
+ for (let x = 1; x < width - 1; x++) canvas[x]![height - 1] = hChar
88
+ canvas[width - 1]![height - 1] = '╯'
89
+ } else {
90
+ // Multi-row stadium ASCII — parentheses on all sides
91
+ for (let y = 0; y < height; y++) {
92
+ canvas[0]![y] = '('
93
+ canvas[width - 1]![y] = ')'
94
+ }
95
+ for (let x = 1; x < width - 1; x++) {
96
+ canvas[x]![0] = hChar
97
+ canvas[x]![height - 1] = hChar
98
+ }
99
+ }
100
+
101
+ // Center the label
102
+ const lines = splitLines(label)
103
+ const startY = centerY - Math.floor((lines.length - 1) / 2)
104
+
105
+ for (let i = 0; i < lines.length; i++) {
106
+ const line = lines[i]!
107
+ const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2)
108
+ const cells = toDisplayCells(line)
109
+ for (let j = 0; j < cells.length; j++) {
110
+ const x = textX + j
111
+ const y = startY + i
112
+ if (x > 0 && x < width - 1 && y >= 0 && y < height) {
113
+ canvas[x]![y] = cells[j]!
114
+ }
115
+ }
116
+ }
117
+
118
+ return canvas
119
+ },
120
+
121
+ getAttachmentPoint: getBoxAttachmentPoint,
122
+ }