altium-toolkit 1.0.2 → 1.0.8
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/renderers.mjs +3 -0
- package/src/styles/altium-renderers.css +6 -0
- package/src/ui/PcbInteractionGeometry.mjs +350 -0
- package/src/ui/PcbInteractionIndex.mjs +588 -0
- package/src/ui/PcbInteractionItemRegistry.mjs +66 -0
- package/src/ui/PcbInteractionLayerModel.mjs +99 -0
- package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +74 -9
- package/src/ui/PcbScene3dBuilder.mjs +32 -4
- package/src/ui/PcbSvgRenderer.mjs +2 -2
- 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
|
@@ -21,6 +21,10 @@ import { SchematicImageRenderer } from './SchematicImageRenderer.mjs'
|
|
|
21
21
|
|
|
22
22
|
const { createSvgText, escapeHtml, formatNumber, projectSchematicY } =
|
|
23
23
|
SchematicSvgUtils
|
|
24
|
+
const SECTION_HEADING_MIN_FONT_SIZE = 18
|
|
25
|
+
const SECTION_HEADING_BASELINE_LIFT_RATIO = 0.36
|
|
26
|
+
const SECTION_HEADING_LINE_Y_TOLERANCE = 0.75
|
|
27
|
+
const SECTION_HEADING_LINE_X_PADDING = 15
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Renders normalized schematic models into presentational SVG.
|
|
@@ -28,7 +32,7 @@ const { createSvgText, escapeHtml, formatNumber, projectSchematicY } =
|
|
|
28
32
|
export class SchematicSvgRenderer {
|
|
29
33
|
/**
|
|
30
34
|
* Renders a normalized schematic model into SVG markup.
|
|
31
|
-
* @param {{ fileName?: string, summary: { title?: string }, schematic?: { sheet: { width: number, height: number, sourceWidth?: number, sourceHeight?: number, paperSize?: string, borderOn?: boolean, titleBlockOn?: boolean, marginWidth?: number, xZones?: number, yZones?: number, titleBlock?: { title?: string, revision?: string, documentNumber?: string, sheetNumber?: string, sheetTotal?: string, date?: string, drawnBy?: string } }, lines: { x1: number, y1: number, x2: number, y2: number, color: string, width: number, lineStyle?: number, isBus?: boolean, ownerIndex?: string, renderOrder?: number, recordType?: string }[], polygons?: { points: { x: number, y: number }[], color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], rectangles?: { x: number, y: number, width: number, height: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], regions?: { x: number, y: number, width: number, height: number, color: string, fill: string, renderOrder?: number }[], ellipses?: { x: number, y: number, radiusX: number, radiusY: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], arcs?: { x: number, y: number, radius: number, startAngle: number, endAngle: number, color: string, width: number, ownerIndex?: string, renderOrder?: number }[], directives?: { x: number, y: number, color: string, name: string, orientation?: number }[], texts: { x: number, y: number, text: string, color: string, recordType?: string, style?: number, fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, sourceOrientation?: number, isMirrored?: boolean, anchor?: 'start' | 'middle' | 'end', powerPortDirection?: 'up' | 'down' | 'left' | 'right', cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }[], components: { x: number, y: number, designator: string }[], pins?: { x: number, y: number, length: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color: string, labelColor?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number', ownerIndex?: string }[], ports?: { x: number, y: number, width: number, height: number, name: string, fill: string, color: string, direction?: 'left' | 'right' | 'up' | 'down', shape?: 'single' | 'double' | 'plain' }[], crosses?: { x: number, y: number, size: number, color: string }[] } }} documentModel
|
|
35
|
+
* @param {{ fileName?: string, summary: { title?: string }, schematic?: { sheet: { width: number, height: number, sourceWidth?: number, sourceHeight?: number, paperSize?: string, borderOn?: boolean, titleBlockOn?: boolean, marginWidth?: number, xZones?: number, yZones?: number, titleBlock?: { title?: string, revision?: string, documentNumber?: string, sheetNumber?: string, sheetTotal?: string, date?: string, drawnBy?: string } }, lines: { x1: number, y1: number, x2: number, y2: number, color: string, width: number, lineStyle?: number, isBus?: boolean, ownerIndex?: string, renderOrder?: number, recordType?: string }[], polygons?: { points: { x: number, y: number }[], color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], rectangles?: { x: number, y: number, width: number, height: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], regions?: { x: number, y: number, width: number, height: number, color: string, fill: string, renderOrder?: number }[], ellipses?: { x: number, y: number, radiusX: number, radiusY: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], arcs?: { x: number, y: number, radius: number, startAngle: number, endAngle: number, color: string, width: number, ownerIndex?: string, renderOrder?: number }[], directives?: { x: number, y: number, color: string, name: string, orientation?: number }[], texts: { x: number, y: number, text: string, color: string, recordType?: string, style?: number, fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number, sourceOrientation?: number, isMirrored?: boolean, anchor?: 'start' | 'middle' | 'end', powerPortDirection?: 'up' | 'down' | 'left' | 'right', cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }[], components: { x: number, y: number, designator: string }[], pins?: { x: number, y: number, length: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color: string, labelColor?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number', ownerIndex?: string }[], ports?: { x: number, y: number, width: number, height: number, name: string, fill: string, color: string, direction?: 'left' | 'right' | 'up' | 'down', shape?: 'single' | 'double' | 'plain' }[], crosses?: { x: number, y: number, size: number, color: string }[] } }} documentModel
|
|
32
36
|
* @returns {string}
|
|
33
37
|
*/
|
|
34
38
|
static render(documentModel) {
|
|
@@ -37,8 +41,16 @@ export class SchematicSvgRenderer {
|
|
|
37
41
|
return '<section class="altium-renderer-empty">No schematic entities were recovered from this file.</section>'
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
const
|
|
41
|
-
|
|
44
|
+
const renderedSheet = SchematicSvgRenderer.#resolveRenderedSheet(
|
|
45
|
+
schematic.sheet
|
|
46
|
+
)
|
|
47
|
+
const width = renderedSheet.width
|
|
48
|
+
const height = renderedSheet.height
|
|
49
|
+
const contentHeight = renderedSheet.contentHeight
|
|
50
|
+
const renderedSchematic =
|
|
51
|
+
renderedSheet.contentSheet === schematic.sheet
|
|
52
|
+
? schematic
|
|
53
|
+
: { ...schematic, sheet: renderedSheet.contentSheet }
|
|
42
54
|
const allTexts = schematic.texts || []
|
|
43
55
|
const lines = schematic.lines.slice(0, 2500)
|
|
44
56
|
const polygons = (schematic.polygons || []).slice(0, 1000)
|
|
@@ -68,27 +80,27 @@ export class SchematicSvgRenderer {
|
|
|
68
80
|
const frameMarkup = SchematicSvgRenderer.#buildSheetChromeMarkup(
|
|
69
81
|
width,
|
|
70
82
|
height,
|
|
71
|
-
|
|
83
|
+
renderedSheet.sheet,
|
|
72
84
|
documentModel?.fileName
|
|
73
85
|
)
|
|
74
86
|
const regionMarkup = SchematicRegionRenderer.buildMarkup(
|
|
75
87
|
regions,
|
|
76
|
-
|
|
88
|
+
contentHeight
|
|
77
89
|
)
|
|
78
90
|
const contentTransform = SchematicContentLayout.buildTransform(
|
|
79
91
|
width,
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
contentHeight,
|
|
93
|
+
renderedSchematic
|
|
82
94
|
)
|
|
83
95
|
const contentClipId = SchematicContentLayout.buildClipId(
|
|
84
96
|
width,
|
|
85
97
|
height,
|
|
86
|
-
|
|
98
|
+
renderedSchematic
|
|
87
99
|
)
|
|
88
100
|
const contentClipMarkup = SchematicContentLayout.buildClipMarkup(
|
|
89
101
|
width,
|
|
90
102
|
height,
|
|
91
|
-
|
|
103
|
+
renderedSchematic,
|
|
92
104
|
contentClipId
|
|
93
105
|
)
|
|
94
106
|
const ownerlessLines = lines.filter((line) => !line.ownerIndex)
|
|
@@ -117,26 +129,40 @@ export class SchematicSvgRenderer {
|
|
|
117
129
|
)
|
|
118
130
|
const polygonMarkup = ownerlessPolygons
|
|
119
131
|
.map((polygon) =>
|
|
120
|
-
SchematicShapeRenderer.buildPolygonMarkup(
|
|
132
|
+
SchematicShapeRenderer.buildPolygonMarkup(
|
|
133
|
+
polygon,
|
|
134
|
+
contentHeight
|
|
135
|
+
)
|
|
121
136
|
)
|
|
122
137
|
.join('')
|
|
123
138
|
const rectangleMarkup = ownerlessRectangles
|
|
124
139
|
.map((rectangle) =>
|
|
125
|
-
SchematicShapeRenderer.buildRectangleMarkup(
|
|
140
|
+
SchematicShapeRenderer.buildRectangleMarkup(
|
|
141
|
+
rectangle,
|
|
142
|
+
contentHeight
|
|
143
|
+
)
|
|
126
144
|
)
|
|
127
145
|
.join('')
|
|
128
146
|
const ellipseMarkup = ownerlessEllipses
|
|
129
147
|
.map((ellipse) =>
|
|
130
|
-
SchematicShapeRenderer.buildEllipseMarkup(
|
|
148
|
+
SchematicShapeRenderer.buildEllipseMarkup(
|
|
149
|
+
ellipse,
|
|
150
|
+
contentHeight
|
|
151
|
+
)
|
|
131
152
|
)
|
|
132
153
|
.join('')
|
|
133
154
|
const lineMarkup = ownerlessLines
|
|
134
155
|
.map((line) =>
|
|
135
|
-
SchematicSvgRenderer.#buildSchematicLineMarkup(
|
|
156
|
+
SchematicSvgRenderer.#buildSchematicLineMarkup(
|
|
157
|
+
line,
|
|
158
|
+
contentHeight
|
|
159
|
+
)
|
|
136
160
|
)
|
|
137
161
|
.join('')
|
|
138
162
|
const arcMarkup = ownerlessArcs
|
|
139
|
-
.map((arc) =>
|
|
163
|
+
.map((arc) =>
|
|
164
|
+
SchematicShapeRenderer.buildArcMarkup(arc, contentHeight)
|
|
165
|
+
)
|
|
140
166
|
.join('')
|
|
141
167
|
const ownerGeometryMarkup =
|
|
142
168
|
SchematicSvgRenderer.#buildOwnerGeometryMarkup(
|
|
@@ -145,41 +171,49 @@ export class SchematicSvgRenderer {
|
|
|
145
171
|
rectangles,
|
|
146
172
|
ellipses,
|
|
147
173
|
arcs,
|
|
148
|
-
|
|
174
|
+
contentHeight
|
|
149
175
|
)
|
|
150
176
|
const sheetSymbolMarkup =
|
|
151
177
|
SchematicSheetSymbolRenderer.buildSheetSymbolMarkup(
|
|
152
178
|
sheetSymbols,
|
|
153
|
-
|
|
179
|
+
contentHeight
|
|
154
180
|
)
|
|
155
181
|
const sheetEntryMarkup =
|
|
156
182
|
SchematicSheetSymbolRenderer.buildSheetEntryMarkup(
|
|
157
183
|
sheetEntries,
|
|
158
|
-
|
|
184
|
+
contentHeight
|
|
159
185
|
)
|
|
160
186
|
const busEntryMarkup = busEntries
|
|
161
187
|
.map((busEntry) =>
|
|
162
188
|
SchematicSvgRenderer.#buildSchematicBusEntryMarkup(
|
|
163
189
|
busEntry,
|
|
164
|
-
|
|
190
|
+
contentHeight
|
|
165
191
|
)
|
|
166
192
|
)
|
|
167
193
|
.join('')
|
|
168
|
-
const
|
|
194
|
+
const resolvedAuthoredJunctions =
|
|
195
|
+
SchematicSvgRenderer.#resolveAuthoredSchematicJunctions(
|
|
196
|
+
authoredJunctions,
|
|
197
|
+
lines
|
|
198
|
+
)
|
|
199
|
+
const authoredJunctionMarkup = resolvedAuthoredJunctions
|
|
169
200
|
.map((junction) =>
|
|
170
201
|
SchematicSvgRenderer.#buildAuthoredSchematicJunctionMarkup(
|
|
171
202
|
junction,
|
|
172
|
-
|
|
203
|
+
contentHeight
|
|
173
204
|
)
|
|
174
205
|
)
|
|
175
206
|
.join('')
|
|
176
|
-
const imageMarkup = SchematicImageRenderer.buildMarkup(
|
|
207
|
+
const imageMarkup = SchematicImageRenderer.buildMarkup(
|
|
208
|
+
images,
|
|
209
|
+
contentHeight
|
|
210
|
+
)
|
|
177
211
|
|
|
178
212
|
const textMarkup = resolvedTexts
|
|
179
213
|
.map((text) =>
|
|
180
214
|
SchematicSvgRenderer.#buildSchematicTextMarkup(
|
|
181
215
|
text,
|
|
182
|
-
|
|
216
|
+
contentHeight,
|
|
183
217
|
lines,
|
|
184
218
|
pins
|
|
185
219
|
)
|
|
@@ -190,8 +224,8 @@ export class SchematicSvgRenderer {
|
|
|
190
224
|
.map((component) =>
|
|
191
225
|
SchematicSvgRenderer.#buildFallbackComponentMarkup(
|
|
192
226
|
component,
|
|
193
|
-
|
|
194
|
-
|
|
227
|
+
contentHeight,
|
|
228
|
+
renderedSheet.contentSheet
|
|
195
229
|
)
|
|
196
230
|
)
|
|
197
231
|
.join('')
|
|
@@ -209,8 +243,8 @@ export class SchematicSvgRenderer {
|
|
|
209
243
|
.map((pin) =>
|
|
210
244
|
SchematicPinSvgRenderer.buildMarkup(
|
|
211
245
|
pin,
|
|
212
|
-
|
|
213
|
-
|
|
246
|
+
contentHeight,
|
|
247
|
+
renderedSheet.contentSheet,
|
|
214
248
|
rotatedVerticalNumberOwners,
|
|
215
249
|
explicitOwnerPinNameLabels,
|
|
216
250
|
explicitOwnerPinLabelOffsets
|
|
@@ -219,24 +253,28 @@ export class SchematicSvgRenderer {
|
|
|
219
253
|
.join('')
|
|
220
254
|
const portMarkup = SchematicPortRenderer.buildMarkup(
|
|
221
255
|
ports,
|
|
222
|
-
|
|
223
|
-
|
|
256
|
+
contentHeight,
|
|
257
|
+
renderedSheet.contentSheet
|
|
224
258
|
)
|
|
225
259
|
const directiveMarkup = SchematicDirectiveRenderer.buildMarkup(
|
|
226
260
|
directives,
|
|
227
|
-
|
|
228
|
-
|
|
261
|
+
contentHeight,
|
|
262
|
+
renderedSheet.contentSheet
|
|
229
263
|
)
|
|
230
264
|
const junctionMarkup = SchematicJunctionRenderer.buildMarkup(
|
|
231
265
|
lines,
|
|
232
266
|
crosses,
|
|
233
267
|
ports,
|
|
234
268
|
resolvedTexts.filter((text) => text.recordType === '17'),
|
|
235
|
-
|
|
269
|
+
contentHeight,
|
|
270
|
+
resolvedAuthoredJunctions
|
|
236
271
|
)
|
|
237
272
|
const crossMarkup = crosses
|
|
238
273
|
.map((cross) =>
|
|
239
|
-
SchematicSvgRenderer.#buildSchematicCrossMarkup(
|
|
274
|
+
SchematicSvgRenderer.#buildSchematicCrossMarkup(
|
|
275
|
+
cross,
|
|
276
|
+
contentHeight
|
|
277
|
+
)
|
|
240
278
|
)
|
|
241
279
|
.join('')
|
|
242
280
|
|
|
@@ -327,6 +365,80 @@ export class SchematicSvgRenderer {
|
|
|
327
365
|
)
|
|
328
366
|
}
|
|
329
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Resolves the rendered dimensions and sheet metadata used by SVG output.
|
|
370
|
+
* @param {{ width?: number, height?: number, sourceWidth?: number, sourceHeight?: number, marginWidth?: number, paperSize?: string, borderOn?: boolean } | undefined} sheet
|
|
371
|
+
* @returns {{ width: number, height: number, contentHeight: number, sheet: object, contentSheet: object }}
|
|
372
|
+
*/
|
|
373
|
+
static #resolveRenderedSheet(sheet) {
|
|
374
|
+
const width = Math.max(Number(sheet?.width || 1000), 100)
|
|
375
|
+
const height = Math.max(Number(sheet?.height || 700), 100)
|
|
376
|
+
const margin = Math.max(Number(sheet?.marginWidth || 20), 10)
|
|
377
|
+
const renderedHeight = SchematicSvgRenderer.#resolveRenderedSheetHeight(
|
|
378
|
+
sheet,
|
|
379
|
+
width,
|
|
380
|
+
height,
|
|
381
|
+
margin
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
if (renderedHeight === height) {
|
|
385
|
+
return {
|
|
386
|
+
width,
|
|
387
|
+
height,
|
|
388
|
+
contentHeight: height,
|
|
389
|
+
sheet: sheet || {},
|
|
390
|
+
contentSheet: sheet || {}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const contentHeight = renderedHeight - margin
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
width,
|
|
397
|
+
height: renderedHeight,
|
|
398
|
+
contentHeight,
|
|
399
|
+
sheet: {
|
|
400
|
+
...(sheet || {}),
|
|
401
|
+
width,
|
|
402
|
+
height: renderedHeight,
|
|
403
|
+
sourceWidth: width,
|
|
404
|
+
sourceHeight: renderedHeight
|
|
405
|
+
},
|
|
406
|
+
contentSheet: {
|
|
407
|
+
...(sheet || {}),
|
|
408
|
+
width,
|
|
409
|
+
height: contentHeight,
|
|
410
|
+
sourceWidth: width,
|
|
411
|
+
sourceHeight: contentHeight
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Adds top and bottom zone bands for preserved custom border sheets whose
|
|
418
|
+
* stored Y extent describes the inner drawing frame.
|
|
419
|
+
* @param {{ width?: number, height?: number, sourceWidth?: number, sourceHeight?: number, marginWidth?: number, paperSize?: string, borderOn?: boolean } | undefined} sheet
|
|
420
|
+
* @param {number} width
|
|
421
|
+
* @param {number} height
|
|
422
|
+
* @param {number} margin
|
|
423
|
+
* @returns {number}
|
|
424
|
+
*/
|
|
425
|
+
static #resolveRenderedSheetHeight(sheet, width, height, margin) {
|
|
426
|
+
const sourceWidth = Number(sheet?.sourceWidth || 0)
|
|
427
|
+
const sourceHeight = Number(sheet?.sourceHeight || 0)
|
|
428
|
+
|
|
429
|
+
if (
|
|
430
|
+
!sheet?.borderOn ||
|
|
431
|
+
sheet?.paperSize ||
|
|
432
|
+
width !== sourceWidth ||
|
|
433
|
+
height !== sourceHeight ||
|
|
434
|
+
height <= margin * 2
|
|
435
|
+
) {
|
|
436
|
+
return height
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return height + margin * 2
|
|
440
|
+
}
|
|
441
|
+
|
|
330
442
|
/**
|
|
331
443
|
* Builds interleaved owner geometry so symbol-internal primitives preserve
|
|
332
444
|
* their recovered Altium paint order instead of batching fills ahead of all
|
|
@@ -550,7 +662,7 @@ export class SchematicSvgRenderer {
|
|
|
550
662
|
|
|
551
663
|
/**
|
|
552
664
|
* Builds one free text primitive with font metadata.
|
|
553
|
-
* @param {{ x: number, y: number, text: string, color: string, recordType?: string, style?: number, fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, sourceOrientation?: number, isMirrored?: boolean, anchor?: 'start' | 'middle' | 'end', cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }} text
|
|
665
|
+
* @param {{ x: number, y: number, text: string, color: string, recordType?: string, style?: number, fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number, sourceOrientation?: number, isMirrored?: boolean, anchor?: 'start' | 'middle' | 'end', cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }} text
|
|
554
666
|
* @param {number} sheetHeight
|
|
555
667
|
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} lines
|
|
556
668
|
* @param {{ x: number, y: number, length: number, name?: string, ownerIndex?: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
|
|
@@ -578,6 +690,7 @@ export class SchematicSvgRenderer {
|
|
|
578
690
|
const placement = SchematicSvgRenderer.#resolveSchematicTextPlacement(
|
|
579
691
|
text,
|
|
580
692
|
sheetHeight,
|
|
693
|
+
lines,
|
|
581
694
|
matchedOwnerPin
|
|
582
695
|
)
|
|
583
696
|
|
|
@@ -601,28 +714,139 @@ export class SchematicSvgRenderer {
|
|
|
601
714
|
|
|
602
715
|
/**
|
|
603
716
|
* Resolves final text placement for schematic free-text annotations.
|
|
604
|
-
* @param {{ x: number, y: number, text: string, recordType?: string, fontSize?: number, rotation?: number, anchor?: 'start' | 'middle' | 'end' }} text
|
|
717
|
+
* @param {{ x: number, y: number, text: string, ownerIndex?: string, recordType?: string, fontSize?: number, rotation?: number, anchor?: 'start' | 'middle' | 'end' }} text
|
|
605
718
|
* @param {number} sheetHeight
|
|
719
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, lineStyle?: number }[]} lines
|
|
606
720
|
* @param {{ x: number, y: number, name?: string, ownerIndex?: string, orientation: 'left' | 'right' | 'top' | 'bottom' } | null} matchedOwnerPin
|
|
607
721
|
* @returns {{ x: number, y: number, anchor: 'start' | 'middle' | 'end' }}
|
|
608
722
|
*/
|
|
609
|
-
static #resolveSchematicTextPlacement(
|
|
723
|
+
static #resolveSchematicTextPlacement(
|
|
724
|
+
text,
|
|
725
|
+
sheetHeight,
|
|
726
|
+
lines,
|
|
727
|
+
matchedOwnerPin
|
|
728
|
+
) {
|
|
610
729
|
const mirroredOwnerPinPlacement =
|
|
611
730
|
SchematicOwnerPinLabelLayout.resolveMirroredOwnerPinLabelPlacement(
|
|
612
731
|
text,
|
|
613
732
|
matchedOwnerPin
|
|
614
733
|
)
|
|
734
|
+
const sourceY = mirroredOwnerPinPlacement?.y ?? text.y
|
|
735
|
+
const projectedY = projectSchematicY(sheetHeight, sourceY)
|
|
736
|
+
const fontSize =
|
|
737
|
+
SchematicTypography.resolveViewerFontSize(text.fontSize) || 0
|
|
738
|
+
const baselineLift =
|
|
739
|
+
SchematicSvgRenderer.#resolveSectionHeadingBaselineLift(
|
|
740
|
+
text,
|
|
741
|
+
lines,
|
|
742
|
+
sourceY,
|
|
743
|
+
fontSize,
|
|
744
|
+
matchedOwnerPin
|
|
745
|
+
)
|
|
615
746
|
|
|
616
747
|
return {
|
|
617
748
|
x: mirroredOwnerPinPlacement?.x ?? text.x,
|
|
618
|
-
y:
|
|
619
|
-
sheetHeight,
|
|
620
|
-
mirroredOwnerPinPlacement?.y ?? text.y
|
|
621
|
-
),
|
|
749
|
+
y: projectedY - baselineLift,
|
|
622
750
|
anchor: text.anchor || 'start'
|
|
623
751
|
}
|
|
624
752
|
}
|
|
625
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Lifts large section headings clear of authored dash-dot frame baselines.
|
|
756
|
+
* @param {{ x: number, y: number, ownerIndex?: string, recordType?: string, fontSize?: number, rotation?: number }} text
|
|
757
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, lineStyle?: number }[]} lines
|
|
758
|
+
* @param {number} sourceY
|
|
759
|
+
* @param {number} fontSize
|
|
760
|
+
* @param {{ x: number, y: number, name?: string, ownerIndex?: string, orientation: 'left' | 'right' | 'top' | 'bottom' } | null} matchedOwnerPin
|
|
761
|
+
* @returns {number}
|
|
762
|
+
*/
|
|
763
|
+
static #resolveSectionHeadingBaselineLift(
|
|
764
|
+
text,
|
|
765
|
+
lines,
|
|
766
|
+
sourceY,
|
|
767
|
+
fontSize,
|
|
768
|
+
matchedOwnerPin
|
|
769
|
+
) {
|
|
770
|
+
if (
|
|
771
|
+
!SchematicSvgRenderer.#isLargeOwnerlessFreeText(
|
|
772
|
+
text,
|
|
773
|
+
fontSize,
|
|
774
|
+
matchedOwnerPin
|
|
775
|
+
)
|
|
776
|
+
) {
|
|
777
|
+
return 0
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return SchematicSvgRenderer.#hasSameCoordinateDashDotFrameLine(
|
|
781
|
+
text,
|
|
782
|
+
lines,
|
|
783
|
+
sourceY
|
|
784
|
+
)
|
|
785
|
+
? fontSize * SECTION_HEADING_BASELINE_LIFT_RATIO
|
|
786
|
+
: 0
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Returns true when one text primitive behaves like a section heading.
|
|
791
|
+
* @param {{ ownerIndex?: string, recordType?: string, rotation?: number }} text
|
|
792
|
+
* @param {number} fontSize
|
|
793
|
+
* @param {{ x: number, y: number, name?: string, ownerIndex?: string, orientation: 'left' | 'right' | 'top' | 'bottom' } | null} matchedOwnerPin
|
|
794
|
+
* @returns {boolean}
|
|
795
|
+
*/
|
|
796
|
+
static #isLargeOwnerlessFreeText(text, fontSize, matchedOwnerPin) {
|
|
797
|
+
if (matchedOwnerPin) return false
|
|
798
|
+
if (text.recordType !== '4') return false
|
|
799
|
+
if (String(text.ownerIndex || '').trim()) return false
|
|
800
|
+
if (fontSize < SECTION_HEADING_MIN_FONT_SIZE) return false
|
|
801
|
+
|
|
802
|
+
return SchematicSvgRenderer.#normalizeDegrees(text.rotation) === 0
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Detects section frame lines that share the title baseline coordinate.
|
|
807
|
+
* @param {{ x: number }} text
|
|
808
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, lineStyle?: number }[]} lines
|
|
809
|
+
* @param {number} sourceY
|
|
810
|
+
* @returns {boolean}
|
|
811
|
+
*/
|
|
812
|
+
static #hasSameCoordinateDashDotFrameLine(text, lines, sourceY) {
|
|
813
|
+
const textX = Number(text.x)
|
|
814
|
+
if (!Number.isFinite(textX) || !Number.isFinite(sourceY)) return false
|
|
815
|
+
|
|
816
|
+
return lines.some((line) => {
|
|
817
|
+
if (Number(line.lineStyle || 0) !== 3) return false
|
|
818
|
+
|
|
819
|
+
const y1 = Number(line.y1)
|
|
820
|
+
const y2 = Number(line.y2)
|
|
821
|
+
if (
|
|
822
|
+
!Number.isFinite(y1) ||
|
|
823
|
+
!Number.isFinite(y2) ||
|
|
824
|
+
Math.abs(y1 - y2) > SECTION_HEADING_LINE_Y_TOLERANCE ||
|
|
825
|
+
Math.abs(y1 - sourceY) > SECTION_HEADING_LINE_Y_TOLERANCE
|
|
826
|
+
) {
|
|
827
|
+
return false
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const minX =
|
|
831
|
+
Math.min(Number(line.x1), Number(line.x2)) -
|
|
832
|
+
SECTION_HEADING_LINE_X_PADDING
|
|
833
|
+
const maxX =
|
|
834
|
+
Math.max(Number(line.x1), Number(line.x2)) +
|
|
835
|
+
SECTION_HEADING_LINE_X_PADDING
|
|
836
|
+
|
|
837
|
+
return textX >= minX && textX <= maxX
|
|
838
|
+
})
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Normalizes text rotation into a whole-degree clockwise range.
|
|
843
|
+
* @param {number | undefined} rotation
|
|
844
|
+
* @returns {number}
|
|
845
|
+
*/
|
|
846
|
+
static #normalizeDegrees(rotation) {
|
|
847
|
+
return ((Math.round(Number(rotation || 0)) % 360) + 360) % 360
|
|
848
|
+
}
|
|
849
|
+
|
|
626
850
|
/**
|
|
627
851
|
* Builds one schematic cross marker.
|
|
628
852
|
* @param {{ x: number, y: number, size: number, color: string }} cross
|
|
@@ -692,6 +916,84 @@ export class SchematicSvgRenderer {
|
|
|
692
916
|
)
|
|
693
917
|
}
|
|
694
918
|
|
|
919
|
+
/**
|
|
920
|
+
* Resolves authored junction colors from their connected wire routes.
|
|
921
|
+
* @param {{ x: number, y: number, color: string }[]} junctions
|
|
922
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, ownerIndex?: string, isBus?: boolean, recordType?: string }[]} lines
|
|
923
|
+
* @returns {{ x: number, y: number, color: string }[]}
|
|
924
|
+
*/
|
|
925
|
+
static #resolveAuthoredSchematicJunctions(junctions, lines) {
|
|
926
|
+
return junctions.map((junction) => ({
|
|
927
|
+
...junction,
|
|
928
|
+
color: SchematicSvgRenderer.#resolveAuthoredSchematicJunctionColor(
|
|
929
|
+
junction,
|
|
930
|
+
lines
|
|
931
|
+
)
|
|
932
|
+
}))
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Returns the connected wire color for one authored junction.
|
|
937
|
+
* @param {{ x: number, y: number, color: string }} junction
|
|
938
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, ownerIndex?: string, isBus?: boolean, recordType?: string }[]} lines
|
|
939
|
+
* @returns {string}
|
|
940
|
+
*/
|
|
941
|
+
static #resolveAuthoredSchematicJunctionColor(junction, lines) {
|
|
942
|
+
const connectedLine = lines.find(
|
|
943
|
+
(line) =>
|
|
944
|
+
SchematicSvgRenderer.#isElectricalSchematicLine(line) &&
|
|
945
|
+
SchematicSvgRenderer.#schematicLineContainsPoint(line, junction)
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
return connectedLine?.color || junction.color
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Returns true when one normalized line can carry schematic net color.
|
|
953
|
+
* @param {{ ownerIndex?: string, isBus?: boolean, recordType?: string } | null | undefined} line
|
|
954
|
+
* @returns {boolean}
|
|
955
|
+
*/
|
|
956
|
+
static #isElectricalSchematicLine(line) {
|
|
957
|
+
if (line?.ownerIndex || line?.isBus === true) {
|
|
958
|
+
return false
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (!Object.prototype.hasOwnProperty.call(line || {}, 'recordType')) {
|
|
962
|
+
return true
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return !['6', '7', '26'].includes(String(line.recordType || ''))
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Returns true when one schematic line segment contains the given point.
|
|
970
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number }} line
|
|
971
|
+
* @param {{ x: number, y: number }} point
|
|
972
|
+
* @returns {boolean}
|
|
973
|
+
*/
|
|
974
|
+
static #schematicLineContainsPoint(line, point) {
|
|
975
|
+
const dx = Number(line.x2) - Number(line.x1)
|
|
976
|
+
const dy = Number(line.y2) - Number(line.y1)
|
|
977
|
+
const pointDx = Number(point.x) - Number(line.x1)
|
|
978
|
+
const pointDy = Number(point.y) - Number(line.y1)
|
|
979
|
+
const lengthSquared = dx * dx + dy * dy
|
|
980
|
+
|
|
981
|
+
if (!lengthSquared) {
|
|
982
|
+
return (
|
|
983
|
+
Math.abs(Number(line.x1) - Number(point.x)) <= 0.01 &&
|
|
984
|
+
Math.abs(Number(line.y1) - Number(point.y)) <= 0.01
|
|
985
|
+
)
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const cross = Math.abs(pointDx * dy - pointDy * dx)
|
|
989
|
+
if (cross > 0.01) {
|
|
990
|
+
return false
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const dot = pointDx * dx + pointDy * dy
|
|
994
|
+
return dot >= -0.01 && dot <= lengthSquared + 0.01
|
|
995
|
+
}
|
|
996
|
+
|
|
695
997
|
/**
|
|
696
998
|
* Builds one schematic bus-entry line marker.
|
|
697
999
|
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, width: number }} busEntry
|
|
@@ -46,7 +46,7 @@ export class SchematicSvgUtils {
|
|
|
46
46
|
* @param {string} text
|
|
47
47
|
* @param {string} color
|
|
48
48
|
* @param {'start' | 'end' | 'middle'} anchor
|
|
49
|
-
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, segments?: { text: string, overline?: boolean }[] }} [options]
|
|
49
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number, segments?: { text: string, overline?: boolean }[] }} [options]
|
|
50
50
|
* @returns {string}
|
|
51
51
|
*/
|
|
52
52
|
static createSvgText(className, x, y, text, color, anchor, options = {}) {
|
|
@@ -102,7 +102,7 @@ export class SchematicSvgUtils {
|
|
|
102
102
|
* Creates optional inline SVG text attributes for typography and rotation.
|
|
103
103
|
* @param {number} x
|
|
104
104
|
* @param {number} y
|
|
105
|
-
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }} options
|
|
105
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number }} options
|
|
106
106
|
* @returns {string}
|
|
107
107
|
*/
|
|
108
108
|
static #buildSvgTextStyleAttributes(x, y, options) {
|
|
@@ -131,6 +131,13 @@ export class SchematicSvgUtils {
|
|
|
131
131
|
'"'
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
if (options.fontStyle && options.fontStyle !== 'normal') {
|
|
135
|
+
attributes +=
|
|
136
|
+
' font-style="' +
|
|
137
|
+
SchematicSvgUtils.escapeHtml(options.fontStyle) +
|
|
138
|
+
'"'
|
|
139
|
+
}
|
|
140
|
+
|
|
134
141
|
if (options.rotation) {
|
|
135
142
|
attributes +=
|
|
136
143
|
' transform="rotate(' +
|
|
@@ -26,28 +26,30 @@ export class SchematicTypography {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Builds the default font options used for synthetic schematic labels.
|
|
29
|
-
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean }> }} sheet
|
|
30
|
-
* @returns {{ fontSize: number, fontFamily: string, fontWeight: number }}
|
|
29
|
+
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean, italic?: boolean }> }} sheet
|
|
30
|
+
* @returns {{ fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }}
|
|
31
31
|
*/
|
|
32
32
|
static buildDefaultSchematicFontOptions(sheet) {
|
|
33
33
|
const font = sheet?.fonts?.['1'] || {
|
|
34
34
|
size: 10,
|
|
35
35
|
family: 'Times New Roman',
|
|
36
|
-
bold: false
|
|
36
|
+
bold: false,
|
|
37
|
+
italic: false
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
return {
|
|
40
41
|
fontSize: SchematicTypography.#toSvgFontSize(font.size),
|
|
41
42
|
fontFamily: font.family || 'Times New Roman',
|
|
42
|
-
fontWeight: font.bold ? 700 : 400
|
|
43
|
+
fontWeight: font.bold ? 700 : 400,
|
|
44
|
+
fontStyle: font.italic ? 'italic' : undefined
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
49
|
* Builds default font options with the viewer-wide one-point reduction
|
|
48
50
|
* already applied.
|
|
49
|
-
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean }> }} sheet
|
|
50
|
-
* @returns {{ fontSize: number | undefined, fontFamily: string, fontWeight: number }}
|
|
51
|
+
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean, italic?: boolean }> }} sheet
|
|
52
|
+
* @returns {{ fontSize: number | undefined, fontFamily: string, fontWeight: number, fontStyle?: string }}
|
|
51
53
|
*/
|
|
52
54
|
static buildViewerSchematicFontOptions(sheet) {
|
|
53
55
|
return SchematicTypography.withViewerFontSize(
|
|
@@ -59,14 +61,15 @@ export class SchematicTypography {
|
|
|
59
61
|
* Builds render options for one schematic text label, including the signed
|
|
60
62
|
* SVG rotation derived from the original Altium orientation and mirrored
|
|
61
63
|
* source state.
|
|
62
|
-
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, sourceOrientation?: number, isMirrored?: boolean }} text
|
|
63
|
-
* @returns {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }}
|
|
64
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number, sourceOrientation?: number, isMirrored?: boolean }} text
|
|
65
|
+
* @returns {{ fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number }}
|
|
64
66
|
*/
|
|
65
67
|
static buildSchematicTextRenderOptions(text) {
|
|
66
68
|
return {
|
|
67
69
|
fontSize: SchematicTypography.resolveViewerFontSize(text.fontSize),
|
|
68
70
|
fontFamily: text.fontFamily,
|
|
69
71
|
fontWeight: text.fontWeight,
|
|
72
|
+
fontStyle: text.fontStyle,
|
|
70
73
|
rotation: SchematicTypography.#resolveSignedTextRotation(
|
|
71
74
|
text.rotation,
|
|
72
75
|
text.sourceOrientation,
|
|
@@ -77,8 +80,8 @@ export class SchematicTypography {
|
|
|
77
80
|
|
|
78
81
|
/**
|
|
79
82
|
* Applies the viewer-wide one-point text reduction to one option bag.
|
|
80
|
-
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }} options
|
|
81
|
-
* @returns {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }}
|
|
83
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number }} options
|
|
84
|
+
* @returns {{ fontSize?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string, rotation?: number }}
|
|
82
85
|
*/
|
|
83
86
|
static withViewerFontSize(options) {
|
|
84
87
|
return {
|