altium-toolkit 1.1.2 → 1.1.3
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/package.json +1 -1
- package/src/core/altium/AltiumLayoutParser.mjs +275 -13
- package/src/core/altium/AltiumParser.mjs +240 -8
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- package/src/core/altium/PrintableTextDecoder.mjs +133 -13
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +13 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +40 -1
- package/src/core/altium/SchematicImageParser.mjs +291 -6
- package/src/core/altium/SchematicMultipartDesignatorNormalizer.mjs +164 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +2 -0
- package/src/core/altium/SchematicPinParser.mjs +175 -4
- package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
- package/src/core/altium/SchematicTextParser.mjs +125 -11
- package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
- package/src/ui/SchematicColorResolver.mjs +78 -0
- package/src/ui/SchematicContentLayout.mjs +58 -1
- package/src/ui/SchematicImageRenderer.mjs +125 -10
- package/src/ui/SchematicJunctionRenderer.mjs +1 -1
- package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
- package/src/ui/SchematicNoteRenderer.mjs +82 -6
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +292 -3
- package/src/ui/SchematicPinSvgRenderer.mjs +197 -15
- package/src/ui/SchematicPowerDiagramImageProcessor.mjs +970 -0
- package/src/ui/SchematicPowerDiagramLineMasks.mjs +631 -0
- package/src/ui/SchematicPowerPortRenderer.mjs +1 -1
- package/src/ui/SchematicShapeRenderer.mjs +82 -23
- package/src/ui/SchematicSvgRenderer.mjs +293 -47
|
@@ -143,6 +143,63 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
143
143
|
return offsets
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Collects compact FET-like owner groups whose numeric contact labels need
|
|
148
|
+
* to stay outside the owner-drawn device body.
|
|
149
|
+
* @param {{ ownerIndex?: string, name?: string, designator?: string, length?: number, orientation: 'left' | 'right' | 'top' | 'bottom', labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[]} pins
|
|
150
|
+
* @returns {Map<string, 'left' | 'right'>}
|
|
151
|
+
*/
|
|
152
|
+
static collectCompactExternalNumberLabelSides(pins) {
|
|
153
|
+
const ownerPins = new Map()
|
|
154
|
+
|
|
155
|
+
for (const pin of pins) {
|
|
156
|
+
const ownerIndex = String(pin.ownerIndex || '').trim()
|
|
157
|
+
if (!ownerIndex) continue
|
|
158
|
+
if (!ownerPins.has(ownerIndex)) ownerPins.set(ownerIndex, [])
|
|
159
|
+
ownerPins.get(ownerIndex).push(pin)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const sides = new Map()
|
|
163
|
+
|
|
164
|
+
for (const [ownerIndex, groupedPins] of ownerPins.entries()) {
|
|
165
|
+
const side =
|
|
166
|
+
SchematicOwnerPinLabelLayout.#resolveCompactExternalNumberLabelSide(
|
|
167
|
+
groupedPins
|
|
168
|
+
)
|
|
169
|
+
if (side) sides.set(ownerIndex, side)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return sides
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Collects rectangular owner bodies whose numeric-only horizontal pin
|
|
177
|
+
* labels are drawn inside the body, close to the body edge.
|
|
178
|
+
* @param {{ ownerIndex?: string, name?: string, designator?: string, length?: number, x: number, y: number, orientation: 'left' | 'right' | 'top' | 'bottom', labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[]} pins
|
|
179
|
+
* @param {{ ownerIndex?: string, x: number, y: number, width: number, height: number }[]} rectangles
|
|
180
|
+
* @returns {Map<string, { left: number, right: number }>}
|
|
181
|
+
*/
|
|
182
|
+
static collectInternalNumberLabelBoxes(pins, rectangles) {
|
|
183
|
+
const ownerPins = SchematicOwnerPinLabelLayout.#groupByOwnerIndex(pins)
|
|
184
|
+
const ownerRectangles =
|
|
185
|
+
SchematicOwnerPinLabelLayout.#groupByOwnerIndex(rectangles)
|
|
186
|
+
const boxes = new Map()
|
|
187
|
+
|
|
188
|
+
for (const [ownerIndex, groupedPins] of ownerPins.entries()) {
|
|
189
|
+
const box =
|
|
190
|
+
SchematicOwnerPinLabelLayout.#resolveInternalNumberLabelBox(
|
|
191
|
+
groupedPins,
|
|
192
|
+
ownerRectangles.get(ownerIndex) || []
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if (box) {
|
|
196
|
+
boxes.set(ownerIndex, box)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return boxes
|
|
201
|
+
}
|
|
202
|
+
|
|
146
203
|
/**
|
|
147
204
|
* Resolves the final SVG text anchor for one schematic free-text label.
|
|
148
205
|
* Mirrored rotated owner pin-name labels need the opposite text edge so
|
|
@@ -170,6 +227,234 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
170
227
|
return Number(text.y) >= Number(matchedOwnerPin.y) ? 'end' : 'start'
|
|
171
228
|
}
|
|
172
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Resolves the side that should carry compact owner-drawn pin numbers.
|
|
232
|
+
* @param {{ name?: string, designator?: string, length?: number, orientation: 'left' | 'right' | 'top' | 'bottom', labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[]} pins
|
|
233
|
+
* @returns {'left' | 'right' | null}
|
|
234
|
+
*/
|
|
235
|
+
static #resolveCompactExternalNumberLabelSide(pins) {
|
|
236
|
+
if (
|
|
237
|
+
pins.length !== 4 ||
|
|
238
|
+
!pins.every(
|
|
239
|
+
(pin) =>
|
|
240
|
+
(pin.labelMode || 'name-and-number') === 'number-only' &&
|
|
241
|
+
/^\d+$/.test(String(pin.designator || '').trim()) &&
|
|
242
|
+
Math.abs(Number(pin.length || 0)) <= 20 &&
|
|
243
|
+
SchematicOwnerPinLabelLayout.#isFetTerminalName(pin.name)
|
|
244
|
+
)
|
|
245
|
+
) {
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const hasTopPin = pins.some((pin) => pin.orientation === 'top')
|
|
250
|
+
const hasBottomPin = pins.some((pin) => pin.orientation === 'bottom')
|
|
251
|
+
const horizontalPins = pins.filter(
|
|
252
|
+
(pin) => pin.orientation === 'left' || pin.orientation === 'right'
|
|
253
|
+
)
|
|
254
|
+
const horizontalSides = new Set(
|
|
255
|
+
horizontalPins.map((pin) => pin.orientation)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
!hasTopPin ||
|
|
260
|
+
!hasBottomPin ||
|
|
261
|
+
horizontalPins.length === 0 ||
|
|
262
|
+
horizontalSides.size !== 1
|
|
263
|
+
) {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return horizontalPins[0].orientation
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Resolves one rectangular body suitable for internal numeric pin labels.
|
|
272
|
+
* @param {{ name?: string, designator?: string, length?: number, x: number, y: number, orientation: 'left' | 'right' | 'top' | 'bottom', labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[]} pins
|
|
273
|
+
* @param {{ x: number, y: number, width: number, height: number }[]} rectangles
|
|
274
|
+
* @returns {{ left: number, right: number } | null}
|
|
275
|
+
*/
|
|
276
|
+
static #resolveInternalNumberLabelBox(pins, rectangles) {
|
|
277
|
+
if (
|
|
278
|
+
pins.length < 4 ||
|
|
279
|
+
!pins.every((pin) =>
|
|
280
|
+
SchematicOwnerPinLabelLayout.#isInternalNumberLabelPin(pin)
|
|
281
|
+
)
|
|
282
|
+
) {
|
|
283
|
+
return null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const sides = new Set(pins.map((pin) => pin.orientation))
|
|
287
|
+
|
|
288
|
+
if (!sides.has('left') || !sides.has('right') || sides.size !== 2) {
|
|
289
|
+
return null
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const rectangle =
|
|
293
|
+
SchematicOwnerPinLabelLayout.#findInternalNumberLabelRectangle(
|
|
294
|
+
pins,
|
|
295
|
+
rectangles
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if (!rectangle) {
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
left: rectangle.left,
|
|
304
|
+
right: rectangle.right
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Returns the owner rectangle whose vertical edges carry every pin.
|
|
310
|
+
* @param {{ x: number, y: number, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
|
|
311
|
+
* @param {{ x: number, y: number, width: number, height: number }[]} rectangles
|
|
312
|
+
* @returns {{ left: number, right: number, top: number, bottom: number } | null}
|
|
313
|
+
*/
|
|
314
|
+
static #findInternalNumberLabelRectangle(pins, rectangles) {
|
|
315
|
+
for (const rectangle of rectangles) {
|
|
316
|
+
const normalized =
|
|
317
|
+
SchematicOwnerPinLabelLayout.#normalizeRectangle(rectangle)
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
normalized &&
|
|
321
|
+
SchematicOwnerPinLabelLayout.#pinsAlignWithRectangleEdges(
|
|
322
|
+
pins,
|
|
323
|
+
normalized
|
|
324
|
+
)
|
|
325
|
+
) {
|
|
326
|
+
return normalized
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return null
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Returns true when every pin body endpoint lies on a vertical body edge.
|
|
335
|
+
* @param {{ x: number, y: number, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
|
|
336
|
+
* @param {{ left: number, right: number, top: number, bottom: number }} rectangle
|
|
337
|
+
* @returns {boolean}
|
|
338
|
+
*/
|
|
339
|
+
static #pinsAlignWithRectangleEdges(pins, rectangle) {
|
|
340
|
+
const tolerance = 1.5
|
|
341
|
+
|
|
342
|
+
for (const pin of pins) {
|
|
343
|
+
const x = Number(pin.x)
|
|
344
|
+
const y = Number(pin.y)
|
|
345
|
+
|
|
346
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (
|
|
351
|
+
y < rectangle.top - tolerance ||
|
|
352
|
+
y > rectangle.bottom + tolerance
|
|
353
|
+
) {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (
|
|
358
|
+
pin.orientation === 'left' &&
|
|
359
|
+
Math.abs(x - rectangle.left) > tolerance
|
|
360
|
+
) {
|
|
361
|
+
return false
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (
|
|
365
|
+
pin.orientation === 'right' &&
|
|
366
|
+
Math.abs(x - rectangle.right) > tolerance
|
|
367
|
+
) {
|
|
368
|
+
return false
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return true
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Normalizes a rectangle to absolute edge coordinates.
|
|
377
|
+
* @param {{ x: number, y: number, width: number, height: number }} rectangle
|
|
378
|
+
* @returns {{ left: number, right: number, top: number, bottom: number } | null}
|
|
379
|
+
*/
|
|
380
|
+
static #normalizeRectangle(rectangle) {
|
|
381
|
+
const x1 = Number(rectangle?.x)
|
|
382
|
+
const y1 = Number(rectangle?.y)
|
|
383
|
+
const x2 = x1 + Number(rectangle?.width)
|
|
384
|
+
const y2 = y1 + Number(rectangle?.height)
|
|
385
|
+
|
|
386
|
+
if (
|
|
387
|
+
!Number.isFinite(x1) ||
|
|
388
|
+
!Number.isFinite(y1) ||
|
|
389
|
+
!Number.isFinite(x2) ||
|
|
390
|
+
!Number.isFinite(y2) ||
|
|
391
|
+
x1 === x2 ||
|
|
392
|
+
y1 === y2
|
|
393
|
+
) {
|
|
394
|
+
return null
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
left: Math.min(x1, x2),
|
|
399
|
+
right: Math.max(x1, x2),
|
|
400
|
+
top: Math.min(y1, y2),
|
|
401
|
+
bottom: Math.max(y1, y2)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Returns true for numeric-only horizontal pins on short owner stubs.
|
|
407
|
+
* @param {{ name?: string, designator?: string, length?: number, orientation: 'left' | 'right' | 'top' | 'bottom', labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }} pin
|
|
408
|
+
* @returns {boolean}
|
|
409
|
+
*/
|
|
410
|
+
static #isInternalNumberLabelPin(pin) {
|
|
411
|
+
const name = String(pin.name || '').trim()
|
|
412
|
+
const designator = String(pin.designator || '').trim()
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
(pin.orientation === 'left' || pin.orientation === 'right') &&
|
|
416
|
+
(pin.labelMode || 'name-and-number') === 'number-only' &&
|
|
417
|
+
/^\d+$/.test(designator) &&
|
|
418
|
+
(!name || /^\d+$/.test(name)) &&
|
|
419
|
+
Math.abs(Number(pin.length || 0)) <= 30
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Groups schematic owner-local primitives by owner index.
|
|
425
|
+
* @template T
|
|
426
|
+
* @param {(T & { ownerIndex?: string })[]} items
|
|
427
|
+
* @returns {Map<string, T[]>}
|
|
428
|
+
*/
|
|
429
|
+
static #groupByOwnerIndex(items) {
|
|
430
|
+
const groups = new Map()
|
|
431
|
+
|
|
432
|
+
for (const item of items) {
|
|
433
|
+
const ownerIndex = String(item.ownerIndex || '').trim()
|
|
434
|
+
|
|
435
|
+
if (!ownerIndex) {
|
|
436
|
+
continue
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!groups.has(ownerIndex)) {
|
|
440
|
+
groups.set(ownerIndex, [])
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
groups.get(ownerIndex).push(item)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return groups
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Returns true for FET terminal names, including numbered gate/source pins.
|
|
451
|
+
* @param {string | undefined} name
|
|
452
|
+
* @returns {boolean}
|
|
453
|
+
*/
|
|
454
|
+
static #isFetTerminalName(name) {
|
|
455
|
+
return /^(?:[DS]|[GS]\d*)$/i.test(String(name || '').trim())
|
|
456
|
+
}
|
|
457
|
+
|
|
173
458
|
/**
|
|
174
459
|
* Moves left/right pin numbers outward by the same horizontal correction
|
|
175
460
|
* already applied to their explicit owner pin-name labels.
|
|
@@ -206,7 +491,7 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
206
491
|
/**
|
|
207
492
|
* Resolves schematic pin-number placement.
|
|
208
493
|
* @param {{ x: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
209
|
-
* @param {'single-in' | 'single-out' | 'double' | null} markerStyle
|
|
494
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross' | null} markerStyle
|
|
210
495
|
* @param {{ rotateTopNumber?: boolean }} options
|
|
211
496
|
* @returns {{ x: number, yOffset: number, anchor: 'start' | 'middle' | 'end', rotation: number } | null}
|
|
212
497
|
*/
|
|
@@ -308,7 +593,7 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
308
593
|
|
|
309
594
|
/**
|
|
310
595
|
* Returns the horizontal pin-number clearance needed by the pin geometry.
|
|
311
|
-
* @param {'single-in' | 'single-out' | 'double' | null} markerStyle
|
|
596
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross' | null} markerStyle
|
|
312
597
|
* @param {{ length?: number }} pin
|
|
313
598
|
* @returns {number}
|
|
314
599
|
*/
|
|
@@ -316,6 +601,8 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
316
601
|
switch (markerStyle) {
|
|
317
602
|
case 'double':
|
|
318
603
|
return 17
|
|
604
|
+
case 'cross':
|
|
605
|
+
return 12
|
|
319
606
|
case 'single-in':
|
|
320
607
|
case 'single-out':
|
|
321
608
|
return 8
|
|
@@ -357,7 +644,7 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
357
644
|
/**
|
|
358
645
|
* Resolves one authored outer pin marker style from the stored symbol flag.
|
|
359
646
|
* @param {{ symbolOuter?: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
360
|
-
* @returns {'single-in' | 'single-out' | 'double' | null}
|
|
647
|
+
* @returns {'single-in' | 'single-out' | 'double' | 'cross' | null}
|
|
361
648
|
*/
|
|
362
649
|
static #resolveOuterPinMarkerStyle(pin) {
|
|
363
650
|
if (pin.orientation !== 'left' && pin.orientation !== 'right') {
|
|
@@ -370,6 +657,8 @@ export class SchematicOwnerPinLabelLayout {
|
|
|
370
657
|
return 'single-out'
|
|
371
658
|
case 2:
|
|
372
659
|
return 'single-in'
|
|
660
|
+
case 6:
|
|
661
|
+
return 'cross'
|
|
373
662
|
case 34:
|
|
374
663
|
return 'double'
|
|
375
664
|
default:
|
|
@@ -22,6 +22,8 @@ export class SchematicPinSvgRenderer {
|
|
|
22
22
|
* @param {Set<string>} rotatedVerticalNumberOwners
|
|
23
23
|
* @param {Set<string>} explicitOwnerPinNameLabels
|
|
24
24
|
* @param {Map<string, number>} explicitOwnerPinLabelOffsets
|
|
25
|
+
* @param {Map<string, 'left' | 'right'>} compactExternalNumberLabelSides
|
|
26
|
+
* @param {Map<string, { left: number, right: number }>} internalNumberLabelBoxes
|
|
25
27
|
* @returns {string}
|
|
26
28
|
*/
|
|
27
29
|
static buildMarkup(
|
|
@@ -30,7 +32,9 @@ export class SchematicPinSvgRenderer {
|
|
|
30
32
|
sheet,
|
|
31
33
|
rotatedVerticalNumberOwners,
|
|
32
34
|
explicitOwnerPinNameLabels,
|
|
33
|
-
explicitOwnerPinLabelOffsets
|
|
35
|
+
explicitOwnerPinLabelOffsets,
|
|
36
|
+
compactExternalNumberLabelSides = new Map(),
|
|
37
|
+
internalNumberLabelBoxes = new Map()
|
|
34
38
|
) {
|
|
35
39
|
const geometry =
|
|
36
40
|
SchematicPinSvgRenderer.#projectSchematicPinGeometry(pin)
|
|
@@ -60,15 +64,43 @@ export class SchematicPinSvgRenderer {
|
|
|
60
64
|
const hasExplicitOwnerPinName =
|
|
61
65
|
Boolean(pin.name) &&
|
|
62
66
|
explicitOwnerPinNameLabels.has(ownerPinLabelKey)
|
|
67
|
+
const compactExternalNumberLabelSide =
|
|
68
|
+
compactExternalNumberLabelSides.get(String(pin.ownerIndex || '')) ||
|
|
69
|
+
null
|
|
70
|
+
const internalNumberLabelBox =
|
|
71
|
+
internalNumberLabelBoxes.get(String(pin.ownerIndex || '')) || null
|
|
63
72
|
|
|
64
73
|
if (pin.orientation === 'left') {
|
|
65
74
|
if (labelMode !== 'hidden' && labelMode !== 'name-only') {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
const internalNumberPlacement =
|
|
76
|
+
SchematicPinSvgRenderer.#resolveInternalHorizontalNumberPlacement(
|
|
77
|
+
pin,
|
|
78
|
+
internalNumberLabelBox
|
|
79
|
+
)
|
|
80
|
+
if (internalNumberPlacement) {
|
|
81
|
+
texts.push(
|
|
82
|
+
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
83
|
+
'schematic-pin-name',
|
|
84
|
+
internalNumberPlacement.x,
|
|
85
|
+
projectedY - 1,
|
|
86
|
+
pin,
|
|
87
|
+
labelColor,
|
|
88
|
+
internalNumberPlacement.anchor,
|
|
89
|
+
textOptions
|
|
90
|
+
)
|
|
71
91
|
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const defaultNumberX = compactExternalNumberLabelSide
|
|
95
|
+
? SchematicPinSvgRenderer.#resolveCompactExternalHorizontalNumberX(
|
|
96
|
+
pin,
|
|
97
|
+
geometry
|
|
98
|
+
)
|
|
99
|
+
: geometry.bodyX -
|
|
100
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
101
|
+
outerMarkerStyle,
|
|
102
|
+
pin
|
|
103
|
+
)
|
|
72
104
|
const numberX = hasExplicitOwnerPinName
|
|
73
105
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
74
106
|
pin,
|
|
@@ -116,12 +148,35 @@ export class SchematicPinSvgRenderer {
|
|
|
116
148
|
|
|
117
149
|
if (pin.orientation === 'right') {
|
|
118
150
|
if (labelMode !== 'hidden' && labelMode !== 'name-only') {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
pin
|
|
151
|
+
const internalNumberPlacement =
|
|
152
|
+
SchematicPinSvgRenderer.#resolveInternalHorizontalNumberPlacement(
|
|
153
|
+
pin,
|
|
154
|
+
internalNumberLabelBox
|
|
124
155
|
)
|
|
156
|
+
if (internalNumberPlacement) {
|
|
157
|
+
texts.push(
|
|
158
|
+
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
159
|
+
'schematic-pin-name',
|
|
160
|
+
internalNumberPlacement.x,
|
|
161
|
+
projectedY - 1,
|
|
162
|
+
pin,
|
|
163
|
+
labelColor,
|
|
164
|
+
internalNumberPlacement.anchor,
|
|
165
|
+
textOptions
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const defaultNumberX = compactExternalNumberLabelSide
|
|
171
|
+
? SchematicPinSvgRenderer.#resolveCompactExternalHorizontalNumberX(
|
|
172
|
+
pin,
|
|
173
|
+
geometry
|
|
174
|
+
)
|
|
175
|
+
: geometry.bodyX +
|
|
176
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
177
|
+
outerMarkerStyle,
|
|
178
|
+
pin
|
|
179
|
+
)
|
|
125
180
|
const numberX = hasExplicitOwnerPinName
|
|
126
181
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
127
182
|
pin,
|
|
@@ -175,7 +230,12 @@ export class SchematicPinSvgRenderer {
|
|
|
175
230
|
texts.push(
|
|
176
231
|
createSvgText(
|
|
177
232
|
'schematic-pin-number',
|
|
178
|
-
|
|
233
|
+
compactExternalNumberLabelSide
|
|
234
|
+
? SchematicPinSvgRenderer.#resolveCompactExternalVerticalNumberX(
|
|
235
|
+
geometry,
|
|
236
|
+
compactExternalNumberLabelSide
|
|
237
|
+
)
|
|
238
|
+
: geometry.bodyX - 2,
|
|
179
239
|
pin.orientation === 'top'
|
|
180
240
|
? projectedInnerY - 6
|
|
181
241
|
: projectedInnerY + 7,
|
|
@@ -324,7 +384,7 @@ export class SchematicPinSvgRenderer {
|
|
|
324
384
|
* @param {{ orientation: 'left' | 'right' | 'top' | 'bottom', labelColor?: string, color: string }} pin
|
|
325
385
|
* @param {{ bodyX: number, bodyY: number }} geometry
|
|
326
386
|
* @param {number} sheetHeight
|
|
327
|
-
* @param {'single-in' | 'single-out' | 'double'} markerStyle
|
|
387
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross'} markerStyle
|
|
328
388
|
* @returns {string}
|
|
329
389
|
*/
|
|
330
390
|
static #buildOuterPinMarkerMarkup(pin, geometry, sheetHeight, markerStyle) {
|
|
@@ -339,6 +399,15 @@ export class SchematicPinSvgRenderer {
|
|
|
339
399
|
'--schematic-text-color'
|
|
340
400
|
)
|
|
341
401
|
|
|
402
|
+
if (markerStyle === 'cross') {
|
|
403
|
+
return SchematicPinSvgRenderer.#buildOuterPinCrossMarkerMarkup(
|
|
404
|
+
geometry.bodyX,
|
|
405
|
+
projectedY,
|
|
406
|
+
pin.orientation,
|
|
407
|
+
strokeColor
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
342
411
|
const polygons = SchematicPinSvgRenderer.#buildOuterPinMarkerPolygons(
|
|
343
412
|
geometry.bodyX,
|
|
344
413
|
projectedY,
|
|
@@ -374,7 +443,7 @@ export class SchematicPinSvgRenderer {
|
|
|
374
443
|
|
|
375
444
|
/**
|
|
376
445
|
* Returns the horizontal pin-number clearance needed by the pin geometry.
|
|
377
|
-
* @param {'single-in' | 'single-out' | 'double' | null} markerStyle
|
|
446
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross' | null} markerStyle
|
|
378
447
|
* @param {{ length?: number }} pin
|
|
379
448
|
* @returns {number}
|
|
380
449
|
*/
|
|
@@ -382,6 +451,8 @@ export class SchematicPinSvgRenderer {
|
|
|
382
451
|
switch (markerStyle) {
|
|
383
452
|
case 'double':
|
|
384
453
|
return 17
|
|
454
|
+
case 'cross':
|
|
455
|
+
return 12
|
|
385
456
|
case 'single-in':
|
|
386
457
|
case 'single-out':
|
|
387
458
|
return 8
|
|
@@ -420,6 +491,115 @@ export class SchematicPinSvgRenderer {
|
|
|
420
491
|
return fallback === 2 ? 10 : 8
|
|
421
492
|
}
|
|
422
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Places numeric-only labels just inside a rectangular owner body.
|
|
496
|
+
* @param {{ name?: string, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
497
|
+
* @param {{ left: number, right: number } | null} box
|
|
498
|
+
* @returns {{ x: number, anchor: 'middle' } | null}
|
|
499
|
+
*/
|
|
500
|
+
static #resolveInternalHorizontalNumberPlacement(pin, box) {
|
|
501
|
+
if (
|
|
502
|
+
!box ||
|
|
503
|
+
(pin.orientation !== 'left' && pin.orientation !== 'right') ||
|
|
504
|
+
!/^\d+$/u.test(String(pin.name || '').trim())
|
|
505
|
+
) {
|
|
506
|
+
return null
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const left = Number(box.left)
|
|
510
|
+
const right = Number(box.right)
|
|
511
|
+
|
|
512
|
+
if (
|
|
513
|
+
!Number.isFinite(left) ||
|
|
514
|
+
!Number.isFinite(right) ||
|
|
515
|
+
right <= left
|
|
516
|
+
) {
|
|
517
|
+
return null
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const inset = Math.min(6, Math.max(3, (right - left) / 10))
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
x: pin.orientation === 'left' ? left + inset : right - inset,
|
|
524
|
+
anchor: 'middle'
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Places compact owner-drawn side pin numbers near the external pin stub.
|
|
530
|
+
* @param {{ length?: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
531
|
+
* @param {{ bodyX: number }} geometry
|
|
532
|
+
* @returns {number}
|
|
533
|
+
*/
|
|
534
|
+
static #resolveCompactExternalHorizontalNumberX(pin, geometry) {
|
|
535
|
+
const length = Math.abs(Number(pin.length || 0))
|
|
536
|
+
const offset = Math.max(8, Math.min(length - 6, 12))
|
|
537
|
+
|
|
538
|
+
if (pin.orientation === 'left') {
|
|
539
|
+
return geometry.bodyX - offset
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (pin.orientation === 'right') {
|
|
543
|
+
return geometry.bodyX + offset
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return geometry.bodyX
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Places compact owner-drawn vertical pin numbers on the same side as the
|
|
551
|
+
* horizontal terminal contacts.
|
|
552
|
+
* @param {{ bodyX: number }} geometry
|
|
553
|
+
* @param {'left' | 'right'} side
|
|
554
|
+
* @returns {number}
|
|
555
|
+
*/
|
|
556
|
+
static #resolveCompactExternalVerticalNumberX(geometry, side) {
|
|
557
|
+
return geometry.bodyX + (side === 'left' ? -10 : 10)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Builds an authored outer pin cross marker.
|
|
562
|
+
* @param {number} bodyX
|
|
563
|
+
* @param {number} projectedY
|
|
564
|
+
* @param {'left' | 'right' | 'top' | 'bottom'} orientation
|
|
565
|
+
* @param {string} strokeColor
|
|
566
|
+
* @returns {string}
|
|
567
|
+
*/
|
|
568
|
+
static #buildOuterPinCrossMarkerMarkup(
|
|
569
|
+
bodyX,
|
|
570
|
+
projectedY,
|
|
571
|
+
orientation,
|
|
572
|
+
strokeColor
|
|
573
|
+
) {
|
|
574
|
+
const direction = orientation === 'left' ? 1 : -1
|
|
575
|
+
const centerX = bodyX - direction * 8
|
|
576
|
+
const halfSize = 3
|
|
577
|
+
|
|
578
|
+
return (
|
|
579
|
+
'<g class="schematic-pin-marker"><line x1="' +
|
|
580
|
+
formatNumber(centerX - halfSize) +
|
|
581
|
+
'" y1="' +
|
|
582
|
+
formatNumber(projectedY - halfSize) +
|
|
583
|
+
'" x2="' +
|
|
584
|
+
formatNumber(centerX + halfSize) +
|
|
585
|
+
'" y2="' +
|
|
586
|
+
formatNumber(projectedY + halfSize) +
|
|
587
|
+
'" stroke="' +
|
|
588
|
+
escapeHtml(strokeColor) +
|
|
589
|
+
'" stroke-width="0.75" vector-effect="non-scaling-stroke" /><line x1="' +
|
|
590
|
+
formatNumber(centerX - halfSize) +
|
|
591
|
+
'" y1="' +
|
|
592
|
+
formatNumber(projectedY + halfSize) +
|
|
593
|
+
'" x2="' +
|
|
594
|
+
formatNumber(centerX + halfSize) +
|
|
595
|
+
'" y2="' +
|
|
596
|
+
formatNumber(projectedY - halfSize) +
|
|
597
|
+
'" stroke="' +
|
|
598
|
+
escapeHtml(strokeColor) +
|
|
599
|
+
'" stroke-width="0.75" vector-effect="non-scaling-stroke" /></g>'
|
|
600
|
+
)
|
|
601
|
+
}
|
|
602
|
+
|
|
423
603
|
/**
|
|
424
604
|
* Builds one or two authored outer-marker polygons for one horizontal pin.
|
|
425
605
|
* @param {number} bodyX
|
|
@@ -469,7 +649,7 @@ export class SchematicPinSvgRenderer {
|
|
|
469
649
|
/**
|
|
470
650
|
* Resolves one authored outer pin marker style from the stored symbol flag.
|
|
471
651
|
* @param {{ symbolOuter?: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
472
|
-
* @returns {'single-in' | 'single-out' | 'double' | null}
|
|
652
|
+
* @returns {'single-in' | 'single-out' | 'double' | 'cross' | null}
|
|
473
653
|
*/
|
|
474
654
|
static #resolveSchematicOuterPinMarkerStyle(pin) {
|
|
475
655
|
if (pin.orientation !== 'left' && pin.orientation !== 'right') {
|
|
@@ -483,6 +663,8 @@ export class SchematicPinSvgRenderer {
|
|
|
483
663
|
return 'single-out'
|
|
484
664
|
case 2:
|
|
485
665
|
return 'single-in'
|
|
666
|
+
case 6:
|
|
667
|
+
return 'cross'
|
|
486
668
|
case 34:
|
|
487
669
|
return 'double'
|
|
488
670
|
default:
|