@oh-my-pi/pi-utils 16.0.6 → 16.0.8
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/CHANGELOG.md +10 -0
- package/dist/types/mermaid-ascii.d.ts +1 -1
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
- package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
- package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
- package/package.json +2 -3
- package/src/mermaid-ascii.ts +1 -1
- package/src/vendor/mermaid-ascii/NOTICE +33 -0
- package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
- package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
- package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
- package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
- package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
- package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
- package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
- package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
- package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
- package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
- package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +215 -0
- package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
- package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
- package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
- package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
- package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
- package/src/vendor/mermaid-ascii/class/types.ts +121 -0
- package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
- package/src/vendor/mermaid-ascii/er/types.ts +91 -0
- package/src/vendor/mermaid-ascii/index.ts +14 -0
- package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
- package/src/vendor/mermaid-ascii/parser.ts +645 -0
- package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
- package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
- package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
- package/src/vendor/mermaid-ascii/types.ts +164 -0
- package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
- package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
- package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Shape registry — pluggable ASCII shape renderers
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import type { AsciiNodeShape, Canvas, DrawingCoord, Direction } from '../types'
|
|
6
|
+
import type { ShapeRenderer, ShapeDimensions, ShapeRenderOptions, ShapeRegistry } from './types'
|
|
7
|
+
|
|
8
|
+
// Import all shape renderers
|
|
9
|
+
import { rectangleRenderer } from './rectangle'
|
|
10
|
+
import { diamondRenderer } from './diamond'
|
|
11
|
+
import { circleRenderer } from './circle'
|
|
12
|
+
import { stateStartRenderer, stateEndRenderer } from './state'
|
|
13
|
+
import { roundedRenderer } from './rounded'
|
|
14
|
+
import { stadiumRenderer } from './stadium'
|
|
15
|
+
import { hexagonRenderer } from './hexagon'
|
|
16
|
+
import {
|
|
17
|
+
subroutineRenderer,
|
|
18
|
+
doublecircleRenderer,
|
|
19
|
+
cylinderRenderer,
|
|
20
|
+
asymmetricRenderer,
|
|
21
|
+
trapezoidRenderer,
|
|
22
|
+
trapezoidAltRenderer,
|
|
23
|
+
} from './special'
|
|
24
|
+
|
|
25
|
+
// Re-export types
|
|
26
|
+
export type { ShapeRenderer, ShapeDimensions, ShapeRenderOptions, ShapeRegistry }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Global shape registry — maps shape types to their renderers.
|
|
30
|
+
* Rectangle is the default fallback for unregistered shapes.
|
|
31
|
+
*/
|
|
32
|
+
export const shapeRegistry: ShapeRegistry = new Map<AsciiNodeShape, ShapeRenderer>([
|
|
33
|
+
// Core shapes
|
|
34
|
+
['rectangle', rectangleRenderer],
|
|
35
|
+
['rounded', roundedRenderer],
|
|
36
|
+
['diamond', diamondRenderer],
|
|
37
|
+
['stadium', stadiumRenderer],
|
|
38
|
+
['circle', circleRenderer],
|
|
39
|
+
|
|
40
|
+
// Batch 1 additions
|
|
41
|
+
['subroutine', subroutineRenderer],
|
|
42
|
+
['doublecircle', doublecircleRenderer],
|
|
43
|
+
['hexagon', hexagonRenderer],
|
|
44
|
+
|
|
45
|
+
// Batch 2 additions
|
|
46
|
+
['cylinder', cylinderRenderer],
|
|
47
|
+
['asymmetric', asymmetricRenderer],
|
|
48
|
+
['trapezoid', trapezoidRenderer],
|
|
49
|
+
['trapezoid-alt', trapezoidAltRenderer],
|
|
50
|
+
|
|
51
|
+
// State diagram pseudo-states
|
|
52
|
+
['state-start', stateStartRenderer],
|
|
53
|
+
['state-end', stateEndRenderer],
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the renderer for a shape type, falling back to rectangle.
|
|
58
|
+
*/
|
|
59
|
+
export function getShapeRenderer(shape: AsciiNodeShape): ShapeRenderer {
|
|
60
|
+
return shapeRegistry.get(shape) ?? rectangleRenderer
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render a node shape to a canvas.
|
|
65
|
+
* This is the main entry point for shape rendering.
|
|
66
|
+
*/
|
|
67
|
+
export function renderShape(
|
|
68
|
+
shape: AsciiNodeShape,
|
|
69
|
+
label: string,
|
|
70
|
+
options: ShapeRenderOptions
|
|
71
|
+
): Canvas {
|
|
72
|
+
const renderer = getShapeRenderer(shape)
|
|
73
|
+
const dimensions = renderer.getDimensions(label, options)
|
|
74
|
+
return renderer.render(label, dimensions, options)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get dimensions for a shape given a label.
|
|
79
|
+
* Used during layout to determine node size.
|
|
80
|
+
*/
|
|
81
|
+
export function getShapeDimensions(
|
|
82
|
+
shape: AsciiNodeShape,
|
|
83
|
+
label: string,
|
|
84
|
+
options: ShapeRenderOptions
|
|
85
|
+
): ShapeDimensions {
|
|
86
|
+
const renderer = getShapeRenderer(shape)
|
|
87
|
+
return renderer.getDimensions(label, options)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get edge attachment point for a shape.
|
|
92
|
+
*/
|
|
93
|
+
export function getShapeAttachmentPoint(
|
|
94
|
+
shape: AsciiNodeShape,
|
|
95
|
+
dir: Direction,
|
|
96
|
+
dimensions: ShapeDimensions,
|
|
97
|
+
baseCoord: DrawingCoord
|
|
98
|
+
): DrawingCoord {
|
|
99
|
+
const renderer = getShapeRenderer(shape)
|
|
100
|
+
return renderer.getAttachmentPoint(dir, dimensions, baseCoord)
|
|
101
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Rectangle shape renderer — standard box with corners
|
|
3
|
+
// ============================================================================
|
|
4
|
+
//
|
|
5
|
+
// This module provides the base box rendering used by all rectangular shapes.
|
|
6
|
+
// The renderBox() function accepts custom corner characters, allowing different
|
|
7
|
+
// shapes to reuse the same rendering logic with different visual markers.
|
|
8
|
+
|
|
9
|
+
import type { Canvas, DrawingCoord, Direction } from '../types'
|
|
10
|
+
import { Up, Down, Left, Right, UpperLeft, UpperRight, LowerLeft, LowerRight, Middle } from '../types'
|
|
11
|
+
import { mkCanvas } from '../canvas'
|
|
12
|
+
import { splitLines } from '../multiline-utils'
|
|
13
|
+
import type { ShapeRenderer, ShapeDimensions, ShapeRenderOptions } from './types'
|
|
14
|
+
import { dirEquals } from '../edge-routing'
|
|
15
|
+
import { type CornerChars, getCorners } from './corners'
|
|
16
|
+
import { displayWidth, toCells } from '../../text-metrics'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Shared dimension calculation
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Calculate standard box dimensions for any rectangular shape.
|
|
24
|
+
* Used by rectangle, circle, diamond, hexagon, etc.
|
|
25
|
+
*/
|
|
26
|
+
export function getBoxDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {
|
|
27
|
+
const lines = splitLines(label)
|
|
28
|
+
const maxLineWidth = Math.max(...lines.map(l => displayWidth(l)), 0)
|
|
29
|
+
const lineCount = lines.length
|
|
30
|
+
|
|
31
|
+
// Width: 2*padding + maxLineWidth + 2 border chars
|
|
32
|
+
const innerWidth = 2 * options.padding + maxLineWidth
|
|
33
|
+
const width = innerWidth + 2
|
|
34
|
+
|
|
35
|
+
// Height: lineCount + 2*padding + 2 border chars
|
|
36
|
+
// Ensure innerHeight is odd for symmetric vertical centering
|
|
37
|
+
const rawInnerHeight = lineCount + 2 * options.padding
|
|
38
|
+
const innerHeight = rawInnerHeight % 2 === 0 ? rawInnerHeight + 1 : rawInnerHeight
|
|
39
|
+
const height = innerHeight + 2
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
labelArea: {
|
|
45
|
+
x: 1 + options.padding,
|
|
46
|
+
y: 1 + options.padding,
|
|
47
|
+
width: maxLineWidth,
|
|
48
|
+
height: lineCount,
|
|
49
|
+
},
|
|
50
|
+
// Grid layout: [border=1, content, border=1]
|
|
51
|
+
gridColumns: [1, innerWidth, 1],
|
|
52
|
+
gridRows: [1, innerHeight, 1],
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Shared box rendering
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Render a box with custom corner characters.
|
|
62
|
+
* This is the core rendering function used by all rectangular shapes.
|
|
63
|
+
*
|
|
64
|
+
* @param label - Text to display in the box
|
|
65
|
+
* @param dimensions - Pre-calculated dimensions
|
|
66
|
+
* @param corners - Corner characters (tl, tr, bl, br)
|
|
67
|
+
* @param useAscii - Whether to use ASCII or Unicode for lines
|
|
68
|
+
*/
|
|
69
|
+
export function renderBox(
|
|
70
|
+
label: string,
|
|
71
|
+
dimensions: ShapeDimensions,
|
|
72
|
+
corners: CornerChars,
|
|
73
|
+
useAscii: boolean
|
|
74
|
+
): Canvas {
|
|
75
|
+
const { width, height } = dimensions
|
|
76
|
+
const canvas = mkCanvas(width - 1, height - 1)
|
|
77
|
+
|
|
78
|
+
const from = { x: 0, y: 0 }
|
|
79
|
+
const to = { x: width - 1, y: height - 1 }
|
|
80
|
+
|
|
81
|
+
// Line characters
|
|
82
|
+
const hLine = useAscii ? '-' : '─'
|
|
83
|
+
const vLine = useAscii ? '|' : '│'
|
|
84
|
+
|
|
85
|
+
// Draw horizontal lines (top and bottom)
|
|
86
|
+
for (let x = from.x + 1; x < to.x; x++) {
|
|
87
|
+
canvas[x]![from.y] = hLine
|
|
88
|
+
canvas[x]![to.y] = hLine
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Draw vertical lines (left and right)
|
|
92
|
+
for (let y = from.y + 1; y < to.y; y++) {
|
|
93
|
+
canvas[from.x]![y] = vLine
|
|
94
|
+
canvas[to.x]![y] = vLine
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Draw corners
|
|
98
|
+
canvas[from.x]![from.y] = corners.tl
|
|
99
|
+
canvas[to.x]![from.y] = corners.tr
|
|
100
|
+
canvas[from.x]![to.y] = corners.bl
|
|
101
|
+
canvas[to.x]![to.y] = corners.br
|
|
102
|
+
|
|
103
|
+
// Center the multi-line label
|
|
104
|
+
const lines = splitLines(label)
|
|
105
|
+
const w = width - 1 // Match original grid-based width calculation
|
|
106
|
+
const h = height - 1
|
|
107
|
+
const centerY = Math.floor(h / 2)
|
|
108
|
+
const startY = centerY - Math.floor((lines.length - 1) / 2)
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i]!
|
|
112
|
+
const cells = toCells(line)
|
|
113
|
+
const textX = Math.floor(w / 2) - Math.ceil(cells.length / 2) + 1
|
|
114
|
+
for (let j = 0; j < cells.length; j++) {
|
|
115
|
+
const x = textX + j
|
|
116
|
+
const y = startY + i
|
|
117
|
+
if (x >= 0 && x < canvas.length && y >= 0 && y < canvas[0]!.length) {
|
|
118
|
+
canvas[x]![y] = cells[j]!
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return canvas
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Shared attachment point calculation
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Calculate edge attachment point for rectangular shapes.
|
|
132
|
+
* All box-based shapes use the same attachment logic.
|
|
133
|
+
*/
|
|
134
|
+
export function getBoxAttachmentPoint(
|
|
135
|
+
dir: Direction,
|
|
136
|
+
dimensions: ShapeDimensions,
|
|
137
|
+
baseCoord: DrawingCoord
|
|
138
|
+
): DrawingCoord {
|
|
139
|
+
const { width, height } = dimensions
|
|
140
|
+
const centerX = baseCoord.x + Math.floor(width / 2)
|
|
141
|
+
const centerY = baseCoord.y + Math.floor(height / 2)
|
|
142
|
+
|
|
143
|
+
if (dirEquals(dir, Up)) return { x: centerX, y: baseCoord.y }
|
|
144
|
+
if (dirEquals(dir, Down)) return { x: centerX, y: baseCoord.y + height - 1 }
|
|
145
|
+
if (dirEquals(dir, Left)) return { x: baseCoord.x, y: centerY }
|
|
146
|
+
if (dirEquals(dir, Right)) return { x: baseCoord.x + width - 1, y: centerY }
|
|
147
|
+
if (dirEquals(dir, UpperLeft)) return { x: baseCoord.x, y: baseCoord.y }
|
|
148
|
+
if (dirEquals(dir, UpperRight)) return { x: baseCoord.x + width - 1, y: baseCoord.y }
|
|
149
|
+
if (dirEquals(dir, LowerLeft)) return { x: baseCoord.x, y: baseCoord.y + height - 1 }
|
|
150
|
+
if (dirEquals(dir, LowerRight)) return { x: baseCoord.x + width - 1, y: baseCoord.y + height - 1 }
|
|
151
|
+
// Middle
|
|
152
|
+
return { x: centerX, y: centerY }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Rectangle renderer
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Rectangle shape renderer — the default box shape.
|
|
161
|
+
* Renders as:
|
|
162
|
+
* ┌─────────┐
|
|
163
|
+
* │ Label │
|
|
164
|
+
* └─────────┘
|
|
165
|
+
*/
|
|
166
|
+
export const rectangleRenderer: ShapeRenderer = {
|
|
167
|
+
getDimensions: getBoxDimensions,
|
|
168
|
+
|
|
169
|
+
render(label: string, dimensions: ShapeDimensions, options: ShapeRenderOptions): Canvas {
|
|
170
|
+
const corners = getCorners('rectangle', options.useAscii)
|
|
171
|
+
return renderBox(label, dimensions, corners, options.useAscii)
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
175
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Rounded rectangle shape renderer — uses rounded corner decorators
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import type { ShapeRenderer } from './types'
|
|
6
|
+
import { getBoxDimensions, renderBox, getBoxAttachmentPoint } from './rectangle'
|
|
7
|
+
import { getCorners } from './corners'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rounded rectangle shape renderer.
|
|
11
|
+
* Uses rounded corner markers (╭╮╰╯) to indicate soft edges.
|
|
12
|
+
*
|
|
13
|
+
* Renders as:
|
|
14
|
+
* ╭─────────╮
|
|
15
|
+
* │ Label │
|
|
16
|
+
* ╰─────────╯
|
|
17
|
+
*/
|
|
18
|
+
export const roundedRenderer: ShapeRenderer = {
|
|
19
|
+
getDimensions: getBoxDimensions,
|
|
20
|
+
|
|
21
|
+
render(label, dimensions, options) {
|
|
22
|
+
const corners = getCorners('rounded', options.useAscii)
|
|
23
|
+
return renderBox(label, dimensions, corners, options.useAscii)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
27
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
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, DrawingCoord, Direction } from '../types'
|
|
9
|
+
import { Up, Down, Left, Right } from '../types'
|
|
10
|
+
import { mkCanvas } from '../canvas'
|
|
11
|
+
import { splitLines } from '../multiline-utils'
|
|
12
|
+
import type { ShapeRenderer, ShapeDimensions, ShapeRenderOptions } from './types'
|
|
13
|
+
import { dirEquals } from '../edge-routing'
|
|
14
|
+
import { getBoxDimensions, renderBox, getBoxAttachmentPoint } from './rectangle'
|
|
15
|
+
import { getCorners } from './corners'
|
|
16
|
+
import { displayWidth, toCells } from '../../text-metrics'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Subroutine — keeps custom double-border rendering
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Subroutine shape renderer — double-bordered rectangle.
|
|
24
|
+
* Renders as:
|
|
25
|
+
* ┌┬─────────┬┐
|
|
26
|
+
* ││ Label ││
|
|
27
|
+
* └┴─────────┴┘
|
|
28
|
+
*/
|
|
29
|
+
export const subroutineRenderer: ShapeRenderer = {
|
|
30
|
+
getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {
|
|
31
|
+
const lines = splitLines(label)
|
|
32
|
+
const maxLineWidth = Math.max(...lines.map(l => displayWidth(l)), 0)
|
|
33
|
+
const lineCount = lines.length
|
|
34
|
+
|
|
35
|
+
const innerWidth = 2 * options.padding + maxLineWidth
|
|
36
|
+
const width = innerWidth + 4 // Double borders on each side
|
|
37
|
+
const innerHeight = lineCount + 2 * options.padding
|
|
38
|
+
const height = innerHeight + 2
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
labelArea: {
|
|
44
|
+
x: 2 + options.padding,
|
|
45
|
+
y: 1 + options.padding,
|
|
46
|
+
width: maxLineWidth,
|
|
47
|
+
height: lineCount,
|
|
48
|
+
},
|
|
49
|
+
gridColumns: [2, innerWidth, 2],
|
|
50
|
+
gridRows: [1, innerHeight, 1],
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
render(label: string, dimensions: ShapeDimensions, options: ShapeRenderOptions): Canvas {
|
|
55
|
+
const { width, height } = dimensions
|
|
56
|
+
const canvas = mkCanvas(width - 1, height - 1)
|
|
57
|
+
|
|
58
|
+
const hChar = options.useAscii ? '-' : '─'
|
|
59
|
+
const vChar = options.useAscii ? '|' : '│'
|
|
60
|
+
|
|
61
|
+
// Top border
|
|
62
|
+
canvas[0]![0] = options.useAscii ? '+' : '┌'
|
|
63
|
+
canvas[1]![0] = options.useAscii ? '+' : '┬'
|
|
64
|
+
for (let x = 2; x < width - 2; x++) canvas[x]![0] = hChar
|
|
65
|
+
canvas[width - 2]![0] = options.useAscii ? '+' : '┬'
|
|
66
|
+
canvas[width - 1]![0] = options.useAscii ? '+' : '┐'
|
|
67
|
+
|
|
68
|
+
// Sides with double border
|
|
69
|
+
for (let y = 1; y < height - 1; y++) {
|
|
70
|
+
canvas[0]![y] = vChar
|
|
71
|
+
canvas[1]![y] = vChar
|
|
72
|
+
canvas[width - 2]![y] = vChar
|
|
73
|
+
canvas[width - 1]![y] = vChar
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Bottom border
|
|
77
|
+
canvas[0]![height - 1] = options.useAscii ? '+' : '└'
|
|
78
|
+
canvas[1]![height - 1] = options.useAscii ? '+' : '┴'
|
|
79
|
+
for (let x = 2; x < width - 2; x++) canvas[x]![height - 1] = hChar
|
|
80
|
+
canvas[width - 2]![height - 1] = options.useAscii ? '+' : '┴'
|
|
81
|
+
canvas[width - 1]![height - 1] = options.useAscii ? '+' : '┘'
|
|
82
|
+
|
|
83
|
+
// Center the label
|
|
84
|
+
const lines = splitLines(label)
|
|
85
|
+
const centerY = Math.floor(height / 2)
|
|
86
|
+
const startY = centerY - Math.floor((lines.length - 1) / 2)
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < lines.length; i++) {
|
|
89
|
+
const line = lines[i]!
|
|
90
|
+
const cells = toCells(line)
|
|
91
|
+
const textX = Math.floor(width / 2) - Math.floor(cells.length / 2)
|
|
92
|
+
for (let j = 0; j < cells.length; j++) {
|
|
93
|
+
const x = textX + j
|
|
94
|
+
const y = startY + i
|
|
95
|
+
if (x > 1 && x < width - 2 && y > 0 && y < height - 1) {
|
|
96
|
+
canvas[x]![y] = cells[j]!
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return canvas
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Double circle — uses corner decorators
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Double circle shape renderer.
|
|
113
|
+
* Uses double circle markers (◎) at corners.
|
|
114
|
+
*
|
|
115
|
+
* Renders as:
|
|
116
|
+
* ◎─────────◎
|
|
117
|
+
* │ Label │
|
|
118
|
+
* ◎─────────◎
|
|
119
|
+
*/
|
|
120
|
+
export const doublecircleRenderer: ShapeRenderer = {
|
|
121
|
+
getDimensions: getBoxDimensions,
|
|
122
|
+
|
|
123
|
+
render(label, dimensions, options) {
|
|
124
|
+
const corners = getCorners('doublecircle', options.useAscii)
|
|
125
|
+
return renderBox(label, dimensions, corners, options.useAscii)
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Cylinder — keeps custom rendering for database appearance
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cylinder shape renderer — database symbol.
|
|
137
|
+
* Renders as:
|
|
138
|
+
* ╭─────╮
|
|
139
|
+
* │─────│
|
|
140
|
+
* │ DB │
|
|
141
|
+
* │─────│
|
|
142
|
+
* ╰─────╯
|
|
143
|
+
*/
|
|
144
|
+
export const cylinderRenderer: ShapeRenderer = {
|
|
145
|
+
getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {
|
|
146
|
+
const lines = splitLines(label)
|
|
147
|
+
const maxLineWidth = Math.max(...lines.map(l => displayWidth(l)), 0)
|
|
148
|
+
const lineCount = lines.length
|
|
149
|
+
|
|
150
|
+
const innerWidth = 2 * options.padding + maxLineWidth
|
|
151
|
+
const width = innerWidth + 2
|
|
152
|
+
const innerHeight = lineCount + 2 * options.padding + 2 // Extra for curved top/bottom
|
|
153
|
+
const height = innerHeight + 2
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
width,
|
|
157
|
+
height,
|
|
158
|
+
labelArea: {
|
|
159
|
+
x: 1 + options.padding,
|
|
160
|
+
y: 2 + options.padding,
|
|
161
|
+
width: maxLineWidth,
|
|
162
|
+
height: lineCount,
|
|
163
|
+
},
|
|
164
|
+
gridColumns: [1, innerWidth, 1],
|
|
165
|
+
gridRows: [2, innerHeight - 2, 2],
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
render(label: string, dimensions: ShapeDimensions, options: ShapeRenderOptions): Canvas {
|
|
170
|
+
const { width, height } = dimensions
|
|
171
|
+
const canvas = mkCanvas(width - 1, height - 1)
|
|
172
|
+
|
|
173
|
+
const hChar = options.useAscii ? '-' : '─'
|
|
174
|
+
const vChar = options.useAscii ? '|' : '│'
|
|
175
|
+
|
|
176
|
+
// Top ellipse
|
|
177
|
+
canvas[0]![0] = options.useAscii ? '.' : '╭'
|
|
178
|
+
for (let x = 1; x < width - 1; x++) canvas[x]![0] = hChar
|
|
179
|
+
canvas[width - 1]![0] = options.useAscii ? '.' : '╮'
|
|
180
|
+
|
|
181
|
+
// Second row - bottom of top ellipse
|
|
182
|
+
canvas[0]![1] = vChar
|
|
183
|
+
for (let x = 1; x < width - 1; x++) canvas[x]![1] = hChar
|
|
184
|
+
canvas[width - 1]![1] = vChar
|
|
185
|
+
|
|
186
|
+
// Middle section
|
|
187
|
+
for (let y = 2; y < height - 2; y++) {
|
|
188
|
+
canvas[0]![y] = vChar
|
|
189
|
+
canvas[width - 1]![y] = vChar
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Second to last row - top of bottom ellipse
|
|
193
|
+
canvas[0]![height - 2] = vChar
|
|
194
|
+
for (let x = 1; x < width - 1; x++) canvas[x]![height - 2] = hChar
|
|
195
|
+
canvas[width - 1]![height - 2] = vChar
|
|
196
|
+
|
|
197
|
+
// Bottom ellipse
|
|
198
|
+
canvas[0]![height - 1] = options.useAscii ? '\'' : '╰'
|
|
199
|
+
for (let x = 1; x < width - 1; x++) canvas[x]![height - 1] = hChar
|
|
200
|
+
canvas[width - 1]![height - 1] = options.useAscii ? '\'' : '╯'
|
|
201
|
+
|
|
202
|
+
// Center the label
|
|
203
|
+
const lines = splitLines(label)
|
|
204
|
+
const centerY = Math.floor(height / 2)
|
|
205
|
+
const startY = centerY - Math.floor((lines.length - 1) / 2)
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < lines.length; i++) {
|
|
208
|
+
const line = lines[i]!
|
|
209
|
+
const cells = toCells(line)
|
|
210
|
+
const textX = Math.floor(width / 2) - Math.floor(cells.length / 2)
|
|
211
|
+
for (let j = 0; j < cells.length; j++) {
|
|
212
|
+
const x = textX + j
|
|
213
|
+
const y = startY + i
|
|
214
|
+
if (x > 0 && x < width - 1 && y > 1 && y < height - 2) {
|
|
215
|
+
canvas[x]![y] = cells[j]!
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return canvas
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Asymmetric (flag) — uses corner decorators
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Asymmetric (flag/banner) shape renderer.
|
|
232
|
+
* Uses arrow markers (▷) on left corners.
|
|
233
|
+
*
|
|
234
|
+
* Renders as:
|
|
235
|
+
* ▷─────────┐
|
|
236
|
+
* │ Label │
|
|
237
|
+
* ▷─────────┘
|
|
238
|
+
*/
|
|
239
|
+
export const asymmetricRenderer: ShapeRenderer = {
|
|
240
|
+
getDimensions: getBoxDimensions,
|
|
241
|
+
|
|
242
|
+
render(label, dimensions, options) {
|
|
243
|
+
const corners = getCorners('asymmetric', options.useAscii)
|
|
244
|
+
return renderBox(label, dimensions, corners, options.useAscii)
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ============================================================================
|
|
251
|
+
// Trapezoid — uses corner decorators instead of diagonal sides
|
|
252
|
+
// ============================================================================
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Trapezoid shape renderer — wider at bottom.
|
|
256
|
+
* Uses slope markers (◸◹) on top corners.
|
|
257
|
+
*
|
|
258
|
+
* Renders as:
|
|
259
|
+
* ◸─────────◹
|
|
260
|
+
* │ Label │
|
|
261
|
+
* └─────────┘
|
|
262
|
+
*/
|
|
263
|
+
export const trapezoidRenderer: ShapeRenderer = {
|
|
264
|
+
getDimensions: getBoxDimensions,
|
|
265
|
+
|
|
266
|
+
render(label, dimensions, options) {
|
|
267
|
+
const corners = getCorners('trapezoid', options.useAscii)
|
|
268
|
+
return renderBox(label, dimensions, corners, options.useAscii)
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Trapezoid-alt — uses corner decorators instead of diagonal sides
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Trapezoid-alt shape renderer — wider at top.
|
|
280
|
+
* Uses slope markers (◺◿) on bottom corners.
|
|
281
|
+
*
|
|
282
|
+
* Renders as:
|
|
283
|
+
* ┌─────────┐
|
|
284
|
+
* │ Label │
|
|
285
|
+
* ◺─────────◿
|
|
286
|
+
*/
|
|
287
|
+
export const trapezoidAltRenderer: ShapeRenderer = {
|
|
288
|
+
getDimensions: getBoxDimensions,
|
|
289
|
+
|
|
290
|
+
render(label, dimensions, options) {
|
|
291
|
+
const corners = getCorners('trapezoid-alt', options.useAscii)
|
|
292
|
+
return renderBox(label, dimensions, corners, options.useAscii)
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
getAttachmentPoint: getBoxAttachmentPoint,
|
|
296
|
+
}
|