@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.
- package/LICENSE +22 -0
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +5392 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
- package/src/__tests__/ascii-arrowhead-direction-1083.test.ts +51 -0
- package/src/__tests__/ascii-canvas-first-claim-wins-1093.test.ts +89 -0
- package/src/__tests__/ascii-canvas-size-offset-1093.test.ts +95 -0
- package/src/__tests__/ascii-canvas-write.test.ts +132 -0
- package/src/__tests__/ascii-chain-edge-overlap-1067.test.ts +100 -0
- package/src/__tests__/ascii-charset-border-junctions.test.ts +63 -0
- package/src/__tests__/ascii-cjk-width.test.ts +150 -0
- package/src/__tests__/ascii-class-box-occupancy.test.ts +462 -0
- package/src/__tests__/ascii-class-column-width-488-489.test.ts +639 -0
- package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
- package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +114 -0
- package/src/__tests__/ascii-class-diagram-compartments.test.ts +70 -0
- package/src/__tests__/ascii-class-label-box-collision.test.ts +60 -0
- package/src/__tests__/ascii-class-label-row-collision-531.test.ts +135 -0
- package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +57 -0
- package/src/__tests__/ascii-class-padding.test.ts +104 -0
- package/src/__tests__/ascii-class-parent-alignment-971.test.ts +139 -0
- package/src/__tests__/ascii-class-parent-alignment-972.test.ts +208 -0
- package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +169 -0
- package/src/__tests__/ascii-combining-mark-width.test.ts +70 -0
- package/src/__tests__/ascii-coords-overlay.test.ts +64 -0
- package/src/__tests__/ascii-decision-lr-box-start.test.ts +106 -0
- package/src/__tests__/ascii-display-width-unit.test.ts +154 -0
- package/src/__tests__/ascii-draw-arrows-coverage.test.ts +224 -0
- package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +105 -0
- package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +72 -0
- package/src/__tests__/ascii-edge-ending-glyphs.test.ts +206 -0
- package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +95 -0
- package/src/__tests__/ascii-edge-routing-fixes.test.ts +178 -0
- package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +103 -0
- package/src/__tests__/ascii-edge-style-consistency-1067.test.ts +89 -0
- package/src/__tests__/ascii-edge-styles.test.ts +149 -0
- package/src/__tests__/ascii-emoji-cluster-width.test.ts +101 -0
- package/src/__tests__/ascii-er-box-occupancy.test.ts +251 -0
- package/src/__tests__/ascii-er-cardinality.test.ts +40 -0
- package/src/__tests__/ascii-er-corner-glyphs.test.ts +249 -0
- package/src/__tests__/ascii-er-jog-stray-line.test.ts +111 -0
- package/src/__tests__/ascii-er-label-padding.test.ts +118 -0
- package/src/__tests__/ascii-er-padding.test.ts +114 -0
- package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +458 -0
- package/src/__tests__/ascii-er-relationship-overwrite.test.ts +325 -0
- package/src/__tests__/ascii-er-stray-connectors.test.ts +344 -0
- package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +98 -0
- package/src/__tests__/ascii-er-vertical-one-marker.test.ts +110 -0
- package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +168 -0
- package/src/__tests__/ascii-lane-search.test.ts +207 -0
- package/src/__tests__/ascii-multibox-cjk-width.test.ts +184 -0
- package/src/__tests__/ascii-multiline.test.ts +288 -0
- package/src/__tests__/ascii-padding-edge-cases.test.ts +154 -0
- package/src/__tests__/ascii-pathfinder-route-edge.test.ts +184 -0
- package/src/__tests__/ascii-sequence-alt-else-label.test.ts +236 -0
- package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +217 -0
- package/src/__tests__/ascii-sequence-box-group.test.ts +159 -0
- package/src/__tests__/ascii-sequence-cjk-width.test.ts +235 -0
- package/src/__tests__/ascii-sequence-create-destroy.test.ts +114 -0
- package/src/__tests__/ascii-sequence-form-invariants.test.ts +432 -0
- package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +219 -0
- package/src/__tests__/ascii-sequence-notes.test.ts +61 -0
- package/src/__tests__/ascii-sequence-padding.test.ts +119 -0
- package/src/__tests__/ascii-sequence-self-arrow.test.ts +206 -0
- package/src/__tests__/ascii-shape-diamond.test.ts +38 -0
- package/src/__tests__/ascii-shape-rectangle.test.ts +258 -0
- package/src/__tests__/ascii-shape-rounded.test.ts +36 -0
- package/src/__tests__/ascii-shapes-circle.test.ts +41 -0
- package/src/__tests__/ascii-shapes-hexagon.test.ts +42 -0
- package/src/__tests__/ascii-shapes-special.test.ts +344 -0
- package/src/__tests__/ascii-shapes-stadium.test.ts +217 -0
- package/src/__tests__/ascii-shapes-state.test.ts +224 -0
- package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +130 -0
- package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +90 -0
- package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +152 -0
- package/src/__tests__/ascii-subgraph-title-padding.test.ts +77 -0
- package/src/__tests__/ascii-territory-unit.test.ts +219 -0
- package/src/__tests__/ascii-validate.test.ts +220 -0
- package/src/__tests__/ascii.test.ts +325 -0
- package/src/__tests__/class-arrow-directions.test.ts +505 -0
- package/src/__tests__/draw-lines.test.ts +93 -0
- package/src/__tests__/edge-cell-styles.test.ts +278 -0
- package/src/__tests__/grid-occupancy.test.ts +240 -0
- package/src/__tests__/helpers/ascii-form.ts +142 -0
- package/src/__tests__/helpers/terminal-display-width.ts +74 -0
- package/src/__tests__/pathfinder.test.ts +239 -0
- package/src/__tests__/testdata/ascii/ampersand_lhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_lhs_and_rhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_rhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_td_fanin.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_td_fanout.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_without_edge.txt +18 -0
- package/src/__tests__/testdata/ascii/back_reference_from_child.txt +10 -0
- package/src/__tests__/testdata/ascii/backlink_from_bottom.txt +22 -0
- package/src/__tests__/testdata/ascii/backlink_from_top.txt +22 -0
- package/src/__tests__/testdata/ascii/backlink_with_short_y_padding.txt +20 -0
- package/src/__tests__/testdata/ascii/cls_all_relationships.txt +19 -0
- package/src/__tests__/testdata/ascii/cls_annotation.txt +29 -0
- package/src/__tests__/testdata/ascii/cls_association.txt +14 -0
- package/src/__tests__/testdata/ascii/cls_basic.txt +15 -0
- package/src/__tests__/testdata/ascii/cls_dependency.txt +14 -0
- package/src/__tests__/testdata/ascii/cls_inheritance.txt +20 -0
- package/src/__tests__/testdata/ascii/cls_methods.txt +21 -0
- package/src/__tests__/testdata/ascii/comments.txt +23 -0
- package/src/__tests__/testdata/ascii/custom_padding.txt +10 -0
- package/src/__tests__/testdata/ascii/duplicate_labels.txt +19 -0
- package/src/__tests__/testdata/ascii/er_attributes.txt +21 -0
- package/src/__tests__/testdata/ascii/er_basic.txt +8 -0
- package/src/__tests__/testdata/ascii/er_identifying.txt +18 -0
- package/src/__tests__/testdata/ascii/flowchart_tb_simple.txt +29 -0
- package/src/__tests__/testdata/ascii/graph_bt_direction.txt +28 -0
- package/src/__tests__/testdata/ascii/graph_tb_direction.txt +26 -0
- package/src/__tests__/testdata/ascii/nested_subgraphs_with_labels.txt +36 -0
- package/src/__tests__/testdata/ascii/preserve_order_of_definition.txt +23 -0
- package/src/__tests__/testdata/ascii/self_reference.txt +10 -0
- package/src/__tests__/testdata/ascii/self_reference_with_edge.txt +10 -0
- package/src/__tests__/testdata/ascii/seq_basic.txt +17 -0
- package/src/__tests__/testdata/ascii/seq_multiple_messages.txt +25 -0
- package/src/__tests__/testdata/ascii/seq_self_message.txt +18 -0
- package/src/__tests__/testdata/ascii/single_node.txt +8 -0
- package/src/__tests__/testdata/ascii/single_node_longer_name.txt +8 -0
- package/src/__tests__/testdata/ascii/subgraph_complex_mixed.txt +38 -0
- package/src/__tests__/testdata/ascii/subgraph_complex_nested.txt +49 -0
- package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +47 -0
- package/src/__tests__/testdata/ascii/subgraph_empty.txt +10 -0
- package/src/__tests__/testdata/ascii/subgraph_mixed_nodes.txt +20 -0
- package/src/__tests__/testdata/ascii/subgraph_mixed_nodes_td.txt +48 -0
- package/src/__tests__/testdata/ascii/subgraph_multiple_edges.txt +32 -0
- package/src/__tests__/testdata/ascii/subgraph_multiple_nodes.txt +16 -0
- package/src/__tests__/testdata/ascii/subgraph_nested.txt +24 -0
- package/src/__tests__/testdata/ascii/subgraph_nested_with_external.txt +30 -0
- package/src/__tests__/testdata/ascii/subgraph_node_outside_lr.txt +17 -0
- package/src/__tests__/testdata/ascii/subgraph_single_node.txt +16 -0
- package/src/__tests__/testdata/ascii/subgraph_td_direction.txt +26 -0
- package/src/__tests__/testdata/ascii/subgraph_td_multiple.txt +44 -0
- package/src/__tests__/testdata/ascii/subgraph_td_multiple_paddingy.txt +42 -0
- package/src/__tests__/testdata/ascii/subgraph_three_levels_nested.txt +32 -0
- package/src/__tests__/testdata/ascii/subgraph_three_separate.txt +24 -0
- package/src/__tests__/testdata/ascii/subgraph_two_separate.txt +20 -0
- package/src/__tests__/testdata/ascii/subgraph_with_labels.txt +20 -0
- package/src/__tests__/testdata/ascii/three_nodes.txt +9 -0
- package/src/__tests__/testdata/ascii/three_nodes_single_line.txt +8 -0
- package/src/__tests__/testdata/ascii/two_layer_single_graph.txt +19 -0
- package/src/__tests__/testdata/ascii/two_layer_single_graph_longer_names.txt +19 -0
- package/src/__tests__/testdata/ascii/two_nodes_linked.txt +8 -0
- package/src/__tests__/testdata/ascii/two_nodes_longer_names.txt +8 -0
- package/src/__tests__/testdata/ascii/two_root_nodes.txt +19 -0
- package/src/__tests__/testdata/ascii/two_root_nodes_longer_names.txt +19 -0
- package/src/__tests__/testdata/ascii/two_single_root_nodes.txt +19 -0
- package/src/__tests__/testdata/unicode/ampersand_lhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_lhs_and_rhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_rhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_without_edge.txt +18 -0
- package/src/__tests__/testdata/unicode/back_reference_from_child.txt +10 -0
- package/src/__tests__/testdata/unicode/backlink_from_bottom.txt +22 -0
- package/src/__tests__/testdata/unicode/backlink_from_top.txt +22 -0
- package/src/__tests__/testdata/unicode/cls_all_relationships.txt +19 -0
- package/src/__tests__/testdata/unicode/cls_annotation.txt +29 -0
- package/src/__tests__/testdata/unicode/cls_association.txt +14 -0
- package/src/__tests__/testdata/unicode/cls_basic.txt +15 -0
- package/src/__tests__/testdata/unicode/cls_dependency.txt +14 -0
- package/src/__tests__/testdata/unicode/cls_inheritance.txt +20 -0
- package/src/__tests__/testdata/unicode/cls_methods.txt +21 -0
- package/src/__tests__/testdata/unicode/comments.txt +23 -0
- package/src/__tests__/testdata/unicode/duplicate_labels.txt +19 -0
- package/src/__tests__/testdata/unicode/er_attributes.txt +21 -0
- package/src/__tests__/testdata/unicode/er_basic.txt +8 -0
- package/src/__tests__/testdata/unicode/er_identifying.txt +18 -0
- package/src/__tests__/testdata/unicode/graph_bt_direction.txt +28 -0
- package/src/__tests__/testdata/unicode/preserve_order_of_definition.txt +23 -0
- package/src/__tests__/testdata/unicode/self_reference.txt +10 -0
- package/src/__tests__/testdata/unicode/self_reference_with_edge.txt +10 -0
- package/src/__tests__/testdata/unicode/seq_basic.txt +17 -0
- package/src/__tests__/testdata/unicode/seq_multiple_messages.txt +25 -0
- package/src/__tests__/testdata/unicode/seq_self_message.txt +18 -0
- package/src/__tests__/testdata/unicode/single_node.txt +8 -0
- package/src/__tests__/testdata/unicode/single_node_longer_name.txt +8 -0
- package/src/__tests__/testdata/unicode/three_nodes.txt +9 -0
- package/src/__tests__/testdata/unicode/three_nodes_single_line.txt +8 -0
- package/src/__tests__/testdata/unicode/two_layer_single_graph.txt +19 -0
- package/src/__tests__/testdata/unicode/two_layer_single_graph_longer_names.txt +19 -0
- package/src/__tests__/testdata/unicode/two_nodes_linked.txt +8 -0
- package/src/__tests__/testdata/unicode/two_nodes_longer_names.txt +8 -0
- package/src/__tests__/testdata/unicode/two_root_nodes.txt +19 -0
- package/src/__tests__/testdata/unicode/two_root_nodes_longer_names.txt +19 -0
- package/src/__tests__/testdata/unicode/two_single_root_nodes.txt +19 -0
- package/src/__tests__/xychart-ascii.test.ts +376 -0
- package/src/ansi.ts +490 -0
- package/src/canvas.ts +757 -0
- package/src/class-diagram.ts +2001 -0
- package/src/converter.ts +446 -0
- package/src/coords.ts +58 -0
- package/src/display-width.ts +151 -0
- package/src/draw-arrows.ts +593 -0
- package/src/draw-boxes.ts +267 -0
- package/src/draw-bundles.ts +611 -0
- package/src/draw-lines.ts +174 -0
- package/src/draw-subgraphs.ts +108 -0
- package/src/draw.ts +350 -0
- package/src/edge-bundling.ts +435 -0
- package/src/edge-cell-styles.ts +209 -0
- package/src/edge-routing.ts +1070 -0
- package/src/er-diagram.ts +1488 -0
- package/src/flowchart.ts +94 -0
- package/src/grid-occupancy.ts +234 -0
- package/src/grid.ts +1309 -0
- package/src/hyperlinks.ts +248 -0
- package/src/index.ts +163 -0
- package/src/lane-search.ts +68 -0
- package/src/multiline-utils.ts +82 -0
- package/src/pathfinder.ts +448 -0
- package/src/registry.ts +88 -0
- package/src/sequence.ts +1318 -0
- package/src/shapes/circle.ts +31 -0
- package/src/shapes/corners.ts +273 -0
- package/src/shapes/diamond.ts +31 -0
- package/src/shapes/hexagon.ts +35 -0
- package/src/shapes/index.ts +123 -0
- package/src/shapes/rectangle.ts +199 -0
- package/src/shapes/rounded.ts +31 -0
- package/src/shapes/special.ts +360 -0
- package/src/shapes/stadium.ts +122 -0
- package/src/shapes/state.ts +204 -0
- package/src/shapes/types.ts +78 -0
- package/src/territory.ts +136 -0
- package/src/types.ts +454 -0
- package/src/validate.ts +189 -0
- 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
|
+
}
|