@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,267 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ASCII renderer — node/box drawing
|
|
3
|
+
//
|
|
4
|
+
// Split out of draw.ts. Renders individual node shapes and the
|
|
5
|
+
// multi-section boxes used by class/ER diagram nodes.
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Canvas,
|
|
10
|
+
DrawingCoord,
|
|
11
|
+
AsciiGraph,
|
|
12
|
+
AsciiNode,
|
|
13
|
+
CharRole,
|
|
14
|
+
} from './types.ts'
|
|
15
|
+
import { requireGridCoord } from './types.ts'
|
|
16
|
+
import { mkCanvas, write } from './canvas.ts'
|
|
17
|
+
import { splitLines } from './multiline-utils.ts'
|
|
18
|
+
import { getCorners } from './shapes/corners.ts'
|
|
19
|
+
import { displayWidth, toDisplayCells } from './display-width.ts'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Node drawing — renders a node using shape-aware rendering
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Draw a node using its shape type.
|
|
27
|
+
* Returns a standalone canvas containing the rendered shape.
|
|
28
|
+
*
|
|
29
|
+
* For basic shapes (rectangle, rounded), uses grid-determined dimensions
|
|
30
|
+
* to ensure consistent sizing across nodes in the same column.
|
|
31
|
+
* For special shapes (diamond, circle, state pseudo-states, etc.),
|
|
32
|
+
* uses shape-specific dimension calculation but centers the content
|
|
33
|
+
* within the grid cell dimensions to ensure proper vertical alignment.
|
|
34
|
+
*/
|
|
35
|
+
export function drawNode(node: AsciiNode, graph: AsciiGraph): Canvas {
|
|
36
|
+
// All shapes use grid-determined dimensions to fill their allocated space.
|
|
37
|
+
// This ensures consistent sizing across nodes and eliminates gaps between
|
|
38
|
+
// nodes and subgraph borders. All shapes are rectangles with distinctive
|
|
39
|
+
// corner characters (defined in corners.ts) to indicate shape type.
|
|
40
|
+
return drawBoxWithGridDimensions(node, graph)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Draw a box shape using grid-determined dimensions.
|
|
45
|
+
* This ensures consistent sizing when multiple nodes share a column,
|
|
46
|
+
* and eliminates gaps between nodes and subgraph borders by filling
|
|
47
|
+
* the entire allocated grid space.
|
|
48
|
+
*
|
|
49
|
+
* All shapes are rendered as rectangles with distinctive corner characters
|
|
50
|
+
* (defined in corners.ts) to indicate shape type.
|
|
51
|
+
*/
|
|
52
|
+
function drawBoxWithGridDimensions(node: AsciiNode, graph: AsciiGraph): Canvas {
|
|
53
|
+
// Every node is guaranteed a gridCoord before drawing starts —
|
|
54
|
+
// createMapping (grid.ts) seeds even nodes in otherwise-unreachable
|
|
55
|
+
// cycles with a pseudo-root (see addPseudoRootsForUnreachableCycles)
|
|
56
|
+
// specifically so every node gets placed. requireGridCoord validates that
|
|
57
|
+
// invariant explicitly rather than trusting it silently across the module
|
|
58
|
+
// boundary, since the type checker can't see it (`gridCoord: GridCoord |
|
|
59
|
+
// null`).
|
|
60
|
+
const gc = requireGridCoord(node)
|
|
61
|
+
const useAscii = graph.config.useAscii
|
|
62
|
+
|
|
63
|
+
// Width spans 2 columns (border + content) - matching original behavior
|
|
64
|
+
let w = 0
|
|
65
|
+
for (let i = 0; i < 2; i++) {
|
|
66
|
+
w += graph.columnWidth.get(gc.x + i) ?? 0
|
|
67
|
+
}
|
|
68
|
+
// Height spans 2 rows (border + content)
|
|
69
|
+
let h = 0
|
|
70
|
+
for (let i = 0; i < 2; i++) {
|
|
71
|
+
h += graph.rowHeight.get(gc.y + i) ?? 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const from: DrawingCoord = { x: 0, y: 0 }
|
|
75
|
+
const to: DrawingCoord = { x: w, y: h }
|
|
76
|
+
const box = mkCanvas(Math.max(from.x, to.x), Math.max(from.y, to.y))
|
|
77
|
+
|
|
78
|
+
// Get corner characters for this shape type
|
|
79
|
+
const corners = getCorners(node.shape, useAscii)
|
|
80
|
+
|
|
81
|
+
// State-end uses double border to differentiate from state-start
|
|
82
|
+
const isDoubleBox = node.shape === 'state-end'
|
|
83
|
+
const hChar = useAscii ? (isDoubleBox ? '=' : '-') : isDoubleBox ? '═' : '─'
|
|
84
|
+
const vChar = useAscii ? (isDoubleBox ? '‖' : '|') : isDoubleBox ? '║' : '│'
|
|
85
|
+
|
|
86
|
+
// Double-box corners (for state-end)
|
|
87
|
+
const doubleCorners = useAscii
|
|
88
|
+
? { tl: '#', tr: '#', bl: '#', br: '#' }
|
|
89
|
+
: { tl: '╔', tr: '╗', bl: '╚', br: '╝' }
|
|
90
|
+
const effectiveCorners = isDoubleBox ? doubleCorners : corners
|
|
91
|
+
|
|
92
|
+
// Draw box border with shape-specific corners
|
|
93
|
+
for (let x = from.x + 1; x < to.x; x++) write(box, x, from.y, hChar)
|
|
94
|
+
for (let x = from.x + 1; x < to.x; x++) write(box, x, to.y, hChar)
|
|
95
|
+
for (let y = from.y + 1; y < to.y; y++) write(box, from.x, y, vChar)
|
|
96
|
+
for (let y = from.y + 1; y < to.y; y++) write(box, to.x, y, vChar)
|
|
97
|
+
write(box, from.x, from.y, effectiveCorners.tl)
|
|
98
|
+
write(box, to.x, from.y, effectiveCorners.tr)
|
|
99
|
+
write(box, from.x, to.y, effectiveCorners.bl)
|
|
100
|
+
write(box, to.x, to.y, effectiveCorners.br)
|
|
101
|
+
|
|
102
|
+
// Center the multi-line display label inside the box
|
|
103
|
+
const label = node.displayLabel
|
|
104
|
+
const lines = splitLines(label)
|
|
105
|
+
const textCenterY = from.y + Math.floor(h / 2)
|
|
106
|
+
const startY = textCenterY - Math.floor((lines.length - 1) / 2)
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < lines.length; i++) {
|
|
109
|
+
const line = lines[i]!
|
|
110
|
+
const lineWidth = displayWidth(line)
|
|
111
|
+
const textX = from.x + Math.floor(w / 2) - Math.ceil(lineWidth / 2) + 1
|
|
112
|
+
const cells = toDisplayCells(line)
|
|
113
|
+
for (let j = 0; j < cells.length; j++) {
|
|
114
|
+
write(box, textX + j, startY + i, cells[j]!)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return box
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Draw a node box with centered label text.
|
|
123
|
+
* Returns a standalone canvas containing just the box.
|
|
124
|
+
* Box size is determined by the grid column/row sizes for the node's position.
|
|
125
|
+
*/
|
|
126
|
+
export function drawBox(node: AsciiNode, graph: AsciiGraph): Canvas {
|
|
127
|
+
return drawNode(node, graph)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Multi-section box drawing — for class and ER diagram nodes
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Classify a character from a multi-section box drawing as 'border' or 'text'.
|
|
136
|
+
* Shared by the class and ER diagram ASCII renderers, which both copy a
|
|
137
|
+
* `drawMultiBox` canvas onto their main canvas and need to tag each
|
|
138
|
+
* non-blank cell with its role for colored output.
|
|
139
|
+
*/
|
|
140
|
+
export function classifyBoxChar(ch: string): CharRole {
|
|
141
|
+
if (/^[┌┐└┘├┤┬┴┼│─╭╮╰╯+\-|]$/.test(ch)) return 'border'
|
|
142
|
+
return 'text'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Outer dimensions, in grid cells, of the box `drawMultiBox` would draw for
|
|
147
|
+
* `sections`.
|
|
148
|
+
*
|
|
149
|
+
* Class and ER diagrams both need these dimensions *before* drawing, to
|
|
150
|
+
* reserve grid space for the box during layout. Deriving them here — rather
|
|
151
|
+
* than each caller re-implementing the same arithmetic — keeps the reserved
|
|
152
|
+
* space and the drawn box in lockstep. When the two disagree (as they did
|
|
153
|
+
* while the callers measured `line.length` and this module measured display
|
|
154
|
+
* width), wide-character boxes overlap their neighbours.
|
|
155
|
+
*/
|
|
156
|
+
export function measureMultiBox(
|
|
157
|
+
sections: string[][],
|
|
158
|
+
padding: number = 1,
|
|
159
|
+
): { width: number; height: number } {
|
|
160
|
+
// A caller-supplied padding (ultimately AsciiConfig.boxBorderPadding, which
|
|
161
|
+
// reaches here unclamped from the public renderMermaidASCII options) could
|
|
162
|
+
// be negative — clamp rather than let it produce a negative box width for
|
|
163
|
+
// a short label (see issue #343's CodeRabbit review).
|
|
164
|
+
const clampedPadding = Math.max(0, padding)
|
|
165
|
+
// Width: widest line across all sections + 2*padding + 2 border chars.
|
|
166
|
+
// displayWidth (not line.length) because CJK/fullwidth glyphs occupy two
|
|
167
|
+
// terminal columns each, so a code-unit count under-measures them.
|
|
168
|
+
let maxTextWidth = 0
|
|
169
|
+
for (const section of sections) {
|
|
170
|
+
for (const line of section) {
|
|
171
|
+
maxTextWidth = Math.max(maxTextWidth, displayWidth(line))
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Height: sum of all section line counts + dividers + 2 border rows
|
|
176
|
+
let totalLines = 0
|
|
177
|
+
for (const section of sections) {
|
|
178
|
+
totalLines += Math.max(section.length, 1) // at least 1 row per section
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
width: maxTextWidth + 2 * clampedPadding + 2,
|
|
183
|
+
height: totalLines + (sections.length - 1) + 2,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Draw a multi-section box with horizontal dividers between sections.
|
|
189
|
+
* Used by class diagrams (header | attributes | methods) and ER diagrams (header | attributes).
|
|
190
|
+
* Each section is an array of text lines to render left-aligned with padding.
|
|
191
|
+
*
|
|
192
|
+
* @param sections - Array of sections, each section is an array of text lines
|
|
193
|
+
* @param useAscii - true for ASCII chars, false for Unicode box-drawing
|
|
194
|
+
* @param padding - horizontal padding inside the box (default 1)
|
|
195
|
+
* @returns A standalone Canvas containing the multi-section box
|
|
196
|
+
*/
|
|
197
|
+
export function drawMultiBox(
|
|
198
|
+
sections: string[][],
|
|
199
|
+
useAscii: boolean,
|
|
200
|
+
padding: number = 1,
|
|
201
|
+
): Canvas {
|
|
202
|
+
// Clamp here too (not just inside measureMultiBox) since `padding` is also
|
|
203
|
+
// used directly below for each line's startX — see the comment in
|
|
204
|
+
// measureMultiBox for why a negative value can reach this function.
|
|
205
|
+
const clampedPadding = Math.max(0, padding)
|
|
206
|
+
const { width: boxWidth, height: boxHeight } = measureMultiBox(
|
|
207
|
+
sections,
|
|
208
|
+
clampedPadding,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
// Box-drawing characters
|
|
212
|
+
const hLine = useAscii ? '-' : '─'
|
|
213
|
+
const vLine = useAscii ? '|' : '│'
|
|
214
|
+
const tl = useAscii ? '+' : '┌'
|
|
215
|
+
const tr = useAscii ? '+' : '┐'
|
|
216
|
+
const bl = useAscii ? '+' : '└'
|
|
217
|
+
const br = useAscii ? '+' : '┘'
|
|
218
|
+
const divL = useAscii ? '+' : '├'
|
|
219
|
+
const divR = useAscii ? '+' : '┤'
|
|
220
|
+
|
|
221
|
+
const canvas = mkCanvas(boxWidth - 1, boxHeight - 1)
|
|
222
|
+
|
|
223
|
+
// Top border
|
|
224
|
+
write(canvas, 0, 0, tl)
|
|
225
|
+
for (let x = 1; x < boxWidth - 1; x++) write(canvas, x, 0, hLine)
|
|
226
|
+
write(canvas, boxWidth - 1, 0, tr)
|
|
227
|
+
|
|
228
|
+
// Bottom border
|
|
229
|
+
write(canvas, 0, boxHeight - 1, bl)
|
|
230
|
+
for (let x = 1; x < boxWidth - 1; x++) write(canvas, x, boxHeight - 1, hLine)
|
|
231
|
+
write(canvas, boxWidth - 1, boxHeight - 1, br)
|
|
232
|
+
|
|
233
|
+
// Left and right borders (full height)
|
|
234
|
+
for (let y = 1; y < boxHeight - 1; y++) {
|
|
235
|
+
write(canvas, 0, y, vLine)
|
|
236
|
+
write(canvas, boxWidth - 1, y, vLine)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Render sections with dividers
|
|
240
|
+
let row = 1 // current y position (starts after top border)
|
|
241
|
+
for (let s = 0; s < sections.length; s++) {
|
|
242
|
+
const section = sections[s]!
|
|
243
|
+
const lines = section.length > 0 ? section : ['']
|
|
244
|
+
|
|
245
|
+
// Draw section text lines. toDisplayCells (not line[i]) so a wide glyph
|
|
246
|
+
// claims the two grid cells it renders across, and a surrogate pair is
|
|
247
|
+
// never split across two cells.
|
|
248
|
+
for (const line of lines) {
|
|
249
|
+
const startX = 1 + clampedPadding
|
|
250
|
+
const cells = toDisplayCells(line)
|
|
251
|
+
for (let i = 0; i < cells.length; i++) {
|
|
252
|
+
write(canvas, startX + i, row, cells[i]!)
|
|
253
|
+
}
|
|
254
|
+
row++
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Draw divider after each section except the last
|
|
258
|
+
if (s < sections.length - 1) {
|
|
259
|
+
write(canvas, 0, row, divL)
|
|
260
|
+
for (let x = 1; x < boxWidth - 1; x++) write(canvas, x, row, hLine)
|
|
261
|
+
write(canvas, boxWidth - 1, row, divR)
|
|
262
|
+
row++
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return canvas
|
|
267
|
+
}
|