altium-toolkit 1.0.8 → 1.0.10

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.
Files changed (102) hide show
  1. package/README.md +18 -6
  2. package/docs/api.md +78 -16
  3. package/docs/model-format.md +229 -8
  4. package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
  5. package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
  6. package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
  7. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
  8. package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
  9. package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
  10. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
  11. package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
  12. package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
  13. package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
  14. package/docs/testing.md +9 -3
  15. package/package.json +1 -1
  16. package/spec/library-scope.md +7 -1
  17. package/src/core/altium/AltiumLayoutParser.mjs +104 -8
  18. package/src/core/altium/AltiumParser.mjs +196 -45
  19. package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
  20. package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
  21. package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
  22. package/src/core/altium/IntLibModelParser.mjs +240 -0
  23. package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
  24. package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
  25. package/src/core/altium/LibrarySearchIndex.mjs +215 -0
  26. package/src/core/altium/NormalizedModelSchema.mjs +36 -0
  27. package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
  28. package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
  29. package/src/core/altium/PcbDefaultsParser.mjs +171 -0
  30. package/src/core/altium/PcbDimensionParser.mjs +229 -0
  31. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
  32. package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
  33. package/src/core/altium/PcbLibModelParser.mjs +235 -14
  34. package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
  35. package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
  36. package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
  37. package/src/core/altium/PcbModelParser.mjs +495 -32
  38. package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
  39. package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
  40. package/src/core/altium/PcbPadStackParser.mjs +229 -2
  41. package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
  42. package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
  43. package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
  44. package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
  45. package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
  46. package/src/core/altium/PcbRuleParser.mjs +354 -33
  47. package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
  48. package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
  49. package/src/core/altium/PcbStatisticsBuilder.mjs +541 -0
  50. package/src/core/altium/PcbStreamExtractor.mjs +111 -4
  51. package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
  52. package/src/core/altium/PcbUnionParser.mjs +307 -0
  53. package/src/core/altium/PcbViaStackParser.mjs +98 -10
  54. package/src/core/altium/PcbViaStructureParser.mjs +335 -0
  55. package/src/core/altium/PrintableTextDecoder.mjs +53 -3
  56. package/src/core/altium/PrjPcbModelParser.mjs +281 -7
  57. package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
  58. package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
  59. package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
  60. package/src/core/altium/ProjectNetlistExporter.mjs +503 -0
  61. package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
  62. package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
  63. package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
  64. package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
  65. package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
  66. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
  67. package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
  68. package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
  69. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
  70. package/src/core/altium/SchematicHarnessParser.mjs +302 -0
  71. package/src/core/altium/SchematicImageParser.mjs +474 -3
  72. package/src/core/altium/SchematicImplementationParser.mjs +518 -0
  73. package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
  74. package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
  75. package/src/core/altium/SchematicPinParser.mjs +84 -1
  76. package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
  77. package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
  78. package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
  79. package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
  80. package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
  81. package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
  82. package/src/core/altium/SchematicTemplateParser.mjs +256 -0
  83. package/src/core/altium/SchematicTextParser.mjs +123 -0
  84. package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
  85. package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
  86. package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
  87. package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
  88. package/src/core/ole/OleCompoundDocument.mjs +20 -0
  89. package/src/parser.mjs +35 -0
  90. package/src/styles/altium-renderers.css +19 -0
  91. package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
  92. package/src/ui/PcbInteractionIndex.mjs +9 -4
  93. package/src/ui/PcbScene3dBuilder.mjs +137 -3
  94. package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
  95. package/src/ui/PcbSvgRenderer.mjs +1252 -34
  96. package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
  97. package/src/ui/SchematicNoteRenderer.mjs +9 -2
  98. package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
  99. package/src/ui/SchematicShapeRenderer.mjs +362 -0
  100. package/src/ui/SchematicSvgRenderer.mjs +1442 -92
  101. package/src/ui/SchematicTypography.mjs +48 -5
  102. package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
package/src/parser.mjs CHANGED
@@ -9,28 +9,63 @@ export { OleDirectoryEntry } from './core/ole/OleDirectoryEntry.mjs'
9
9
  export { AltiumParser } from './core/altium/AltiumParser.mjs'
10
10
  export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
11
11
  export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
12
+ export { IntLibModelParser } from './core/altium/IntLibModelParser.mjs'
13
+ export { IntLibStreamExtractor } from './core/altium/IntLibStreamExtractor.mjs'
14
+ export { EmbeddedFileInventoryBuilder } from './core/altium/EmbeddedFileInventoryBuilder.mjs'
15
+ export { CiArtifactBundleBuilder } from './core/altium/CiArtifactBundleBuilder.mjs'
16
+ export { DraftsmanDigestParser } from './core/altium/DraftsmanDigestParser.mjs'
17
+ export { LibraryRenderManifestBuilder } from './core/altium/LibraryRenderManifestBuilder.mjs'
18
+ export { LibrarySearchIndex } from './core/altium/LibrarySearchIndex.mjs'
12
19
  export { ParserUtils } from './core/altium/ParserUtils.mjs'
20
+ export { ParserCompatibilityFuzzer } from './core/altium/ParserCompatibilityFuzzer.mjs'
13
21
  export { NormalizedModelSchema } from './core/altium/NormalizedModelSchema.mjs'
22
+ export { ProjectDesignBundleBuilder } from './core/altium/ProjectDesignBundleBuilder.mjs'
23
+ export { ProjectDocumentGraphBuilder } from './core/altium/ProjectDocumentGraphBuilder.mjs'
24
+ export { ProjectAnnotationParser } from './core/altium/ProjectAnnotationParser.mjs'
25
+ export { ProjectNetlistExporter } from './core/altium/ProjectNetlistExporter.mjs'
26
+ export { ProjectVariantViewBuilder } from './core/altium/ProjectVariantViewBuilder.mjs'
14
27
  export { CircuitJsonModelSchema } from './core/circuit-json/CircuitJsonModelSchema.mjs'
15
28
  export { CircuitJsonModelAdapter } from './core/circuit-json/CircuitJsonModelAdapter.mjs'
16
29
  export { PcbBinaryPrimitiveParser } from './core/altium/PcbBinaryPrimitiveParser.mjs'
17
30
  export { PcbBoardRegionSemanticsParser } from './core/altium/PcbBoardRegionSemanticsParser.mjs'
18
31
  export { PcbComponentPrimitiveIndexer } from './core/altium/PcbComponentPrimitiveIndexer.mjs'
32
+ export { PcbCustomPadShapeParser } from './core/altium/PcbCustomPadShapeParser.mjs'
33
+ export { PcbDimensionParser } from './core/altium/PcbDimensionParser.mjs'
19
34
  export { PcbEmbeddedFontExtractor } from './core/altium/PcbEmbeddedFontExtractor.mjs'
20
35
  export { PcbEmbeddedModelExtractor } from './core/altium/PcbEmbeddedModelExtractor.mjs'
36
+ export { PcbExtendedPrimitiveInformationParser } from './core/altium/PcbExtendedPrimitiveInformationParser.mjs'
21
37
  export { PcbFontMetricsParser } from './core/altium/PcbFontMetricsParser.mjs'
22
38
  export { PcbLayerIdCodec } from './core/altium/PcbLayerIdCodec.mjs'
23
39
  export { PcbLibModelParser } from './core/altium/PcbLibModelParser.mjs'
24
40
  export { PcbLibStreamExtractor } from './core/altium/PcbLibStreamExtractor.mjs'
41
+ export { PcbMechanicalLayerPairParser } from './core/altium/PcbMechanicalLayerPairParser.mjs'
25
42
  export { PcbModelParser } from './core/altium/PcbModelParser.mjs'
26
43
  export { PcbOutlineRasterizer } from './core/altium/PcbOutlineRasterizer.mjs'
27
44
  export { PcbOutlineRecovery } from './core/altium/PcbOutlineRecovery.mjs'
45
+ export { PcbOwnershipGraphBuilder } from './core/altium/PcbOwnershipGraphBuilder.mjs'
28
46
  export { PcbPadStackParser } from './core/altium/PcbPadStackParser.mjs'
47
+ export { PcbPickPlacePositionResolver } from './core/altium/PcbPickPlacePositionResolver.mjs'
29
48
  export { PcbRawRecordRegistry } from './core/altium/PcbRawRecordRegistry.mjs'
49
+ export { PcbRouteAnalysisBuilder } from './core/altium/PcbRouteAnalysisBuilder.mjs'
30
50
  export { PcbRuleParser } from './core/altium/PcbRuleParser.mjs'
51
+ export { PcbSidecarRecordParser } from './core/altium/PcbSidecarRecordParser.mjs'
52
+ export { PcbSpecialStringResolver } from './core/altium/PcbSpecialStringResolver.mjs'
53
+ export { PcbStatisticsBuilder } from './core/altium/PcbStatisticsBuilder.mjs'
31
54
  export { PcbStreamExtractor } from './core/altium/PcbStreamExtractor.mjs'
55
+ export { PcbUnionParser } from './core/altium/PcbUnionParser.mjs'
32
56
  export { PcbViaStackParser } from './core/altium/PcbViaStackParser.mjs'
57
+ export { PcbViaStructureParser } from './core/altium/PcbViaStructureParser.mjs'
33
58
  export { PrintableTextDecoder } from './core/altium/PrintableTextDecoder.mjs'
34
59
  export { PrjPcbModelParser } from './core/altium/PrjPcbModelParser.mjs'
35
60
  export { SchematicMultipartOwnerMatcher } from './core/altium/SchematicMultipartOwnerMatcher.mjs'
61
+ export { SchematicCrossSheetConnectorParser } from './core/altium/SchematicCrossSheetConnectorParser.mjs'
62
+ export { SchematicHarnessParser } from './core/altium/SchematicHarnessParser.mjs'
63
+ export { SchematicImplementationParser } from './core/altium/SchematicImplementationParser.mjs'
64
+ export { SchematicOwnershipGraphParser } from './core/altium/SchematicOwnershipGraphParser.mjs'
65
+ export { SchematicProjectParameterResolver } from './core/altium/SchematicProjectParameterResolver.mjs'
66
+ export { SchematicQaReportBuilder } from './core/altium/SchematicQaReportBuilder.mjs'
67
+ export { SchematicRecordTypeRegistry } from './core/altium/SchematicRecordTypeRegistry.mjs'
68
+ export { SchematicRepeatedChannelParser } from './core/altium/SchematicRepeatedChannelParser.mjs'
69
+ export { SchematicTemplateParser } from './core/altium/SchematicTemplateParser.mjs'
36
70
  export { SchematicStandaloneCalloutNormalizer } from './core/altium/SchematicStandaloneCalloutNormalizer.mjs'
71
+ export { SvgModelCrossLinkValidator } from './core/altium/SvgModelCrossLinkValidator.mjs'
@@ -295,6 +295,25 @@
295
295
  fill: #000;
296
296
  }
297
297
 
298
+ .pcb-barcode__background,
299
+ .pcb-barcode__bar {
300
+ fill: var(--pcb-footprint-track-color);
301
+ }
302
+
303
+ .pcb-text--barcode-inverted .pcb-barcode__background {
304
+ fill: var(--pcb-text-knockout-fill);
305
+ }
306
+
307
+ .pcb-text--barcode-inverted .pcb-barcode__bar {
308
+ fill: var(--pcb-footprint-track-color);
309
+ }
310
+
311
+ .pcb-barcode__caption {
312
+ fill: var(--pcb-footprint-track-color);
313
+ font-family: Arial, sans-serif;
314
+ font-weight: 700;
315
+ }
316
+
298
317
  .bom-panel {
299
318
  display: grid;
300
319
  gap: 1rem;
@@ -0,0 +1,436 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
6
+
7
+ /**
8
+ * Renders PCB barcode text primitives as deterministic SVG bar groups.
9
+ */
10
+ export class PcbBarcodeTextRenderer {
11
+ static #DEFAULT_MODULE_WIDTH = 2
12
+ static #DEFAULT_HEIGHT_RATIO = 1.8
13
+ static #CAPTION_RATIO = 0.34
14
+
15
+ static #CODE39_PATTERNS = new Map([
16
+ ['0', '101001101101'],
17
+ ['1', '110100101011'],
18
+ ['2', '101100101011'],
19
+ ['3', '110110010101'],
20
+ ['4', '101001101011'],
21
+ ['5', '110100110101'],
22
+ ['6', '101100110101'],
23
+ ['7', '101001011011'],
24
+ ['8', '110100101101'],
25
+ ['9', '101100101101'],
26
+ ['A', '110101001011'],
27
+ ['B', '101101001011'],
28
+ ['C', '110110100101'],
29
+ ['D', '101011001011'],
30
+ ['E', '110101100101'],
31
+ ['F', '101101100101'],
32
+ ['G', '101010011011'],
33
+ ['H', '110101001101'],
34
+ ['I', '101101001101'],
35
+ ['J', '101011001101'],
36
+ ['K', '110101010011'],
37
+ ['L', '101101010011'],
38
+ ['M', '110110101001'],
39
+ ['N', '101011010011'],
40
+ ['O', '110101101001'],
41
+ ['P', '101101101001'],
42
+ ['Q', '101010110011'],
43
+ ['R', '110101011001'],
44
+ ['S', '101101011001'],
45
+ ['T', '101011011001'],
46
+ ['U', '110010101011'],
47
+ ['V', '100110101011'],
48
+ ['W', '110011010101'],
49
+ ['X', '100101101011'],
50
+ ['Y', '110010110101'],
51
+ ['Z', '100110110101'],
52
+ ['-', '100101011011'],
53
+ ['.', '110010101101'],
54
+ [' ', '100110101101'],
55
+ ['$', '100100100101'],
56
+ ['/', '100100101001'],
57
+ ['+', '100101001001'],
58
+ ['%', '101001001001'],
59
+ ['*', '100101101101']
60
+ ])
61
+
62
+ static #CODE128_PATTERNS = [
63
+ '11011001100',
64
+ '11001101100',
65
+ '11001100110',
66
+ '10010011000',
67
+ '10010001100',
68
+ '10001001100',
69
+ '10011001000',
70
+ '10011000100',
71
+ '10001100100',
72
+ '11001001000',
73
+ '11001000100',
74
+ '11000100100',
75
+ '10110011100',
76
+ '10011011100',
77
+ '10011001110',
78
+ '10111001100',
79
+ '10011101100',
80
+ '10011100110',
81
+ '11001110010',
82
+ '11001011100',
83
+ '11001001110',
84
+ '11011100100',
85
+ '11001110100',
86
+ '11101101110',
87
+ '11101001100',
88
+ '11100101100',
89
+ '11100100110',
90
+ '11101100100',
91
+ '11100110100',
92
+ '11100110010',
93
+ '11011011000',
94
+ '11011000110',
95
+ '11000110110',
96
+ '10100011000',
97
+ '10001011000',
98
+ '10001000110',
99
+ '10110001000',
100
+ '10001101000',
101
+ '10001100010',
102
+ '11010001000',
103
+ '11000101000',
104
+ '11000100010',
105
+ '10110111000',
106
+ '10110001110',
107
+ '10001101110',
108
+ '10111011000',
109
+ '10111000110',
110
+ '10001110110',
111
+ '11101110110',
112
+ '11010001110',
113
+ '11000101110',
114
+ '11011101000',
115
+ '11011100010',
116
+ '11011101110',
117
+ '11101011000',
118
+ '11101000110',
119
+ '11100010110',
120
+ '11101101000',
121
+ '11101100010',
122
+ '11100011010',
123
+ '11101111010',
124
+ '11001000010',
125
+ '11110001010',
126
+ '10100110000',
127
+ '10100001100',
128
+ '10010110000',
129
+ '10010000110',
130
+ '10000101100',
131
+ '10000100110',
132
+ '10110010000',
133
+ '10110000100',
134
+ '10011010000',
135
+ '10011000010',
136
+ '10000110100',
137
+ '10000110010',
138
+ '11000010010',
139
+ '11001010000',
140
+ '11110111010',
141
+ '11000010100',
142
+ '10001111010',
143
+ '10100111100',
144
+ '10010111100',
145
+ '10010011110',
146
+ '10111100100',
147
+ '10011110100',
148
+ '10011110010',
149
+ '11110100100',
150
+ '11110010100',
151
+ '11110010010',
152
+ '11011011110',
153
+ '11011110110',
154
+ '11110110110',
155
+ '10101111000',
156
+ '10100011110',
157
+ '10001011110',
158
+ '10111101000',
159
+ '10111100010',
160
+ '11110101000',
161
+ '11110100010',
162
+ '10111011110',
163
+ '10111101110',
164
+ '11101011110',
165
+ '11110101110',
166
+ '11010000100',
167
+ '11010010000',
168
+ '11010011100',
169
+ '1100011101011'
170
+ ]
171
+
172
+ /**
173
+ * Renders one barcode text primitive.
174
+ * @param {{ text: string, layerId?: number, barcode?: object }} text Text primitive.
175
+ * @param {{ transform: string, fontSize: number, semanticAttributes: string }} options Render options.
176
+ * @returns {string}
177
+ */
178
+ static render(text, options) {
179
+ const encoding = PcbBarcodeTextRenderer.#encoding(text)
180
+ const layout = PcbBarcodeTextRenderer.#layout(
181
+ text,
182
+ options.fontSize,
183
+ encoding.pattern.length
184
+ )
185
+ const bars = PcbBarcodeTextRenderer.#bars(encoding.pattern, layout)
186
+ const className =
187
+ 'pcb-text pcb-text--layer-' +
188
+ SchematicSvgUtils.escapeHtml(String(Number(text.layerId || 0))) +
189
+ ' pcb-text--barcode' +
190
+ (text?.barcode?.inverted ? ' pcb-text--barcode-inverted' : '')
191
+ const background = text?.barcode?.inverted
192
+ ? '<rect class="pcb-barcode__background" x="0" y="0" width="' +
193
+ SchematicSvgUtils.formatNumber(layout.width) +
194
+ '" height="' +
195
+ SchematicSvgUtils.formatNumber(layout.height) +
196
+ '" />'
197
+ : ''
198
+ const caption = text?.barcode?.showText
199
+ ? PcbBarcodeTextRenderer.#caption(text, layout)
200
+ : ''
201
+
202
+ return (
203
+ '<g class="' +
204
+ className +
205
+ '" transform="' +
206
+ options.transform +
207
+ '"' +
208
+ options.semanticAttributes +
209
+ ' data-barcode-symbology="' +
210
+ SchematicSvgUtils.escapeHtml(encoding.symbology) +
211
+ '" data-barcode-module-count="' +
212
+ SchematicSvgUtils.escapeHtml(String(encoding.pattern.length)) +
213
+ '"' +
214
+ '>' +
215
+ background +
216
+ '<g class="pcb-barcode__bars" transform="translate(' +
217
+ SchematicSvgUtils.formatNumber(layout.marginX) +
218
+ ' ' +
219
+ SchematicSvgUtils.formatNumber(layout.marginY) +
220
+ ')">' +
221
+ bars +
222
+ '</g>' +
223
+ caption +
224
+ '</g>'
225
+ )
226
+ }
227
+
228
+ /**
229
+ * Resolves barcode layout dimensions.
230
+ * @param {{ text: string, height?: number, barcode?: object }} text Text primitive.
231
+ * @param {number} fontSize Resolved text font size.
232
+ * @param {number} patternLength Encoded module count.
233
+ * @returns {{ width: number, height: number, barHeight: number, marginX: number, marginY: number, moduleWidth: number }}
234
+ */
235
+ static #layout(text, fontSize, patternLength) {
236
+ const barcode = text?.barcode || {}
237
+ const moduleWidth = Math.max(
238
+ Number(barcode.minBarWidth) ||
239
+ PcbBarcodeTextRenderer.#DEFAULT_MODULE_WIDTH,
240
+ 0.1
241
+ )
242
+ const contentWidth = patternLength * moduleWidth
243
+ const marginX = Math.max(Number(barcode.marginX) || moduleWidth, 0)
244
+ const marginY = Math.max(Number(barcode.marginY) || moduleWidth, 0)
245
+ const width = Math.max(
246
+ Number(barcode.fullWidth) || 0,
247
+ contentWidth + marginX * 2
248
+ )
249
+ const height = Math.max(
250
+ Number(barcode.fullHeight) || 0,
251
+ fontSize * PcbBarcodeTextRenderer.#DEFAULT_HEIGHT_RATIO
252
+ )
253
+ const captionHeight = barcode.showText
254
+ ? fontSize * PcbBarcodeTextRenderer.#CAPTION_RATIO
255
+ : 0
256
+
257
+ return {
258
+ width,
259
+ height,
260
+ barHeight: Math.max(height - marginY * 2 - captionHeight, 1),
261
+ marginX,
262
+ marginY,
263
+ moduleWidth:
264
+ contentWidth > 0
265
+ ? Math.max((width - marginX * 2) / patternLength, 0.1)
266
+ : moduleWidth
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Renders barcode bars.
272
+ * @param {string} pattern Encoded barcode module pattern.
273
+ * @param {{ barHeight: number, moduleWidth: number }} layout Barcode layout.
274
+ * @returns {string}
275
+ */
276
+ static #bars(pattern, layout) {
277
+ const runs = []
278
+ let cursor = 0
279
+
280
+ while (cursor < pattern.length) {
281
+ const value = pattern[cursor]
282
+ let length = 1
283
+ while (pattern[cursor + length] === value) {
284
+ length += 1
285
+ }
286
+ if (value === '1') {
287
+ runs.push({ offset: cursor, length })
288
+ }
289
+ cursor += length
290
+ }
291
+
292
+ return runs
293
+ .map(
294
+ (run) =>
295
+ '<rect class="pcb-barcode__bar" x="' +
296
+ SchematicSvgUtils.formatNumber(
297
+ run.offset * layout.moduleWidth
298
+ ) +
299
+ '" y="0" width="' +
300
+ SchematicSvgUtils.formatNumber(
301
+ run.length * layout.moduleWidth
302
+ ) +
303
+ '" height="' +
304
+ SchematicSvgUtils.formatNumber(layout.barHeight) +
305
+ '" />'
306
+ )
307
+ .join('')
308
+ }
309
+
310
+ /**
311
+ * Renders optional human-readable barcode text.
312
+ * @param {{ text: string }} text Text primitive.
313
+ * @param {{ width: number, height: number, marginY: number }} layout Barcode layout.
314
+ * @returns {string}
315
+ */
316
+ static #caption(text, layout) {
317
+ const fontSize = Math.max(layout.height * 0.16, 4)
318
+
319
+ return (
320
+ '<text class="pcb-barcode__caption" x="' +
321
+ SchematicSvgUtils.formatNumber(layout.width / 2) +
322
+ '" y="' +
323
+ SchematicSvgUtils.formatNumber(layout.height - layout.marginY) +
324
+ '" font-size="' +
325
+ SchematicSvgUtils.formatNumber(fontSize) +
326
+ '" text-anchor="middle" dominant-baseline="alphabetic">' +
327
+ SchematicSvgUtils.escapeHtml(String(text.text || '')) +
328
+ '</text>'
329
+ )
330
+ }
331
+
332
+ /**
333
+ * Encodes one barcode payload.
334
+ * @param {{ text: string, barcode?: object }} text Text primitive.
335
+ * @returns {{ pattern: string, symbology: string }}
336
+ */
337
+ static #encoding(text) {
338
+ const kind = String(text?.barcode?.kindName || '').toLowerCase()
339
+ if (kind === 'code39') {
340
+ return PcbBarcodeTextRenderer.#encodeCode39(text)
341
+ }
342
+ if (kind === 'code128') {
343
+ return PcbBarcodeTextRenderer.#encodeCode128B(text)
344
+ }
345
+
346
+ return {
347
+ pattern: PcbBarcodeTextRenderer.#fallbackPattern(text),
348
+ symbology: 'deterministic'
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Encodes valid Code 39 content.
354
+ * @param {{ text: string }} text Text primitive.
355
+ * @returns {{ pattern: string, symbology: string }}
356
+ */
357
+ static #encodeCode39(text) {
358
+ const content = String(text?.text || '').toUpperCase()
359
+ const encoded = '*' + content + '*'
360
+ const parts = []
361
+
362
+ for (const character of encoded) {
363
+ const pattern =
364
+ PcbBarcodeTextRenderer.#CODE39_PATTERNS.get(character)
365
+ if (!pattern) {
366
+ return {
367
+ pattern: PcbBarcodeTextRenderer.#fallbackPattern(text),
368
+ symbology: 'deterministic'
369
+ }
370
+ }
371
+ parts.push(pattern)
372
+ }
373
+
374
+ return {
375
+ pattern: parts.join('0'),
376
+ symbology: 'Code 39'
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Encodes printable ASCII content as Code 128 set B.
382
+ * @param {{ text: string }} text Text primitive.
383
+ * @returns {{ pattern: string, symbology: string }}
384
+ */
385
+ static #encodeCode128B(text) {
386
+ const values = [104]
387
+ for (const character of String(text?.text || '')) {
388
+ const codePoint = character.codePointAt(0) || 63
389
+ const value =
390
+ codePoint >= 32 && codePoint <= 127 ? codePoint - 32 : 31
391
+ values.push(value)
392
+ }
393
+
394
+ let checksum = values[0]
395
+ for (let index = 1; index < values.length; index += 1) {
396
+ checksum += values[index] * index
397
+ }
398
+ values.push(checksum % 103)
399
+ values.push(106)
400
+
401
+ return {
402
+ pattern: values
403
+ .map((value) => PcbBarcodeTextRenderer.#CODE128_PATTERNS[value])
404
+ .join(''),
405
+ symbology: 'Code 128B'
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Builds a deterministic fallback module pattern for unsupported content.
411
+ * @param {{ text: string }} text Text primitive.
412
+ * @returns {string}
413
+ */
414
+ static #fallbackPattern(text) {
415
+ const value = String(text?.text || '')
416
+ const parts = ['11010010000']
417
+ for (const character of value) {
418
+ parts.push(
419
+ PcbBarcodeTextRenderer.#fallbackCharacterPattern(character)
420
+ )
421
+ }
422
+ parts.push('1100011101011')
423
+ return parts.join('0')
424
+ }
425
+
426
+ /**
427
+ * Builds a stable 11-module fallback pattern for one character.
428
+ * @param {string} character Barcode character.
429
+ * @returns {string}
430
+ */
431
+ static #fallbackCharacterPattern(character) {
432
+ const code = character.codePointAt(0) || 0
433
+ const mixed = (code * 1103515245 + 12345) >>> 0
434
+ return mixed.toString(2).padStart(32, '0').slice(0, 11)
435
+ }
436
+ }
@@ -469,12 +469,17 @@ export class PcbInteractionIndex {
469
469
  const componentIndex = Number(primitive?.componentIndex)
470
470
  if (!Number.isInteger(componentIndex)) return null
471
471
 
472
- return (
472
+ const explicitMatch =
473
473
  context.components.find(
474
- (component, index) =>
475
- Number(component?.componentIndex) === componentIndex ||
476
- index === componentIndex
474
+ (component) =>
475
+ Number(component?.componentIndex) === componentIndex
477
476
  ) || null
477
+ if (explicitMatch) return explicitMatch
478
+
479
+ return (
480
+ context.components.find((component, index) => {
481
+ return index === componentIndex
482
+ }) || null
478
483
  )
479
484
  }
480
485