@schemd/core 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +84 -32
- package/dist/compiler.d.ts +39 -0
- package/dist/compiler.js +17 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/layout.d.ts +60 -1
- package/dist/layout.js +536 -65
- package/dist/math-label.d.ts +5 -3
- package/dist/math-label.js +14 -28
- package/dist/parser.js +409 -40
- package/dist/renderer.js +377 -45
- package/dist/types.d.ts +124 -19
- package/dist/types.js +167 -4
- package/package.json +83 -78
- package/assets/brand/README.md +0 -6
- package/assets/brand/schemd-logo.svg +0 -29
- package/assets/brand/schemd-mark.svg +0 -23
package/dist/renderer.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { classicalGateHeight, componentTextAnchors, distributedCoordinate, enumerateComponentPorts,
|
|
1
|
+
import { classicalGateHeight, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, PORT_HOTSPOT_RADIUS, quantumGateDimensions, quantumTrackCoordinate, quantumTrackSpan, 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
4
|
import { parsedSchematicRoutes } from './route-cache.js';
|
|
5
|
-
import { CLASSICAL_GATE_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
5
|
+
import { CLASSICAL_GATE_KINDS, DIGITAL_COMPONENT_KINDS, QUANTUM_GATE_KINDS, QUANTUM_SPECIAL_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
6
6
|
import { mathLabelTextWidth, renderMathLabelTspans } from './math-label.js';
|
|
7
7
|
import { escapeXml } from './xml.js';
|
|
8
8
|
export const MAX_SVG_OUTPUT_BYTES = MAX_SCHEMATIC_SVG_OUTPUT_BYTES;
|
|
@@ -105,6 +105,29 @@ function stableHash(value) {
|
|
|
105
105
|
function isClassicalGate(component) {
|
|
106
106
|
return CLASSICAL_GATE_KINDS.includes(component.kind);
|
|
107
107
|
}
|
|
108
|
+
function isDigitalComponent(component) {
|
|
109
|
+
return DIGITAL_COMPONENT_KINDS.includes(component.kind);
|
|
110
|
+
}
|
|
111
|
+
function isQuantumSpecial(component) {
|
|
112
|
+
return QUANTUM_SPECIAL_KINDS.includes(component.kind);
|
|
113
|
+
}
|
|
114
|
+
function isQuantumGate(component) {
|
|
115
|
+
return QUANTUM_GATE_KINDS.includes(component.kind);
|
|
116
|
+
}
|
|
117
|
+
function componentRotation(component) {
|
|
118
|
+
if (!('orientation' in component))
|
|
119
|
+
return 0;
|
|
120
|
+
switch (component.orientation) {
|
|
121
|
+
case 'down':
|
|
122
|
+
return 90;
|
|
123
|
+
case 'left':
|
|
124
|
+
return 180;
|
|
125
|
+
case 'up':
|
|
126
|
+
return 270;
|
|
127
|
+
default:
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
108
131
|
function isUmlComponent(component) {
|
|
109
132
|
return UML_COMPONENT_KINDS.includes(component.kind);
|
|
110
133
|
}
|
|
@@ -123,7 +146,7 @@ function colorAttributes(color, extraClass = '') {
|
|
|
123
146
|
function orInputBoundaryX(kind, y, height) {
|
|
124
147
|
const halfHeight = height / 2;
|
|
125
148
|
const progress = (1 - y / halfHeight) / 2;
|
|
126
|
-
const start = kind === 'xor' ? -38 : -32;
|
|
149
|
+
const start = kind === 'xor' || kind === 'xnor' ? -38 : -32;
|
|
127
150
|
return start + 40 * progress * (1 - progress);
|
|
128
151
|
}
|
|
129
152
|
function orOutputBoundaryX(y, height) {
|
|
@@ -137,7 +160,7 @@ function gateBoundaryX(component, direction, y) {
|
|
|
137
160
|
return direction === 'input' ? -32 : 32;
|
|
138
161
|
const height = classicalGateHeight(component);
|
|
139
162
|
if (direction === 'input') {
|
|
140
|
-
if (component.kind === 'or' || component.kind === 'nor' || component.kind === 'xor') {
|
|
163
|
+
if (component.kind === 'or' || component.kind === 'nor' || component.kind === 'xor' || component.kind === 'xnor') {
|
|
141
164
|
return orInputBoundaryX(component.kind, y, height);
|
|
142
165
|
}
|
|
143
166
|
return component.kind === 'not' ? -30 : -32;
|
|
@@ -146,13 +169,13 @@ function gateBoundaryX(component, direction, y) {
|
|
|
146
169
|
const normalized = y / (height / 2);
|
|
147
170
|
return 32 * Math.sqrt(Math.max(0, 1 - normalized * normalized));
|
|
148
171
|
}
|
|
149
|
-
if (component.kind === 'or' || component.kind === 'nor' || component.kind === 'xor') {
|
|
172
|
+
if (component.kind === 'or' || component.kind === 'nor' || component.kind === 'xor' || component.kind === 'xnor') {
|
|
150
173
|
return orOutputBoundaryX(y, height);
|
|
151
174
|
}
|
|
152
175
|
return 30 - 60 * (Math.abs(y) / (height / 2));
|
|
153
176
|
}
|
|
154
177
|
function hasOutputInversion(component) {
|
|
155
|
-
return component.kind === 'nand' || component.kind === 'nor' || component.kind === 'not';
|
|
178
|
+
return component.kind === 'nand' || component.kind === 'nor' || component.kind === 'xnor' || component.kind === 'not';
|
|
156
179
|
}
|
|
157
180
|
function gateStubs(component, paint) {
|
|
158
181
|
const height = classicalGateHeight(component);
|
|
@@ -186,6 +209,7 @@ function iecGate(component, paint) {
|
|
|
186
209
|
or: '≥1',
|
|
187
210
|
nor: '≥1',
|
|
188
211
|
xor: '=1',
|
|
212
|
+
xnor: '=1',
|
|
189
213
|
not: '1'
|
|
190
214
|
};
|
|
191
215
|
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)}`;
|
|
@@ -202,19 +226,30 @@ function ieeeGate(component, paint) {
|
|
|
202
226
|
contour = `<path ${paint} d="M -32 ${top} H 0 A 32 ${height / 2} 0 0 1 0 ${bottom} H -32 Z" />`;
|
|
203
227
|
}
|
|
204
228
|
else {
|
|
205
|
-
const xorArc = component.kind === 'xor' ? `<path ${paint} d="M -38 ${top} Q -18 0 -38 ${bottom}" />` : '';
|
|
229
|
+
const xorArc = component.kind === 'xor' || component.kind === 'xnor' ? `<path ${paint} d="M -38 ${top} Q -18 0 -38 ${bottom}" />` : '';
|
|
206
230
|
contour = `<path ${paint} d="M -32 ${top} Q 3 ${top} 32 0 Q 3 ${bottom} -32 ${bottom} Q -12 0 -32 ${top} Z" />${xorArc}`;
|
|
207
231
|
}
|
|
208
232
|
return `${contour}${inversionBubbles(component, paint)}${gateStubs(component, paint)}`;
|
|
209
233
|
}
|
|
210
234
|
function quantumText(component) {
|
|
235
|
+
const symbols = {
|
|
236
|
+
qgate: '', cnot: '⊕',
|
|
237
|
+
hadamard: 'H', xgate: 'X', ygate: 'Y', zgate: 'Z', sgate: 'S', sdg: 'S†',
|
|
238
|
+
tgate: 'T', tdg: 'T†', sx: '√X', phase: 'P', rx: 'Rx', ry: 'Ry', rz: 'Rz', ugate: 'U'
|
|
239
|
+
};
|
|
211
240
|
const details = [component.parameter, component.phase, component.matrix].filter((value) => value !== undefined && value !== '');
|
|
212
|
-
const rows = [component.label, ...details];
|
|
213
|
-
const
|
|
241
|
+
const rows = [component.kind === 'qgate' ? component.label : symbols[component.kind], ...details];
|
|
242
|
+
const { bodyWidth } = quantumGateDimensions(component);
|
|
243
|
+
const start = -((rows.length - 1) * 15) / 2 + 4;
|
|
214
244
|
return rows
|
|
215
|
-
.map((value, index) => `<text class="${index === 0 ? 'schematic-gate-symbol' : 'schematic-quantum-detail'}" fill="currentColor" stroke="none" x="0" y="${start + index *
|
|
245
|
+
.map((value, index) => `<text class="${index === 0 ? 'schematic-gate-symbol' : 'schematic-quantum-detail'}" fill="currentColor" stroke="none" x="0" y="${start + index * 15}" text-anchor="middle" font-size="${index === 0 ? 14 : 10}" textLength="${fittedTextLength(value, bodyWidth - 12, index === 0 ? 7 : 5)}" lengthAdjust="spacingAndGlyphs">${renderMathLabelTspans(value)}</text>`)
|
|
216
246
|
.join('');
|
|
217
247
|
}
|
|
248
|
+
function quantumGateShell(component, paint) {
|
|
249
|
+
const { bodyWidth, bodyHeight, stubExtent } = quantumGateDimensions(component);
|
|
250
|
+
const halfWidth = bodyWidth / 2;
|
|
251
|
+
return `<rect ${paint} x="${svgNumber(-halfWidth)}" y="${svgNumber(-bodyHeight / 2)}" width="${svgNumber(bodyWidth)}" height="${svgNumber(bodyHeight)}" rx="3" /><path ${paint} d="M ${svgNumber(-stubExtent)} 0 H ${svgNumber(-halfWidth)} M ${svgNumber(halfWidth)} 0 H ${svgNumber(stubExtent)}" />`;
|
|
252
|
+
}
|
|
218
253
|
function diodeShape(component, paint) {
|
|
219
254
|
const junction = `<path ${paint} d="M -42 0 H -14 M -14 -16 L 10 0 L -14 16 Z M 10 -16 V 16 M 10 0 H 42" />`;
|
|
220
255
|
switch (component.diodeType) {
|
|
@@ -226,6 +261,14 @@ function diodeShape(component, paint) {
|
|
|
226
261
|
return `${junction}<path ${paint} d="M 4 -20 L 10 -16 M 10 16 L 16 20" />`;
|
|
227
262
|
case 'led':
|
|
228
263
|
return `${junction}<path ${paint} d="M -1 -18 L 11 -30 M 6 -30 H 11 V -25 M 8 -14 L 20 -26 M 15 -26 H 20 V -21" />`;
|
|
264
|
+
case 'photodiode':
|
|
265
|
+
return `${junction}<path ${paint} d="M 18 -30 L 6 -18 M 6 -18 H 11 V -23 M 27 -22 L 15 -10 M 15 -10 H 20 V -15" />`;
|
|
266
|
+
case 'varactor':
|
|
267
|
+
return `${junction}<path ${paint} d="M 16 -16 V 16" />`;
|
|
268
|
+
case 'scr':
|
|
269
|
+
return `${junction}<path ${paint} d="M 10 10 L 24 22 H 34" />`;
|
|
270
|
+
case 'triac':
|
|
271
|
+
return `<path ${paint} d="M -42 0 H -15 L 9 -15 V 15 L -15 0 M 42 0 H 15 L -9 15 V -15 L 15 0 M 5 12 L 23 25 H 34" />`;
|
|
229
272
|
}
|
|
230
273
|
}
|
|
231
274
|
function bjtShape(component, paint) {
|
|
@@ -233,12 +276,18 @@ function bjtShape(component, paint) {
|
|
|
233
276
|
return `<circle ${paint} cx="0" cy="0" r="30" /><path ${paint} d="M -42 0 H -9 M -9 -18 V 18 M -9 -10 L 20 -22 H 42 M -9 10 L 20 22 H 42 ${emitterArrow}" />`;
|
|
234
277
|
}
|
|
235
278
|
function mosfetShape(component, paint) {
|
|
236
|
-
const pChannel = component.transistorType
|
|
279
|
+
const pChannel = component.transistorType.startsWith('p');
|
|
280
|
+
const jfet = component.transistorType.endsWith('jfet');
|
|
281
|
+
const igbt = component.transistorType.endsWith('igbt');
|
|
282
|
+
if (jfet) {
|
|
283
|
+
const arrow = pChannel ? 'M -5 -5 L 5 0 L -5 5' : 'M 5 -5 L -5 0 L 5 5';
|
|
284
|
+
return `<circle ${paint} cx="0" cy="0" r="30" /><path ${paint} d="M -42 0 H 4 M 4 -18 V 18 M 4 -12 L 20 -22 H 42 M 4 12 L 20 22 H 42 ${arrow}" />`;
|
|
285
|
+
}
|
|
237
286
|
const control = pChannel
|
|
238
287
|
? `<path ${paint} d="M -42 0 H -16" /><circle ${paint} cx="-11" cy="0" r="5" />`
|
|
239
288
|
: `<path ${paint} d="M -42 0 H -10" />`;
|
|
240
289
|
const arrow = pChannel ? 'M 16 -3 L 5 3 L 16 9' : 'M 5 -3 L 16 3 L 5 9';
|
|
241
|
-
return `<circle ${paint} cx="0" cy="0" r="30" />${control}<path ${paint} d="M -10 -19 V 19 M 4 -17 V 17 M 4 -11 L 20 -22 H 42 M 4 11 L 20 22 H 42 ${arrow}" />`;
|
|
290
|
+
return `<circle ${paint} cx="0" cy="0" r="30" />${control}<path ${paint} d="M -10 -19 V 19 M 4 -17 V 17 M 4 -11 L 20 -22 H 42 M 4 11 L 20 22 H 42 ${arrow}${igbt ? ' M 9 -11 V 11' : ''}" />`;
|
|
242
291
|
}
|
|
243
292
|
function transistorShape(component, paint) {
|
|
244
293
|
return component.transistorType === 'npn' || component.transistorType === 'pnp'
|
|
@@ -260,9 +309,13 @@ function icPinMarkup(component, side, paint) {
|
|
|
260
309
|
const pins = component.pins[side];
|
|
261
310
|
return pins
|
|
262
311
|
.map((pin, index) => {
|
|
263
|
-
const
|
|
264
|
-
const x =
|
|
265
|
-
|
|
312
|
+
const vertical = side === 'left' || side === 'right';
|
|
313
|
+
const x = vertical
|
|
314
|
+
? (side === 'left' ? -1 : 1) * (component.bodyWidth / 2 + 16)
|
|
315
|
+
: distributedCoordinate(index, pins.length, component.bodyWidth);
|
|
316
|
+
const y = vertical
|
|
317
|
+
? distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
318
|
+
: (side === 'top' ? -1 : 1) * (component.bodyHeight / 2 + 16);
|
|
266
319
|
const horizontalTextLength = fittedTextLength(pin, Math.max(8, component.bodyWidth / 2 - 10), 4);
|
|
267
320
|
const verticalTextLength = fittedTextLength(pin, Math.max(8, component.bodyHeight / 2 - 10), 4);
|
|
268
321
|
if (side === 'left') {
|
|
@@ -283,7 +336,9 @@ function icPinMarkup(component, side, paint) {
|
|
|
283
336
|
function integratedCircuitShape(component, paint) {
|
|
284
337
|
const left = -component.bodyWidth / 2;
|
|
285
338
|
const top = -component.bodyHeight / 2;
|
|
286
|
-
|
|
339
|
+
const rotation = componentRotation(component);
|
|
340
|
+
const label = `<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>`;
|
|
341
|
+
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)}${rotation === 0 ? label : `<g transform="rotate(${-rotation})">${label}</g>`}`;
|
|
287
342
|
}
|
|
288
343
|
function umlRow(value, x, y) {
|
|
289
344
|
return `<text class="schematic-uml-text schematic-uml-row" fill="currentColor" stroke="none" x="${svgNumber(x)}" y="${svgNumber(y)}" font-size="12">${renderMathLabelTspans(value)}</text>`;
|
|
@@ -317,7 +372,8 @@ function umlStateShape(component, paint) {
|
|
|
317
372
|
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}`;
|
|
318
373
|
}
|
|
319
374
|
function umlComponentShape(component, paint, nodePaint) {
|
|
320
|
-
if (component.kind === 'class'
|
|
375
|
+
if (component.kind === 'class' || component.kind === 'interface' ||
|
|
376
|
+
component.kind === 'enumeration' || component.kind === 'datatype' || component.kind === 'object')
|
|
321
377
|
return umlClassShape(component, paint);
|
|
322
378
|
if (component.kind === 'state')
|
|
323
379
|
return umlStateShape(component, paint);
|
|
@@ -342,25 +398,230 @@ function umlComponentShape(component, paint, nodePaint) {
|
|
|
342
398
|
const top = -component.bodyHeight / 2;
|
|
343
399
|
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>`;
|
|
344
400
|
}
|
|
401
|
+
case 'component':
|
|
402
|
+
case 'artifact':
|
|
403
|
+
case 'node':
|
|
404
|
+
case 'device':
|
|
405
|
+
case 'execution':
|
|
406
|
+
case 'system':
|
|
407
|
+
case 'action':
|
|
408
|
+
case 'object-node':
|
|
409
|
+
case 'partition':
|
|
410
|
+
case 'activation':
|
|
411
|
+
case 'fragment':
|
|
412
|
+
case 'interaction':
|
|
413
|
+
case 'region': {
|
|
414
|
+
const left = -component.bodyWidth / 2;
|
|
415
|
+
const top = -component.bodyHeight / 2;
|
|
416
|
+
const right = -left;
|
|
417
|
+
const bottom = -top;
|
|
418
|
+
if (component.kind === 'activation')
|
|
419
|
+
return `<rect ${paint} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${svgNumber(component.bodyWidth)}" height="${svgNumber(component.bodyHeight)}" />`;
|
|
420
|
+
if (component.kind === 'fragment' || component.kind === 'interaction') {
|
|
421
|
+
return `<rect ${paint} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${svgNumber(component.bodyWidth)}" height="${svgNumber(component.bodyHeight)}" /><path ${paint} d="M ${svgNumber(left)} ${svgNumber(top + 20)} H ${svgNumber(left + 58)} L ${svgNumber(left + 68)} ${svgNumber(top + 10)} V ${svgNumber(top)}" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="${svgNumber(left + 6)}" y="${svgNumber(top + 14)}" font-size="11">${component.kind === 'interaction' ? 'ref' : 'alt'}</text><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="12">${renderMathLabelTspans(component.label)}</text>`;
|
|
422
|
+
}
|
|
423
|
+
const rx = component.kind === 'action' ? 10 : 1;
|
|
424
|
+
const dash = component.kind === 'region' ? ' stroke-dasharray="6 4"' : '';
|
|
425
|
+
const icon = component.kind === 'component'
|
|
426
|
+
? `<path ${paint} d="M ${svgNumber(right - 22)} ${svgNumber(top + 10)} h 14 v 10 h -14 z M ${svgNumber(right - 22)} ${svgNumber(top + 25)} h 14 v 10 h -14 z" />`
|
|
427
|
+
: component.kind === 'artifact'
|
|
428
|
+
? `<path ${paint} d="M ${svgNumber(right - 16)} ${svgNumber(top)} v 16 h 16" />`
|
|
429
|
+
: component.kind === 'node' || component.kind === 'device' || component.kind === 'execution'
|
|
430
|
+
? `<path ${paint} d="M ${svgNumber(left)} ${svgNumber(top + 10)} l 12 -10 h ${svgNumber(component.bodyWidth)} l -12 10 M ${svgNumber(right)} ${svgNumber(top)} v ${svgNumber(component.bodyHeight - 10)} l -12 10" />`
|
|
431
|
+
: component.kind === 'partition' ? `<path ${paint} d="M ${svgNumber(left + 28)} ${svgNumber(top)} V ${svgNumber(bottom)}" />` : '';
|
|
432
|
+
return `<rect ${paint}${dash} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${svgNumber(component.bodyWidth)}" height="${svgNumber(component.bodyHeight)}" rx="${rx}" />${icon}<text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="12">${renderMathLabelTspans(component.label)}</text>`;
|
|
433
|
+
}
|
|
434
|
+
case 'provided-interface':
|
|
435
|
+
return `<circle ${paint} cx="0" cy="0" r="10" />`;
|
|
436
|
+
case 'required-interface':
|
|
437
|
+
return `<path ${paint} d="M 0 -10 A 10 10 0 0 0 0 10" />`;
|
|
438
|
+
case 'component-port':
|
|
439
|
+
return `<rect ${paint} x="-7" y="-7" width="14" height="14" />`;
|
|
440
|
+
case 'decision':
|
|
441
|
+
case 'merge':
|
|
442
|
+
case 'choice':
|
|
443
|
+
return `<path ${paint} d="M 0 -17 L 17 0 0 17 -17 0 Z" />`;
|
|
444
|
+
case 'fork':
|
|
445
|
+
case 'join':
|
|
446
|
+
return `<rect ${nodePaint} x="-38" y="-4" width="76" height="8" rx="1" />`;
|
|
447
|
+
case 'activity-final':
|
|
448
|
+
return `<circle ${paint} cx="0" cy="0" r="11" /><circle ${nodePaint} cx="0" cy="0" r="6" />`;
|
|
449
|
+
case 'flow-final':
|
|
450
|
+
return `<circle ${paint} cx="0" cy="0" r="11" /><path ${paint} d="M -7 -7 L 7 7 M 7 -7 L -7 7" />`;
|
|
451
|
+
case 'send-signal':
|
|
452
|
+
return `<path ${paint} d="M -21 -14 H 5 L 21 0 5 14 H -21 Z" />`;
|
|
453
|
+
case 'receive-signal':
|
|
454
|
+
return `<path ${paint} d="M -21 -14 H 21 L 5 0 21 14 H -21 Z" />`;
|
|
455
|
+
case 'destruction':
|
|
456
|
+
return `<path ${paint} d="M -10 -10 L 10 10 M 10 -10 L -10 10" />`;
|
|
457
|
+
case 'gate':
|
|
458
|
+
case 'found':
|
|
459
|
+
case 'lost':
|
|
460
|
+
return `<circle ${component.kind === 'lost' ? paint : nodePaint} cx="0" cy="0" r="5" />`;
|
|
461
|
+
case 'state-junction':
|
|
462
|
+
return `<circle ${nodePaint} cx="0" cy="0" r="7" />`;
|
|
463
|
+
case 'history':
|
|
464
|
+
return `<circle ${paint} cx="0" cy="0" r="11" /><text class="schematic-uml-text" fill="currentColor" stroke="none" x="0" y="4" text-anchor="middle" font-size="11">H${component.variant === 'deep' ? '*' : ''}</text>`;
|
|
465
|
+
case 'entry':
|
|
466
|
+
case 'exit':
|
|
467
|
+
return `<circle ${paint} cx="0" cy="0" r="10" />${component.kind === 'exit' ? `<path ${paint} d="M -6 -6 L 6 6 M 6 -6 L -6 6" />` : ''}`;
|
|
468
|
+
case 'terminate':
|
|
469
|
+
return `<path ${paint} d="M -9 -9 L 9 9 M 9 -9 L -9 9" />`;
|
|
345
470
|
case 'initial':
|
|
346
471
|
return `<circle ${nodePaint} cx="0" cy="0" r="10" />`;
|
|
347
472
|
case 'final':
|
|
348
473
|
return `<circle ${paint} cx="0" cy="0" r="11" /><circle ${nodePaint} cx="0" cy="0" r="6" />`;
|
|
349
474
|
}
|
|
350
475
|
}
|
|
476
|
+
function passiveShape(component, paint) {
|
|
477
|
+
const variant = component.passiveType ?? 'fixed';
|
|
478
|
+
if (component.kind === 'resistor') {
|
|
479
|
+
const base = `<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" />`;
|
|
480
|
+
if (variant === 'variable' || variant === 'rheostat' || variant === 'potentiometer')
|
|
481
|
+
return `${base}<path ${paint} d="M -18 20 L 18 -20 M 11 -20 H 18 V -13${variant === 'potentiometer' ? ' M 0 28 V 12' : ''}" />`;
|
|
482
|
+
if (variant === 'thermistor')
|
|
483
|
+
return `${base}<path ${paint} d="M -18 19 L 18 -19 M 10 -19 H 18 V -11" />`;
|
|
484
|
+
if (variant === 'ldr')
|
|
485
|
+
return `${base}<path ${paint} d="M 15 -27 L 4 -16 M 4 -16 H 9 V -21 M 27 -20 L 16 -9 M 16 -9 H 21 V -14" />`;
|
|
486
|
+
return base;
|
|
487
|
+
}
|
|
488
|
+
if (component.kind === 'capacitor') {
|
|
489
|
+
const rightPlate = variant === 'polarized' ? 'M 7 -18 Q 13 0 7 18' : 'M 7 -18 V 18';
|
|
490
|
+
const variable = variant === 'variable' ? `<path ${paint} d="M -18 20 L 18 -20 M 11 -20 H 18 V -13" />` : '';
|
|
491
|
+
const positive = variant === 'polarized' ? `<path ${paint} d="M -17 -12 H -9 M -13 -16 V -8" />` : '';
|
|
492
|
+
return `<path ${paint} d="M -42 0 H -7 M -7 -18 V 18 ${rightPlate} M 7 0 H 42" />${variable}${positive}`;
|
|
493
|
+
}
|
|
494
|
+
const base = `<path ${paint} d="M -42 0 H -28 C -28 -16 -14 -16 -14 0 C -14 -16 0 -16 0 0 C 0 -16 14 -16 14 0 C 14 -16 28 -16 28 0 H 42" />`;
|
|
495
|
+
return variant === 'fixed' ? base : `${base}<path ${paint} d="M -28 12 C -28 28 -14 28 -14 12 C -14 28 0 28 0 12 C 0 28 14 28 14 12 C 14 28 28 28 28 12${variant === 'transformer' ? ' M -33 5 H 33 M -33 8 H 33' : ''}" />`;
|
|
496
|
+
}
|
|
497
|
+
function electricalShape(component, paint, nodePaint) {
|
|
498
|
+
const variant = component.variant ?? '';
|
|
499
|
+
switch (component.kind) {
|
|
500
|
+
case 'junction':
|
|
501
|
+
return `<circle ${nodePaint} cx="0" cy="0" r="4" />`;
|
|
502
|
+
case 'testpoint':
|
|
503
|
+
return `<circle ${paint} cx="0" cy="0" r="9" /><circle ${nodePaint} cx="0" cy="0" r="2" />`;
|
|
504
|
+
case 'connector':
|
|
505
|
+
return `<path ${paint} d="M -32 0 H -9 M 9 0 H 32" /><circle ${paint} cx="0" cy="0" r="9" />`;
|
|
506
|
+
case 'source': {
|
|
507
|
+
if (variant === 'battery')
|
|
508
|
+
return `<path ${paint} d="M -42 0 H -8 M -8 -16 V 16 M 8 -10 V 10 M 8 0 H 42" />`;
|
|
509
|
+
const dependent = ['vcvs', 'vccs', 'ccvs', 'cccs'].includes(variant);
|
|
510
|
+
const body = dependent ? `<path ${paint} d="M -24 0 L 0 -24 24 0 0 24 Z" />` : `<circle ${paint} cx="0" cy="0" r="24" />`;
|
|
511
|
+
const controls = dependent ? `<path ${paint} d="M 0 -34 V -24 M 0 24 V 34" />` : '';
|
|
512
|
+
const current = variant.startsWith('current') || variant.endsWith('cs');
|
|
513
|
+
const glyph = variant.endsWith('ac')
|
|
514
|
+
? `<path ${paint} d="M -13 0 C -8 -12 -4 -12 0 0 C 4 12 8 12 13 0" />`
|
|
515
|
+
: variant === 'voltage-pulse'
|
|
516
|
+
? `<path ${paint} d="M -14 8 H -7 V -8 H 7 V 8 H 14" />`
|
|
517
|
+
: current ? `<path ${paint} d="M 0 12 V -12 M -6 -5 L 0 -12 6 -5" />` : `<path ${paint} d="M -7 -8 H 7 M 0 -15 V -1 M -7 9 H 7" />`;
|
|
518
|
+
return `<path ${paint} d="M -42 0 H -24 M 24 0 H 42" />${body}${controls}${glyph}`;
|
|
519
|
+
}
|
|
520
|
+
case 'power':
|
|
521
|
+
return `<path ${paint} d="M -32 0 H 0 M 0 -13 V 13 M 0 -13 L 15 0 0 13" />`;
|
|
522
|
+
case 'switch': {
|
|
523
|
+
const relay = variant === 'relay' ? `<path ${paint} d="M -12 34 C -12 18 12 18 12 34 M 0 19 V 8" />` : '';
|
|
524
|
+
const throw2 = variant === 'spdt' ? `<path ${paint} d="M 26 12 H 42" />` : '';
|
|
525
|
+
return `<path ${paint} d="M -42 0 H -24 M -24 0 L 22 -12 M 26 -12 H 42" />${throw2}${relay}`;
|
|
526
|
+
}
|
|
527
|
+
case 'protection':
|
|
528
|
+
return variant === 'breaker'
|
|
529
|
+
? `<path ${paint} d="M -42 0 H -18 M -18 0 L 18 -12 M 18 0 H 42" />`
|
|
530
|
+
: `<path ${paint} d="M -42 0 H -24 M 24 0 H 42" /><rect ${paint} x="-24" y="-9" width="48" height="18" rx="4" />`;
|
|
531
|
+
case 'amplifier':
|
|
532
|
+
return `<path ${paint} d="M -48 -13 H -30 M -48 13 H -30 M -30 -30 L 30 0 -30 30 Z M 30 0 H 48 M 0 -36 V -15 M 0 15 V 36 M -25 -17 H -17 M -21 -21 V -13 M -25 13 H -17" />`;
|
|
533
|
+
case 'resonator':
|
|
534
|
+
return `<path ${paint} d="M -42 0 H -12 M -12 -18 V 18 M 12 -18 V 18 M 12 0 H 42" /><rect ${paint} x="-7" y="-14" width="14" height="28" />`;
|
|
535
|
+
case 'meter':
|
|
536
|
+
case 'load':
|
|
537
|
+
return `<path ${paint} d="M -42 0 H -24 M 24 0 H 42" /><circle ${paint} cx="0" cy="0" r="24" />${variant === 'lamp' ? `<path ${paint} d="M -15 -15 L 15 15 M 15 -15 L -15 15" />` : variant === 'speaker' ? `<path ${paint} d="M -12 -9 H -3 L 8 -19 V 19 L -3 9 H -12 Z M 13 -10 Q 22 0 13 10" />` : variant === 'buzzer' ? `<path ${paint} d="M -12 -9 H -3 L 8 -18 V 18 L -3 9 H -12 Z M 13 -8 L 20 -14 M 15 0 H 24 M 13 8 L 20 14" />` : ''}`;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function digitalShape(component, paint) {
|
|
541
|
+
if (component.kind === 'buffer') {
|
|
542
|
+
const bubble = component.variant === 'tristate-inverter' || component.variant === 'schmitt-inverter';
|
|
543
|
+
return `<path ${paint} d="M -44 0 H -28 M -28 -22 L 24 0 -28 22 Z M ${bubble ? 32 : 24} 0 H 44${component.variant?.startsWith('schmitt') === true ? ' M -17 -8 H -6 V 8 H 5' : ''}" />${bubble ? `<circle ${paint} cx="28" cy="0" r="4" />` : ''}${component.variant?.startsWith('tristate') === true ? `<path ${paint} d="M 0 34 V 12" />` : ''}`;
|
|
544
|
+
}
|
|
545
|
+
const left = -component.bodyWidth / 2;
|
|
546
|
+
const right = -left;
|
|
547
|
+
const top = -component.bodyHeight / 2;
|
|
548
|
+
const stubs = `${Array.from({ length: component.inputs }, (_, index) => `<path ${paint} d="M ${svgNumber(left - 16)} ${svgNumber(distributedCoordinate(index, Math.max(1, component.inputs), component.bodyHeight))} H ${svgNumber(left)}" />`).join('')}${Array.from({ length: component.outputs }, (_, index) => `<path ${paint} d="M ${svgNumber(right)} ${svgNumber(distributedCoordinate(index, component.outputs, component.bodyHeight))} H ${svgNumber(right + 16)}" />`).join('')}`;
|
|
549
|
+
return `<rect ${paint} x="${svgNumber(left)}" y="${svgNumber(top)}" width="${component.bodyWidth}" height="${component.bodyHeight}" rx="3" />${stubs}`;
|
|
550
|
+
}
|
|
551
|
+
function quantumSpecialShape(component, paint, nodePaint) {
|
|
552
|
+
if (component.kind === 'measure')
|
|
553
|
+
return `<path ${paint} d="M -40 0 H -22 M 22 0 H 40 M 0 22 V 34" /><rect ${paint} x="-22" y="-22" width="44" height="44" rx="3" /><path ${paint} d="M -12 8 A 15 15 0 0 1 12 8 M 0 8 L 11 -9" />`;
|
|
554
|
+
if (component.kind === 'prepare')
|
|
555
|
+
return `<path ${paint} d="M 0 0 H 40 M -10 -10 L 10 0 -10 10 Z" />`;
|
|
556
|
+
if (component.kind === 'reset')
|
|
557
|
+
return `<path ${paint} d="M -40 0 H -18 M 18 0 H 40" /><rect ${paint} x="-18" y="-18" width="36" height="36" rx="3" /><path ${paint} d="M 9 -8 A 12 12 0 1 0 10 7 M 9 -8 H 1 M 9 -8 V 0" />`;
|
|
558
|
+
if (component.kind === 'control')
|
|
559
|
+
return `<path ${paint} d="M -42 0 H 42" />${component.controlType === 'negative' ? `<circle ${paint} cx="0" cy="0" r="5" />` : `<circle ${nodePaint} cx="0" cy="0" r="5" />`}`;
|
|
560
|
+
if (component.kind === 'classical-bit' || component.kind === 'classical-register')
|
|
561
|
+
return `<path ${paint} d="M -40 -2 H 40 M -40 2 H 40" />`;
|
|
562
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
563
|
+
const span = quantumTrackSpan(tracks);
|
|
564
|
+
const lines = Array.from({ length: tracks }, (_, index) => `<path ${paint} d="M -42 ${svgNumber(quantumTrackCoordinate(index, tracks))} H 42" />`).join('');
|
|
565
|
+
if (component.kind === 'barrier')
|
|
566
|
+
return `${lines}<path ${paint} stroke-dasharray="4 3" d="M 0 ${svgNumber(-span / 2 - 8)} V ${svgNumber(span / 2 + 8)}" />`;
|
|
567
|
+
if (component.kind === 'delay')
|
|
568
|
+
return `${lines}<rect ${paint} x="-12" y="${svgNumber(-span / 2 - 7)}" width="24" height="${svgNumber(span + 14)}" rx="2" />`;
|
|
569
|
+
const marks = Array.from({ length: tracks }, (_, index) => {
|
|
570
|
+
const y = quantumTrackCoordinate(index, tracks);
|
|
571
|
+
if (component.kind === 'swap')
|
|
572
|
+
return `<path ${paint} d="M -6 ${svgNumber(y - 6)} L 6 ${svgNumber(y + 6)} M 6 ${svgNumber(y - 6)} L -6 ${svgNumber(y + 6)}" />`;
|
|
573
|
+
if (component.kind === 'cz')
|
|
574
|
+
return `<circle ${nodePaint} cx="0" cy="${svgNumber(y)}" r="4" />`;
|
|
575
|
+
if (index < component.controls) {
|
|
576
|
+
if (component.controlType === 'negative')
|
|
577
|
+
return `<circle ${paint} cx="0" cy="${svgNumber(y)}" r="5" />`;
|
|
578
|
+
if (component.controlType === 'classical')
|
|
579
|
+
return `<rect ${nodePaint} x="-5" y="${svgNumber(y - 5)}" width="10" height="10" />`;
|
|
580
|
+
return `<circle ${nodePaint} cx="0" cy="${svgNumber(y)}" r="4" />`;
|
|
581
|
+
}
|
|
582
|
+
if (component.kind === 'controlled' || component.kind === 'cphase')
|
|
583
|
+
return `<rect ${paint} x="-10" y="${svgNumber(y - 10)}" width="20" height="20" rx="2" />`;
|
|
584
|
+
return `<circle ${paint} cx="0" cy="${svgNumber(y)}" r="10" /><path ${paint} d="M -7 ${svgNumber(y)} H 7 M 0 ${svgNumber(y - 7)} V ${svgNumber(y + 7)}" />`;
|
|
585
|
+
}).join('');
|
|
586
|
+
return `${lines}<path ${paint} d="M 0 ${svgNumber(-span / 2)} V ${svgNumber(span / 2)}" />${marks}`;
|
|
587
|
+
}
|
|
588
|
+
function componentInnerText(component) {
|
|
589
|
+
if (isQuantumGate(component) && component.kind !== 'cnot') {
|
|
590
|
+
return `<g ${colorAttributes(component.color)}>${quantumText(component)}</g>`;
|
|
591
|
+
}
|
|
592
|
+
if (isDigitalComponent(component)) {
|
|
593
|
+
const labels = {
|
|
594
|
+
buffer: '', logic: component.variant === 'high' ? '1' : component.variant === 'low' ? '0' : component.variant === 'high-z' ? 'Z' : 'X',
|
|
595
|
+
clock: 'CLK', flipflop: String(component.variant).toUpperCase(), mux: component.variant === 'demux' ? 'DEMUX' : 'MUX',
|
|
596
|
+
encoder: 'ENC', decoder: 'DEC', register: 'REG', counter: 'CTR', adder: component.variant === 'full' ? 'FA' : 'HA', comparator: 'CMP', bus: String(component.variant).toUpperCase()
|
|
597
|
+
};
|
|
598
|
+
const value = labels[component.kind];
|
|
599
|
+
return value === '' ? '' : `<text ${colorAttributes(component.color, 'schematic-gate-symbol')} stroke="none" x="0" y="4" text-anchor="middle" font-size="11">${value}</text>`;
|
|
600
|
+
}
|
|
601
|
+
if ((component.kind === 'meter' || component.kind === 'load') && component.variant !== 'lamp' && component.variant !== 'speaker' && component.variant !== 'buzzer') {
|
|
602
|
+
const value = component.variant === 'voltmeter' ? 'V' : component.variant === 'ammeter' ? 'A' : 'M';
|
|
603
|
+
return `<text ${colorAttributes(component.color, 'schematic-gate-symbol')} stroke="none" x="0" y="5" text-anchor="middle" font-size="14">${value}</text>`;
|
|
604
|
+
}
|
|
605
|
+
if (isQuantumSpecial(component) && component.kind === 'controlled' && component.operator !== undefined) {
|
|
606
|
+
return `<text ${colorAttributes(component.color, 'schematic-gate-symbol')} stroke="none" x="0" y="4" text-anchor="middle" font-size="11">${renderMathLabelTspans(component.operator)}</text>`;
|
|
607
|
+
}
|
|
608
|
+
return '';
|
|
609
|
+
}
|
|
351
610
|
function componentShape(component, paint = colorAttributes(component.color), nodePaint = colorAttributes(component.color, 'schematic-node-fill')) {
|
|
352
611
|
if (isClassicalGate(component)) {
|
|
353
612
|
return component.standard === 'iec' ? iecGate(component, paint) : ieeeGate(component, paint);
|
|
354
613
|
}
|
|
614
|
+
if (isDigitalComponent(component))
|
|
615
|
+
return digitalShape(component, paint);
|
|
616
|
+
if (isQuantumSpecial(component))
|
|
617
|
+
return quantumSpecialShape(component, paint, nodePaint);
|
|
355
618
|
if (isUmlComponent(component))
|
|
356
619
|
return umlComponentShape(component, paint, nodePaint);
|
|
357
620
|
switch (component.kind) {
|
|
358
621
|
case 'resistor':
|
|
359
|
-
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" />`;
|
|
360
622
|
case 'capacitor':
|
|
361
|
-
return `<path ${paint} d="M -42 0 H -7 M -7 -18 V 18 M 7 -18 V 18 M 7 0 H 42" />`;
|
|
362
623
|
case 'inductor':
|
|
363
|
-
return
|
|
624
|
+
return passiveShape(component, paint);
|
|
364
625
|
case 'diode':
|
|
365
626
|
return diodeShape(component, paint);
|
|
366
627
|
case 'transistor':
|
|
@@ -370,28 +631,66 @@ function componentShape(component, paint = colorAttributes(component.color), nod
|
|
|
370
631
|
case 'ground':
|
|
371
632
|
return groundShape(component, paint);
|
|
372
633
|
case 'hadamard':
|
|
373
|
-
|
|
634
|
+
case 'qgate':
|
|
635
|
+
case 'xgate':
|
|
636
|
+
case 'ygate':
|
|
637
|
+
case 'zgate':
|
|
638
|
+
case 'sgate':
|
|
639
|
+
case 'sdg':
|
|
640
|
+
case 'tgate':
|
|
641
|
+
case 'tdg':
|
|
642
|
+
case 'sx':
|
|
643
|
+
case 'phase':
|
|
644
|
+
case 'rx':
|
|
645
|
+
case 'ry':
|
|
646
|
+
case 'rz':
|
|
647
|
+
case 'ugate':
|
|
648
|
+
return quantumGateShell(component, paint);
|
|
374
649
|
case 'cnot':
|
|
375
650
|
return `<path ${paint} d="M -42 0 H 42 M 0 -26 V 26 M -8 16 H 8 M 0 8 V 24" /><circle ${nodePaint} cx="0" cy="-16" r="4" /><circle ${paint} cx="0" cy="16" r="10" />`;
|
|
376
|
-
case 'qgate':
|
|
377
|
-
return `<rect ${paint} x="-34" y="-30" width="68" height="60" rx="4" /><path ${paint} d="M -48 0 H -34 M 34 0 H 48" />${quantumText(component)}`;
|
|
378
651
|
case 'ic':
|
|
379
652
|
return integratedCircuitShape(component, paint);
|
|
653
|
+
case 'source':
|
|
654
|
+
case 'junction':
|
|
655
|
+
case 'testpoint':
|
|
656
|
+
case 'connector':
|
|
657
|
+
case 'power':
|
|
658
|
+
case 'switch':
|
|
659
|
+
case 'protection':
|
|
660
|
+
case 'amplifier':
|
|
661
|
+
case 'resonator':
|
|
662
|
+
case 'meter':
|
|
663
|
+
case 'load':
|
|
664
|
+
return electricalShape(component, paint, nodePaint);
|
|
380
665
|
}
|
|
381
666
|
}
|
|
382
667
|
function reusableSymbolKey(component) {
|
|
383
668
|
if (isClassicalGate(component) ||
|
|
384
669
|
isUmlComponent(component) ||
|
|
385
|
-
component.kind === 'qgate' ||
|
|
386
670
|
component.kind === 'ic') {
|
|
387
671
|
return undefined;
|
|
388
672
|
}
|
|
673
|
+
if (isQuantumGate(component) && component.kind !== 'cnot') {
|
|
674
|
+
const dimensions = quantumGateDimensions(component);
|
|
675
|
+
return `quantum-shell-${dimensions.bodyWidth}-${dimensions.bodyHeight}`;
|
|
676
|
+
}
|
|
677
|
+
if (isDigitalComponent(component))
|
|
678
|
+
return `digital-${component.kind}-${component.variant ?? ''}-${component.inputs}-${component.outputs}-${component.bodyWidth}-${component.bodyHeight}`;
|
|
679
|
+
if (isQuantumSpecial(component))
|
|
680
|
+
return `quantum-${component.kind}-${component.controlType ?? ''}-${component.controls}-${component.targets}-${component.wires}`;
|
|
389
681
|
if (component.kind === 'diode')
|
|
390
682
|
return `diode-${component.diodeType}`;
|
|
391
683
|
if (component.kind === 'transistor')
|
|
392
684
|
return `transistor-${component.transistorType}`;
|
|
393
685
|
if (component.kind === 'ground')
|
|
394
686
|
return `ground-${component.groundStyle}`;
|
|
687
|
+
if (component.kind === 'resistor' || component.kind === 'capacitor' || component.kind === 'inductor')
|
|
688
|
+
return component.passiveType === undefined ? component.kind : `${component.kind}-${component.passiveType}`;
|
|
689
|
+
if (component.kind === 'source' || component.kind === 'junction' || component.kind === 'testpoint' ||
|
|
690
|
+
component.kind === 'connector' || component.kind === 'power' || component.kind === 'switch' ||
|
|
691
|
+
component.kind === 'protection' || component.kind === 'amplifier' || component.kind === 'resonator' ||
|
|
692
|
+
component.kind === 'meter' || component.kind === 'load')
|
|
693
|
+
return `${component.kind}-${component.variant ?? ''}`;
|
|
395
694
|
return component.kind;
|
|
396
695
|
}
|
|
397
696
|
function reusableSymbolDefinition(component, symbolId) {
|
|
@@ -404,7 +703,9 @@ function connectionMarkup(connection, routed, index, idPrefix, glowId) {
|
|
|
404
703
|
const source = escapeXml(`${connection.from.componentId}.${connection.from.port}`);
|
|
405
704
|
const target = escapeXml(`${connection.to.componentId}.${connection.to.port}`);
|
|
406
705
|
const traceId = `${idPrefix}-wire-${index}-vector`;
|
|
407
|
-
const
|
|
706
|
+
const signalKind = connection.signalKind ?? 'electrical';
|
|
707
|
+
const signalClass = connection.signalKind === undefined ? '' : ` schematic-signal--${signalKind}`;
|
|
708
|
+
const dataAttributes = ` data-wire-source="${source}" data-wire-target="${target}" data-source-line="${connection.line}"${connection.signalKind === undefined ? '' : ` data-signal-kind="${signalKind}"`}${connection.width === undefined ? '' : ` data-bus-width="${connection.width}"`}`;
|
|
408
709
|
const relation = connection.relation ?? 'signal';
|
|
409
710
|
const accessibility = ` tabindex="0" role="group" aria-label="${escapeXml(relation)} from ${source} to ${target}"`;
|
|
410
711
|
const vectorId = ` id="${traceId}"`;
|
|
@@ -413,7 +714,7 @@ function connectionMarkup(connection, routed, index, idPrefix, glowId) {
|
|
|
413
714
|
const endpoint = relation === 'signal' && connection.markerEnd === 'none'
|
|
414
715
|
? `<circle ${colorAttributes(connection.color, 'schematic-node-fill')} cx="${svgNumber(end.x)}" cy="${svgNumber(end.y)}" r="3" />`
|
|
415
716
|
: '';
|
|
416
|
-
return `<g class="schematic-wire"${dataAttributes}${accessibility}><path${vectorId} ${colorAttributes(connection.color,
|
|
717
|
+
return `<g class="schematic-wire${signalClass}"${dataAttributes}${accessibility}><path${vectorId} ${colorAttributes(connection.color, `schematic-trace${signalClass}`)} d="${routed.d}"${markerAttributes} />${glow}${endpoint}${connectionLabelMarkup(connection, routed)}</g>`;
|
|
417
718
|
}
|
|
418
719
|
function connectionMarkerAttributes(connection, idPrefix) {
|
|
419
720
|
const start = connection.markerStart;
|
|
@@ -463,10 +764,11 @@ function compactConnectionMarkup(connections, routes, idPrefix, embedVisuals, gl
|
|
|
463
764
|
const batches = new Map();
|
|
464
765
|
for (const [index, connection] of connections.entries()) {
|
|
465
766
|
const markerAttributes = connectionMarkerAttributes(connection, idPrefix);
|
|
466
|
-
const
|
|
767
|
+
const signalKind = connection.signalKind ?? 'electrical';
|
|
768
|
+
const key = `${colorKey(connection.color)}|${markerAttributes}|${signalKind}|${connection.width ?? 1}`;
|
|
467
769
|
let batch = batches.get(key);
|
|
468
770
|
if (batch === undefined) {
|
|
469
|
-
batch = { color: connection.color, markerAttributes, traces: [], endpoints: [], labels: [] };
|
|
771
|
+
batch = { color: connection.color, markerAttributes, signalKind, traces: [], endpoints: [], labels: [] };
|
|
470
772
|
batches.set(key, batch);
|
|
471
773
|
}
|
|
472
774
|
const routed = routes[index];
|
|
@@ -480,7 +782,7 @@ function compactConnectionMarkup(connections, routes, idPrefix, embedVisuals, gl
|
|
|
480
782
|
batch.labels.push(label);
|
|
481
783
|
}
|
|
482
784
|
return Array.from(batches.values(), (batch, index) => {
|
|
483
|
-
const tracePaint = colorAttributes(batch.color,
|
|
785
|
+
const tracePaint = colorAttributes(batch.color, `schematic-trace${batch.signalKind === 'electrical' ? '' : ` schematic-signal--${batch.signalKind}`}`);
|
|
484
786
|
const nodePaint = colorAttributes(batch.color, 'schematic-node-fill');
|
|
485
787
|
const traceId = `${idPrefix}-wire-batch-${index}-vector`;
|
|
486
788
|
const trace = `<path${embedVisuals ? ` id="${traceId}"` : ''} ${tracePaint} d="${batch.traces.join(' ')}"${batch.markerAttributes} />`;
|
|
@@ -488,19 +790,47 @@ function compactConnectionMarkup(connections, routes, idPrefix, embedVisuals, gl
|
|
|
488
790
|
const labels = batch.labels.join('');
|
|
489
791
|
if (!embedVisuals)
|
|
490
792
|
return trace + endpoints + labels;
|
|
491
|
-
return `<g class="schematic-wire"
|
|
793
|
+
return `<g class="schematic-wire">${trace}<use class="schematic-glow-layer" href="#${traceId}" filter="url(#${glowId})" aria-hidden="true" pointer-events="none" />${endpoints}${labels}</g>`;
|
|
492
794
|
}).join('');
|
|
493
795
|
}
|
|
494
796
|
function componentMetadata(component) {
|
|
797
|
+
if (isDigitalComponent(component)) {
|
|
798
|
+
return [component.variant, component.width > 1 ? `${component.width} bit` : undefined]
|
|
799
|
+
.filter(Boolean)
|
|
800
|
+
.join(', ');
|
|
801
|
+
}
|
|
802
|
+
if (isQuantumSpecial(component)) {
|
|
803
|
+
return [
|
|
804
|
+
component.operator,
|
|
805
|
+
component.controlType,
|
|
806
|
+
component.controls > 0 ? `${component.controls} controls` : undefined,
|
|
807
|
+
component.targets > 0 ? `${component.targets} targets` : undefined,
|
|
808
|
+
component.width > 1 ? `${component.width} bit` : undefined
|
|
809
|
+
].filter(Boolean).join(', ');
|
|
810
|
+
}
|
|
811
|
+
if (isQuantumGate(component)) {
|
|
812
|
+
return [component.parameter, component.phase, component.matrix].filter(Boolean).join(', ');
|
|
813
|
+
}
|
|
495
814
|
switch (component.kind) {
|
|
496
|
-
case '
|
|
497
|
-
|
|
815
|
+
case 'resistor':
|
|
816
|
+
case 'capacitor':
|
|
817
|
+
case 'inductor':
|
|
818
|
+
return component.passiveType ?? 'fixed';
|
|
498
819
|
case 'diode':
|
|
499
820
|
return component.diodeType;
|
|
500
821
|
case 'transistor':
|
|
501
822
|
return component.transistorType;
|
|
502
823
|
case 'ground':
|
|
503
824
|
return component.groundStyle;
|
|
825
|
+
case 'source':
|
|
826
|
+
case 'power':
|
|
827
|
+
case 'switch':
|
|
828
|
+
case 'protection':
|
|
829
|
+
case 'amplifier':
|
|
830
|
+
case 'resonator':
|
|
831
|
+
case 'meter':
|
|
832
|
+
case 'load':
|
|
833
|
+
return String(component.variant);
|
|
504
834
|
case 'ic':
|
|
505
835
|
return `${Object.values(component.pins).reduce((total, pins) => total + pins.length, 0)} pins`;
|
|
506
836
|
default:
|
|
@@ -524,15 +854,17 @@ function componentMarkup(component, index, idPrefix, glowId, mode, nodeHooks, po
|
|
|
524
854
|
const anchors = componentTextAnchors(component);
|
|
525
855
|
const id = escapeXml(component.id);
|
|
526
856
|
const vectorId = `${idPrefix}-node-${index}-vector`;
|
|
857
|
+
const rotation = componentRotation(component);
|
|
858
|
+
const vectorTransform = rotation === 0 ? '' : ` transform="rotate(${rotation})"`;
|
|
527
859
|
const styles = mode === 'embedded-css' || mode === 'full';
|
|
528
860
|
const dataAttributes = nodeHooks
|
|
529
|
-
? ` data-node-id="${id}" data-node-kind="${component.kind}" data-node-label="${label}" data-component="${id}" data-kind="${component.kind}"`
|
|
861
|
+
? ` data-node-id="${id}" data-node-kind="${component.kind}" data-node-label="${label}" data-component="${id}" data-kind="${component.kind}" data-source-line="${component.line}"${'orientation' in component && component.orientation !== undefined ? ` data-orientation="${component.orientation}"` : ''}`
|
|
530
862
|
: '';
|
|
531
863
|
let accessibility = '';
|
|
532
|
-
if (
|
|
864
|
+
if (mode === 'full') {
|
|
533
865
|
const metadata = componentMetadata(component);
|
|
534
866
|
const ariaLabel = escapeXml(`${component.id}, ${component.kind}, ${component.label}${metadata === '' ? '' : `, ${metadata}`}`);
|
|
535
|
-
accessibility =
|
|
867
|
+
accessibility = `${portHooks ? '' : ' tabindex="0"'} role="group" aria-label="${ariaLabel}"`;
|
|
536
868
|
}
|
|
537
869
|
const vectorIdAttribute = styles ? ` id="${vectorId}"` : '';
|
|
538
870
|
const glow = styles
|
|
@@ -540,25 +872,24 @@ function componentMarkup(component, index, idPrefix, glowId, mode, nodeHooks, po
|
|
|
540
872
|
: '';
|
|
541
873
|
const hotspots = portHooks ? componentPortMarkup(component) : '';
|
|
542
874
|
const vector = symbolId === undefined
|
|
543
|
-
? `<g${vectorIdAttribute} class="schematic-component-vector">${componentShape(component)}</g>`
|
|
544
|
-
: `<use${vectorIdAttribute} ${colorAttributes(component.color, 'schematic-component-vector')} href="#${symbolId}" />`;
|
|
875
|
+
? `<g${vectorIdAttribute} class="schematic-component-vector"${vectorTransform}>${componentShape(component)}</g>`
|
|
876
|
+
: `<use${vectorIdAttribute} ${colorAttributes(component.color, 'schematic-component-vector')} href="#${symbolId}"${vectorTransform} />`;
|
|
877
|
+
const innerText = componentInnerText(component);
|
|
545
878
|
const externalLabels = isUmlComponent(component)
|
|
546
879
|
? ''
|
|
547
880
|
: `<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>`;
|
|
548
|
-
return `<g class="schematic-component"${dataAttributes} transform="translate(${svgNumber(component.x)} ${svgNumber(component.y)})"${accessibility}>${vector}${glow}${externalLabels}${hotspots}</g>`;
|
|
881
|
+
return `<g class="schematic-component"${dataAttributes} transform="translate(${svgNumber(component.x)} ${svgNumber(component.y)})"${accessibility}>${vector}${glow}${innerText}${externalLabels}${hotspots}</g>`;
|
|
549
882
|
}
|
|
550
883
|
export function renderSchematic(document, options) {
|
|
551
884
|
assertParsedSchematicDocument(document);
|
|
552
885
|
const normalized = normalizeCompileOptions(options);
|
|
553
|
-
const components = new Map(document.components.map((component) => [component.id, component]));
|
|
554
886
|
const routedConnections = parsedSchematicRoutes(document, normalized.bounds) ??
|
|
555
|
-
routeConnections(document.connections, components, normalized.bounds);
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
const candidatePrefix = (normalized.idPrefix ?? `schematic-${stableHash(signature)}`).replace(/[^A-Za-z0-9_-]/g, '-');
|
|
887
|
+
validateDocumentGeometry(document, normalized, routeConnections(document.connections, new Map(document.components.map((component) => [component.id, component])), normalized.bounds));
|
|
888
|
+
const signatureHash = () => stableHash(`${normalized.bounds.width}x${normalized.bounds.height}:${normalized.title}:${JSON.stringify(document)}`);
|
|
889
|
+
const candidatePrefix = (normalized.idPrefix ?? `schematic-${signatureHash()}`).replace(/[^A-Za-z0-9_-]/g, '-');
|
|
559
890
|
const safePrefix = /[A-Za-z0-9]/.test(candidatePrefix)
|
|
560
891
|
? candidatePrefix
|
|
561
|
-
: `schematic-${
|
|
892
|
+
: `schematic-${signatureHash()}`;
|
|
562
893
|
const titleId = `${safePrefix}-title`;
|
|
563
894
|
const descriptionId = `${safePrefix}-description`;
|
|
564
895
|
const gridId = `${safePrefix}-grid`;
|
|
@@ -591,7 +922,8 @@ export function renderSchematic(document, options) {
|
|
|
591
922
|
? `<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}`
|
|
592
923
|
: '';
|
|
593
924
|
const definitions = `${embeddedDefinitions}${markerDefinitions}${symbolDefinitions}`;
|
|
594
|
-
|
|
925
|
+
const svgRole = hooks ? 'group' : 'img';
|
|
926
|
+
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="${svgRole}" 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">`);
|
|
595
927
|
if (wireHooks) {
|
|
596
928
|
for (const [index, connection] of document.connections.entries()) {
|
|
597
929
|
writer.append(connectionMarkup(connection, routedConnections[index], index, safePrefix, glowId));
|