altium-toolkit 1.1.2 → 1.1.22
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/docs/api.md +37 -0
- package/docs/model-format.md +18 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +2 -2
- package/docs/testing.md +5 -0
- package/package.json +1 -1
- package/spec/library-scope.md +5 -0
- package/src/core/altium/AltiumLayoutParser.mjs +275 -13
- package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
- package/src/core/altium/AltiumParser.mjs +245 -10
- package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
- package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
- package/src/core/altium/AsciiRecordParser.mjs +43 -11
- package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
- package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
- 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/SchematicDirectiveParser.mjs +5 -17
- 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/SchematicNoErcSymbolResolver.mjs +36 -0
- package/src/core/altium/SchematicPinParser.mjs +262 -24
- package/src/core/altium/SchematicPrimitiveParser.mjs +116 -8
- package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +62 -15
- package/src/core/altium/SchematicTextParser.mjs +125 -11
- package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
- package/src/core/altium/SourceBundleExporter.mjs +156 -0
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
- package/src/core/altium/SourceComponentClient.mjs +239 -0
- package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
- package/src/parser.mjs +8 -0
- package/src/styles/altium-renderers.css +6 -6
- package/src/ui/PcbArcUtils.mjs +19 -2
- package/src/ui/PcbScene3dBuilder.mjs +202 -20
- package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
- package/src/ui/SchematicColorResolver.mjs +263 -0
- package/src/ui/SchematicContentLayout.mjs +58 -1
- package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
- package/src/ui/SchematicImageRenderer.mjs +125 -10
- package/src/ui/SchematicJunctionRenderer.mjs +1 -1
- package/src/ui/SchematicLineColorResolver.mjs +88 -0
- package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
- package/src/ui/SchematicNoteRenderer.mjs +87 -7
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +560 -10
- package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +397 -48
- 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 +109 -24
- package/src/ui/SchematicSvgRenderer.mjs +1210 -71
|
@@ -31,6 +31,7 @@ export class SchematicColorResolver {
|
|
|
31
31
|
* Resolves one SVG color value to a schematic theme variable.
|
|
32
32
|
* @param {string | undefined} color
|
|
33
33
|
* @param {string} fallbackVariable
|
|
34
|
+
* @param {boolean} [preserveUnknown]
|
|
34
35
|
* @returns {string}
|
|
35
36
|
*/
|
|
36
37
|
static resolveColor(color, fallbackVariable, preserveUnknown = false) {
|
|
@@ -59,10 +60,36 @@ export class SchematicColorResolver {
|
|
|
59
60
|
: SchematicColorResolver.#toVariable(fallbackVariable)
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Resolves one non-text primitive color, using the semantic fallback when
|
|
65
|
+
* source artwork only supplies the default black text color.
|
|
66
|
+
* @param {string | undefined} color
|
|
67
|
+
* @param {string} fallbackVariable
|
|
68
|
+
* @param {boolean} [preserveUnknown]
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
static resolveNonTextColor(
|
|
72
|
+
color,
|
|
73
|
+
fallbackVariable,
|
|
74
|
+
preserveUnknown = false
|
|
75
|
+
) {
|
|
76
|
+
const resolved = SchematicColorResolver.resolveColor(
|
|
77
|
+
color,
|
|
78
|
+
fallbackVariable,
|
|
79
|
+
preserveUnknown
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return resolved ===
|
|
83
|
+
SchematicColorResolver.#toVariable('--schematic-text-color')
|
|
84
|
+
? SchematicColorResolver.#toVariable(fallbackVariable)
|
|
85
|
+
: resolved
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
/**
|
|
63
89
|
* Resolves one SVG fill value to a schematic theme variable.
|
|
64
90
|
* @param {string | undefined} fill
|
|
65
91
|
* @param {string} fallbackVariable
|
|
92
|
+
* @param {boolean} [preserveUnknown]
|
|
66
93
|
* @returns {string}
|
|
67
94
|
*/
|
|
68
95
|
static resolveFill(fill, fallbackVariable, preserveUnknown = false) {
|
|
@@ -100,6 +127,121 @@ export class SchematicColorResolver {
|
|
|
100
127
|
: SchematicColorResolver.#toVariable(fallbackVariable)
|
|
101
128
|
}
|
|
102
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Resolves an explicitly authored source stroke color without mapping it to
|
|
132
|
+
* a semantic theme token.
|
|
133
|
+
* @param {string | undefined} color
|
|
134
|
+
* @param {string} fallbackVariable
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
static resolveSourceColor(color, fallbackVariable) {
|
|
138
|
+
return SchematicColorResolver.#resolveSourcePaint(
|
|
139
|
+
color,
|
|
140
|
+
fallbackVariable
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolves an explicitly authored source fill color without mapping it to a
|
|
146
|
+
* semantic theme token.
|
|
147
|
+
* @param {string | undefined} fill
|
|
148
|
+
* @param {string} fallbackVariable
|
|
149
|
+
* @returns {string}
|
|
150
|
+
*/
|
|
151
|
+
static resolveSourceFill(fill, fallbackVariable) {
|
|
152
|
+
return SchematicColorResolver.#resolveSourcePaint(
|
|
153
|
+
fill,
|
|
154
|
+
fallbackVariable
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolves an authored source stroke through a muted source palette.
|
|
160
|
+
* @param {string | undefined} color
|
|
161
|
+
* @param {string} fallbackVariable
|
|
162
|
+
* @returns {string}
|
|
163
|
+
*/
|
|
164
|
+
static resolveMutedSourceColor(color, fallbackVariable) {
|
|
165
|
+
return SchematicColorResolver.#resolveMutedSourcePaint(
|
|
166
|
+
color,
|
|
167
|
+
fallbackVariable
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolves an authored source fill through a muted source palette.
|
|
173
|
+
* @param {string | undefined} fill
|
|
174
|
+
* @param {string} fallbackVariable
|
|
175
|
+
* @returns {string}
|
|
176
|
+
*/
|
|
177
|
+
static resolveMutedSourceFill(fill, fallbackVariable) {
|
|
178
|
+
return SchematicColorResolver.#resolveMutedSourcePaint(
|
|
179
|
+
fill,
|
|
180
|
+
fallbackVariable
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolves one literal source paint value while preserving SVG control
|
|
186
|
+
* values and falling back only when no source paint exists.
|
|
187
|
+
* @param {string | undefined} paint
|
|
188
|
+
* @param {string} fallbackVariable
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
static #resolveSourcePaint(paint, fallbackVariable) {
|
|
192
|
+
const normalized = SchematicColorResolver.#normalizeColor(paint)
|
|
193
|
+
|
|
194
|
+
if (!normalized) {
|
|
195
|
+
return SchematicColorResolver.#toVariable(fallbackVariable)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
normalized === 'none' ||
|
|
200
|
+
normalized === 'transparent' ||
|
|
201
|
+
normalized.startsWith('var(')
|
|
202
|
+
) {
|
|
203
|
+
return normalized
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return normalized
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Resolves one literal source paint while reducing vivid palette colors.
|
|
211
|
+
* @param {string | undefined} paint
|
|
212
|
+
* @param {string} fallbackVariable
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
static #resolveMutedSourcePaint(paint, fallbackVariable) {
|
|
216
|
+
const normalized = SchematicColorResolver.#normalizeColor(paint)
|
|
217
|
+
|
|
218
|
+
if (!normalized) {
|
|
219
|
+
return SchematicColorResolver.#toVariable(fallbackVariable)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
normalized === 'none' ||
|
|
224
|
+
normalized === 'transparent' ||
|
|
225
|
+
normalized.startsWith('var(')
|
|
226
|
+
) {
|
|
227
|
+
return normalized
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const rgb = SchematicColorResolver.#parseHexColor(normalized)
|
|
231
|
+
if (!rgb) {
|
|
232
|
+
return normalized
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const hsl = SchematicColorResolver.#rgbToHsl(rgb)
|
|
236
|
+
const mutedRgb = SchematicColorResolver.#hslToRgb({
|
|
237
|
+
h: hsl.h,
|
|
238
|
+
s: Math.min(hsl.s, 45),
|
|
239
|
+
l: Math.min(Math.max(hsl.l, 30), 68)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return SchematicColorResolver.#formatHexColor(mutedRgb)
|
|
243
|
+
}
|
|
244
|
+
|
|
103
245
|
/**
|
|
104
246
|
* Normalizes one raw color string for token lookup.
|
|
105
247
|
* @param {string | undefined} color
|
|
@@ -111,6 +253,127 @@ export class SchematicColorResolver {
|
|
|
111
253
|
.toLowerCase()
|
|
112
254
|
}
|
|
113
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Parses a six-digit hex color.
|
|
258
|
+
* @param {string} color
|
|
259
|
+
* @returns {{ r: number, g: number, b: number } | null}
|
|
260
|
+
*/
|
|
261
|
+
static #parseHexColor(color) {
|
|
262
|
+
const match = /^#([0-9a-f]{6})$/u.exec(color)
|
|
263
|
+
if (!match) {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const value = match[1]
|
|
268
|
+
return {
|
|
269
|
+
r: Number.parseInt(value.slice(0, 2), 16),
|
|
270
|
+
g: Number.parseInt(value.slice(2, 4), 16),
|
|
271
|
+
b: Number.parseInt(value.slice(4, 6), 16)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Converts RGB channels to HSL.
|
|
277
|
+
* @param {{ r: number, g: number, b: number }} color
|
|
278
|
+
* @returns {{ h: number, s: number, l: number }}
|
|
279
|
+
*/
|
|
280
|
+
static #rgbToHsl(color) {
|
|
281
|
+
const r = color.r / 255
|
|
282
|
+
const g = color.g / 255
|
|
283
|
+
const b = color.b / 255
|
|
284
|
+
const max = Math.max(r, g, b)
|
|
285
|
+
const min = Math.min(r, g, b)
|
|
286
|
+
let h = 0
|
|
287
|
+
let s = 0
|
|
288
|
+
const l = ((max + min) / 2) * 100
|
|
289
|
+
|
|
290
|
+
if (max !== min) {
|
|
291
|
+
const delta = max - min
|
|
292
|
+
s =
|
|
293
|
+
((max + min) / 2 > 0.5
|
|
294
|
+
? delta / (2 - max - min)
|
|
295
|
+
: delta / (max + min)) * 100
|
|
296
|
+
|
|
297
|
+
switch (max) {
|
|
298
|
+
case r:
|
|
299
|
+
h = (g - b) / delta + (g < b ? 6 : 0)
|
|
300
|
+
break
|
|
301
|
+
case g:
|
|
302
|
+
h = (b - r) / delta + 2
|
|
303
|
+
break
|
|
304
|
+
default:
|
|
305
|
+
h = (r - g) / delta + 4
|
|
306
|
+
break
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
h /= 6
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return { h, s, l }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Converts HSL channels to RGB.
|
|
317
|
+
* @param {{ h: number, s: number, l: number }} color
|
|
318
|
+
* @returns {{ r: number, g: number, b: number }}
|
|
319
|
+
*/
|
|
320
|
+
static #hslToRgb(color) {
|
|
321
|
+
const s = color.s / 100
|
|
322
|
+
const l = color.l / 100
|
|
323
|
+
|
|
324
|
+
if (s === 0) {
|
|
325
|
+
const value = Math.round(l * 255)
|
|
326
|
+
return { r: value, g: value, b: value }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
|
330
|
+
const p = 2 * l - q
|
|
331
|
+
const r = SchematicColorResolver.#hueToRgb(p, q, color.h + 1 / 3)
|
|
332
|
+
const g = SchematicColorResolver.#hueToRgb(p, q, color.h)
|
|
333
|
+
const b = SchematicColorResolver.#hueToRgb(p, q, color.h - 1 / 3)
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
r: Math.round(r * 255),
|
|
337
|
+
g: Math.round(g * 255),
|
|
338
|
+
b: Math.round(b * 255)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Converts one hue channel to RGB.
|
|
344
|
+
* @param {number} p
|
|
345
|
+
* @param {number} q
|
|
346
|
+
* @param {number} t
|
|
347
|
+
* @returns {number}
|
|
348
|
+
*/
|
|
349
|
+
static #hueToRgb(p, q, t) {
|
|
350
|
+
let value = t
|
|
351
|
+
if (value < 0) value += 1
|
|
352
|
+
if (value > 1) value -= 1
|
|
353
|
+
if (value < 1 / 6) return p + (q - p) * 6 * value
|
|
354
|
+
if (value < 1 / 2) return q
|
|
355
|
+
if (value < 2 / 3) return p + (q - p) * (2 / 3 - value) * 6
|
|
356
|
+
return p
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Formats RGB channels as a hex color.
|
|
361
|
+
* @param {{ r: number, g: number, b: number }} color
|
|
362
|
+
* @returns {string}
|
|
363
|
+
*/
|
|
364
|
+
static #formatHexColor(color) {
|
|
365
|
+
return (
|
|
366
|
+
'#' +
|
|
367
|
+
[color.r, color.g, color.b]
|
|
368
|
+
.map((channel) =>
|
|
369
|
+
Math.max(0, Math.min(255, channel))
|
|
370
|
+
.toString(16)
|
|
371
|
+
.padStart(2, '0')
|
|
372
|
+
)
|
|
373
|
+
.join('')
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
114
377
|
/**
|
|
115
378
|
* Wraps one CSS custom property name in `var(...)` markup.
|
|
116
379
|
* @param {string} variableName
|
|
@@ -721,7 +721,12 @@ export class SchematicContentLayout {
|
|
|
721
721
|
}
|
|
722
722
|
|
|
723
723
|
for (const text of schematic?.texts || []) {
|
|
724
|
-
coordinates.push(
|
|
724
|
+
coordinates.push(
|
|
725
|
+
...SchematicContentLayout.#collectRenderedTextPoints(
|
|
726
|
+
text,
|
|
727
|
+
sheetHeight
|
|
728
|
+
)
|
|
729
|
+
)
|
|
725
730
|
}
|
|
726
731
|
|
|
727
732
|
for (const component of schematic?.components || []) {
|
|
@@ -829,6 +834,58 @@ export class SchematicContentLayout {
|
|
|
829
834
|
}))
|
|
830
835
|
}
|
|
831
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Collects rendered bounds points for a text primitive.
|
|
839
|
+
* @param {{ x?: number, y?: number, recordType?: string, cornerX?: number, cornerY?: number, noteLines?: string[] }} text Text primitive.
|
|
840
|
+
* @param {number} sheetHeight Sheet height.
|
|
841
|
+
* @returns {number[][]}
|
|
842
|
+
*/
|
|
843
|
+
static #collectRenderedTextPoints(text, sheetHeight) {
|
|
844
|
+
const x = Number(text?.x)
|
|
845
|
+
const y = Number(text?.y)
|
|
846
|
+
|
|
847
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
848
|
+
return []
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (SchematicContentLayout.#hasRenderedTextFrame(text)) {
|
|
852
|
+
const cornerX = Number(text.cornerX)
|
|
853
|
+
const cornerY = Number(text.cornerY)
|
|
854
|
+
const left = Math.min(x, cornerX)
|
|
855
|
+
const right = Math.max(x, cornerX)
|
|
856
|
+
const top = Math.min(
|
|
857
|
+
projectSchematicY(sheetHeight, y),
|
|
858
|
+
projectSchematicY(sheetHeight, cornerY)
|
|
859
|
+
)
|
|
860
|
+
const bottom = Math.max(
|
|
861
|
+
projectSchematicY(sheetHeight, y),
|
|
862
|
+
projectSchematicY(sheetHeight, cornerY)
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
return [
|
|
866
|
+
[left, top],
|
|
867
|
+
[right, bottom]
|
|
868
|
+
]
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return [[x, projectSchematicY(sheetHeight, y)]]
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Returns true when a text primitive renders as a framed note/callout box.
|
|
876
|
+
* @param {{ recordType?: string, cornerX?: number, cornerY?: number, noteLines?: string[] }} text Text primitive.
|
|
877
|
+
* @returns {boolean}
|
|
878
|
+
*/
|
|
879
|
+
static #hasRenderedTextFrame(text) {
|
|
880
|
+
const recordType = String(text?.recordType || '')
|
|
881
|
+
|
|
882
|
+
return Boolean(
|
|
883
|
+
(recordType === '28' || recordType === '209') &&
|
|
884
|
+
Number.isFinite(Number(text?.cornerX)) &&
|
|
885
|
+
Number.isFinite(Number(text?.cornerY))
|
|
886
|
+
)
|
|
887
|
+
}
|
|
888
|
+
|
|
832
889
|
/**
|
|
833
890
|
* Computes the inner endpoint for one schematic pin stub.
|
|
834
891
|
* @param {{ x: number, y: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
@@ -50,32 +50,45 @@ export class SchematicDirectiveRenderer {
|
|
|
50
50
|
return SchematicDirectiveRenderer.#buildRouteMarkup(
|
|
51
51
|
directive,
|
|
52
52
|
sheetHeight,
|
|
53
|
-
sheet
|
|
53
|
+
sheet,
|
|
54
|
+
'route'
|
|
54
55
|
)
|
|
55
56
|
default:
|
|
56
|
-
return
|
|
57
|
+
return directive?.name
|
|
58
|
+
? SchematicDirectiveRenderer.#buildRouteMarkup(
|
|
59
|
+
directive,
|
|
60
|
+
sheetHeight,
|
|
61
|
+
sheet,
|
|
62
|
+
'parameter-set'
|
|
63
|
+
)
|
|
64
|
+
: ''
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
|
-
* Builds the labeled
|
|
62
|
-
*
|
|
63
|
-
* @param {{ x: number, y: number, color: string, orientation?: number }} directive
|
|
69
|
+
* Builds the labeled info-callout marker for one parameter-set directive.
|
|
70
|
+
* @param {{ x: number, y: number, color: string, name: string, orientation?: number }} directive
|
|
64
71
|
* @param {number} sheetHeight
|
|
65
72
|
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean }> }} sheet
|
|
73
|
+
* @param {string} classModifier
|
|
66
74
|
* @returns {string}
|
|
67
75
|
*/
|
|
68
|
-
static #buildRouteMarkup(directive, sheetHeight, sheet) {
|
|
76
|
+
static #buildRouteMarkup(directive, sheetHeight, sheet, classModifier) {
|
|
69
77
|
const color = SchematicColorResolver.resolveColor(
|
|
70
78
|
directive.color,
|
|
71
79
|
'--schematic-alert-color'
|
|
72
80
|
)
|
|
73
81
|
const projectedY = projectSchematicY(sheetHeight, directive.y)
|
|
74
|
-
const
|
|
75
|
-
|
|
82
|
+
const direction = SchematicDirectiveRenderer.#resolveCalloutDirection(
|
|
83
|
+
directive.orientation
|
|
84
|
+
)
|
|
76
85
|
const circleRadius = 7
|
|
77
|
-
const
|
|
78
|
-
|
|
86
|
+
const calloutDistance =
|
|
87
|
+
SchematicDirectiveRenderer.#resolveCalloutDistance(direction)
|
|
88
|
+
const circleCenterX = directive.x + direction.x * calloutDistance
|
|
89
|
+
const circleCenterY = projectedY + direction.y * calloutDistance
|
|
90
|
+
const leaderEndX = circleCenterX - direction.x * circleRadius
|
|
91
|
+
const leaderEndY = circleCenterY - direction.y * circleRadius
|
|
79
92
|
const labelOptions =
|
|
80
93
|
SchematicTypography.buildViewerSchematicFontOptions(sheet)
|
|
81
94
|
const infoOptions = {
|
|
@@ -83,26 +96,32 @@ export class SchematicDirectiveRenderer {
|
|
|
83
96
|
fontSize: Math.max(Number(labelOptions.fontSize || 9) - 1, 6),
|
|
84
97
|
fontWeight: 700
|
|
85
98
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
const labelPlacement =
|
|
100
|
+
SchematicDirectiveRenderer.#resolveLabelPlacement(
|
|
101
|
+
circleCenterX,
|
|
102
|
+
circleCenterY,
|
|
103
|
+
direction,
|
|
104
|
+
circleRadius,
|
|
105
|
+
Number(labelOptions.fontSize || 9)
|
|
106
|
+
)
|
|
90
107
|
|
|
91
108
|
return (
|
|
92
|
-
'<g class="schematic-directive schematic-directive--
|
|
109
|
+
'<g class="schematic-directive schematic-directive--' +
|
|
110
|
+
escapeHtml(classModifier) +
|
|
111
|
+
'">' +
|
|
93
112
|
'<line x1="' +
|
|
94
113
|
formatNumber(directive.x) +
|
|
95
114
|
'" y1="' +
|
|
96
115
|
formatNumber(projectedY) +
|
|
97
116
|
'" x2="' +
|
|
98
|
-
formatNumber(
|
|
117
|
+
formatNumber(leaderEndX) +
|
|
99
118
|
'" y2="' +
|
|
100
119
|
formatNumber(leaderEndY) +
|
|
101
120
|
'" stroke="' +
|
|
102
121
|
escapeHtml(color) +
|
|
103
122
|
'" stroke-width="1" />' +
|
|
104
123
|
'<circle cx="' +
|
|
105
|
-
formatNumber(
|
|
124
|
+
formatNumber(circleCenterX) +
|
|
106
125
|
'" cy="' +
|
|
107
126
|
formatNumber(circleCenterY) +
|
|
108
127
|
'" r="' +
|
|
@@ -112,17 +131,20 @@ export class SchematicDirectiveRenderer {
|
|
|
112
131
|
'" stroke-width="1" />' +
|
|
113
132
|
createSvgText(
|
|
114
133
|
'schematic-directive-label',
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
labelPlacement.x,
|
|
135
|
+
labelPlacement.y,
|
|
117
136
|
String(directive.name || ''),
|
|
118
137
|
color,
|
|
119
|
-
|
|
138
|
+
labelPlacement.anchor,
|
|
120
139
|
labelOptions
|
|
121
140
|
) +
|
|
122
141
|
createSvgText(
|
|
123
142
|
'schematic-directive-info',
|
|
124
|
-
|
|
125
|
-
circleCenterY +
|
|
143
|
+
circleCenterX,
|
|
144
|
+
circleCenterY +
|
|
145
|
+
SchematicDirectiveRenderer.#baselineOffset(
|
|
146
|
+
Number(infoOptions.fontSize || 8)
|
|
147
|
+
),
|
|
126
148
|
'i',
|
|
127
149
|
color,
|
|
128
150
|
'middle',
|
|
@@ -132,6 +154,95 @@ export class SchematicDirectiveRenderer {
|
|
|
132
154
|
)
|
|
133
155
|
}
|
|
134
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Resolves Altium's four-way callout orientation into an outward vector.
|
|
159
|
+
* @param {number | undefined} orientation
|
|
160
|
+
* @returns {{ x: number, y: number }}
|
|
161
|
+
*/
|
|
162
|
+
static #resolveCalloutDirection(orientation) {
|
|
163
|
+
switch (Number(orientation || 0)) {
|
|
164
|
+
case 1:
|
|
165
|
+
return { x: 0, y: -1 }
|
|
166
|
+
case 2:
|
|
167
|
+
return { x: -1, y: 0 }
|
|
168
|
+
case 3:
|
|
169
|
+
return { x: 0, y: 1 }
|
|
170
|
+
default:
|
|
171
|
+
return { x: 1, y: 0 }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolves the distance from a directive anchor to its info marker center.
|
|
177
|
+
* @param {{ x: number, y: number }} direction
|
|
178
|
+
* @returns {number}
|
|
179
|
+
*/
|
|
180
|
+
static #resolveCalloutDistance(direction) {
|
|
181
|
+
return direction.y < 0 ? 12 : 18
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolves label placement outside one directive info marker.
|
|
186
|
+
* @param {number} circleCenterX
|
|
187
|
+
* @param {number} circleCenterY
|
|
188
|
+
* @param {{ x: number, y: number }} direction
|
|
189
|
+
* @param {number} circleRadius
|
|
190
|
+
* @param {number} fontSize
|
|
191
|
+
* @returns {{ x: number, y: number, anchor: 'start' | 'middle' | 'end' }}
|
|
192
|
+
*/
|
|
193
|
+
static #resolveLabelPlacement(
|
|
194
|
+
circleCenterX,
|
|
195
|
+
circleCenterY,
|
|
196
|
+
direction,
|
|
197
|
+
circleRadius,
|
|
198
|
+
fontSize
|
|
199
|
+
) {
|
|
200
|
+
const labelDistance = circleRadius + fontSize
|
|
201
|
+
|
|
202
|
+
if (direction.x < 0) {
|
|
203
|
+
return {
|
|
204
|
+
x: circleCenterX - labelDistance,
|
|
205
|
+
y:
|
|
206
|
+
circleCenterY +
|
|
207
|
+
SchematicDirectiveRenderer.#baselineOffset(fontSize),
|
|
208
|
+
anchor: 'end'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (direction.x > 0) {
|
|
213
|
+
return {
|
|
214
|
+
x: circleCenterX + labelDistance,
|
|
215
|
+
y:
|
|
216
|
+
circleCenterY +
|
|
217
|
+
SchematicDirectiveRenderer.#baselineOffset(fontSize),
|
|
218
|
+
anchor: 'start'
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (direction.y < 0) {
|
|
223
|
+
return {
|
|
224
|
+
x: circleCenterX,
|
|
225
|
+
y: circleCenterY - circleRadius - 2,
|
|
226
|
+
anchor: 'middle'
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
x: circleCenterX,
|
|
232
|
+
y: circleCenterY + direction.y * labelDistance,
|
|
233
|
+
anchor: 'middle'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns a baseline offset that visually centers text around a marker.
|
|
239
|
+
* @param {number} fontSize
|
|
240
|
+
* @returns {number}
|
|
241
|
+
*/
|
|
242
|
+
static #baselineOffset(fontSize) {
|
|
243
|
+
return fontSize * 0.34
|
|
244
|
+
}
|
|
245
|
+
|
|
135
246
|
/**
|
|
136
247
|
* Builds the paired-trace differential-pair marker glyph.
|
|
137
248
|
* @param {{ x: number, y: number, color: string }} directive
|