altium-toolkit 1.0.2 → 1.0.7
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/AltiumParser.mjs +17 -6
- package/src/core/altium/AsciiRecordParser.mjs +28 -1
- package/src/core/altium/SchematicPinDesignatorInferer.mjs +36 -3
- package/src/core/altium/SchematicPinParser.mjs +139 -14
- package/src/core/altium/SchematicPrimitiveParser.mjs +75 -4
- package/src/core/altium/SchematicTextParser.mjs +22 -18
- package/src/core/altium/SchematicTextPostProcessor.mjs +127 -10
- package/src/core/altium/SchematicWireNormalizer.mjs +1162 -0
- package/src/ui/PcbTextPrimitiveRenderer.mjs +58 -2
- package/src/ui/SchematicContentLayout.mjs +124 -22
- package/src/ui/SchematicJunctionRenderer.mjs +21 -3
- package/src/ui/SchematicNoteRenderer.mjs +75 -10
- package/src/ui/SchematicPinSvgRenderer.mjs +48 -7
- package/src/ui/SchematicPowerPortRenderer.mjs +53 -160
- package/src/ui/SchematicSheetChromeRenderer.mjs +29 -15
- package/src/ui/SchematicSvgRenderer.mjs +341 -39
- package/src/ui/SchematicSvgUtils.mjs +9 -2
- package/src/ui/SchematicTypography.mjs +13 -10
|
@@ -132,7 +132,7 @@ export class PcbTextPrimitiveRenderer {
|
|
|
132
132
|
metrics.lineHeight
|
|
133
133
|
)
|
|
134
134
|
const rectX = -layout.anchorX
|
|
135
|
-
const rectY = layout.anchorY
|
|
135
|
+
const rectY = -layout.anchorY
|
|
136
136
|
const cornerRadius = Math.min(
|
|
137
137
|
padding,
|
|
138
138
|
layout.width / 2,
|
|
@@ -360,7 +360,21 @@ export class PcbTextPrimitiveRenderer {
|
|
|
360
360
|
return null
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
|
|
363
|
+
const canvasFont = PcbTextPrimitiveRenderer.#buildCanvasFont(
|
|
364
|
+
text,
|
|
365
|
+
fontSize
|
|
366
|
+
)
|
|
367
|
+
if (
|
|
368
|
+
!PcbTextPrimitiveRenderer.#canMeasureCanvasFont(
|
|
369
|
+
text,
|
|
370
|
+
canvasFont,
|
|
371
|
+
fontSize
|
|
372
|
+
)
|
|
373
|
+
) {
|
|
374
|
+
return null
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
context.font = canvasFont
|
|
364
378
|
|
|
365
379
|
const measured = lines.map((line) => context.measureText(line || ' '))
|
|
366
380
|
const ascent = PcbTextPrimitiveRenderer.#resolveMeasuredExtent(
|
|
@@ -388,6 +402,29 @@ export class PcbTextPrimitiveRenderer {
|
|
|
388
402
|
}
|
|
389
403
|
}
|
|
390
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Checks whether canvas text metrics can be trusted for the requested
|
|
407
|
+
* imported font.
|
|
408
|
+
* @param {{ fontMetrics?: { averageAdvanceWidth?: number, unitsPerEm?: number } }} text Text record.
|
|
409
|
+
* @param {string} canvasFont Full canvas font shorthand.
|
|
410
|
+
* @param {number} fontSize Text font size.
|
|
411
|
+
* @returns {boolean}
|
|
412
|
+
*/
|
|
413
|
+
static #canMeasureCanvasFont(text, canvasFont, fontSize) {
|
|
414
|
+
const fonts = globalThis.document?.fonts
|
|
415
|
+
if (typeof fonts?.check === 'function') {
|
|
416
|
+
return fonts.check(
|
|
417
|
+
PcbTextPrimitiveRenderer.#buildPrimaryCanvasFont(text, fontSize)
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (PcbTextPrimitiveRenderer.#fontMetricsAverageWidthRatio(text)) {
|
|
422
|
+
return false
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return Boolean(canvasFont)
|
|
426
|
+
}
|
|
427
|
+
|
|
391
428
|
/**
|
|
392
429
|
* Resolves a measured text extent with a stable fallback.
|
|
393
430
|
* @param {TextMetrics[]} measured Browser text metrics.
|
|
@@ -420,6 +457,25 @@ export class PcbTextPrimitiveRenderer {
|
|
|
420
457
|
return `${style} ${weight} ${fontSize}px ${family}`
|
|
421
458
|
}
|
|
422
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Builds a single-family font shorthand for readiness checks.
|
|
462
|
+
* @param {{ fontFamily?: string, fontName?: string, isBold?: boolean, fontWeight?: number, isItalic?: boolean }} text Text record.
|
|
463
|
+
* @param {number} fontSize Text font size.
|
|
464
|
+
* @returns {string}
|
|
465
|
+
*/
|
|
466
|
+
static #buildPrimaryCanvasFont(text, fontSize) {
|
|
467
|
+
const weight =
|
|
468
|
+
text?.isBold || Number(text?.fontWeight) >= 600 ? '700' : '400'
|
|
469
|
+
const style = text?.isItalic ? 'italic' : 'normal'
|
|
470
|
+
const family = PcbTextPrimitiveRenderer.#quoteFontFamily(
|
|
471
|
+
PcbTextPrimitiveRenderer.#cleanFontFamily(
|
|
472
|
+
text?.fontFamily || text?.fontName
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return `${style} ${weight} ${fontSize}px ${family}`
|
|
477
|
+
}
|
|
478
|
+
|
|
423
479
|
/**
|
|
424
480
|
* Builds a browser font-family stack matching the 3D TrueType path.
|
|
425
481
|
* @param {unknown} family Font family value.
|
|
@@ -195,7 +195,7 @@ export class SchematicContentLayout {
|
|
|
195
195
|
' ' +
|
|
196
196
|
formatNumber(targetMinY) +
|
|
197
197
|
') scale(' +
|
|
198
|
-
|
|
198
|
+
SchematicContentLayout.#formatScale(scale) +
|
|
199
199
|
') translate(' +
|
|
200
200
|
formatNumber(-bounds.minX) +
|
|
201
201
|
' ' +
|
|
@@ -252,20 +252,28 @@ export class SchematicContentLayout {
|
|
|
252
252
|
return ''
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
const pivotX = margin
|
|
256
|
-
const pivotY = height - margin
|
|
257
255
|
const topLimit = margin + contentPadding * 0.2
|
|
258
256
|
const bottomLimit = height - margin - footerReserve
|
|
259
257
|
const rightLimit = width - margin
|
|
258
|
+
const usedWidth = bounds.maxX - bounds.minX
|
|
259
|
+
const usedHeight = bounds.maxY - bounds.minY
|
|
260
|
+
|
|
261
|
+
if (usedWidth <= 0 || usedHeight <= 0) {
|
|
262
|
+
return ''
|
|
263
|
+
}
|
|
264
|
+
|
|
260
265
|
const scale = Math.min(
|
|
261
266
|
targetScale,
|
|
262
|
-
|
|
263
|
-
bounds,
|
|
264
|
-
|
|
265
|
-
pivotY,
|
|
267
|
+
SchematicContentLayout.#resolveContentScaleLimit(
|
|
268
|
+
bounds.minX,
|
|
269
|
+
bounds.maxX,
|
|
266
270
|
margin,
|
|
271
|
+
rightLimit
|
|
272
|
+
),
|
|
273
|
+
SchematicContentLayout.#resolveContentScaleLimit(
|
|
274
|
+
bounds.minY,
|
|
275
|
+
bounds.maxY,
|
|
267
276
|
topLimit,
|
|
268
|
-
rightLimit,
|
|
269
277
|
bottomLimit
|
|
270
278
|
)
|
|
271
279
|
)
|
|
@@ -274,10 +282,18 @@ export class SchematicContentLayout {
|
|
|
274
282
|
return ''
|
|
275
283
|
}
|
|
276
284
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
285
|
+
const targetMinX =
|
|
286
|
+
SchematicContentLayout.#resolveCenteredContentTargetMinX(
|
|
287
|
+
bounds,
|
|
288
|
+
scale,
|
|
289
|
+
margin,
|
|
290
|
+
rightLimit
|
|
291
|
+
)
|
|
292
|
+
const projectedMinX = targetMinX
|
|
293
|
+
const projectedMaxX = targetMinX + usedWidth * scale
|
|
294
|
+
const targetMinY = bottomLimit - usedHeight * scale
|
|
295
|
+
const projectedMinY = targetMinY
|
|
296
|
+
const projectedMaxY = bottomLimit
|
|
281
297
|
|
|
282
298
|
if (
|
|
283
299
|
projectedMinX < margin - 0.01 ||
|
|
@@ -290,19 +306,73 @@ export class SchematicContentLayout {
|
|
|
290
306
|
|
|
291
307
|
return (
|
|
292
308
|
' transform="translate(' +
|
|
293
|
-
formatNumber(
|
|
309
|
+
formatNumber(targetMinX) +
|
|
294
310
|
' ' +
|
|
295
|
-
formatNumber(
|
|
311
|
+
formatNumber(targetMinY) +
|
|
296
312
|
') scale(' +
|
|
297
|
-
|
|
313
|
+
SchematicContentLayout.#formatScale(scale) +
|
|
298
314
|
') translate(' +
|
|
299
|
-
formatNumber(-
|
|
315
|
+
formatNumber(-bounds.minX) +
|
|
300
316
|
' ' +
|
|
301
|
-
formatNumber(-
|
|
317
|
+
formatNumber(-bounds.minY) +
|
|
302
318
|
')"'
|
|
303
319
|
)
|
|
304
320
|
}
|
|
305
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Resolves one scale cap that keeps a horizontally centered content
|
|
324
|
+
* envelope inside the sheet frame.
|
|
325
|
+
* @param {number} minCoordinate
|
|
326
|
+
* @param {number} maxCoordinate
|
|
327
|
+
* @param {number} minLimit
|
|
328
|
+
* @param {number} maxLimit
|
|
329
|
+
* @returns {number}
|
|
330
|
+
*/
|
|
331
|
+
static #resolveContentScaleLimit(
|
|
332
|
+
minCoordinate,
|
|
333
|
+
maxCoordinate,
|
|
334
|
+
minLimit,
|
|
335
|
+
maxLimit
|
|
336
|
+
) {
|
|
337
|
+
const usedWidth = maxCoordinate - minCoordinate
|
|
338
|
+
const availableWidth = maxLimit - minLimit
|
|
339
|
+
|
|
340
|
+
return usedWidth > 0 && availableWidth > 0
|
|
341
|
+
? availableWidth / usedWidth
|
|
342
|
+
: Infinity
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Formats SVG transform scales with enough precision to avoid visible
|
|
347
|
+
* placement drift on large schematic sheets.
|
|
348
|
+
* @param {number} value
|
|
349
|
+
* @returns {string}
|
|
350
|
+
*/
|
|
351
|
+
static #formatScale(value) {
|
|
352
|
+
return Number(value).toFixed(4).replace(/0+$/, '').replace(/\.$/, '')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Resolves the target SVG x-coordinate for a scaled content envelope.
|
|
357
|
+
* @param {{ minX: number, maxX: number }} bounds
|
|
358
|
+
* @param {number} scale
|
|
359
|
+
* @param {number} leftLimit
|
|
360
|
+
* @param {number} rightLimit
|
|
361
|
+
* @returns {number}
|
|
362
|
+
*/
|
|
363
|
+
static #resolveCenteredContentTargetMinX(
|
|
364
|
+
bounds,
|
|
365
|
+
scale,
|
|
366
|
+
leftLimit,
|
|
367
|
+
rightLimit
|
|
368
|
+
) {
|
|
369
|
+
const scaledWidth = (bounds.maxX - bounds.minX) * scale
|
|
370
|
+
const availableWidth = rightLimit - leftLimit
|
|
371
|
+
const remainingWidth = Math.max(availableWidth - scaledWidth, 0)
|
|
372
|
+
|
|
373
|
+
return leftLimit + remainingWidth / 2
|
|
374
|
+
}
|
|
375
|
+
|
|
306
376
|
/**
|
|
307
377
|
* Resolves per-edge maximum scale factors for a bottom-left pivot.
|
|
308
378
|
* @param {{ minX: number, minY: number, maxX: number, maxY: number }} bounds
|
|
@@ -436,11 +506,43 @@ export class SchematicContentLayout {
|
|
|
436
506
|
const heightSlackRatio =
|
|
437
507
|
(matchingSheet.height - requiredHeight) / requiredHeight
|
|
438
508
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
509
|
+
if (
|
|
510
|
+
widthSlackRatio > RELAXED_STANDARD_PAGE_MAX_SLACK_RATIO ||
|
|
511
|
+
heightSlackRatio > RELAXED_STANDARD_PAGE_MAX_SLACK_RATIO ||
|
|
512
|
+
matchingSheet.width >= width
|
|
513
|
+
) {
|
|
514
|
+
return null
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
...matchingSheet,
|
|
519
|
+
width: SchematicContentLayout.#resolveTightVirtualDimension(
|
|
520
|
+
requiredWidth,
|
|
521
|
+
matchingSheet.width,
|
|
522
|
+
margin
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Drops near-zero standard-page rounding slack from the scale axis so
|
|
529
|
+
* custom sheets that almost fill a standard source size do not render with
|
|
530
|
+
* a visible artificial gutter.
|
|
531
|
+
* @param {number} requiredDimension
|
|
532
|
+
* @param {number} candidateDimension
|
|
533
|
+
* @param {number} margin
|
|
534
|
+
* @returns {number}
|
|
535
|
+
*/
|
|
536
|
+
static #resolveTightVirtualDimension(
|
|
537
|
+
requiredDimension,
|
|
538
|
+
candidateDimension,
|
|
539
|
+
margin
|
|
540
|
+
) {
|
|
541
|
+
const slack = candidateDimension - requiredDimension
|
|
542
|
+
|
|
543
|
+
return slack > 0 && slack <= margin
|
|
544
|
+
? requiredDimension
|
|
545
|
+
: candidateDimension
|
|
444
546
|
}
|
|
445
547
|
|
|
446
548
|
/**
|
|
@@ -18,6 +18,7 @@ export class SchematicJunctionRenderer {
|
|
|
18
18
|
* @param {{ x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down' }[]} [ports]
|
|
19
19
|
* @param {{ x: number, y: number, style?: number, powerPortDirection?: 'up' | 'down' | 'left' | 'right' }[]} [powerPorts]
|
|
20
20
|
* @param {number} sheetHeight
|
|
21
|
+
* @param {{ x: number, y: number }[]} [authoredJunctions]
|
|
21
22
|
* @returns {string}
|
|
22
23
|
*/
|
|
23
24
|
static buildMarkup(
|
|
@@ -25,13 +26,15 @@ export class SchematicJunctionRenderer {
|
|
|
25
26
|
crosses,
|
|
26
27
|
ports = [],
|
|
27
28
|
powerPorts = [],
|
|
28
|
-
sheetHeight
|
|
29
|
+
sheetHeight,
|
|
30
|
+
authoredJunctions = []
|
|
29
31
|
) {
|
|
30
32
|
return SchematicJunctionRenderer.#resolveJunctions(
|
|
31
33
|
lines,
|
|
32
34
|
crosses,
|
|
33
35
|
ports,
|
|
34
|
-
powerPorts
|
|
36
|
+
powerPorts,
|
|
37
|
+
authoredJunctions
|
|
35
38
|
)
|
|
36
39
|
.map(
|
|
37
40
|
(junction) =>
|
|
@@ -57,9 +60,16 @@ export class SchematicJunctionRenderer {
|
|
|
57
60
|
* @param {{ x: number, y: number }[]} crosses
|
|
58
61
|
* @param {{ x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down' }[]} ports
|
|
59
62
|
* @param {{ x: number, y: number, style?: number, powerPortDirection?: 'up' | 'down' | 'left' | 'right' }[]} powerPorts
|
|
63
|
+
* @param {{ x: number, y: number }[]} authoredJunctions
|
|
60
64
|
* @returns {{ x: number, y: number, color: string }[]}
|
|
61
65
|
*/
|
|
62
|
-
static #resolveJunctions(
|
|
66
|
+
static #resolveJunctions(
|
|
67
|
+
lines,
|
|
68
|
+
crosses,
|
|
69
|
+
ports,
|
|
70
|
+
powerPorts,
|
|
71
|
+
authoredJunctions
|
|
72
|
+
) {
|
|
63
73
|
const wireLines = lines.filter((line) =>
|
|
64
74
|
SchematicJunctionRenderer.#isElectricalWireLine(line)
|
|
65
75
|
)
|
|
@@ -69,6 +79,11 @@ export class SchematicJunctionRenderer {
|
|
|
69
79
|
const visiblePowerPorts = powerPorts.filter((powerPort) =>
|
|
70
80
|
SchematicJunctionRenderer.#isDrawablePowerPort(powerPort)
|
|
71
81
|
)
|
|
82
|
+
const authoredJunctionKeys = new Set(
|
|
83
|
+
authoredJunctions.map((junction) =>
|
|
84
|
+
SchematicJunctionRenderer.#pointKey(junction)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
72
87
|
|
|
73
88
|
return SchematicJunctionRenderer.#collectCandidatePoints(
|
|
74
89
|
wireLines,
|
|
@@ -77,6 +92,9 @@ export class SchematicJunctionRenderer {
|
|
|
77
92
|
)
|
|
78
93
|
.filter(
|
|
79
94
|
(point) =>
|
|
95
|
+
!authoredJunctionKeys.has(
|
|
96
|
+
SchematicJunctionRenderer.#pointKey(point)
|
|
97
|
+
) &&
|
|
80
98
|
!SchematicJunctionRenderer.#hasNearbyCross(point, crosses)
|
|
81
99
|
)
|
|
82
100
|
.flatMap((point) => {
|
|
@@ -15,7 +15,7 @@ const MINIMUM_NOTE_TEXT_SIZE = 4
|
|
|
15
15
|
export class SchematicNoteRenderer {
|
|
16
16
|
/**
|
|
17
17
|
* Builds one boxed schematic note/callout with wrapped text rows.
|
|
18
|
-
* @param {{ x: number, y: number, color: string, fontSize?: number, fontFamily?: string, fontWeight?: number, cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }} text
|
|
18
|
+
* @param {{ x: number, y: number, color: string, fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }} text
|
|
19
19
|
* @param {number} sheetHeight
|
|
20
20
|
* @returns {string}
|
|
21
21
|
*/
|
|
@@ -52,6 +52,18 @@ export class SchematicNoteRenderer {
|
|
|
52
52
|
)
|
|
53
53
|
const noteStroke = text.showBorder ? borderColor : 'none'
|
|
54
54
|
const noteSourceLines = text.noteLines || []
|
|
55
|
+
const compactSingleLineNote =
|
|
56
|
+
SchematicNoteRenderer.#isCompactSingleLineNote(
|
|
57
|
+
noteSourceLines,
|
|
58
|
+
height,
|
|
59
|
+
requestedTextSize
|
|
60
|
+
)
|
|
61
|
+
const horizontalTextMargin =
|
|
62
|
+
SchematicNoteRenderer.#resolveHorizontalTextMargin(
|
|
63
|
+
textMargin,
|
|
64
|
+
compactSingleLineNote,
|
|
65
|
+
text
|
|
66
|
+
)
|
|
55
67
|
const verticalTextMargin =
|
|
56
68
|
SchematicNoteRenderer.#resolveVerticalTextMargin(
|
|
57
69
|
textMargin,
|
|
@@ -60,14 +72,10 @@ export class SchematicNoteRenderer {
|
|
|
60
72
|
)
|
|
61
73
|
const layout = SchematicNoteRenderer.#resolveTextLayout(
|
|
62
74
|
noteSourceLines,
|
|
63
|
-
Math.max(width -
|
|
75
|
+
Math.max(width - horizontalTextMargin * 2, requestedTextSize),
|
|
64
76
|
Math.max(height - verticalTextMargin * 2, requestedTextSize),
|
|
65
77
|
requestedTextSize,
|
|
66
|
-
|
|
67
|
-
noteSourceLines,
|
|
68
|
-
height,
|
|
69
|
-
requestedTextSize
|
|
70
|
-
)
|
|
78
|
+
compactSingleLineNote
|
|
71
79
|
)
|
|
72
80
|
const noteLines = layout.noteLines
|
|
73
81
|
const textSize = layout.textSize
|
|
@@ -80,10 +88,11 @@ export class SchematicNoteRenderer {
|
|
|
80
88
|
left,
|
|
81
89
|
right,
|
|
82
90
|
top,
|
|
83
|
-
|
|
91
|
+
horizontalTextMargin,
|
|
84
92
|
verticalTextMargin,
|
|
85
93
|
lineHeight,
|
|
86
94
|
textSize,
|
|
95
|
+
compactSingleLineNote,
|
|
87
96
|
text
|
|
88
97
|
)
|
|
89
98
|
)
|
|
@@ -310,6 +319,25 @@ export class SchematicNoteRenderer {
|
|
|
310
319
|
return visibleLineCount === 1 && height <= requestedTextSize * 1.5
|
|
311
320
|
}
|
|
312
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Resolves horizontal text padding for one note box.
|
|
324
|
+
* @param {number} textMargin
|
|
325
|
+
* @param {boolean} compactSingleLineNote
|
|
326
|
+
* @param {{ showBorder?: boolean }} text
|
|
327
|
+
* @returns {number}
|
|
328
|
+
*/
|
|
329
|
+
static #resolveHorizontalTextMargin(
|
|
330
|
+
textMargin,
|
|
331
|
+
compactSingleLineNote,
|
|
332
|
+
text
|
|
333
|
+
) {
|
|
334
|
+
if (compactSingleLineNote && text.showBorder === false) {
|
|
335
|
+
return 0
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return textMargin
|
|
339
|
+
}
|
|
340
|
+
|
|
313
341
|
/**
|
|
314
342
|
* Reduces vertical padding for short note rectangles so readable text is
|
|
315
343
|
* centered instead of scaled down to satisfy the default margin.
|
|
@@ -426,7 +454,8 @@ export class SchematicNoteRenderer {
|
|
|
426
454
|
* @param {number} verticalTextMargin
|
|
427
455
|
* @param {number} lineHeight
|
|
428
456
|
* @param {number} textSize
|
|
429
|
-
* @param {
|
|
457
|
+
* @param {boolean} compactSingleLineNote
|
|
458
|
+
* @param {{ color: string, fontFamily?: string, fontWeight?: number, fontStyle?: string }} text
|
|
430
459
|
* @returns {string}
|
|
431
460
|
*/
|
|
432
461
|
static #buildNoteLineMarkup(
|
|
@@ -439,10 +468,18 @@ export class SchematicNoteRenderer {
|
|
|
439
468
|
verticalTextMargin,
|
|
440
469
|
lineHeight,
|
|
441
470
|
textSize,
|
|
471
|
+
compactSingleLineNote,
|
|
442
472
|
text
|
|
443
473
|
) {
|
|
444
474
|
const x = left + horizontalTextMargin
|
|
445
|
-
const y =
|
|
475
|
+
const y =
|
|
476
|
+
top +
|
|
477
|
+
verticalTextMargin +
|
|
478
|
+
SchematicNoteRenderer.#resolveBaselineOffset(
|
|
479
|
+
textSize,
|
|
480
|
+
compactSingleLineNote
|
|
481
|
+
) +
|
|
482
|
+
index * lineHeight
|
|
446
483
|
|
|
447
484
|
if (/^_+$/.test(String(line || '').trim())) {
|
|
448
485
|
return (
|
|
@@ -483,9 +520,37 @@ export class SchematicNoteRenderer {
|
|
|
483
520
|
escapeHtml(text.fontFamily || 'Times New Roman') +
|
|
484
521
|
'" font-weight="' +
|
|
485
522
|
formatNumber(text.fontWeight || 400) +
|
|
523
|
+
SchematicNoteRenderer.#buildFontStyleAttribute(text.fontStyle) +
|
|
486
524
|
'" xml:space="preserve">' +
|
|
487
525
|
escapeHtml(line) +
|
|
488
526
|
'</text>'
|
|
489
527
|
)
|
|
490
528
|
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Builds an optional note font-style attribute.
|
|
532
|
+
* @param {string | undefined} fontStyle
|
|
533
|
+
* @returns {string}
|
|
534
|
+
*/
|
|
535
|
+
static #buildFontStyleAttribute(fontStyle) {
|
|
536
|
+
if (!fontStyle || fontStyle === 'normal') {
|
|
537
|
+
return ''
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return '" font-style="' + escapeHtml(fontStyle)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Resolves the text baseline offset for a rendered note line.
|
|
545
|
+
* @param {number} textSize
|
|
546
|
+
* @param {boolean} compactSingleLineNote
|
|
547
|
+
* @returns {number}
|
|
548
|
+
*/
|
|
549
|
+
static #resolveBaselineOffset(textSize, compactSingleLineNote) {
|
|
550
|
+
if (compactSingleLineNote) {
|
|
551
|
+
return textSize * 0.85
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return textSize
|
|
555
|
+
}
|
|
491
556
|
}
|
|
@@ -66,7 +66,8 @@ export class SchematicPinSvgRenderer {
|
|
|
66
66
|
const defaultNumberX =
|
|
67
67
|
geometry.bodyX -
|
|
68
68
|
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
69
|
-
outerMarkerStyle
|
|
69
|
+
outerMarkerStyle,
|
|
70
|
+
pin
|
|
70
71
|
)
|
|
71
72
|
const numberX = hasExplicitOwnerPinName
|
|
72
73
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
@@ -98,7 +99,11 @@ export class SchematicPinSvgRenderer {
|
|
|
98
99
|
texts.push(
|
|
99
100
|
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
100
101
|
'schematic-pin-name',
|
|
101
|
-
geometry.bodyX +
|
|
102
|
+
geometry.bodyX +
|
|
103
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNameInset(
|
|
104
|
+
pin,
|
|
105
|
+
labelMode
|
|
106
|
+
),
|
|
102
107
|
projectedY + 3,
|
|
103
108
|
pin,
|
|
104
109
|
labelColor,
|
|
@@ -114,7 +119,8 @@ export class SchematicPinSvgRenderer {
|
|
|
114
119
|
const defaultNumberX =
|
|
115
120
|
geometry.bodyX +
|
|
116
121
|
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
117
|
-
outerMarkerStyle
|
|
122
|
+
outerMarkerStyle,
|
|
123
|
+
pin
|
|
118
124
|
)
|
|
119
125
|
const numberX = hasExplicitOwnerPinName
|
|
120
126
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
@@ -146,7 +152,11 @@ export class SchematicPinSvgRenderer {
|
|
|
146
152
|
texts.push(
|
|
147
153
|
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
148
154
|
'schematic-pin-name',
|
|
149
|
-
geometry.bodyX -
|
|
155
|
+
geometry.bodyX -
|
|
156
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNameInset(
|
|
157
|
+
pin,
|
|
158
|
+
labelMode
|
|
159
|
+
),
|
|
150
160
|
projectedY + 3,
|
|
151
161
|
pin,
|
|
152
162
|
labelColor,
|
|
@@ -363,11 +373,12 @@ export class SchematicPinSvgRenderer {
|
|
|
363
373
|
}
|
|
364
374
|
|
|
365
375
|
/**
|
|
366
|
-
* Returns the horizontal pin-number clearance needed by
|
|
376
|
+
* Returns the horizontal pin-number clearance needed by the pin geometry.
|
|
367
377
|
* @param {'single-in' | 'single-out' | 'double' | null} markerStyle
|
|
378
|
+
* @param {{ length?: number }} pin
|
|
368
379
|
* @returns {number}
|
|
369
380
|
*/
|
|
370
|
-
static #resolveHorizontalPinNumberClearance(markerStyle) {
|
|
381
|
+
static #resolveHorizontalPinNumberClearance(markerStyle, pin) {
|
|
371
382
|
switch (markerStyle) {
|
|
372
383
|
case 'double':
|
|
373
384
|
return 17
|
|
@@ -375,10 +386,40 @@ export class SchematicPinSvgRenderer {
|
|
|
375
386
|
case 'single-out':
|
|
376
387
|
return 8
|
|
377
388
|
default:
|
|
378
|
-
return 2
|
|
389
|
+
return SchematicPinSvgRenderer.#resolveLongPinInset(pin, 2)
|
|
379
390
|
}
|
|
380
391
|
}
|
|
381
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Returns the horizontal pin-name inset used inside the symbol body.
|
|
395
|
+
* @param {{ length?: number }} pin
|
|
396
|
+
* @param {'hidden' | 'number-only' | 'name-only' | 'name-and-number'} labelMode
|
|
397
|
+
* @returns {number}
|
|
398
|
+
*/
|
|
399
|
+
static #resolveHorizontalPinNameInset(pin, labelMode) {
|
|
400
|
+
if (labelMode === 'name-only') {
|
|
401
|
+
return 10
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return SchematicPinSvgRenderer.#resolveLongPinInset(pin, 4)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Adds extra text clearance for long connector-style pin stubs.
|
|
409
|
+
* @param {{ length?: number }} pin
|
|
410
|
+
* @param {number} fallback
|
|
411
|
+
* @returns {number}
|
|
412
|
+
*/
|
|
413
|
+
static #resolveLongPinInset(pin, fallback) {
|
|
414
|
+
const length = Math.abs(Number(pin?.length || 0))
|
|
415
|
+
|
|
416
|
+
if (length < 30) {
|
|
417
|
+
return fallback
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return fallback === 2 ? 10 : 8
|
|
421
|
+
}
|
|
422
|
+
|
|
382
423
|
/**
|
|
383
424
|
* Builds one or two authored outer-marker polygons for one horizontal pin.
|
|
384
425
|
* @param {number} bodyX
|