@schemd/core 0.2.1 → 0.3.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/CHANGELOG.md +46 -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 +48 -1
- package/dist/layout.js +526 -64
- package/dist/parser.js +409 -40
- package/dist/renderer.js +371 -37
- 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/layout.js
CHANGED
|
@@ -1,9 +1,64 @@
|
|
|
1
|
-
import { CLASSICAL_GATE_KINDS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
1
|
+
import { CLASSICAL_GATE_KINDS, DIGITAL_COMPONENT_KINDS, QUANTUM_SPECIAL_KINDS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
2
2
|
import { mathLabelTextWidth } from './math-label.js';
|
|
3
3
|
import { MAX_SCHEMATIC_WIRE_CROSSINGS } from './limits.js';
|
|
4
4
|
export const PORT_HOTSPOT_RADIUS = 4;
|
|
5
5
|
export const SCHEMATIC_OBSTACLE_CLEARANCE = 12;
|
|
6
6
|
export const SCHEMATIC_BRIDGE_RADIUS = 5;
|
|
7
|
+
export function orientationQuarterTurn(orientation) {
|
|
8
|
+
switch (orientation) {
|
|
9
|
+
case 'right':
|
|
10
|
+
return 0;
|
|
11
|
+
case 'down':
|
|
12
|
+
return 1;
|
|
13
|
+
case 'left':
|
|
14
|
+
return 2;
|
|
15
|
+
case 'up':
|
|
16
|
+
return 3;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function normalizedQuarterTurn(value) {
|
|
20
|
+
switch (value & 3) {
|
|
21
|
+
case 0:
|
|
22
|
+
return 0;
|
|
23
|
+
case 1:
|
|
24
|
+
return 1;
|
|
25
|
+
case 2:
|
|
26
|
+
return 2;
|
|
27
|
+
default:
|
|
28
|
+
return 3;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function composeQuarterTurns(first, second) {
|
|
32
|
+
return normalizedQuarterTurn(first + second);
|
|
33
|
+
}
|
|
34
|
+
export function inverseQuarterTurn(turn) {
|
|
35
|
+
return normalizedQuarterTurn(4 - turn);
|
|
36
|
+
}
|
|
37
|
+
function negatedCoordinate(value) {
|
|
38
|
+
return value === 0 ? 0 : -value;
|
|
39
|
+
}
|
|
40
|
+
function canonicalCoordinate(value) {
|
|
41
|
+
return value === 0 ? 0 : value;
|
|
42
|
+
}
|
|
43
|
+
export function rotateQuarterPoint(point, turn) {
|
|
44
|
+
switch (turn) {
|
|
45
|
+
case 0:
|
|
46
|
+
return { x: canonicalCoordinate(point.x), y: canonicalCoordinate(point.y) };
|
|
47
|
+
case 1:
|
|
48
|
+
return { x: negatedCoordinate(point.y), y: canonicalCoordinate(point.x) };
|
|
49
|
+
case 2:
|
|
50
|
+
return { x: negatedCoordinate(point.x), y: negatedCoordinate(point.y) };
|
|
51
|
+
case 3:
|
|
52
|
+
return { x: canonicalCoordinate(point.y), y: negatedCoordinate(point.x) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function rotateQuarterExtents(extents, turn) {
|
|
56
|
+
const halfWidth = canonicalCoordinate(extents.halfWidth);
|
|
57
|
+
const halfHeight = canonicalCoordinate(extents.halfHeight);
|
|
58
|
+
return turn === 1 || turn === 3
|
|
59
|
+
? { halfWidth: halfHeight, halfHeight: halfWidth }
|
|
60
|
+
: { halfWidth, halfHeight };
|
|
61
|
+
}
|
|
7
62
|
const MAX_ROUTER_STATES = 40_000;
|
|
8
63
|
const ROUTER_BEND_PENALTY = 0.25;
|
|
9
64
|
const CROSSING_BUCKET_SIZE = 64;
|
|
@@ -31,26 +86,96 @@ export function distributedCoordinate(index, count, span) {
|
|
|
31
86
|
export function classicalGateHeight(component) {
|
|
32
87
|
return Math.max(48, Math.max(component.inputs, component.outputs) * 16);
|
|
33
88
|
}
|
|
89
|
+
export function quantumGateDimensions(component) {
|
|
90
|
+
const details = [component.parameter, component.phase, component.matrix].filter((value) => value !== undefined);
|
|
91
|
+
let widest = 16;
|
|
92
|
+
if (details.length > 0 && component.kind === 'qgate')
|
|
93
|
+
widest = mathLabelTextWidth(component.label, 8);
|
|
94
|
+
for (const detail of details)
|
|
95
|
+
widest = Math.max(widest, mathLabelTextWidth(detail, 7));
|
|
96
|
+
const bodyWidth = Math.max(50, Math.min(104, widest + 20));
|
|
97
|
+
const bodyHeight = 50 + details.length * 15;
|
|
98
|
+
return { bodyWidth, bodyHeight, stubExtent: bodyWidth / 2 + 23 };
|
|
99
|
+
}
|
|
34
100
|
function isClassicalGate(component) {
|
|
35
101
|
return CLASSICAL_GATE_KINDS.includes(component.kind);
|
|
36
102
|
}
|
|
103
|
+
function isDigitalComponent(component) {
|
|
104
|
+
return DIGITAL_COMPONENT_KINDS.includes(component.kind);
|
|
105
|
+
}
|
|
106
|
+
function isQuantumSpecial(component) {
|
|
107
|
+
return QUANTUM_SPECIAL_KINDS.includes(component.kind);
|
|
108
|
+
}
|
|
37
109
|
function isUmlComponent(component) {
|
|
38
110
|
return UML_COMPONENT_KINDS.includes(component.kind);
|
|
39
111
|
}
|
|
112
|
+
function componentTurn(component) {
|
|
113
|
+
return 'orientation' in component && component.orientation !== undefined
|
|
114
|
+
? orientationQuarterTurn(component.orientation)
|
|
115
|
+
: 0;
|
|
116
|
+
}
|
|
117
|
+
function positionedQuarterPoint(component, local) {
|
|
118
|
+
const rotated = rotateQuarterPoint(local, componentTurn(component));
|
|
119
|
+
return { x: component.x + rotated.x, y: component.y + rotated.y };
|
|
120
|
+
}
|
|
121
|
+
function positionedQuarterNormal(component, normal) {
|
|
122
|
+
return rotateQuarterPoint(normal, componentTurn(component));
|
|
123
|
+
}
|
|
40
124
|
function umlHalfExtents(component) {
|
|
41
125
|
switch (component.kind) {
|
|
42
126
|
case 'class':
|
|
127
|
+
case 'interface':
|
|
128
|
+
case 'enumeration':
|
|
129
|
+
case 'datatype':
|
|
130
|
+
case 'object':
|
|
43
131
|
case 'state':
|
|
44
132
|
case 'usecase':
|
|
45
133
|
case 'lifeline':
|
|
46
134
|
case 'note':
|
|
47
135
|
case 'package':
|
|
136
|
+
case 'component':
|
|
137
|
+
case 'artifact':
|
|
138
|
+
case 'node':
|
|
139
|
+
case 'device':
|
|
140
|
+
case 'execution':
|
|
141
|
+
case 'system':
|
|
142
|
+
case 'action':
|
|
143
|
+
case 'object-node':
|
|
144
|
+
case 'partition':
|
|
145
|
+
case 'activation':
|
|
146
|
+
case 'fragment':
|
|
147
|
+
case 'interaction':
|
|
148
|
+
case 'region':
|
|
48
149
|
return { halfWidth: component.bodyWidth / 2, halfHeight: component.bodyHeight / 2 };
|
|
49
150
|
case 'actor':
|
|
50
151
|
return { halfWidth: 24, halfHeight: 50 };
|
|
51
152
|
case 'initial':
|
|
52
153
|
case 'final':
|
|
154
|
+
case 'activity-final':
|
|
155
|
+
case 'flow-final':
|
|
156
|
+
case 'provided-interface':
|
|
157
|
+
case 'required-interface':
|
|
158
|
+
case 'component-port':
|
|
159
|
+
case 'destruction':
|
|
160
|
+
case 'gate':
|
|
161
|
+
case 'found':
|
|
162
|
+
case 'lost':
|
|
163
|
+
case 'state-junction':
|
|
164
|
+
case 'history':
|
|
165
|
+
case 'entry':
|
|
166
|
+
case 'exit':
|
|
167
|
+
case 'terminate':
|
|
53
168
|
return { halfWidth: 12, halfHeight: 12 };
|
|
169
|
+
case 'decision':
|
|
170
|
+
case 'merge':
|
|
171
|
+
case 'choice':
|
|
172
|
+
return { halfWidth: 18, halfHeight: 18 };
|
|
173
|
+
case 'fork':
|
|
174
|
+
case 'join':
|
|
175
|
+
return { halfWidth: 38, halfHeight: 5 };
|
|
176
|
+
case 'send-signal':
|
|
177
|
+
case 'receive-signal':
|
|
178
|
+
return { halfWidth: 22, halfHeight: 15 };
|
|
54
179
|
}
|
|
55
180
|
}
|
|
56
181
|
function umlPortPoint(component, port) {
|
|
@@ -82,28 +207,30 @@ function indexedPortPoint(component, port) {
|
|
|
82
207
|
const count = output ? component.outputs : component.inputs;
|
|
83
208
|
const suffix = port.match(/\d+$/)?.[0];
|
|
84
209
|
const index = suffix === undefined ? 0 : Number(suffix) - 1;
|
|
85
|
-
return {
|
|
86
|
-
x:
|
|
87
|
-
y:
|
|
88
|
-
};
|
|
210
|
+
return positionedQuarterPoint(component, {
|
|
211
|
+
x: output ? 48 : -48,
|
|
212
|
+
y: distributedCoordinate(index, count, classicalGateHeight(component))
|
|
213
|
+
});
|
|
89
214
|
}
|
|
90
215
|
export function positionIcPin(component, side, index) {
|
|
91
216
|
const pins = component.pins[side];
|
|
92
217
|
if (!Number.isInteger(index) || index < 0 || index >= pins.length) {
|
|
93
218
|
throw new RangeError(`IC pin index ${index} is outside ${component.id}.${side}.`);
|
|
94
219
|
}
|
|
220
|
+
let local;
|
|
95
221
|
if (side === 'left' || side === 'right') {
|
|
96
|
-
|
|
97
|
-
x: component.
|
|
98
|
-
|
|
99
|
-
y: component.y + distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
222
|
+
local = {
|
|
223
|
+
x: side === 'left' ? -component.bodyWidth / 2 - 16 : component.bodyWidth / 2 + 16,
|
|
224
|
+
y: distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
100
225
|
};
|
|
101
226
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
227
|
+
else {
|
|
228
|
+
local = {
|
|
229
|
+
x: distributedCoordinate(index, pins.length, component.bodyWidth),
|
|
230
|
+
y: side === 'top' ? -component.bodyHeight / 2 - 16 : component.bodyHeight / 2 + 16
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return positionedQuarterPoint(component, local);
|
|
107
234
|
}
|
|
108
235
|
function findIcPinLocation(component, port) {
|
|
109
236
|
const sides = ['left', 'right', 'top', 'bottom'];
|
|
@@ -150,73 +277,205 @@ export function resolvePortPoint(component, port) {
|
|
|
150
277
|
return point;
|
|
151
278
|
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
152
279
|
}
|
|
153
|
-
|
|
154
|
-
|
|
280
|
+
if (component.kind === 'ic') {
|
|
281
|
+
const location = icPinLocation(component, port);
|
|
282
|
+
if (location !== undefined)
|
|
283
|
+
return location.point;
|
|
284
|
+
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
285
|
+
}
|
|
286
|
+
let local;
|
|
287
|
+
const left = { x: -42, y: 0 };
|
|
288
|
+
const right = { x: 42, y: 0 };
|
|
155
289
|
switch (component.kind) {
|
|
156
290
|
case 'resistor':
|
|
157
291
|
case 'capacitor':
|
|
158
292
|
case 'inductor':
|
|
159
293
|
if (PASSIVE_INPUT_PORTS.has(port))
|
|
160
|
-
|
|
294
|
+
local = left;
|
|
161
295
|
if (PASSIVE_OUTPUT_PORTS.has(port))
|
|
162
|
-
|
|
296
|
+
local = right;
|
|
163
297
|
break;
|
|
164
298
|
case 'diode':
|
|
165
299
|
if (DIODE_ANODE_PORTS.has(port))
|
|
166
|
-
|
|
300
|
+
local = left;
|
|
167
301
|
if (DIODE_CATHODE_PORTS.has(port))
|
|
168
|
-
|
|
302
|
+
local = right;
|
|
169
303
|
break;
|
|
170
304
|
case 'transistor':
|
|
171
305
|
if (TRANSISTOR_CONTROL_PORTS.has(port))
|
|
172
|
-
|
|
173
|
-
if (TRANSISTOR_UPPER_PORTS.has(port))
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return { x: component.x + 42, y: component.y + 22 };
|
|
178
|
-
}
|
|
306
|
+
local = left;
|
|
307
|
+
if (TRANSISTOR_UPPER_PORTS.has(port))
|
|
308
|
+
local = { x: 42, y: -22 };
|
|
309
|
+
if (TRANSISTOR_LOWER_PORTS.has(port))
|
|
310
|
+
local = { x: 42, y: 22 };
|
|
179
311
|
break;
|
|
180
312
|
case 'port':
|
|
181
313
|
if (port === 'in')
|
|
182
|
-
|
|
314
|
+
local = left;
|
|
183
315
|
if (port === 'out')
|
|
184
|
-
|
|
316
|
+
local = right;
|
|
185
317
|
break;
|
|
186
318
|
case 'ground':
|
|
187
319
|
if (port === 'in')
|
|
188
|
-
|
|
320
|
+
local = { x: 0, y: -42 };
|
|
189
321
|
break;
|
|
190
322
|
case 'hadamard':
|
|
191
323
|
case 'qgate':
|
|
324
|
+
case 'xgate':
|
|
325
|
+
case 'ygate':
|
|
326
|
+
case 'zgate':
|
|
327
|
+
case 'sgate':
|
|
328
|
+
case 'sdg':
|
|
329
|
+
case 'tgate':
|
|
330
|
+
case 'tdg':
|
|
331
|
+
case 'sx':
|
|
332
|
+
case 'phase':
|
|
333
|
+
case 'rx':
|
|
334
|
+
case 'ry':
|
|
335
|
+
case 'rz':
|
|
336
|
+
case 'ugate':
|
|
192
337
|
if (port === 'in')
|
|
193
|
-
|
|
338
|
+
local = { x: -quantumGateDimensions(component).stubExtent, y: 0 };
|
|
194
339
|
if (port === 'out')
|
|
195
|
-
|
|
340
|
+
local = { x: quantumGateDimensions(component).stubExtent, y: 0 };
|
|
196
341
|
break;
|
|
197
342
|
case 'cnot':
|
|
198
343
|
if (port === 'in')
|
|
199
|
-
|
|
344
|
+
local = left;
|
|
200
345
|
if (port === 'out')
|
|
201
|
-
|
|
346
|
+
local = right;
|
|
202
347
|
if (port === 'control')
|
|
203
|
-
|
|
348
|
+
local = { x: 0, y: -16 };
|
|
204
349
|
if (port === 'target')
|
|
205
|
-
|
|
350
|
+
local = { x: 0, y: 16 };
|
|
206
351
|
break;
|
|
207
|
-
case '
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
352
|
+
case 'junction':
|
|
353
|
+
case 'testpoint':
|
|
354
|
+
local = { x: 0, y: 0 };
|
|
355
|
+
break;
|
|
356
|
+
case 'connector':
|
|
357
|
+
local = port === 'out' ? { x: 32, y: 0 } : { x: -32, y: 0 };
|
|
358
|
+
break;
|
|
359
|
+
case 'source':
|
|
360
|
+
if (port === 'in' || port === 'negative')
|
|
361
|
+
local = left;
|
|
362
|
+
else if (port === 'out' || port === 'positive')
|
|
363
|
+
local = right;
|
|
364
|
+
else
|
|
365
|
+
local = { x: 0, y: port === 'control-positive' ? -34 : 34 };
|
|
366
|
+
break;
|
|
367
|
+
case 'power':
|
|
368
|
+
local = { x: -32, y: 0 };
|
|
369
|
+
break;
|
|
370
|
+
case 'switch':
|
|
371
|
+
if (port === 'in' || port === 'common')
|
|
372
|
+
local = left;
|
|
373
|
+
else if (port === 'normally-open')
|
|
374
|
+
local = { x: 42, y: -12 };
|
|
375
|
+
else if (port === 'normally-closed')
|
|
376
|
+
local = { x: 42, y: 12 };
|
|
377
|
+
else if (port === 'coil1')
|
|
378
|
+
local = { x: -12, y: 34 };
|
|
379
|
+
else if (port === 'coil2')
|
|
380
|
+
local = { x: 12, y: 34 };
|
|
381
|
+
else
|
|
382
|
+
local = right;
|
|
383
|
+
break;
|
|
384
|
+
case 'amplifier':
|
|
385
|
+
if (port === 'out')
|
|
386
|
+
local = { x: 48, y: 0 };
|
|
387
|
+
else if (port === 'v+')
|
|
388
|
+
local = { x: 0, y: -36 };
|
|
389
|
+
else if (port === 'v-')
|
|
390
|
+
local = { x: 0, y: 36 };
|
|
391
|
+
else
|
|
392
|
+
local = { x: -48, y: port === 'negative' ? 13 : -13 };
|
|
393
|
+
break;
|
|
394
|
+
case 'protection':
|
|
395
|
+
case 'resonator':
|
|
396
|
+
case 'meter':
|
|
397
|
+
case 'load':
|
|
398
|
+
local = port === 'out' ? right : left;
|
|
399
|
+
break;
|
|
400
|
+
case 'buffer':
|
|
401
|
+
case 'logic':
|
|
402
|
+
case 'clock':
|
|
403
|
+
case 'flipflop':
|
|
404
|
+
case 'mux':
|
|
405
|
+
case 'encoder':
|
|
406
|
+
case 'decoder':
|
|
407
|
+
case 'register':
|
|
408
|
+
case 'counter':
|
|
409
|
+
case 'adder':
|
|
410
|
+
case 'comparator':
|
|
411
|
+
case 'bus': {
|
|
412
|
+
const output = port === 'out' || port.startsWith('out') || ['q', 'nq', 'gt', 'eq', 'lt', 'tap'].includes(port) || (component.kind === 'bus' && component.variant === 'joiner' && port === 'bus');
|
|
413
|
+
const match = port.match(/\d+$/);
|
|
414
|
+
const count = output ? component.outputs : component.inputs;
|
|
415
|
+
const index = match === null ? 0 : Number(match[0]) - 1;
|
|
416
|
+
if (port === 'clock' || port === 'enable' || port === 'select')
|
|
417
|
+
local = { x: 0, y: component.bodyHeight / 2 + 16 };
|
|
418
|
+
else if (port === 'preset' || port === 'clear')
|
|
419
|
+
local = { x: 0, y: -component.bodyHeight / 2 - 16 };
|
|
420
|
+
else
|
|
421
|
+
local = {
|
|
422
|
+
x: (output ? 1 : -1) * (component.bodyWidth / 2 + 16),
|
|
423
|
+
y: distributedCoordinate(index, Math.max(1, count), component.bodyHeight)
|
|
424
|
+
};
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case 'measure':
|
|
428
|
+
if (port === 'classical')
|
|
429
|
+
local = { x: 0, y: 34 };
|
|
430
|
+
else
|
|
431
|
+
local = port === 'out' ? { x: 40, y: 0 } : { x: -40, y: 0 };
|
|
432
|
+
break;
|
|
433
|
+
case 'prepare':
|
|
434
|
+
local = { x: 40, y: 0 };
|
|
435
|
+
break;
|
|
436
|
+
case 'control':
|
|
437
|
+
local = port === 'control' ? { x: 0, y: 0 } : port === 'out' ? right : left;
|
|
438
|
+
break;
|
|
439
|
+
case 'reset':
|
|
440
|
+
case 'classical-bit':
|
|
441
|
+
case 'classical-register':
|
|
442
|
+
local = port === 'out' ? { x: 40, y: 0 } : { x: -40, y: 0 };
|
|
443
|
+
break;
|
|
444
|
+
case 'swap':
|
|
445
|
+
case 'cz':
|
|
446
|
+
case 'cphase':
|
|
447
|
+
case 'toffoli':
|
|
448
|
+
case 'controlled':
|
|
449
|
+
case 'barrier':
|
|
450
|
+
case 'delay': {
|
|
451
|
+
const indexed = port.match(/^(?:in|out|control|target)([1-9]\d*)$/);
|
|
452
|
+
let index = indexed === null ? 0 : Number(indexed[1]) - 1;
|
|
453
|
+
if (port.startsWith('target'))
|
|
454
|
+
index += component.controls;
|
|
455
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
456
|
+
if (port.startsWith('control') || port.startsWith('target')) {
|
|
457
|
+
local = { x: 0, y: distributedCoordinate(index, Math.max(1, tracks), Math.max(16, (tracks - 1) * 18)) };
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
local = {
|
|
461
|
+
x: port.startsWith('out') ? 42 : -42,
|
|
462
|
+
y: distributedCoordinate(index, Math.max(1, tracks), Math.max(16, (tracks - 1) * 18))
|
|
463
|
+
};
|
|
464
|
+
}
|
|
211
465
|
break;
|
|
212
466
|
}
|
|
213
467
|
}
|
|
468
|
+
if (local !== undefined)
|
|
469
|
+
return positionedQuarterPoint(component, local);
|
|
214
470
|
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
215
471
|
}
|
|
216
472
|
export function resolvePortGeometry(component, port) {
|
|
217
473
|
const point = resolvePortPoint(component, port);
|
|
218
474
|
if (isClassicalGate(component)) {
|
|
219
|
-
return {
|
|
475
|
+
return {
|
|
476
|
+
point,
|
|
477
|
+
normal: positionedQuarterNormal(component, port.startsWith('out') ? { x: 1, y: 0 } : { x: -1, y: 0 })
|
|
478
|
+
};
|
|
220
479
|
}
|
|
221
480
|
if (isUmlComponent(component)) {
|
|
222
481
|
if (port === 'left' || port === 'in' || port.startsWith('left')) {
|
|
@@ -227,40 +486,103 @@ export function resolvePortGeometry(component, port) {
|
|
|
227
486
|
}
|
|
228
487
|
return { point, normal: port === 'top' ? { x: 0, y: -1 } : { x: 0, y: 1 } };
|
|
229
488
|
}
|
|
489
|
+
const geometry = (normal) => ({
|
|
490
|
+
point,
|
|
491
|
+
normal: positionedQuarterNormal(component, normal)
|
|
492
|
+
});
|
|
230
493
|
switch (component.kind) {
|
|
231
494
|
case 'resistor':
|
|
232
495
|
case 'capacitor':
|
|
233
496
|
case 'inductor':
|
|
234
|
-
return {
|
|
235
|
-
point,
|
|
236
|
-
normal: PASSIVE_OUTPUT_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 }
|
|
237
|
-
};
|
|
497
|
+
return geometry(PASSIVE_OUTPUT_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
238
498
|
case 'diode':
|
|
239
|
-
return {
|
|
240
|
-
point,
|
|
241
|
-
normal: DIODE_CATHODE_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 }
|
|
242
|
-
};
|
|
499
|
+
return geometry(DIODE_CATHODE_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
243
500
|
case 'transistor':
|
|
244
|
-
return {
|
|
245
|
-
point,
|
|
246
|
-
normal: TRANSISTOR_CONTROL_PORTS.has(port) ? { x: -1, y: 0 } : { x: 1, y: 0 }
|
|
247
|
-
};
|
|
501
|
+
return geometry(TRANSISTOR_CONTROL_PORTS.has(port) ? { x: -1, y: 0 } : { x: 1, y: 0 });
|
|
248
502
|
case 'port':
|
|
249
503
|
case 'hadamard':
|
|
250
504
|
case 'qgate':
|
|
251
|
-
|
|
505
|
+
case 'xgate':
|
|
506
|
+
case 'ygate':
|
|
507
|
+
case 'zgate':
|
|
508
|
+
case 'sgate':
|
|
509
|
+
case 'sdg':
|
|
510
|
+
case 'tgate':
|
|
511
|
+
case 'tdg':
|
|
512
|
+
case 'sx':
|
|
513
|
+
case 'phase':
|
|
514
|
+
case 'rx':
|
|
515
|
+
case 'ry':
|
|
516
|
+
case 'rz':
|
|
517
|
+
case 'ugate':
|
|
518
|
+
case 'connector':
|
|
519
|
+
case 'protection':
|
|
520
|
+
case 'resonator':
|
|
521
|
+
case 'meter':
|
|
522
|
+
case 'load':
|
|
523
|
+
case 'reset':
|
|
524
|
+
case 'prepare':
|
|
525
|
+
case 'classical-bit':
|
|
526
|
+
case 'classical-register':
|
|
527
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
252
528
|
case 'ground':
|
|
253
|
-
return {
|
|
529
|
+
return geometry({ x: 0, y: -1 });
|
|
254
530
|
case 'cnot':
|
|
255
531
|
if (port === 'control')
|
|
256
|
-
return {
|
|
532
|
+
return geometry({ x: 0, y: -1 });
|
|
257
533
|
if (port === 'target')
|
|
258
|
-
return {
|
|
259
|
-
return
|
|
534
|
+
return geometry({ x: 0, y: 1 });
|
|
535
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
260
536
|
case 'ic': {
|
|
261
537
|
const location = icPinLocation(component, port);
|
|
262
|
-
return
|
|
538
|
+
return geometry(sideNormal(location.side));
|
|
263
539
|
}
|
|
540
|
+
case 'junction':
|
|
541
|
+
case 'testpoint':
|
|
542
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
543
|
+
case 'source':
|
|
544
|
+
if (port.startsWith('control-'))
|
|
545
|
+
return geometry({ x: 0, y: port.endsWith('positive') ? -1 : 1 });
|
|
546
|
+
return geometry(port === 'out' || port === 'positive' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
547
|
+
case 'power':
|
|
548
|
+
return geometry({ x: -1, y: 0 });
|
|
549
|
+
case 'switch':
|
|
550
|
+
if (port.startsWith('coil'))
|
|
551
|
+
return geometry({ x: 0, y: 1 });
|
|
552
|
+
return geometry(port === 'in' || port === 'common' ? { x: -1, y: 0 } : { x: 1, y: 0 });
|
|
553
|
+
case 'amplifier':
|
|
554
|
+
if (port === 'v+' || port === 'v-')
|
|
555
|
+
return geometry({ x: 0, y: port === 'v+' ? -1 : 1 });
|
|
556
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
557
|
+
case 'buffer':
|
|
558
|
+
case 'logic':
|
|
559
|
+
case 'clock':
|
|
560
|
+
case 'flipflop':
|
|
561
|
+
case 'mux':
|
|
562
|
+
case 'encoder':
|
|
563
|
+
case 'decoder':
|
|
564
|
+
case 'register':
|
|
565
|
+
case 'counter':
|
|
566
|
+
case 'adder':
|
|
567
|
+
case 'comparator':
|
|
568
|
+
case 'bus':
|
|
569
|
+
if (['clock', 'enable', 'select'].includes(port))
|
|
570
|
+
return geometry({ x: 0, y: 1 });
|
|
571
|
+
if (['preset', 'clear'].includes(port))
|
|
572
|
+
return geometry({ x: 0, y: -1 });
|
|
573
|
+
return geometry(port === 'out' || port.startsWith('out') || ['q', 'nq', 'gt', 'eq', 'lt', 'tap'].includes(port) || (component.kind === 'bus' && component.variant === 'joiner' && port === 'bus') ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
574
|
+
case 'measure':
|
|
575
|
+
return geometry(port === 'classical' ? { x: 0, y: 1 } : port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
576
|
+
case 'control':
|
|
577
|
+
return geometry(port === 'control' ? { x: 0, y: -1 } : port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
578
|
+
case 'swap':
|
|
579
|
+
case 'cz':
|
|
580
|
+
case 'cphase':
|
|
581
|
+
case 'toffoli':
|
|
582
|
+
case 'controlled':
|
|
583
|
+
case 'barrier':
|
|
584
|
+
case 'delay':
|
|
585
|
+
return geometry(port.startsWith('out') ? { x: 1, y: 0 } : port.startsWith('in') ? { x: -1, y: 0 } : { x: 0, y: -1 });
|
|
264
586
|
}
|
|
265
587
|
throw new Error(`Validated port ${port} is missing.`);
|
|
266
588
|
}
|
|
@@ -274,6 +596,50 @@ export function enumerateComponentPorts(component) {
|
|
|
274
596
|
...Array.from({ length: component.outputs }, (_, index) => namedPort(component, `out${index + 1}`))
|
|
275
597
|
];
|
|
276
598
|
}
|
|
599
|
+
if (isDigitalComponent(component)) {
|
|
600
|
+
let ports = [
|
|
601
|
+
...Array.from({ length: component.inputs }, (_, index) => `in${index + 1}`),
|
|
602
|
+
...Array.from({ length: component.outputs }, (_, index) => `out${index + 1}`)
|
|
603
|
+
];
|
|
604
|
+
if (component.kind === 'logic' || component.kind === 'clock')
|
|
605
|
+
ports = ['out'];
|
|
606
|
+
else if (component.kind === 'flipflop') {
|
|
607
|
+
const inputs = component.variant === 'jk' ? ['j', 'k', 'clock'] : component.variant === 't' ? ['t', 'clock', 'clear'] : component.variant === 'sr-latch' ? ['s', 'r', 'enable'] : ['d', 'clock', 'clear'];
|
|
608
|
+
ports = [...inputs, 'q', 'nq', 'preset'];
|
|
609
|
+
}
|
|
610
|
+
else if (component.kind === 'register')
|
|
611
|
+
ports = ['in', 'out', 'clock', 'enable', 'clear'];
|
|
612
|
+
else if (component.kind === 'counter')
|
|
613
|
+
ports.push('clock', 'enable', 'clear');
|
|
614
|
+
else if (component.kind === 'mux')
|
|
615
|
+
ports.push('select', 'enable');
|
|
616
|
+
else if (component.kind === 'comparator')
|
|
617
|
+
ports = ['in1', 'in2', 'gt', 'eq', 'lt'];
|
|
618
|
+
else if (component.kind === 'bus') {
|
|
619
|
+
ports = component.variant === 'tap' ? ['bus', 'tap'] : component.variant === 'joiner' ? [...Array.from({ length: component.inputs }, (_, index) => `in${index + 1}`), 'bus'] : ['bus', ...Array.from({ length: component.outputs }, (_, index) => `out${index + 1}`)];
|
|
620
|
+
}
|
|
621
|
+
else if (component.kind === 'buffer' && component.variant?.startsWith('tristate') === true)
|
|
622
|
+
ports.push('enable');
|
|
623
|
+
return ports.map((port) => namedPort(component, port));
|
|
624
|
+
}
|
|
625
|
+
if (isQuantumSpecial(component)) {
|
|
626
|
+
if (component.kind === 'prepare')
|
|
627
|
+
return [namedPort(component, 'out')];
|
|
628
|
+
if (component.kind === 'measure')
|
|
629
|
+
return ['in', 'out', 'classical'].map((port) => namedPort(component, port));
|
|
630
|
+
if (component.kind === 'control')
|
|
631
|
+
return ['in', 'out', 'control'].map((port) => namedPort(component, port));
|
|
632
|
+
if (component.kind === 'reset' || component.kind === 'classical-bit' || component.kind === 'classical-register') {
|
|
633
|
+
return ['in', 'out'].map((port) => namedPort(component, port));
|
|
634
|
+
}
|
|
635
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
636
|
+
return [
|
|
637
|
+
...Array.from({ length: tracks }, (_, index) => namedPort(component, `in${index + 1}`)),
|
|
638
|
+
...Array.from({ length: tracks }, (_, index) => namedPort(component, `out${index + 1}`)),
|
|
639
|
+
...Array.from({ length: component.controls }, (_, index) => namedPort(component, `control${index + 1}`)),
|
|
640
|
+
...Array.from({ length: component.targets }, (_, index) => namedPort(component, `target${index + 1}`))
|
|
641
|
+
];
|
|
642
|
+
}
|
|
277
643
|
if (isUmlComponent(component)) {
|
|
278
644
|
return ['left', 'right', 'top', 'bottom'].map((port) => namedPort(component, port));
|
|
279
645
|
}
|
|
@@ -284,6 +650,24 @@ export function enumerateComponentPorts(component) {
|
|
|
284
650
|
case 'port':
|
|
285
651
|
case 'hadamard':
|
|
286
652
|
case 'qgate':
|
|
653
|
+
case 'xgate':
|
|
654
|
+
case 'ygate':
|
|
655
|
+
case 'zgate':
|
|
656
|
+
case 'sgate':
|
|
657
|
+
case 'sdg':
|
|
658
|
+
case 'tgate':
|
|
659
|
+
case 'tdg':
|
|
660
|
+
case 'sx':
|
|
661
|
+
case 'phase':
|
|
662
|
+
case 'rx':
|
|
663
|
+
case 'ry':
|
|
664
|
+
case 'rz':
|
|
665
|
+
case 'ugate':
|
|
666
|
+
case 'connector':
|
|
667
|
+
case 'protection':
|
|
668
|
+
case 'resonator':
|
|
669
|
+
case 'meter':
|
|
670
|
+
case 'load':
|
|
287
671
|
return [namedPort(component, 'in'), namedPort(component, 'out')];
|
|
288
672
|
case 'diode':
|
|
289
673
|
return [namedPort(component, 'anode'), namedPort(component, 'cathode')];
|
|
@@ -310,12 +694,34 @@ export function enumerateComponentPorts(component) {
|
|
|
310
694
|
namedPort(component, 'control'),
|
|
311
695
|
namedPort(component, 'target')
|
|
312
696
|
];
|
|
697
|
+
case 'junction':
|
|
698
|
+
case 'testpoint':
|
|
699
|
+
return [namedPort(component, 'node')];
|
|
700
|
+
case 'source': {
|
|
701
|
+
const ports = ['negative', 'positive'];
|
|
702
|
+
if (component.variant !== undefined && ['vcvs', 'vccs', 'ccvs', 'cccs'].includes(component.variant)) {
|
|
703
|
+
ports.push('control-positive', 'control-negative');
|
|
704
|
+
}
|
|
705
|
+
return ports.map((port) => namedPort(component, port));
|
|
706
|
+
}
|
|
707
|
+
case 'power':
|
|
708
|
+
return [namedPort(component, 'in')];
|
|
709
|
+
case 'switch': {
|
|
710
|
+
const ports = component.variant === 'spdt'
|
|
711
|
+
? ['common', 'normally-open', 'normally-closed']
|
|
712
|
+
: component.variant === 'relay'
|
|
713
|
+
? ['in', 'out', 'coil1', 'coil2']
|
|
714
|
+
: ['in', 'out'];
|
|
715
|
+
return ports.map((port) => namedPort(component, port));
|
|
716
|
+
}
|
|
717
|
+
case 'amplifier':
|
|
718
|
+
return ['positive', 'negative', 'out', 'v+', 'v-'].map((port) => namedPort(component, port));
|
|
313
719
|
case 'ic': {
|
|
314
720
|
const sides = ['left', 'right', 'top', 'bottom'];
|
|
315
721
|
return sides.flatMap((side) => component.pins[side].map((id, index) => ({
|
|
316
722
|
id,
|
|
317
723
|
point: positionIcPin(component, side, index),
|
|
318
|
-
normal: sideNormal(side)
|
|
724
|
+
normal: positionedQuarterNormal(component, sideNormal(side))
|
|
319
725
|
})));
|
|
320
726
|
}
|
|
321
727
|
}
|
|
@@ -871,6 +1277,17 @@ function componentHalfExtents(component) {
|
|
|
871
1277
|
else if (isUmlComponent(component)) {
|
|
872
1278
|
return umlHalfExtents(component);
|
|
873
1279
|
}
|
|
1280
|
+
else if (isDigitalComponent(component)) {
|
|
1281
|
+
halfWidth = component.bodyWidth / 2 + 16;
|
|
1282
|
+
halfHeight = component.bodyHeight / 2 + 16;
|
|
1283
|
+
}
|
|
1284
|
+
else if (isQuantumSpecial(component)) {
|
|
1285
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
1286
|
+
halfWidth = 42;
|
|
1287
|
+
halfHeight = Math.max(24, (tracks - 1) * 9 + 14);
|
|
1288
|
+
if (component.kind === 'measure')
|
|
1289
|
+
halfHeight = 34;
|
|
1290
|
+
}
|
|
874
1291
|
else {
|
|
875
1292
|
switch (component.kind) {
|
|
876
1293
|
case 'resistor':
|
|
@@ -881,7 +1298,7 @@ function componentHalfExtents(component) {
|
|
|
881
1298
|
break;
|
|
882
1299
|
case 'diode':
|
|
883
1300
|
halfWidth = 42;
|
|
884
|
-
halfHeight = component.diodeType === 'led' ? 30 : 20;
|
|
1301
|
+
halfHeight = component.diodeType === 'led' || component.diodeType === 'photodiode' ? 30 : 20;
|
|
885
1302
|
break;
|
|
886
1303
|
case 'transistor':
|
|
887
1304
|
halfWidth = 42;
|
|
@@ -897,9 +1314,24 @@ function componentHalfExtents(component) {
|
|
|
897
1314
|
break;
|
|
898
1315
|
case 'hadamard':
|
|
899
1316
|
case 'qgate':
|
|
900
|
-
|
|
901
|
-
|
|
1317
|
+
case 'xgate':
|
|
1318
|
+
case 'ygate':
|
|
1319
|
+
case 'zgate':
|
|
1320
|
+
case 'sgate':
|
|
1321
|
+
case 'sdg':
|
|
1322
|
+
case 'tgate':
|
|
1323
|
+
case 'tdg':
|
|
1324
|
+
case 'sx':
|
|
1325
|
+
case 'phase':
|
|
1326
|
+
case 'rx':
|
|
1327
|
+
case 'ry':
|
|
1328
|
+
case 'rz':
|
|
1329
|
+
case 'ugate': {
|
|
1330
|
+
const dimensions = quantumGateDimensions(component);
|
|
1331
|
+
halfWidth = dimensions.stubExtent;
|
|
1332
|
+
halfHeight = dimensions.bodyHeight / 2;
|
|
902
1333
|
break;
|
|
1334
|
+
}
|
|
903
1335
|
case 'cnot':
|
|
904
1336
|
halfWidth = 42;
|
|
905
1337
|
halfHeight = 26;
|
|
@@ -908,9 +1340,39 @@ function componentHalfExtents(component) {
|
|
|
908
1340
|
halfWidth = component.bodyWidth / 2 + 16;
|
|
909
1341
|
halfHeight = component.bodyHeight / 2 + 16;
|
|
910
1342
|
break;
|
|
1343
|
+
case 'source':
|
|
1344
|
+
halfWidth = 42;
|
|
1345
|
+
halfHeight = 34;
|
|
1346
|
+
break;
|
|
1347
|
+
case 'junction':
|
|
1348
|
+
halfWidth = halfHeight = 5;
|
|
1349
|
+
break;
|
|
1350
|
+
case 'testpoint':
|
|
1351
|
+
halfWidth = halfHeight = 12;
|
|
1352
|
+
break;
|
|
1353
|
+
case 'connector':
|
|
1354
|
+
case 'power':
|
|
1355
|
+
halfWidth = 32;
|
|
1356
|
+
halfHeight = 18;
|
|
1357
|
+
break;
|
|
1358
|
+
case 'switch':
|
|
1359
|
+
halfWidth = 42;
|
|
1360
|
+
halfHeight = component.variant === 'relay' ? 34 : 20;
|
|
1361
|
+
break;
|
|
1362
|
+
case 'amplifier':
|
|
1363
|
+
halfWidth = 48;
|
|
1364
|
+
halfHeight = 36;
|
|
1365
|
+
break;
|
|
1366
|
+
case 'protection':
|
|
1367
|
+
case 'resonator':
|
|
1368
|
+
case 'meter':
|
|
1369
|
+
case 'load':
|
|
1370
|
+
halfWidth = 42;
|
|
1371
|
+
halfHeight = 24;
|
|
1372
|
+
break;
|
|
911
1373
|
}
|
|
912
1374
|
}
|
|
913
|
-
return { halfWidth, halfHeight };
|
|
1375
|
+
return rotateQuarterExtents({ halfWidth, halfHeight }, componentTurn(component));
|
|
914
1376
|
}
|
|
915
1377
|
export function componentTextAnchors(component) {
|
|
916
1378
|
const { halfHeight } = componentHalfExtents(component);
|