@schemd/core 0.1.1 → 0.2.0
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/README.md +22 -83
- package/dist/compiler.d.ts +16 -0
- package/dist/compiler.js +17 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +5 -5
- package/dist/layout.d.ts +14 -4
- package/dist/layout.js +510 -138
- package/dist/limits.d.ts +4 -3
- package/dist/limits.js +2 -0
- package/dist/math-label.d.ts +20 -3
- package/dist/math-label.js +149 -56
- package/dist/parser.js +248 -27
- package/dist/renderer.js +184 -37
- package/dist/types.d.ts +64 -14
- package/dist/types.js +34 -1
- package/package.json +28 -8
- package/dist/marked-extension.d.ts +0 -23
- package/dist/marked-extension.js +0 -75
package/dist/renderer.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { classicalGateHeight, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, positionIcPin, PORT_HOTSPOT_RADIUS, routeConnections, validateDocumentGeometry } from './layout.js';
|
|
2
2
|
import { MAX_SCHEMATIC_SVG_OUTPUT_BYTES, utf8ByteLength } from './limits.js';
|
|
3
3
|
import { assertParsedSchematicDocument } from './parser.js';
|
|
4
|
-
import { CLASSICAL_GATE_KINDS, SCHEMD_OUTPUT_MODES, SchematicSyntaxError } from './types.js';
|
|
5
|
-
import { renderMathLabelTspans } from './math-label.js';
|
|
4
|
+
import { CLASSICAL_GATE_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
5
|
+
import { mathLabelTextWidth, renderMathLabelTspans } from './math-label.js';
|
|
6
6
|
export const MAX_SVG_OUTPUT_BYTES = MAX_SCHEMATIC_SVG_OUTPUT_BYTES;
|
|
7
|
-
const STATIC_SVG_STYLES = '.schematic-token{fill:none;stroke:var(--schematic-vector,var(--schematic-vector-fallback,currentColor));stroke-linecap:round;stroke-linejoin:round;stroke-width:var(--schematic-stroke-width,1.65);vector-effect:non-scaling-stroke}.schematic-node-fill{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))}.schematic-designator,.schematic-label,.schematic-gate-symbol,.schematic-quantum-detail,.schematic-pin-label{fill:currentColor}.schematic-surface{fill:var(--schematic-surface,transparent)}.schematic-grid-line{fill:none;stroke:var(--schematic-grid,currentColor);stroke-width:1;opacity:.12;vector-effect:non-scaling-stroke}';
|
|
7
|
+
const STATIC_SVG_STYLES = '.schematic-token{fill:none;stroke:var(--schematic-vector,var(--schematic-vector-fallback,currentColor));stroke-linecap:round;stroke-linejoin:round;stroke-width:var(--schematic-stroke-width,1.65);vector-effect:non-scaling-stroke}.schematic-node-fill{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))}.schematic-designator,.schematic-label,.schematic-gate-symbol,.schematic-quantum-detail,.schematic-pin-label,.schematic-uml-text,.schematic-connection-label{fill:currentColor}.schematic-uml-row{font-size:12px}.schematic-uml-stereotype{font-size:11px}.schematic-connection-label{font-size:11px;paint-order:stroke;stroke:var(--schematic-surface,#fff);stroke-width:4;stroke-linejoin:round}.schematic-surface{fill:var(--schematic-surface,transparent)}.schematic-grid-line{fill:none;stroke:var(--schematic-grid,currentColor);stroke-width:1;opacity:.12;vector-effect:non-scaling-stroke}';
|
|
8
8
|
const HOOK_SVG_STYLES = '.schematic-port-hotspot{fill:transparent!important;stroke:transparent!important;stroke-width:8;vector-effect:non-scaling-stroke;pointer-events:all;outline:none}.schematic-port-hotspot:focus-visible{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))!important;fill-opacity:.22!important;stroke:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))!important;stroke-width:2}';
|
|
9
9
|
const INTERACTIVE_SVG_STYLES = '.schematic-component,.schematic-wire{outline:none;transition:opacity .2s ease,filter .2s ease}.schematic-component .schematic-token,.schematic-wire .schematic-token{transition:stroke .2s ease,fill .2s ease,color .2s ease,opacity .2s ease,stroke-width .2s ease}.schematic-glow-layer{opacity:0;pointer-events:none;transition:opacity .2s ease}.schematic-component:hover>.schematic-glow-layer,.schematic-component:focus>.schematic-glow-layer,.schematic-component:focus-within>.schematic-glow-layer,.schematic-component.is-hovered>.schematic-glow-layer,.schematic-component.is-active>.schematic-glow-layer,.schematic-component.is-selected>.schematic-glow-layer,.schematic-wire:hover>.schematic-glow-layer,.schematic-wire:focus>.schematic-glow-layer,.schematic-wire.is-hovered>.schematic-glow-layer,.schematic-wire.is-active>.schematic-glow-layer,.schematic-wire.is-selected>.schematic-glow-layer{opacity:1}.schematic-component.is-degraded,.schematic-wire.is-degraded{opacity:.45}.schematic-component.is-degraded>.schematic-glow-layer,.schematic-wire.is-degraded>.schematic-glow-layer{opacity:0}.schematic-component:hover .schematic-token,.schematic-component:focus .schematic-token,.schematic-component:focus-within .schematic-token,.schematic-component.is-hovered .schematic-token,.schematic-component.is-active .schematic-token,.schematic-component.is-selected .schematic-token,.schematic-wire:hover .schematic-token,.schematic-wire:focus .schematic-token,.schematic-wire.is-hovered .schematic-token,.schematic-wire.is-active .schematic-token,.schematic-wire.is-selected .schematic-token{stroke-width:var(--schematic-interactive-stroke-width,2.25)}.schematic-port-hotspot{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))!important;fill-opacity:0;transition:fill-opacity .2s ease,opacity .2s ease}.schematic-port-hotspot:hover,.schematic-port-hotspot:focus,.schematic-port-hotspot.is-hovered,.schematic-port-hotspot.is-active,.schematic-port-hotspot.is-selected{fill-opacity:.2}.schematic-port-hotspot.is-degraded{opacity:.45}@media(prefers-reduced-motion:reduce){.schematic-component,.schematic-wire,.schematic-token,.schematic-glow-layer,.schematic-port-hotspot{transition:none}}';
|
|
10
|
+
const NODE_HOOK = 1;
|
|
11
|
+
const PORT_HOOK = 2;
|
|
12
|
+
const WIRE_HOOK = 4;
|
|
13
|
+
function semanticHookBit(hook) {
|
|
14
|
+
if (hook === 'nodes')
|
|
15
|
+
return NODE_HOOK;
|
|
16
|
+
if (hook === 'ports')
|
|
17
|
+
return PORT_HOOK;
|
|
18
|
+
return WIRE_HOOK;
|
|
19
|
+
}
|
|
10
20
|
function validXmlCodePoint(codePoint) {
|
|
11
21
|
if (codePoint === 0x09 || codePoint === 0x0a || codePoint === 0x0d)
|
|
12
22
|
return true;
|
|
@@ -82,15 +92,30 @@ function normalizeCompileOptions(value) {
|
|
|
82
92
|
throw new SchematicSyntaxError('Render mode must be one of: default, embedded-css, or full.');
|
|
83
93
|
}
|
|
84
94
|
const normalizedMode = (mode ?? 'default');
|
|
95
|
+
const rawSemanticHooks = candidate.semanticHooks;
|
|
96
|
+
let semanticHookMask = NODE_HOOK | PORT_HOOK | WIRE_HOOK;
|
|
97
|
+
if (rawSemanticHooks !== undefined) {
|
|
98
|
+
if (!Array.isArray(rawSemanticHooks)) {
|
|
99
|
+
throw new SchematicSyntaxError('Render semanticHooks must be an array.');
|
|
100
|
+
}
|
|
101
|
+
semanticHookMask = 0;
|
|
102
|
+
for (const hook of rawSemanticHooks) {
|
|
103
|
+
if (typeof hook !== 'string' ||
|
|
104
|
+
!SCHEMD_SEMANTIC_HOOKS.includes(hook)) {
|
|
105
|
+
throw new SchematicSyntaxError('Render semanticHooks may contain only: nodes, ports, or wires.');
|
|
106
|
+
}
|
|
107
|
+
semanticHookMask |= semanticHookBit(hook);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
85
110
|
return idPrefix === undefined
|
|
86
|
-
? { bounds: { width, height }, title, mode: normalizedMode }
|
|
87
|
-
: { bounds: { width, height }, title, idPrefix, mode: normalizedMode };
|
|
111
|
+
? { bounds: { width, height }, title, mode: normalizedMode, semanticHookMask }
|
|
112
|
+
: { bounds: { width, height }, title, idPrefix, mode: normalizedMode, semanticHookMask };
|
|
88
113
|
}
|
|
89
114
|
function svgNumber(value) {
|
|
90
115
|
return String(Number(value.toFixed(3)));
|
|
91
116
|
}
|
|
92
117
|
function fittedTextLength(value, maximum, advance) {
|
|
93
|
-
return Math.max(1, Math.min(
|
|
118
|
+
return Math.max(1, Math.min(Math.ceil(mathLabelTextWidth(value, advance)), maximum));
|
|
94
119
|
}
|
|
95
120
|
function stableHash(value) {
|
|
96
121
|
let hash = 2166136261;
|
|
@@ -103,6 +128,9 @@ function stableHash(value) {
|
|
|
103
128
|
function isClassicalGate(component) {
|
|
104
129
|
return CLASSICAL_GATE_KINDS.includes(component.kind);
|
|
105
130
|
}
|
|
131
|
+
function isUmlComponent(component) {
|
|
132
|
+
return UML_COMPONENT_KINDS.includes(component.kind);
|
|
133
|
+
}
|
|
106
134
|
function colorAttributes(color, extraClass = '') {
|
|
107
135
|
const suffix = extraClass === '' ? '' : ` ${extraClass}`;
|
|
108
136
|
const fill = extraClass.split(' ').includes('schematic-node-fill') ? ' fill="currentColor"' : '';
|
|
@@ -183,7 +211,7 @@ function iecGate(component, paint) {
|
|
|
183
211
|
xor: '=1',
|
|
184
212
|
not: '1'
|
|
185
213
|
};
|
|
186
|
-
return `<rect ${paint} x="-32" y="${-height / 2}" width="64" height="${height}" rx="2" /><text class="schematic-gate-symbol" fill="currentColor" stroke="none" x="0" y="4">${symbol[component.kind]}</text>${inversionBubbles(component, paint)}${gateStubs(component, paint)}`;
|
|
214
|
+
return `<rect ${paint} x="-32" y="${-height / 2}" width="64" height="${height}" rx="2" /><text class="schematic-gate-symbol" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="14">${symbol[component.kind]}</text>${inversionBubbles(component, paint)}${gateStubs(component, paint)}`;
|
|
187
215
|
}
|
|
188
216
|
function ieeeGate(component, paint) {
|
|
189
217
|
const height = classicalGateHeight(component);
|
|
@@ -205,9 +233,9 @@ function ieeeGate(component, paint) {
|
|
|
205
233
|
function quantumText(component) {
|
|
206
234
|
const details = [component.parameter, component.phase, component.matrix].filter((value) => value !== undefined && value !== '');
|
|
207
235
|
const rows = [component.label, ...details];
|
|
208
|
-
const start = -((rows.length - 1) *
|
|
236
|
+
const start = -((rows.length - 1) * 12) / 2 + 4;
|
|
209
237
|
return rows
|
|
210
|
-
.map((value, index) => `<text class="${index === 0 ? 'schematic-gate-symbol' : 'schematic-quantum-detail'}" fill="currentColor" stroke="none" x="0" y="${start + index * 10}" textLength="${fittedTextLength(value, 56, index === 0 ? 7 : 5)}" lengthAdjust="spacingAndGlyphs">${renderMathLabelTspans(value)}</text>`)
|
|
238
|
+
.map((value, index) => `<text class="${index === 0 ? 'schematic-gate-symbol' : 'schematic-quantum-detail'}" fill="currentColor" stroke="none" x="0" y="${start + index * 12}" text-anchor="middle" font-size="${index === 0 ? 14 : 10}" textLength="${fittedTextLength(value, 56, index === 0 ? 7 : 5)}" lengthAdjust="spacingAndGlyphs">${renderMathLabelTspans(value)}</text>`)
|
|
211
239
|
.join('');
|
|
212
240
|
}
|
|
213
241
|
function diodeShape(component, paint) {
|
|
@@ -261,29 +289,94 @@ function icPinMarkup(component, side, paint) {
|
|
|
261
289
|
const horizontalTextLength = fittedTextLength(pin, Math.max(8, component.bodyWidth / 2 - 10), 4);
|
|
262
290
|
const verticalTextLength = fittedTextLength(pin, Math.max(8, component.bodyHeight / 2 - 10), 4);
|
|
263
291
|
if (side === 'left') {
|
|
264
|
-
return `<path ${paint} d="M ${x} ${y} H ${-component.bodyWidth / 2}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${-component.bodyWidth / 2 + 5}" y="${y + 3}" text-anchor="start" textLength="${horizontalTextLength}" lengthAdjust="spacingAndGlyphs">${escapeXml(pin)}</text>`;
|
|
292
|
+
return `<path ${paint} d="M ${x} ${y} H ${-component.bodyWidth / 2}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${-component.bodyWidth / 2 + 5}" y="${y + 3}" text-anchor="start" font-size="10" textLength="${horizontalTextLength}" lengthAdjust="spacingAndGlyphs">${escapeXml(pin)}</text>`;
|
|
265
293
|
}
|
|
266
294
|
if (side === 'right') {
|
|
267
|
-
return `<path ${paint} d="M ${component.bodyWidth / 2} ${y} H ${x}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${component.bodyWidth / 2 - 5}" y="${y + 3}" text-anchor="end" textLength="${horizontalTextLength}" lengthAdjust="spacingAndGlyphs">${escapeXml(pin)}</text>`;
|
|
295
|
+
return `<path ${paint} d="M ${component.bodyWidth / 2} ${y} H ${x}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${component.bodyWidth / 2 - 5}" y="${y + 3}" text-anchor="end" font-size="10" textLength="${horizontalTextLength}" lengthAdjust="spacingAndGlyphs">${escapeXml(pin)}</text>`;
|
|
268
296
|
}
|
|
269
297
|
if (side === 'top') {
|
|
270
298
|
const labelY = -component.bodyHeight / 2 + 5;
|
|
271
|
-
return `<path ${paint} d="M ${x} ${y} V ${-component.bodyHeight / 2}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${x}" y="${labelY}" text-anchor="start" textLength="${verticalTextLength}" lengthAdjust="spacingAndGlyphs" transform="rotate(90 ${x} ${labelY})">${escapeXml(pin)}</text>`;
|
|
299
|
+
return `<path ${paint} d="M ${x} ${y} V ${-component.bodyHeight / 2}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${x}" y="${labelY}" text-anchor="start" font-size="10" textLength="${verticalTextLength}" lengthAdjust="spacingAndGlyphs" transform="rotate(90 ${x} ${labelY})">${escapeXml(pin)}</text>`;
|
|
272
300
|
}
|
|
273
301
|
const labelY = component.bodyHeight / 2 - 5;
|
|
274
|
-
return `<path ${paint} d="M ${x} ${component.bodyHeight / 2} V ${y}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${x}" y="${labelY}" text-anchor="start" textLength="${verticalTextLength}" lengthAdjust="spacingAndGlyphs" transform="rotate(-90 ${x} ${labelY})">${escapeXml(pin)}</text>`;
|
|
302
|
+
return `<path ${paint} d="M ${x} ${component.bodyHeight / 2} V ${y}" /><text class="schematic-pin-label" fill="currentColor" stroke="none" x="${x}" y="${labelY}" text-anchor="start" font-size="10" textLength="${verticalTextLength}" lengthAdjust="spacingAndGlyphs" transform="rotate(-90 ${x} ${labelY})">${escapeXml(pin)}</text>`;
|
|
275
303
|
})
|
|
276
304
|
.join('');
|
|
277
305
|
}
|
|
278
306
|
function integratedCircuitShape(component, paint) {
|
|
279
307
|
const left = -component.bodyWidth / 2;
|
|
280
308
|
const top = -component.bodyHeight / 2;
|
|
281
|
-
return `<rect ${paint} x="${left}" y="${top}" width="${component.bodyWidth}" height="${component.bodyHeight}" rx="3" />${icPinMarkup(component, 'left', paint)}${icPinMarkup(component, 'right', paint)}${icPinMarkup(component, 'top', paint)}${icPinMarkup(component, 'bottom', paint)}<text class="schematic-gate-symbol" fill="currentColor" stroke="none" x="0" y="4" textLength="${fittedTextLength(component.label, component.bodyWidth - 12, 7)}" lengthAdjust="spacingAndGlyphs">${renderMathLabelTspans(component.label)}</text>`;
|
|
309
|
+
return `<rect ${paint} x="${left}" y="${top}" width="${component.bodyWidth}" height="${component.bodyHeight}" rx="3" />${icPinMarkup(component, 'left', paint)}${icPinMarkup(component, 'right', paint)}${icPinMarkup(component, 'top', paint)}${icPinMarkup(component, 'bottom', paint)}<text class="schematic-gate-symbol" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="12" textLength="${fittedTextLength(component.label, component.bodyWidth - 12, 7)}" lengthAdjust="spacingAndGlyphs">${renderMathLabelTspans(component.label)}</text>`;
|
|
310
|
+
}
|
|
311
|
+
function umlRow(value, x, y) {
|
|
312
|
+
return `<text class="schematic-uml-text schematic-uml-row" fill="currentColor" stroke="none" x="${svgNumber(x)}" y="${svgNumber(y)}" font-size="12">${escapeXml(value)}</text>`;
|
|
313
|
+
}
|
|
314
|
+
function umlClassShape(component, paint) {
|
|
315
|
+
const left = -component.bodyWidth / 2;
|
|
316
|
+
const top = -component.bodyHeight / 2;
|
|
317
|
+
const headerHeight = 36 + (component.stereotype === undefined ? 0 : 14);
|
|
318
|
+
const attributeHeight = Math.max(24, component.attributes.length * 16 + 8);
|
|
319
|
+
const attributeSeparator = top + headerHeight;
|
|
320
|
+
const operationSeparator = attributeSeparator + attributeHeight;
|
|
321
|
+
const stereotype = component.stereotype === undefined
|
|
322
|
+
? ''
|
|
323
|
+
: `<text class="schematic-uml-text schematic-uml-stereotype" fill="currentColor" stroke="none" x="0" y="${svgNumber(top + 13)}" text-anchor="middle" font-size="11">«${escapeXml(component.stereotype)}»</text>`;
|
|
324
|
+
const nameY = top + (component.stereotype === undefined ? 23 : 32);
|
|
325
|
+
const attributes = component.attributes
|
|
326
|
+
.map((row, index) => umlRow(row, left + 8, attributeSeparator + 17 + index * 16))
|
|
327
|
+
.join('');
|
|
328
|
+
const operations = component.operations
|
|
329
|
+
.map((row, index) => umlRow(row, left + 8, operationSeparator + 17 + index * 16))
|
|
330
|
+
.join('');
|
|
331
|
+
return `<rect ${paint} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${svgNumber(component.bodyWidth)}" height="${svgNumber(component.bodyHeight)}" rx="2" /><path ${paint} d="M ${svgNumber(left)} ${svgNumber(attributeSeparator)} H ${svgNumber(-left)} M ${svgNumber(left)} ${svgNumber(operationSeparator)} H ${svgNumber(-left)}" />${stereotype}<text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="${svgNumber(nameY)}" text-anchor="middle" font-size="13" font-weight="600">${renderMathLabelTspans(component.label)}</text>${attributes}${operations}`;
|
|
332
|
+
}
|
|
333
|
+
function umlStateShape(component, paint) {
|
|
334
|
+
const left = -component.bodyWidth / 2;
|
|
335
|
+
const top = -component.bodyHeight / 2;
|
|
336
|
+
const separator = top + 32;
|
|
337
|
+
const rows = component.details
|
|
338
|
+
.map((row, index) => umlRow(row, left + 8, separator + 17 + index * 16))
|
|
339
|
+
.join('');
|
|
340
|
+
return `<rect ${paint} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${svgNumber(component.bodyWidth)}" height="${svgNumber(component.bodyHeight)}" rx="10" />${component.details.length === 0 ? '' : `<path ${paint} d="M ${svgNumber(left)} ${svgNumber(separator)} H ${svgNumber(-left)}" />`}<text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="${svgNumber(top + 21)}" text-anchor="middle" font-size="13" font-weight="600">${renderMathLabelTspans(component.label)}</text>${rows}`;
|
|
341
|
+
}
|
|
342
|
+
function umlComponentShape(component, paint, nodePaint) {
|
|
343
|
+
if (component.kind === 'class')
|
|
344
|
+
return umlClassShape(component, paint);
|
|
345
|
+
if (component.kind === 'state')
|
|
346
|
+
return umlStateShape(component, paint);
|
|
347
|
+
switch (component.kind) {
|
|
348
|
+
case 'actor':
|
|
349
|
+
return `<circle ${paint} cx="0" cy="-28" r="9" /><path ${paint} d="M 0 -19 V 12 M -20 -5 H 20 M 0 12 L -17 37 M 0 12 L 17 37" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="49" text-anchor="middle" font-size="12">${renderMathLabelTspans(component.label)}</text>`;
|
|
350
|
+
case 'usecase':
|
|
351
|
+
return `<ellipse ${paint} cx="0" cy="0" rx="${svgNumber(component.bodyWidth / 2)}" ry="${svgNumber(component.bodyHeight / 2)}" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="13">${renderMathLabelTspans(component.label)}</text>`;
|
|
352
|
+
case 'lifeline': {
|
|
353
|
+
const top = -component.bodyHeight / 2;
|
|
354
|
+
const left = -component.bodyWidth / 2;
|
|
355
|
+
return `<rect ${paint} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${svgNumber(component.bodyWidth)}" height="32" rx="2" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="${svgNumber(top + 21)}" text-anchor="middle" font-size="12">${renderMathLabelTspans(component.label)}</text><path ${paint} stroke-dasharray="6 5" d="M 0 ${svgNumber(top + 32)} V ${svgNumber(component.bodyHeight / 2)}" />`;
|
|
356
|
+
}
|
|
357
|
+
case 'note': {
|
|
358
|
+
const left = -component.bodyWidth / 2;
|
|
359
|
+
const top = -component.bodyHeight / 2;
|
|
360
|
+
const right = component.bodyWidth / 2;
|
|
361
|
+
return `<path ${paint} d="M ${svgNumber(left)} ${svgNumber(top)} H ${svgNumber(right - 14)} L ${svgNumber(right)} ${svgNumber(top + 14)} V ${svgNumber(-top)} H ${svgNumber(left)} Z M ${svgNumber(right - 14)} ${svgNumber(top)} V ${svgNumber(top + 14)} H ${svgNumber(right)}" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="12">${renderMathLabelTspans(component.label)}</text>`;
|
|
362
|
+
}
|
|
363
|
+
case 'package': {
|
|
364
|
+
const left = -component.bodyWidth / 2;
|
|
365
|
+
const top = -component.bodyHeight / 2;
|
|
366
|
+
return `<path ${paint} d="M ${svgNumber(left)} ${svgNumber(top + 12)} V ${svgNumber(-top)} H ${svgNumber(-left)} V ${svgNumber(top)} H ${svgNumber(left + Math.min(54, component.bodyWidth / 2))} L ${svgNumber(left + Math.min(64, component.bodyWidth / 2 + 10))} ${svgNumber(top + 12)} Z" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="13">${renderMathLabelTspans(component.label)}</text>`;
|
|
367
|
+
}
|
|
368
|
+
case 'initial':
|
|
369
|
+
return `<circle ${nodePaint} cx="0" cy="0" r="10" />`;
|
|
370
|
+
case 'final':
|
|
371
|
+
return `<circle ${paint} cx="0" cy="0" r="11" /><circle ${nodePaint} cx="0" cy="0" r="6" />`;
|
|
372
|
+
}
|
|
282
373
|
}
|
|
283
374
|
function componentShape(component, paint = colorAttributes(component.color), nodePaint = colorAttributes(component.color, 'schematic-node-fill')) {
|
|
284
375
|
if (isClassicalGate(component)) {
|
|
285
376
|
return component.standard === 'iec' ? iecGate(component, paint) : ieeeGate(component, paint);
|
|
286
377
|
}
|
|
378
|
+
if (isUmlComponent(component))
|
|
379
|
+
return umlComponentShape(component, paint, nodePaint);
|
|
287
380
|
switch (component.kind) {
|
|
288
381
|
case 'resistor':
|
|
289
382
|
return `<path ${paint} d="M -42 0 H -26 L -20 -12 L -10 12 L 0 -12 L 10 12 L 20 -12 L 26 0 H 42" />`;
|
|
@@ -310,7 +403,10 @@ function componentShape(component, paint = colorAttributes(component.color), nod
|
|
|
310
403
|
}
|
|
311
404
|
}
|
|
312
405
|
function reusableSymbolKey(component) {
|
|
313
|
-
if (isClassicalGate(component) ||
|
|
406
|
+
if (isClassicalGate(component) ||
|
|
407
|
+
isUmlComponent(component) ||
|
|
408
|
+
component.kind === 'qgate' ||
|
|
409
|
+
component.kind === 'ic') {
|
|
314
410
|
return undefined;
|
|
315
411
|
}
|
|
316
412
|
if (component.kind === 'diode')
|
|
@@ -332,16 +428,56 @@ function connectionMarkup(connection, routed, index, idPrefix, glowId) {
|
|
|
332
428
|
const target = escapeXml(`${connection.to.componentId}.${connection.to.port}`);
|
|
333
429
|
const traceId = `${idPrefix}-wire-${index}-vector`;
|
|
334
430
|
const dataAttributes = ` data-wire-source="${source}" data-wire-target="${target}"`;
|
|
335
|
-
const
|
|
431
|
+
const relation = connection.relation ?? 'signal';
|
|
432
|
+
const accessibility = ` tabindex="0" role="group" aria-label="${escapeXml(relation)} from ${source} to ${target}"`;
|
|
336
433
|
const vectorId = ` id="${traceId}"`;
|
|
337
434
|
const markerAttributes = connectionMarkerAttributes(connection, idPrefix);
|
|
338
435
|
const glow = `<use class="schematic-glow-layer" href="#${traceId}" filter="url(#${glowId})" aria-hidden="true" pointer-events="none" />`;
|
|
339
|
-
|
|
436
|
+
const endpoint = relation === 'signal' && connection.markerEnd === 'none'
|
|
437
|
+
? `<circle ${colorAttributes(connection.color, 'schematic-node-fill')} cx="${svgNumber(end.x)}" cy="${svgNumber(end.y)}" r="3" />`
|
|
438
|
+
: '';
|
|
439
|
+
return `<g class="schematic-wire"${dataAttributes}${accessibility}><path${vectorId} ${colorAttributes(connection.color, 'schematic-trace')} d="${routed.d}"${markerAttributes} />${glow}${endpoint}${connectionLabelMarkup(connection, routed)}</g>`;
|
|
340
440
|
}
|
|
341
441
|
function connectionMarkerAttributes(connection, idPrefix) {
|
|
342
442
|
const start = connection.markerStart;
|
|
343
443
|
const end = connection.markerEnd;
|
|
344
|
-
return `${start === 'none' ? '' : ` marker-start="url(#${idPrefix}-marker-${start})"`}${end === 'none' ? '' : ` marker-end="url(#${idPrefix}-marker-${end})"`}`;
|
|
444
|
+
return `${start === 'none' ? '' : ` marker-start="url(#${idPrefix}-marker-${start})"`}${end === 'none' ? '' : ` marker-end="url(#${idPrefix}-marker-${end})"`}${connection.dashed === true ? ' stroke-dasharray="7 5"' : ''}`;
|
|
445
|
+
}
|
|
446
|
+
function connectionLabelPoint(route) {
|
|
447
|
+
if (route.curve === 'bezier' && route.points.length >= 4) {
|
|
448
|
+
const a = route.points[0];
|
|
449
|
+
const b = route.points[1];
|
|
450
|
+
const c = route.points[2];
|
|
451
|
+
const d = route.points[3];
|
|
452
|
+
return {
|
|
453
|
+
x: (a.x + 3 * b.x + 3 * c.x + d.x) / 8,
|
|
454
|
+
y: (a.y + 3 * b.y + 3 * c.y + d.y) / 8
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
let total = 0;
|
|
458
|
+
for (let index = 1; index < route.points.length; index += 1) {
|
|
459
|
+
total +=
|
|
460
|
+
Math.abs(route.points[index].x - route.points[index - 1].x) +
|
|
461
|
+
Math.abs(route.points[index].y - route.points[index - 1].y);
|
|
462
|
+
}
|
|
463
|
+
let remaining = total / 2;
|
|
464
|
+
for (let index = 1; index < route.points.length; index += 1) {
|
|
465
|
+
const start = route.points[index - 1];
|
|
466
|
+
const end = route.points[index];
|
|
467
|
+
const length = Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
|
|
468
|
+
if (remaining <= length) {
|
|
469
|
+
const ratio = length === 0 ? 0 : remaining / length;
|
|
470
|
+
return { x: start.x + (end.x - start.x) * ratio, y: start.y + (end.y - start.y) * ratio };
|
|
471
|
+
}
|
|
472
|
+
remaining -= length;
|
|
473
|
+
}
|
|
474
|
+
return route.points.at(-1);
|
|
475
|
+
}
|
|
476
|
+
function connectionLabelMarkup(connection, route) {
|
|
477
|
+
if (connection.label === undefined)
|
|
478
|
+
return '';
|
|
479
|
+
const point = connectionLabelPoint(route);
|
|
480
|
+
return `<text class="schematic-connection-label" fill="currentColor" stroke="none" x="${svgNumber(point.x)}" y="${svgNumber(point.y - 7)}" text-anchor="middle" font-size="11">${renderMathLabelTspans(connection.label)}</text>`;
|
|
345
481
|
}
|
|
346
482
|
function colorKey(color) {
|
|
347
483
|
return `${color.kind}:${color.value}`;
|
|
@@ -353,23 +489,29 @@ function compactConnectionMarkup(connections, routes, idPrefix, embedVisuals, gl
|
|
|
353
489
|
const key = `${colorKey(connection.color)}|${markerAttributes}`;
|
|
354
490
|
let batch = batches.get(key);
|
|
355
491
|
if (batch === undefined) {
|
|
356
|
-
batch = { color: connection.color, markerAttributes, traces: [], endpoints: [] };
|
|
492
|
+
batch = { color: connection.color, markerAttributes, traces: [], endpoints: [], labels: [] };
|
|
357
493
|
batches.set(key, batch);
|
|
358
494
|
}
|
|
359
495
|
const routed = routes[index];
|
|
360
496
|
const end = routed.points.at(-1);
|
|
361
497
|
batch.traces.push(routed.d);
|
|
362
|
-
|
|
498
|
+
if ((connection.relation ?? 'signal') === 'signal' && connection.markerEnd === 'none') {
|
|
499
|
+
batch.endpoints.push(`M ${svgNumber(end.x - 3)} ${svgNumber(end.y)} a 3 3 0 1 0 6 0 a 3 3 0 1 0 -6 0`);
|
|
500
|
+
}
|
|
501
|
+
const label = connectionLabelMarkup(connection, routed);
|
|
502
|
+
if (label !== '')
|
|
503
|
+
batch.labels.push(label);
|
|
363
504
|
}
|
|
364
505
|
return Array.from(batches.values(), (batch, index) => {
|
|
365
506
|
const tracePaint = colorAttributes(batch.color, 'schematic-trace');
|
|
366
507
|
const nodePaint = colorAttributes(batch.color, 'schematic-node-fill');
|
|
367
508
|
const traceId = `${idPrefix}-wire-batch-${index}-vector`;
|
|
368
509
|
const trace = `<path${embedVisuals ? ` id="${traceId}"` : ''} ${tracePaint} d="${batch.traces.join(' ')}"${batch.markerAttributes} />`;
|
|
369
|
-
const endpoints = `<path ${nodePaint} d="${batch.endpoints.join(' ')}" />`;
|
|
510
|
+
const endpoints = batch.endpoints.length === 0 ? '' : `<path ${nodePaint} d="${batch.endpoints.join(' ')}" />`;
|
|
511
|
+
const labels = batch.labels.join('');
|
|
370
512
|
if (!embedVisuals)
|
|
371
|
-
return trace + endpoints;
|
|
372
|
-
return `<g class="schematic-wire" tabindex="0" role="group" aria-label="${batch.traces.length} grouped
|
|
513
|
+
return trace + endpoints + labels;
|
|
514
|
+
return `<g class="schematic-wire" tabindex="0" role="group" aria-label="${batch.traces.length} grouped connection${batch.traces.length === 1 ? '' : 's'}">${trace}<use class="schematic-glow-layer" href="#${traceId}" filter="url(#${glowId})" aria-hidden="true" pointer-events="none" />${endpoints}${labels}</g>`;
|
|
373
515
|
}).join('');
|
|
374
516
|
}
|
|
375
517
|
function componentMetadata(component) {
|
|
@@ -399,19 +541,18 @@ function componentPortMarkup(component) {
|
|
|
399
541
|
})
|
|
400
542
|
.join('');
|
|
401
543
|
}
|
|
402
|
-
function componentMarkup(component, index, idPrefix, glowId, mode, symbolId) {
|
|
544
|
+
function componentMarkup(component, index, idPrefix, glowId, mode, nodeHooks, portHooks, symbolId) {
|
|
403
545
|
const label = escapeXml(component.label);
|
|
404
546
|
const renderedLabel = renderMathLabelTspans(component.label);
|
|
405
547
|
const anchors = componentTextAnchors(component);
|
|
406
548
|
const id = escapeXml(component.id);
|
|
407
549
|
const vectorId = `${idPrefix}-node-${index}-vector`;
|
|
408
550
|
const styles = mode === 'embedded-css' || mode === 'full';
|
|
409
|
-
const
|
|
410
|
-
const dataAttributes = hooks
|
|
551
|
+
const dataAttributes = nodeHooks
|
|
411
552
|
? ` data-node-id="${id}" data-node-kind="${component.kind}" data-node-label="${label}" data-component="${id}" data-kind="${component.kind}"`
|
|
412
553
|
: '';
|
|
413
554
|
let accessibility = '';
|
|
414
|
-
if (styles
|
|
555
|
+
if (styles) {
|
|
415
556
|
const metadata = componentMetadata(component);
|
|
416
557
|
const ariaLabel = escapeXml(`${component.id}, ${component.kind}, ${component.label}${metadata === '' ? '' : `, ${metadata}`}`);
|
|
417
558
|
accessibility = ` tabindex="0" role="group" aria-label="${ariaLabel}"`;
|
|
@@ -420,16 +561,21 @@ function componentMarkup(component, index, idPrefix, glowId, mode, symbolId) {
|
|
|
420
561
|
const glow = styles
|
|
421
562
|
? `<use class="schematic-glow-layer" href="#${vectorId}" filter="url(#${glowId})" aria-hidden="true" pointer-events="none" />`
|
|
422
563
|
: '';
|
|
423
|
-
const hotspots =
|
|
564
|
+
const hotspots = portHooks ? componentPortMarkup(component) : '';
|
|
424
565
|
const vector = symbolId === undefined
|
|
425
566
|
? `<g${vectorIdAttribute} class="schematic-component-vector">${componentShape(component)}</g>`
|
|
426
567
|
: `<use${vectorIdAttribute} ${colorAttributes(component.color, 'schematic-component-vector')} href="#${symbolId}" />`;
|
|
427
|
-
|
|
568
|
+
const externalLabels = isUmlComponent(component)
|
|
569
|
+
? ''
|
|
570
|
+
: `<text class="schematic-designator" fill="currentColor" stroke="none" x="0" y="${anchors.designatorY}" text-anchor="middle" font-size="12" textLength="${anchors.designatorWidth}" lengthAdjust="spacingAndGlyphs">${id}</text><text class="schematic-label" fill="currentColor" stroke="none" x="0" y="${anchors.labelY}" text-anchor="middle" font-size="12" textLength="${anchors.labelWidth}" lengthAdjust="spacingAndGlyphs">${renderedLabel}</text>`;
|
|
571
|
+
return `<g class="schematic-component"${dataAttributes} transform="translate(${svgNumber(component.x)} ${svgNumber(component.y)})"${accessibility}>${vector}${glow}${externalLabels}${hotspots}</g>`;
|
|
428
572
|
}
|
|
429
573
|
export function renderSchematic(document, options) {
|
|
430
574
|
assertParsedSchematicDocument(document);
|
|
431
575
|
const normalized = normalizeCompileOptions(options);
|
|
432
|
-
|
|
576
|
+
const components = new Map(document.components.map((component) => [component.id, component]));
|
|
577
|
+
const routedConnections = routeConnections(document.connections, components, normalized.bounds);
|
|
578
|
+
validateDocumentGeometry(document, normalized, routedConnections);
|
|
433
579
|
const signature = `${normalized.bounds.width}x${normalized.bounds.height}:${normalized.title}:${JSON.stringify(document)}`;
|
|
434
580
|
const candidatePrefix = (normalized.idPrefix ?? `schematic-${stableHash(signature)}`).replace(/[^A-Za-z0-9_-]/g, '-');
|
|
435
581
|
const safePrefix = /[A-Za-z0-9]/.test(candidatePrefix)
|
|
@@ -441,8 +587,9 @@ export function renderSchematic(document, options) {
|
|
|
441
587
|
const glowId = `${safePrefix}-schematic-glow-filter`;
|
|
442
588
|
const styles = normalized.mode === 'embedded-css' || normalized.mode === 'full';
|
|
443
589
|
const hooks = normalized.mode === 'full';
|
|
444
|
-
const
|
|
445
|
-
const
|
|
590
|
+
const nodeHooks = hooks && (normalized.semanticHookMask & NODE_HOOK) !== 0;
|
|
591
|
+
const portHooks = hooks && (normalized.semanticHookMask & PORT_HOOK) !== 0;
|
|
592
|
+
const wireHooks = hooks && (normalized.semanticHookMask & WIRE_HOOK) !== 0;
|
|
446
593
|
const reusableSymbols = new Map();
|
|
447
594
|
for (const component of document.components) {
|
|
448
595
|
const key = reusableSymbolKey(component);
|
|
@@ -452,22 +599,22 @@ export function renderSchematic(document, options) {
|
|
|
452
599
|
}
|
|
453
600
|
const title = escapeXml(normalized.title);
|
|
454
601
|
const componentCount = document.components.length;
|
|
455
|
-
const description = `${componentCount} component${componentCount === 1 ? '' : 's'} and ${document.connections.length}
|
|
602
|
+
const description = `${componentCount} component${componentCount === 1 ? '' : 's'} and ${document.connections.length} connection${document.connections.length === 1 ? '' : 's'}.`;
|
|
456
603
|
const writer = new BoundedSvgWriter();
|
|
457
|
-
const hookStyles =
|
|
604
|
+
const hookStyles = portHooks ? HOOK_SVG_STYLES : '';
|
|
458
605
|
const interactionStyles = styles ? INTERACTIVE_SVG_STYLES : '';
|
|
459
606
|
const glowFilter = styles
|
|
460
607
|
? `<filter id="${glowId}" x="-30%" y="-30%" width="160%" height="160%" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="2.2" result="blur" /><feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge></filter>`
|
|
461
608
|
: '';
|
|
462
609
|
const symbolDefinitions = Array.from(reusableSymbols.values(), ({ id, component }) => reusableSymbolDefinition(component, id)).join('');
|
|
463
610
|
const usedMarkers = new Set(document.connections.flatMap((connection) => [connection.markerStart, connection.markerEnd]));
|
|
464
|
-
const markerDefinitions = `${usedMarkers.has('arrow') ? `<marker id="${safePrefix}-marker-arrow" viewBox="0 0 8 8" refX="
|
|
611
|
+
const markerDefinitions = `${usedMarkers.has('arrow') ? `<marker id="${safePrefix}-marker-arrow" viewBox="0 0 8 8" refX="8" refY="4" markerWidth="8" markerHeight="8" markerUnits="userSpaceOnUse" orient="auto-start-reverse" overflow="visible"><path d="M0 0 8 4 0 8Z" fill="context-stroke" /></marker>` : ''}${usedMarkers.has('open-arrow') ? `<marker id="${safePrefix}-marker-open-arrow" viewBox="0 0 9 10" refX="9" refY="5" markerWidth="9" markerHeight="10" markerUnits="userSpaceOnUse" orient="auto-start-reverse" overflow="visible"><path d="M0 0 9 5 0 10" fill="none" stroke="context-stroke" stroke-width="1.5" /></marker>` : ''}${usedMarkers.has('dot') ? `<marker id="${safePrefix}-marker-dot" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="8" markerHeight="8" markerUnits="userSpaceOnUse"><circle cx="4" cy="4" r="3" fill="context-stroke" /></marker>` : ''}${usedMarkers.has('triangle') ? `<marker id="${safePrefix}-marker-triangle" viewBox="0 0 12 12" refX="11" refY="6" markerWidth="12" markerHeight="12" markerUnits="userSpaceOnUse" orient="auto-start-reverse" overflow="visible"><path d="M0 1 11 6 0 11Z" fill="var(--schematic-surface,#fff)" stroke="context-stroke" stroke-width="1.5" /></marker>` : ''}${usedMarkers.has('diamond') ? `<marker id="${safePrefix}-marker-diamond" viewBox="0 0 13 12" refX="12" refY="6" markerWidth="13" markerHeight="12" markerUnits="userSpaceOnUse" orient="auto-start-reverse" overflow="visible"><path d="M0 6 6 1 12 6 6 11Z" fill="var(--schematic-surface,#fff)" stroke="context-stroke" stroke-width="1.5" /></marker>` : ''}${usedMarkers.has('diamond-filled') ? `<marker id="${safePrefix}-marker-diamond-filled" viewBox="0 0 13 12" refX="12" refY="6" markerWidth="13" markerHeight="12" markerUnits="userSpaceOnUse" orient="auto-start-reverse" overflow="visible"><path d="M0 6 6 1 12 6 6 11Z" fill="context-stroke" stroke="context-stroke" /></marker>` : ''}`;
|
|
465
612
|
const embeddedDefinitions = styles
|
|
466
613
|
? `<style>${STATIC_SVG_STYLES}${hookStyles}${interactionStyles}</style><pattern id="${gridId}" width="20" height="20" patternUnits="userSpaceOnUse"><path class="schematic-grid-line" d="M20 0H0V20" /></pattern>${glowFilter}`
|
|
467
614
|
: '';
|
|
468
615
|
const definitions = `${embeddedDefinitions}${markerDefinitions}${symbolDefinitions}`;
|
|
469
616
|
writer.append(`<figure class="schematic-frame"${hooks ? ' data-schematic' : ''}><svg class="schematic-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${normalized.bounds.width} ${normalized.bounds.height}" width="${normalized.bounds.width}" height="${normalized.bounds.height}" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" role="img" aria-labelledby="${titleId} ${descriptionId}" preserveAspectRatio="xMidYMid meet"><title id="${titleId}">${title}</title><desc id="${descriptionId}">${description}</desc>${definitions === '' ? '' : `<defs>${definitions}</defs>`}${styles ? `<rect class="schematic-surface" width="100%" height="100%" /><rect class="schematic-grid" width="100%" height="100%" fill="url(#${gridId})" />` : ''}<g class="schematic-vectors">`);
|
|
470
|
-
if (
|
|
617
|
+
if (wireHooks) {
|
|
471
618
|
for (const [index, connection] of document.connections.entries()) {
|
|
472
619
|
writer.append(connectionMarkup(connection, routedConnections[index], index, safePrefix, glowId));
|
|
473
620
|
}
|
|
@@ -478,7 +625,7 @@ export function renderSchematic(document, options) {
|
|
|
478
625
|
for (const [index, component] of document.components.entries()) {
|
|
479
626
|
const key = reusableSymbolKey(component);
|
|
480
627
|
const symbolId = key === undefined ? undefined : reusableSymbols.get(key)?.id;
|
|
481
|
-
writer.append(componentMarkup(component, index, safePrefix, glowId, normalized.mode, symbolId));
|
|
628
|
+
writer.append(componentMarkup(component, index, safePrefix, glowId, normalized.mode, nodeHooks, portHooks, symbolId));
|
|
482
629
|
}
|
|
483
630
|
writer.append(`</g></svg><figcaption>${title}</figcaption></figure>`);
|
|
484
631
|
return writer.finish();
|
package/dist/types.d.ts
CHANGED
|
@@ -35,8 +35,12 @@ export type ClassicalGateKind = (typeof CLASSICAL_GATE_KINDS)[number];
|
|
|
35
35
|
export declare const QUANTUM_GATE_KINDS: readonly ["hadamard", "cnot", "qgate"];
|
|
36
36
|
/** A supported native or polymorphic quantum gate keyword. */
|
|
37
37
|
export type QuantumGateKind = (typeof QUANTUM_GATE_KINDS)[number];
|
|
38
|
+
/** First-class UML node keywords spanning structural and behavioral diagrams. */
|
|
39
|
+
export declare const UML_COMPONENT_KINDS: readonly ["class", "actor", "usecase", "state", "lifeline", "note", "package", "initial", "final"];
|
|
40
|
+
/** A supported UML node keyword. */
|
|
41
|
+
export type UmlComponentKind = (typeof UML_COMPONENT_KINDS)[number];
|
|
38
42
|
/** Complete, collision-free component keyword registry. */
|
|
39
|
-
export declare const COMPONENT_KINDS: readonly ["resistor", "capacitor", "inductor", "diode", "transistor", "port", "ground", "nand", "nor", "xor", "and", "or", "not", "hadamard", "cnot", "qgate", "ic"];
|
|
43
|
+
export declare const COMPONENT_KINDS: readonly ["resistor", "capacitor", "inductor", "diode", "transistor", "port", "ground", "nand", "nor", "xor", "and", "or", "not", "hadamard", "cnot", "qgate", "class", "actor", "usecase", "state", "lifeline", "note", "package", "initial", "final", "ic"];
|
|
40
44
|
/** Any component keyword that can begin a declaration. */
|
|
41
45
|
export type ComponentKind = (typeof COMPONENT_KINDS)[number];
|
|
42
46
|
/** Built-in semantic color tokens that hosts can theme through CSS. */
|
|
@@ -169,8 +173,53 @@ export interface IcComponent extends ComponentBase {
|
|
|
169
173
|
}
|
|
170
174
|
/** Descriptive alias for a parsed integrated-circuit block. */
|
|
171
175
|
export type IntegratedCircuitComponent = IcComponent;
|
|
176
|
+
/** Parsed UML class with independently sized attribute and operation compartments. */
|
|
177
|
+
export interface UmlClassComponent extends ComponentBase {
|
|
178
|
+
/** UML class node discriminant. */
|
|
179
|
+
kind: 'class';
|
|
180
|
+
/** Optional stereotype displayed above the class name. */
|
|
181
|
+
stereotype?: string;
|
|
182
|
+
/** Attribute declarations in source order. */
|
|
183
|
+
attributes: readonly string[];
|
|
184
|
+
/** Operation declarations in source order. */
|
|
185
|
+
operations: readonly string[];
|
|
186
|
+
/** Deterministic box width derived from all visible rows. */
|
|
187
|
+
bodyWidth: number;
|
|
188
|
+
/** Deterministic three-compartment box height. */
|
|
189
|
+
bodyHeight: number;
|
|
190
|
+
}
|
|
191
|
+
/** Parsed UML state with optional behavior rows. */
|
|
192
|
+
export interface UmlStateComponent extends ComponentBase {
|
|
193
|
+
kind: 'state';
|
|
194
|
+
/** Entry, do, exit, or author-defined behavior rows. */
|
|
195
|
+
details: readonly string[];
|
|
196
|
+
bodyWidth: number;
|
|
197
|
+
bodyHeight: number;
|
|
198
|
+
}
|
|
199
|
+
/** Parsed UML ellipse, note, package, or sequence lifeline. */
|
|
200
|
+
export interface UmlSizedComponent extends ComponentBase {
|
|
201
|
+
kind: 'usecase' | 'lifeline' | 'note' | 'package';
|
|
202
|
+
bodyWidth: number;
|
|
203
|
+
bodyHeight: number;
|
|
204
|
+
}
|
|
205
|
+
/** Parsed UML actor figure. */
|
|
206
|
+
export interface UmlActorComponent extends ComponentBase {
|
|
207
|
+
kind: 'actor';
|
|
208
|
+
}
|
|
209
|
+
/** Parsed UML initial or final pseudostate. */
|
|
210
|
+
export interface UmlPseudostateComponent extends ComponentBase {
|
|
211
|
+
kind: 'initial' | 'final';
|
|
212
|
+
}
|
|
213
|
+
/** Every UML component accepted by the compiler. */
|
|
214
|
+
export type UmlComponent = UmlClassComponent | UmlStateComponent | UmlSizedComponent | UmlActorComponent | UmlPseudostateComponent;
|
|
172
215
|
/** Discriminated union of every component node accepted by the renderer. */
|
|
173
|
-
export type SchematicComponent = PassiveComponent | DiodeComponent | TransistorComponent | PortComponent | GroundComponent | ClassicalGateComponent | QuantumGateComponent | IcComponent;
|
|
216
|
+
export type SchematicComponent = PassiveComponent | DiodeComponent | TransistorComponent | PortComponent | GroundComponent | ClassicalGateComponent | QuantumGateComponent | IcComponent | UmlComponent;
|
|
217
|
+
/** UML relationship semantics used to derive line style and endpoint markers. */
|
|
218
|
+
export declare const UML_RELATION_KINDS: readonly ["association", "dependency", "generalization", "realization", "aggregation", "composition", "message", "transition", "include", "extend"];
|
|
219
|
+
/** A supported UML relationship. */
|
|
220
|
+
export type UmlRelationKind = (typeof UML_RELATION_KINDS)[number];
|
|
221
|
+
/** Electrical signal or a first-class UML relationship. */
|
|
222
|
+
export type SchematicRelationKind = 'signal' | UmlRelationKind;
|
|
174
223
|
/** Address of one component terminal in a connection declaration. */
|
|
175
224
|
export interface SchematicEndpoint {
|
|
176
225
|
/** Document-local component identifier. */
|
|
@@ -192,6 +241,12 @@ export interface SchematicConnection {
|
|
|
192
241
|
markerStart: SchematicSignalMarker;
|
|
193
242
|
/** Optional marker drawn at the destination terminal. */
|
|
194
243
|
markerEnd: SchematicSignalMarker;
|
|
244
|
+
/** Electrical or UML relationship semantics. */
|
|
245
|
+
relation?: SchematicRelationKind;
|
|
246
|
+
/** Optional text centered beside the routed connector. */
|
|
247
|
+
label?: string;
|
|
248
|
+
/** Whether the trace uses the UML dependency dash pattern. */
|
|
249
|
+
dashed?: boolean;
|
|
195
250
|
/** One-based source line used for routing diagnostics. */
|
|
196
251
|
line: number;
|
|
197
252
|
}
|
|
@@ -206,8 +261,12 @@ export interface SchematicDocument {
|
|
|
206
261
|
export declare const SCHEMD_OUTPUT_MODES: readonly ["default", "embedded-css", "full"];
|
|
207
262
|
/** Static, CSS-enhanced, or fully attributed SVG output mode. */
|
|
208
263
|
export type SchemdOutputMode = (typeof SCHEMD_OUTPUT_MODES)[number];
|
|
264
|
+
/** Optional semantic payloads emitted by `full` mode. */
|
|
265
|
+
export declare const SCHEMD_SEMANTIC_HOOKS: readonly ["nodes", "ports", "wires"];
|
|
266
|
+
/** A selectable delegated-interaction payload. */
|
|
267
|
+
export type SchematicSemanticHook = (typeof SCHEMD_SEMANTIC_HOOKS)[number];
|
|
209
268
|
/** Marker primitives that can terminate or originate a signal trace. */
|
|
210
|
-
export declare const SCHEMATIC_SIGNAL_MARKERS: readonly ["none", "arrow", "dot"];
|
|
269
|
+
export declare const SCHEMATIC_SIGNAL_MARKERS: readonly ["none", "arrow", "open-arrow", "dot", "triangle", "diamond", "diamond-filled"];
|
|
211
270
|
/** A validated connection marker selection. */
|
|
212
271
|
export type SchematicSignalMarker = (typeof SCHEMATIC_SIGNAL_MARKERS)[number];
|
|
213
272
|
/** Per-render options extending the fence's intrinsic layout contract. */
|
|
@@ -216,17 +275,8 @@ export interface CompileSchematicOptions extends SchematicFence {
|
|
|
216
275
|
idPrefix?: string;
|
|
217
276
|
/** Markup and interaction budget for the generated SVG. */
|
|
218
277
|
mode?: SchemdOutputMode;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
export interface SchematicMarkedOptions {
|
|
222
|
-
/** Accessible title used when the fence omits one. */
|
|
223
|
-
defaultTitle?: string;
|
|
224
|
-
/** Fixed output mode for every schemd fence in a Markdown pass. */
|
|
225
|
-
mode?: SchemdOutputMode;
|
|
226
|
-
/** Synchronous mode resolver for hosts with request-scoped rendering state. */
|
|
227
|
-
resolveMode?: () => SchemdOutputMode;
|
|
228
|
-
/** Optional safe fallback renderer for bounded syntax failures. */
|
|
229
|
-
onError?: (error: SchematicSyntaxError, source: string) => string;
|
|
278
|
+
/** Full-mode metadata to emit. Defaults to nodes, ports, and wires. */
|
|
279
|
+
semanticHooks?: readonly SchematicSemanticHook[];
|
|
230
280
|
}
|
|
231
281
|
/**
|
|
232
282
|
* Deterministic syntax or geometry failure with optional source location.
|
package/dist/types.js
CHANGED
|
@@ -5,16 +5,49 @@ export const TRANSISTOR_TYPES = ['npn', 'pnp', 'nmos', 'pmos'];
|
|
|
5
5
|
export const GROUND_STYLES = ['chassis', 'earth', 'signal'];
|
|
6
6
|
export const CLASSICAL_GATE_KINDS = ['nand', 'nor', 'xor', 'and', 'or', 'not'];
|
|
7
7
|
export const QUANTUM_GATE_KINDS = ['hadamard', 'cnot', 'qgate'];
|
|
8
|
+
export const UML_COMPONENT_KINDS = [
|
|
9
|
+
'class',
|
|
10
|
+
'actor',
|
|
11
|
+
'usecase',
|
|
12
|
+
'state',
|
|
13
|
+
'lifeline',
|
|
14
|
+
'note',
|
|
15
|
+
'package',
|
|
16
|
+
'initial',
|
|
17
|
+
'final'
|
|
18
|
+
];
|
|
8
19
|
export const COMPONENT_KINDS = [
|
|
9
20
|
...PASSIVE_KINDS,
|
|
10
21
|
...ANALOG_KINDS,
|
|
11
22
|
...CLASSICAL_GATE_KINDS,
|
|
12
23
|
...QUANTUM_GATE_KINDS,
|
|
24
|
+
...UML_COMPONENT_KINDS,
|
|
13
25
|
'ic'
|
|
14
26
|
];
|
|
15
27
|
export const SEMANTIC_COLORS = ['amber', 'blue', 'cyan', 'purple', 'slate', 'emerald'];
|
|
28
|
+
export const UML_RELATION_KINDS = [
|
|
29
|
+
'association',
|
|
30
|
+
'dependency',
|
|
31
|
+
'generalization',
|
|
32
|
+
'realization',
|
|
33
|
+
'aggregation',
|
|
34
|
+
'composition',
|
|
35
|
+
'message',
|
|
36
|
+
'transition',
|
|
37
|
+
'include',
|
|
38
|
+
'extend'
|
|
39
|
+
];
|
|
16
40
|
export const SCHEMD_OUTPUT_MODES = ['default', 'embedded-css', 'full'];
|
|
17
|
-
export const
|
|
41
|
+
export const SCHEMD_SEMANTIC_HOOKS = ['nodes', 'ports', 'wires'];
|
|
42
|
+
export const SCHEMATIC_SIGNAL_MARKERS = [
|
|
43
|
+
'none',
|
|
44
|
+
'arrow',
|
|
45
|
+
'open-arrow',
|
|
46
|
+
'dot',
|
|
47
|
+
'triangle',
|
|
48
|
+
'diamond',
|
|
49
|
+
'diamond-filled'
|
|
50
|
+
];
|
|
18
51
|
export class SchematicSyntaxError extends Error {
|
|
19
52
|
line;
|
|
20
53
|
constructor(message, line) {
|