@schemd/core 0.1.2 → 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 +4 -6
- package/dist/compiler.d.ts +16 -0
- package/dist/compiler.js +17 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +2 -2
- package/dist/limits.d.ts +1 -3
- package/dist/renderer.js +38 -11
- package/dist/types.d.ts +6 -11
- package/dist/types.js +1 -0
- package/package.json +5 -15
- package/dist/marked-extension.d.ts +0 -23
- package/dist/marked-extension.js +0 -75
package/README.md
CHANGED
|
@@ -10,18 +10,18 @@ Schemd has no runtime dependencies, browser layout pass, or DOM requirement. It
|
|
|
10
10
|
npm install @schemd/core
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Node.js 24+ is required.
|
|
13
|
+
Node.js 24+ is required.
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import {
|
|
18
|
+
import { compileSchematic, parseSchematicFence } from "@schemd/core";
|
|
19
19
|
|
|
20
20
|
const fence = parseSchematicFence(
|
|
21
21
|
'schemd bounds="640x260" title="Sensor input"',
|
|
22
22
|
)!;
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const { svg, document, metrics } = compileSchematic(`
|
|
25
25
|
port:VIN "Input" at (60, 130) #blue
|
|
26
26
|
resistor:R1 "10 k\\Omega" at (220, 130) #amber
|
|
27
27
|
capacitor:C1 "100 nF" at (400, 130) #cyan
|
|
@@ -29,8 +29,6 @@ capacitor:C1 "100 nF" at (400, 130) #cyan
|
|
|
29
29
|
VIN.out -> R1.in #blue [ortho]
|
|
30
30
|
R1.out -> C1.in #amber [ortho]
|
|
31
31
|
`, fence);
|
|
32
|
-
|
|
33
|
-
const html = renderSchematic(diagram, fence);
|
|
34
32
|
```
|
|
35
33
|
|
|
36
34
|
The DSL is intentionally small:
|
|
@@ -55,6 +53,6 @@ Class, actor, use-case, state, lifeline, note, package, initial, and final nodes
|
|
|
55
53
|
|
|
56
54
|
Use `mode: "embedded-css"` for built-in styling or `mode: "full"` for interaction metadata. The default output stays compact and static.
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
Markdown belongs at the host boundary. Detect `schemd` fences in your server-side Markdown renderer and pass their text to `compileSchematic`; core does not ship or require a Markdown parser.
|
|
59
57
|
|
|
60
58
|
[Documentation](https://johnowolabiidogun.dev/tools/schemd/docs/overview) · [Issues](https://github.com/Sirneij/schemd/issues) · [MIT](https://github.com/Sirneij/schemd/blob/main/LICENSE)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CompileSchematicOptions, SchematicDocument } from './types.js';
|
|
2
|
+
/** Small, allocation-bounded compilation counters. */
|
|
3
|
+
export interface SchematicCompilationMetrics {
|
|
4
|
+
readonly sourceCharacters: number;
|
|
5
|
+
readonly components: number;
|
|
6
|
+
readonly connections: number;
|
|
7
|
+
readonly svgBytes: number;
|
|
8
|
+
}
|
|
9
|
+
/** Validated AST, rendered SVG, and useful host-side counters. */
|
|
10
|
+
export interface SchematicCompilation {
|
|
11
|
+
readonly document: SchematicDocument;
|
|
12
|
+
readonly svg: string;
|
|
13
|
+
readonly metrics: SchematicCompilationMetrics;
|
|
14
|
+
}
|
|
15
|
+
/** Parse and render a schematic with one stable public call. */
|
|
16
|
+
export declare function compileSchematic(source: string, options: CompileSchematicOptions): SchematicCompilation;
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { utf8ByteLength } from './limits.js';
|
|
2
|
+
import { parseSchematic } from './parser.js';
|
|
3
|
+
import { renderSchematic } from './renderer.js';
|
|
4
|
+
export function compileSchematic(source, options) {
|
|
5
|
+
const document = parseSchematic(source, options);
|
|
6
|
+
const svg = renderSchematic(document, options);
|
|
7
|
+
return {
|
|
8
|
+
document,
|
|
9
|
+
svg,
|
|
10
|
+
metrics: {
|
|
11
|
+
sourceCharacters: source.length,
|
|
12
|
+
components: document.components.length,
|
|
13
|
+
connections: document.connections.length,
|
|
14
|
+
svgBytes: utf8ByteLength(svg)
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stable public entry point for the `schemd` server-side compiler.
|
|
3
3
|
*
|
|
4
|
-
* All runtime exports are dependency-free
|
|
5
|
-
* Marked contracts as types only, so applications pay for no Markdown runtime
|
|
6
|
-
* unless they already provide one at the host boundary.
|
|
4
|
+
* All runtime exports are dependency-free and safe to run without a DOM.
|
|
7
5
|
*
|
|
8
6
|
* @packageDocumentation
|
|
9
7
|
*/
|
|
10
8
|
export { parseSchematic, parseSchematicColor, parseSchematicFence } from './parser.js';
|
|
11
9
|
export { renderSchematic } from './renderer.js';
|
|
12
|
-
export {
|
|
10
|
+
export { compileSchematic, type SchematicCompilation, type SchematicCompilationMetrics } from './compiler.js';
|
|
13
11
|
export { mathLabelGlyphLength, mathLabelTextWidth, mathLabelText, parseMathLabel, renderMathLabelTspans, type MathLabelSegment, type MathLabelSegmentKind } from './math-label.js';
|
|
14
12
|
export { MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, MAX_SCHEMATIC_WIRE_CROSSINGS, MAX_SCHEMATIC_SOURCE_CHARACTERS, MAX_SCHEMATIC_SVG_OUTPUT_BYTES, SCHEMATIC_LIMITS } from './limits.js';
|
|
15
13
|
export { classicalGateHeight, componentObstacleRectangle, componentRectangle, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, positionIcPin, PORT_HOTSPOT_RADIUS, resolvePortPoint, resolvePortGeometry, routeConnections, routeConnection, SCHEMATIC_BRIDGE_RADIUS, SCHEMATIC_OBSTACLE_CLEARANCE, validateDocumentGeometry, type RoutedConnection, type ComponentTextAnchors, type ComponentPort, type IcPinSide, type SchematicRectangle } from './layout.js';
|
|
16
|
-
export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SchematicSyntaxError, type AnalogKind, type CompileSchematicOptions, type ClassicalGateComponent, type ClassicalGateKind, type ComponentKind, type DiodeComponent, type DiodeType, type GroundComponent, type GroundStyle, type IcComponent, type IntegratedCircuitComponent, type IntegratedCircuitPins, type PassiveComponent, type PassiveKind, type PortComponent, type SemanticColor, type SchematicBounds, type SchematicComponent, type SchematicConnection, type SchematicColor, type SchematicDocument, type SchematicEndpoint, type SchematicFence, type SchematicSignalMarker, type
|
|
14
|
+
export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, SchematicSyntaxError, type AnalogKind, type CompileSchematicOptions, type ClassicalGateComponent, type ClassicalGateKind, type ComponentKind, type DiodeComponent, type DiodeType, type GroundComponent, type GroundStyle, type IcComponent, type IntegratedCircuitComponent, type IntegratedCircuitPins, type PassiveComponent, type PassiveKind, type PortComponent, type SemanticColor, type SchematicBounds, type SchematicComponent, type SchematicConnection, type SchematicColor, type SchematicDocument, type SchematicEndpoint, type SchematicFence, type SchematicSignalMarker, type SchematicSemanticHook, type SchematicPoint, type SchemdOutputMode, type TransistorComponent, type TransistorType, type QuantumGateComponent, type QuantumGateKind, type SchematicRelationKind, type UmlActorComponent, type UmlClassComponent, type UmlComponent, type UmlComponentKind, type UmlPseudostateComponent, type UmlRelationKind, type UmlSizedComponent, type UmlStateComponent } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { parseSchematic, parseSchematicColor, parseSchematicFence } from './parser.js';
|
|
2
2
|
export { renderSchematic } from './renderer.js';
|
|
3
|
-
export {
|
|
3
|
+
export { compileSchematic } from './compiler.js';
|
|
4
4
|
export { mathLabelGlyphLength, mathLabelTextWidth, mathLabelText, parseMathLabel, renderMathLabelTspans } from './math-label.js';
|
|
5
5
|
export { MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, MAX_SCHEMATIC_WIRE_CROSSINGS, MAX_SCHEMATIC_SOURCE_CHARACTERS, MAX_SCHEMATIC_SVG_OUTPUT_BYTES, SCHEMATIC_LIMITS } from './limits.js';
|
|
6
6
|
export { classicalGateHeight, componentObstacleRectangle, componentRectangle, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, positionIcPin, PORT_HOTSPOT_RADIUS, resolvePortPoint, resolvePortGeometry, routeConnections, routeConnection, SCHEMATIC_BRIDGE_RADIUS, SCHEMATIC_OBSTACLE_CLEARANCE, validateDocumentGeometry } from './layout.js';
|
|
7
|
-
export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SchematicSyntaxError } from './types.js';
|
|
7
|
+
export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, SchematicSyntaxError } from './types.js';
|
package/dist/limits.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hard compiler budgets shared by the parser
|
|
2
|
+
* Hard compiler budgets shared by the parser and renderer.
|
|
3
3
|
*
|
|
4
4
|
* `parseSchematic` and `renderSchematic` enforce these limits for one diagram.
|
|
5
|
-
* `schematicMarkedExtension` additionally enforces them cumulatively across all
|
|
6
|
-
* schematic fences encountered during one Marked document pass.
|
|
7
5
|
*
|
|
8
6
|
* @packageDocumentation
|
|
9
7
|
*/
|
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, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
4
|
+
import { CLASSICAL_GATE_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
5
5
|
import { mathLabelTextWidth, renderMathLabelTspans } from './math-label.js';
|
|
6
6
|
export const MAX_SVG_OUTPUT_BYTES = MAX_SCHEMATIC_SVG_OUTPUT_BYTES;
|
|
7
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,9 +92,24 @@ 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)));
|
|
@@ -516,19 +541,18 @@ function componentPortMarkup(component) {
|
|
|
516
541
|
})
|
|
517
542
|
.join('');
|
|
518
543
|
}
|
|
519
|
-
function componentMarkup(component, index, idPrefix, glowId, mode, symbolId) {
|
|
544
|
+
function componentMarkup(component, index, idPrefix, glowId, mode, nodeHooks, portHooks, symbolId) {
|
|
520
545
|
const label = escapeXml(component.label);
|
|
521
546
|
const renderedLabel = renderMathLabelTspans(component.label);
|
|
522
547
|
const anchors = componentTextAnchors(component);
|
|
523
548
|
const id = escapeXml(component.id);
|
|
524
549
|
const vectorId = `${idPrefix}-node-${index}-vector`;
|
|
525
550
|
const styles = mode === 'embedded-css' || mode === 'full';
|
|
526
|
-
const
|
|
527
|
-
const dataAttributes = hooks
|
|
551
|
+
const dataAttributes = nodeHooks
|
|
528
552
|
? ` data-node-id="${id}" data-node-kind="${component.kind}" data-node-label="${label}" data-component="${id}" data-kind="${component.kind}"`
|
|
529
553
|
: '';
|
|
530
554
|
let accessibility = '';
|
|
531
|
-
if (styles
|
|
555
|
+
if (styles) {
|
|
532
556
|
const metadata = componentMetadata(component);
|
|
533
557
|
const ariaLabel = escapeXml(`${component.id}, ${component.kind}, ${component.label}${metadata === '' ? '' : `, ${metadata}`}`);
|
|
534
558
|
accessibility = ` tabindex="0" role="group" aria-label="${ariaLabel}"`;
|
|
@@ -537,7 +561,7 @@ function componentMarkup(component, index, idPrefix, glowId, mode, symbolId) {
|
|
|
537
561
|
const glow = styles
|
|
538
562
|
? `<use class="schematic-glow-layer" href="#${vectorId}" filter="url(#${glowId})" aria-hidden="true" pointer-events="none" />`
|
|
539
563
|
: '';
|
|
540
|
-
const hotspots =
|
|
564
|
+
const hotspots = portHooks ? componentPortMarkup(component) : '';
|
|
541
565
|
const vector = symbolId === undefined
|
|
542
566
|
? `<g${vectorIdAttribute} class="schematic-component-vector">${componentShape(component)}</g>`
|
|
543
567
|
: `<use${vectorIdAttribute} ${colorAttributes(component.color, 'schematic-component-vector')} href="#${symbolId}" />`;
|
|
@@ -563,6 +587,9 @@ export function renderSchematic(document, options) {
|
|
|
563
587
|
const glowId = `${safePrefix}-schematic-glow-filter`;
|
|
564
588
|
const styles = normalized.mode === 'embedded-css' || normalized.mode === 'full';
|
|
565
589
|
const hooks = normalized.mode === 'full';
|
|
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;
|
|
566
593
|
const reusableSymbols = new Map();
|
|
567
594
|
for (const component of document.components) {
|
|
568
595
|
const key = reusableSymbolKey(component);
|
|
@@ -574,7 +601,7 @@ export function renderSchematic(document, options) {
|
|
|
574
601
|
const componentCount = document.components.length;
|
|
575
602
|
const description = `${componentCount} component${componentCount === 1 ? '' : 's'} and ${document.connections.length} connection${document.connections.length === 1 ? '' : 's'}.`;
|
|
576
603
|
const writer = new BoundedSvgWriter();
|
|
577
|
-
const hookStyles =
|
|
604
|
+
const hookStyles = portHooks ? HOOK_SVG_STYLES : '';
|
|
578
605
|
const interactionStyles = styles ? INTERACTIVE_SVG_STYLES : '';
|
|
579
606
|
const glowFilter = styles
|
|
580
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>`
|
|
@@ -587,7 +614,7 @@ export function renderSchematic(document, options) {
|
|
|
587
614
|
: '';
|
|
588
615
|
const definitions = `${embeddedDefinitions}${markerDefinitions}${symbolDefinitions}`;
|
|
589
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">`);
|
|
590
|
-
if (
|
|
617
|
+
if (wireHooks) {
|
|
591
618
|
for (const [index, connection] of document.connections.entries()) {
|
|
592
619
|
writer.append(connectionMarkup(connection, routedConnections[index], index, safePrefix, glowId));
|
|
593
620
|
}
|
|
@@ -598,7 +625,7 @@ export function renderSchematic(document, options) {
|
|
|
598
625
|
for (const [index, component] of document.components.entries()) {
|
|
599
626
|
const key = reusableSymbolKey(component);
|
|
600
627
|
const symbolId = key === undefined ? undefined : reusableSymbols.get(key)?.id;
|
|
601
|
-
writer.append(componentMarkup(component, index, safePrefix, glowId, normalized.mode, symbolId));
|
|
628
|
+
writer.append(componentMarkup(component, index, safePrefix, glowId, normalized.mode, nodeHooks, portHooks, symbolId));
|
|
602
629
|
}
|
|
603
630
|
writer.append(`</g></svg><figcaption>${title}</figcaption></figure>`);
|
|
604
631
|
return writer.finish();
|
package/dist/types.d.ts
CHANGED
|
@@ -261,6 +261,10 @@ export interface SchematicDocument {
|
|
|
261
261
|
export declare const SCHEMD_OUTPUT_MODES: readonly ["default", "embedded-css", "full"];
|
|
262
262
|
/** Static, CSS-enhanced, or fully attributed SVG output mode. */
|
|
263
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];
|
|
264
268
|
/** Marker primitives that can terminate or originate a signal trace. */
|
|
265
269
|
export declare const SCHEMATIC_SIGNAL_MARKERS: readonly ["none", "arrow", "open-arrow", "dot", "triangle", "diamond", "diamond-filled"];
|
|
266
270
|
/** A validated connection marker selection. */
|
|
@@ -271,17 +275,8 @@ export interface CompileSchematicOptions extends SchematicFence {
|
|
|
271
275
|
idPrefix?: string;
|
|
272
276
|
/** Markup and interaction budget for the generated SVG. */
|
|
273
277
|
mode?: SchemdOutputMode;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
export interface SchematicMarkedOptions {
|
|
277
|
-
/** Accessible title used when the fence omits one. */
|
|
278
|
-
defaultTitle?: string;
|
|
279
|
-
/** Fixed output mode for every schemd fence in a Markdown pass. */
|
|
280
|
-
mode?: SchemdOutputMode;
|
|
281
|
-
/** Synchronous mode resolver for hosts with request-scoped rendering state. */
|
|
282
|
-
resolveMode?: () => SchemdOutputMode;
|
|
283
|
-
/** Optional safe fallback renderer for bounded syntax failures. */
|
|
284
|
-
onError?: (error: SchematicSyntaxError, source: string) => string;
|
|
278
|
+
/** Full-mode metadata to emit. Defaults to nodes, ports, and wires. */
|
|
279
|
+
semanticHooks?: readonly SchematicSemanticHook[];
|
|
285
280
|
}
|
|
286
281
|
/**
|
|
287
282
|
* Deterministic syntax or geometry failure with optional source location.
|
package/dist/types.js
CHANGED
|
@@ -38,6 +38,7 @@ export const UML_RELATION_KINDS = [
|
|
|
38
38
|
'extend'
|
|
39
39
|
];
|
|
40
40
|
export const SCHEMD_OUTPUT_MODES = ['default', 'embedded-css', 'full'];
|
|
41
|
+
export const SCHEMD_SEMANTIC_HOOKS = ['nodes', 'ports', 'wires'];
|
|
41
42
|
export const SCHEMATIC_SIGNAL_MARKERS = [
|
|
42
43
|
'none',
|
|
43
44
|
'arrow',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schemd/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Zero-dependency text-to-SVG compiler for schematics and UML.",
|
|
5
5
|
"homepage": "https://johnowolabiidogun.dev/tools/schemd/docs/overview",
|
|
6
6
|
"bugs": {
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"types": "./dist/renderer.d.ts",
|
|
33
33
|
"import": "./dist/renderer.js"
|
|
34
34
|
},
|
|
35
|
+
"./compiler": {
|
|
36
|
+
"types": "./dist/compiler.d.ts",
|
|
37
|
+
"import": "./dist/compiler.js"
|
|
38
|
+
},
|
|
35
39
|
"./layout": {
|
|
36
40
|
"types": "./dist/layout.d.ts",
|
|
37
41
|
"import": "./dist/layout.js"
|
|
@@ -40,10 +44,6 @@
|
|
|
40
44
|
"types": "./dist/math-label.d.ts",
|
|
41
45
|
"import": "./dist/math-label.js"
|
|
42
46
|
},
|
|
43
|
-
"./marked": {
|
|
44
|
-
"types": "./dist/marked-extension.d.ts",
|
|
45
|
-
"import": "./dist/marked-extension.js"
|
|
46
|
-
},
|
|
47
47
|
"./types": {
|
|
48
48
|
"types": "./dist/types.d.ts",
|
|
49
49
|
"import": "./dist/types.js"
|
|
@@ -56,17 +56,8 @@
|
|
|
56
56
|
"test": "vitest run",
|
|
57
57
|
"test:coverage": "vitest run --coverage"
|
|
58
58
|
},
|
|
59
|
-
"peerDependencies": {
|
|
60
|
-
"marked": ">=17 <19"
|
|
61
|
-
},
|
|
62
|
-
"peerDependenciesMeta": {
|
|
63
|
-
"marked": {
|
|
64
|
-
"optional": true
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
59
|
"devDependencies": {
|
|
68
60
|
"@vitest/coverage-v8": "^4.1.10",
|
|
69
|
-
"marked": "^18.0.6",
|
|
70
61
|
"typescript": "^6.0.3",
|
|
71
62
|
"vitest": "^4.1.10"
|
|
72
63
|
},
|
|
@@ -75,7 +66,6 @@
|
|
|
75
66
|
},
|
|
76
67
|
"keywords": [
|
|
77
68
|
"schemd",
|
|
78
|
-
"marked",
|
|
79
69
|
"schematic",
|
|
80
70
|
"svg",
|
|
81
71
|
"compiler",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type-only Marked adapter for compiling fenced `schemd` diagrams during SSR.
|
|
3
|
-
*
|
|
4
|
-
* The module imports no Marked runtime. Its factory returns ordinary extension
|
|
5
|
-
* hooks to a host-owned parser and enforces both per-diagram compiler ceilings
|
|
6
|
-
* and cumulative limits across each Markdown document pass.
|
|
7
|
-
*
|
|
8
|
-
* @packageDocumentation
|
|
9
|
-
*/
|
|
10
|
-
import type { MarkedExtension } from 'marked';
|
|
11
|
-
import { type SchematicMarkedOptions } from './types.js';
|
|
12
|
-
/**
|
|
13
|
-
* Create a bounded Marked extension that compiles `schemd` fences into inline SVG.
|
|
14
|
-
*
|
|
15
|
-
* The returned extension keeps aggregate per-document budgets in closure state,
|
|
16
|
-
* resets them from Marked's `preprocess` hook, and recognizes only the canonical
|
|
17
|
-
* `schemd` language identifier.
|
|
18
|
-
*
|
|
19
|
-
* @param options - Output mode, accessible-title fallback, dynamic mode resolver,
|
|
20
|
-
* and trusted server-side diagnostic renderer.
|
|
21
|
-
* @returns A Marked extension suitable for a server-owned `Marked` instance.
|
|
22
|
-
*/
|
|
23
|
-
export declare function schematicMarkedExtension(options?: SchematicMarkedOptions): MarkedExtension;
|
package/dist/marked-extension.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, MAX_SCHEMATIC_SOURCE_CHARACTERS, MAX_SCHEMATIC_SVG_OUTPUT_BYTES, utf8ByteLength } from './limits.js';
|
|
2
|
-
import { parseSchematic, parseSchematicFence } from './parser.js';
|
|
3
|
-
import { renderSchematic } from './renderer.js';
|
|
4
|
-
import { SchematicSyntaxError } from './types.js';
|
|
5
|
-
class SchematicDocumentBudgetError extends SchematicSyntaxError {
|
|
6
|
-
}
|
|
7
|
-
function emptyDocumentBudget() {
|
|
8
|
-
return { sourceCharacters: 0, components: 0, connections: 0, svgOutputBytes: 0 };
|
|
9
|
-
}
|
|
10
|
-
function consumeBudget(used, amount, limit, unit) {
|
|
11
|
-
if (amount > limit - used) {
|
|
12
|
-
throw new SchematicDocumentBudgetError(`Schematic document exceeds the cumulative ${limit.toLocaleString('en-US')} ${unit} limit.`);
|
|
13
|
-
}
|
|
14
|
-
return used + amount;
|
|
15
|
-
}
|
|
16
|
-
function escapeHtml(value) {
|
|
17
|
-
return value
|
|
18
|
-
.replaceAll('&', '&')
|
|
19
|
-
.replaceAll('<', '<')
|
|
20
|
-
.replaceAll('>', '>')
|
|
21
|
-
.replaceAll('"', '"')
|
|
22
|
-
.replaceAll("'", ''');
|
|
23
|
-
}
|
|
24
|
-
function defaultErrorRenderer(error, source) {
|
|
25
|
-
return `<figure class="schematic-error" role="group" aria-label="Schematic compilation error"><figcaption>${escapeHtml(error.message)}</figcaption><pre><code class="language-schemd">${escapeHtml(source)}</code></pre></figure>`;
|
|
26
|
-
}
|
|
27
|
-
export function schematicMarkedExtension(options = {}) {
|
|
28
|
-
let diagramIndex = 0;
|
|
29
|
-
let budget = emptyDocumentBudget();
|
|
30
|
-
let budgetError;
|
|
31
|
-
return {
|
|
32
|
-
hooks: {
|
|
33
|
-
preprocess(source) {
|
|
34
|
-
diagramIndex = 0;
|
|
35
|
-
budget = emptyDocumentBudget();
|
|
36
|
-
budgetError = undefined;
|
|
37
|
-
return source;
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
renderer: {
|
|
41
|
-
code(token) {
|
|
42
|
-
if (!token.lang || !/^schemd(?:\s|$)/i.test(token.lang.trim())) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
if (budgetError)
|
|
47
|
-
throw budgetError;
|
|
48
|
-
budget.sourceCharacters = consumeBudget(budget.sourceCharacters, token.text.length, MAX_SCHEMATIC_SOURCE_CHARACTERS, 'schematic source character');
|
|
49
|
-
const fence = parseSchematicFence(token.lang, options.defaultTitle);
|
|
50
|
-
diagramIndex += 1;
|
|
51
|
-
const document = parseSchematic(token.text, fence);
|
|
52
|
-
const nextComponents = consumeBudget(budget.components, document.components.length, MAX_SCHEMATIC_COMPONENTS, 'component');
|
|
53
|
-
const nextConnections = consumeBudget(budget.connections, document.connections.length, MAX_SCHEMATIC_CONNECTIONS, 'connection');
|
|
54
|
-
const html = renderSchematic(document, {
|
|
55
|
-
...fence,
|
|
56
|
-
idPrefix: `schematic-${diagramIndex}`,
|
|
57
|
-
mode: options.resolveMode?.() ?? options.mode ?? 'default'
|
|
58
|
-
});
|
|
59
|
-
const nextSvgOutputBytes = consumeBudget(budget.svgOutputBytes, utf8ByteLength(html), MAX_SCHEMATIC_SVG_OUTPUT_BYTES, 'compiled SVG byte');
|
|
60
|
-
budget.components = nextComponents;
|
|
61
|
-
budget.connections = nextConnections;
|
|
62
|
-
budget.svgOutputBytes = nextSvgOutputBytes;
|
|
63
|
-
return html;
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (!(error instanceof SchematicSyntaxError))
|
|
67
|
-
throw error;
|
|
68
|
-
if (error instanceof SchematicDocumentBudgetError)
|
|
69
|
-
budgetError = error;
|
|
70
|
-
return (options.onError ?? defaultErrorRenderer)(error, token.text);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|