altium-toolkit 1.1.24 → 1.1.26

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 (35) hide show
  1. package/README.md +5 -3
  2. package/docs/api.md +43 -17
  3. package/docs/model-format.md +51 -11
  4. package/docs/schemas/altium_toolkit/library_compatibility_a1.schema.json +116 -0
  5. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +3 -0
  6. package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +58 -1
  7. package/package.json +1 -1
  8. package/spec/library-compatibility.md +65 -0
  9. package/src/core/altium/AltiumParser.mjs +8 -1
  10. package/src/core/altium/LibraryCompatibilityGeometry.mjs +618 -0
  11. package/src/core/altium/LibraryCompatibilityModelHintBuilder.mjs +296 -0
  12. package/src/core/altium/LibraryCompatibilityReportBuilder.mjs +930 -0
  13. package/src/core/altium/LibraryQaReportBuilder.mjs +29 -1
  14. package/src/core/altium/PcbDimensionParser.mjs +1 -0
  15. package/src/core/altium/PcbDimensionReportBuilder.mjs +190 -0
  16. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +140 -10
  17. package/src/core/altium/PcbFabricationReadinessReportBuilder.mjs +607 -0
  18. package/src/core/altium/PcbInspectionReportBuilder.mjs +56 -3
  19. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +138 -3
  20. package/src/core/altium/PcbLibModelParser.mjs +24 -2
  21. package/src/core/altium/PcbLibStorageNameResolver.mjs +171 -0
  22. package/src/core/altium/PcbLibStreamExtractor.mjs +15 -93
  23. package/src/core/altium/PcbPrimitiveFieldOffsetReportBuilder.mjs +352 -0
  24. package/src/core/altium/PcbRuleImpactReportBuilder.mjs +313 -0
  25. package/src/core/altium/PcbShapeBasedBodyGeometryParser.mjs +514 -0
  26. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +442 -2
  27. package/src/core/altium/SchematicImageDiagnosticsBuilder.mjs +214 -0
  28. package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +523 -3
  29. package/src/parser.mjs +8 -0
  30. package/src/styles/altium-renderers.css +36 -0
  31. package/src/ui/PcbDimensionPrimitiveRenderer.mjs +422 -0
  32. package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +6 -0
  33. package/src/ui/PcbScene3dBuilder.mjs +10 -2
  34. package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +225 -0
  35. package/src/ui/PcbSvgRenderer.mjs +78 -2
@@ -25,10 +25,17 @@ export class PcbLayerStackReadModelBuilder {
25
25
  const fields = PcbLayerStackReadModelBuilder.#mergeFields(
26
26
  input.boardRecords || []
27
27
  )
28
+ const nativeLayerOrder = (input.layers || []).length
29
+ ? null
30
+ : PcbLayerStackReadModelBuilder.#nativeLayerOrder(
31
+ fields,
32
+ input.primitiveLayers || []
33
+ )
28
34
  const layers = PcbLayerStackReadModelBuilder.#layers(
29
35
  input.layers || [],
30
36
  input.primitiveLayers || [],
31
- fields
37
+ fields,
38
+ nativeLayerOrder
32
39
  )
33
40
  const layerById = new Map(
34
41
  layers
@@ -85,6 +92,9 @@ export class PcbLayerStackReadModelBuilder {
85
92
  cavityRegionCount: cavityReport.cavityRegionCount,
86
93
  stiffenerLayerCount: cavityReport.stiffenerLayerCount,
87
94
  adhesiveLayerCount: cavityReport.adhesiveLayerCount,
95
+ ...(nativeLayerOrder
96
+ ? { nativeLayerOrderCount: nativeLayerOrder.layerIds.length }
97
+ : {}),
88
98
  diagnosticCount: diagnostics.length
89
99
  }
90
100
 
@@ -101,6 +111,7 @@ export class PcbLayerStackReadModelBuilder {
101
111
  topLevelBendLines,
102
112
  cavityReport
103
113
  ),
114
+ ...(nativeLayerOrder ? { nativeLayerOrder } : {}),
104
115
  layers,
105
116
  substacks,
106
117
  branches,
@@ -160,9 +171,10 @@ export class PcbLayerStackReadModelBuilder {
160
171
  * @param {object[]} layers Parsed physical layers.
161
172
  * @param {object[]} primitiveLayers Primitive layer map.
162
173
  * @param {Record<string, string | string[]>} fields Source fields.
174
+ * @param {{ layerIds: number[] } | null} nativeLayerOrder Native order.
163
175
  * @returns {object[]}
164
176
  */
165
- static #layers(layers, primitiveLayers, fields) {
177
+ static #layers(layers, primitiveLayers, fields, nativeLayerOrder) {
166
178
  if (layers.length) {
167
179
  return layers.map((layer) =>
168
180
  PcbLayerStackReadModelBuilder.#stripUndefined({
@@ -187,7 +199,19 @@ export class PcbLayerStackReadModelBuilder {
187
199
  )
188
200
  }
189
201
 
190
- return (primitiveLayers || []).map((layer, index) =>
202
+ const nativeOrderByLayerId = new Map(
203
+ (nativeLayerOrder?.layerIds || []).map((layerId, index) => [
204
+ layerId,
205
+ index + 1
206
+ ])
207
+ )
208
+ const orderedPrimitiveLayers =
209
+ PcbLayerStackReadModelBuilder.#orderPrimitiveLayers(
210
+ primitiveLayers || [],
211
+ nativeLayerOrder
212
+ )
213
+
214
+ return orderedPrimitiveLayers.map((layer, index) =>
191
215
  PcbLayerStackReadModelBuilder.#stripUndefined({
192
216
  index: index + 1,
193
217
  layerId: layer.layerId,
@@ -196,6 +220,7 @@ export class PcbLayerStackReadModelBuilder {
196
220
  : undefined,
197
221
  name: layer.name,
198
222
  kind: layer.kind || layer.role,
223
+ nativeOrderIndex: nativeOrderByLayerId.get(layer.layerId),
199
224
  ...PcbLayerStackSourceMetadataParser.layerSourceFields(
200
225
  fields,
201
226
  index + 1
@@ -204,6 +229,116 @@ export class PcbLayerStackReadModelBuilder {
204
229
  )
205
230
  }
206
231
 
232
+ /**
233
+ * Parses the native layer linked-list order stored in Board6 records.
234
+ * @param {Record<string, string | string[]>} fields Board fields.
235
+ * @param {object[]} primitiveLayers Primitive layer fallbacks.
236
+ * @returns {{ source: string, layerIds: number[], complete: boolean } | null}
237
+ */
238
+ static #nativeLayerOrder(fields, primitiveLayers) {
239
+ const layerIds = new Set(
240
+ (primitiveLayers || [])
241
+ .map((layer) => Number(layer.layerId))
242
+ .filter(Number.isFinite)
243
+ )
244
+ if (!layerIds.size) return null
245
+
246
+ const links = PcbLayerStackReadModelBuilder.#nativeLayerLinks(fields)
247
+ const firstLayerId = [...links.entries()].find(
248
+ ([, link]) => link.prev === 0 && link.next !== 0
249
+ )?.[0]
250
+ if (!firstLayerId) return null
251
+
252
+ const orderedLayerIds = []
253
+ const visited = new Set()
254
+ let currentLayerId = firstLayerId
255
+
256
+ while (
257
+ Number.isFinite(currentLayerId) &&
258
+ currentLayerId !== 0 &&
259
+ !visited.has(currentLayerId)
260
+ ) {
261
+ visited.add(currentLayerId)
262
+
263
+ if (layerIds.has(currentLayerId)) {
264
+ orderedLayerIds.push(currentLayerId)
265
+ }
266
+
267
+ const nextLayerId = links.get(currentLayerId)?.next || 0
268
+ currentLayerId = Number(nextLayerId)
269
+ }
270
+
271
+ if (!orderedLayerIds.length) return null
272
+
273
+ return {
274
+ source: 'Board6/Data',
275
+ layerIds: orderedLayerIds,
276
+ complete: orderedLayerIds.length === layerIds.size
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Builds native layer link rows from Board6 LAYERnNEXT/PREV fields.
282
+ * @param {Record<string, string | string[]>} fields Board fields.
283
+ * @returns {Map<number, { next: number, prev: number }>}
284
+ */
285
+ static #nativeLayerLinks(fields) {
286
+ const layerIndexes = PcbLayerStackReadModelBuilder.#indexedRows(
287
+ fields,
288
+ [/^LAYER(\d+)NEXT$/iu, /^LAYER(\d+)PREV$/iu]
289
+ )
290
+ const links = new Map()
291
+
292
+ for (const layerIndex of layerIndexes) {
293
+ const next = Number(
294
+ PcbLayerStackReadModelBuilder.#field(
295
+ fields,
296
+ 'LAYER' + layerIndex + 'NEXT'
297
+ ) || 0
298
+ )
299
+ const prev = Number(
300
+ PcbLayerStackReadModelBuilder.#field(
301
+ fields,
302
+ 'LAYER' + layerIndex + 'PREV'
303
+ ) || 0
304
+ )
305
+
306
+ if (Number.isFinite(next) || Number.isFinite(prev)) {
307
+ links.set(layerIndex, {
308
+ next: Number.isFinite(next) ? next : 0,
309
+ prev: Number.isFinite(prev) ? prev : 0
310
+ })
311
+ }
312
+ }
313
+
314
+ return links
315
+ }
316
+
317
+ /**
318
+ * Orders primitive fallback layers with native linked-list evidence first.
319
+ * @param {object[]} primitiveLayers Primitive layer rows.
320
+ * @param {{ layerIds: number[] } | null} nativeLayerOrder Native order.
321
+ * @returns {object[]}
322
+ */
323
+ static #orderPrimitiveLayers(primitiveLayers, nativeLayerOrder) {
324
+ if (!nativeLayerOrder?.layerIds?.length) return primitiveLayers
325
+
326
+ const layerById = new Map(
327
+ primitiveLayers
328
+ .filter((layer) => Number.isFinite(Number(layer.layerId)))
329
+ .map((layer) => [Number(layer.layerId), layer])
330
+ )
331
+ const orderedLayers = nativeLayerOrder.layerIds
332
+ .map((layerId) => layerById.get(layerId))
333
+ .filter(Boolean)
334
+ const orderedIds = new Set(nativeLayerOrder.layerIds)
335
+ const remainingLayers = primitiveLayers.filter(
336
+ (layer) => !orderedIds.has(Number(layer.layerId))
337
+ )
338
+
339
+ return [...orderedLayers, ...remainingLayers]
340
+ }
341
+
207
342
  /**
208
343
  * Normalizes substacks and links them to board-region rows.
209
344
  * @param {object[]} layerSubstacks Parsed substacks.
@@ -20,7 +20,7 @@ export class PcbLibModelParser {
20
20
  /**
21
21
  * Parses one extracted PcbLib into a normalized read-only model.
22
22
  * @param {string} fileName
23
- * @param {{ libraryHeader?: Record<string, string>, componentParamsToc?: Record<string, object>, sectionKeys?: Record<string, string>, footprints?: object[], embeddedFonts?: { fonts?: object[] }, nativeStreams?: object, streamNames?: string[], diagnostics?: Record<string, number> } | null} extraction
23
+ * @param {{ libraryHeader?: Record<string, string>, componentParamsToc?: Record<string, object>, sectionKeys?: Record<string, string>, footprints?: object[], embeddedFonts?: { fonts?: object[] }, nativeStreams?: object, streamNames?: string[], diagnostics?: Record<string, number | object[]> } | null} extraction
24
24
  * @returns {{ schema: string, kind: 'pcb-library', fileType: 'PcbLib', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], pcbLibrary: { libraryHeader: Record<string, string>, componentParamsToc: Record<string, object>, sectionKeys: Record<string, string>, footprints: object[], embeddedFonts: object[] }, bom: [] }}
25
25
  */
26
26
  static parse(fileName, extraction) {
@@ -72,6 +72,7 @@ export class PcbLibModelParser {
72
72
  embeddedFonts,
73
73
  embeddedModels,
74
74
  componentBodies,
75
+ ...PcbLibModelParser.#storageDiagnostics(safeExtraction),
75
76
  ...(safeExtraction.nativeStreams
76
77
  ? { nativeStreams: safeExtraction.nativeStreams }
77
78
  : {}),
@@ -141,7 +142,7 @@ export class PcbLibModelParser {
141
142
  * @param {object[]} footprints
142
143
  * @param {object[]} embeddedFonts
143
144
  * @param {object[]} embeddedModels
144
- * @param {{ streamNames?: string[], diagnostics?: Record<string, number> }} extraction
145
+ * @param {{ streamNames?: string[], diagnostics?: Record<string, number | object[]> }} extraction
145
146
  * @returns {{ severity: 'info' | 'warning', message: string }[]}
146
147
  */
147
148
  static #buildDiagnostics(
@@ -203,6 +204,27 @@ export class PcbLibModelParser {
203
204
  return diagnostics
204
205
  }
205
206
 
207
+ /**
208
+ * Builds storage-level diagnostics for the public PCB library model.
209
+ * @param {{ diagnostics?: Record<string, number | object[]> }} extraction Extracted PcbLib data.
210
+ * @returns {{ storageDiagnostics?: object }}
211
+ */
212
+ static #storageDiagnostics(extraction) {
213
+ const missingFootprints = Array.isArray(
214
+ extraction?.diagnostics?.missingFootprints
215
+ )
216
+ ? extraction.diagnostics.missingFootprints
217
+ : []
218
+
219
+ return missingFootprints.length
220
+ ? {
221
+ storageDiagnostics: {
222
+ missingFootprints
223
+ }
224
+ }
225
+ : {}
226
+ }
227
+
206
228
  /**
207
229
  * Normalizes one extracted footprint object with stable primitive arrays.
208
230
  * @param {object} footprint
@@ -0,0 +1,171 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Resolves declared PcbLib footprint names to compound-document storage names.
7
+ */
8
+ export class PcbLibStorageNameResolver {
9
+ /**
10
+ * Resolves all declared footprint storage names.
11
+ * @param {Map<string, Uint8Array>} streams PcbLib compound streams.
12
+ * @param {string[]} footprintNames Declared footprint names.
13
+ * @param {Record<string, string>} sectionKeys Section-key mapping.
14
+ * @returns {{ resolvedFootprints: { name: string, storageName: string }[], missingFootprints: object[] }}
15
+ */
16
+ static resolveFootprints(streams, footprintNames, sectionKeys) {
17
+ const resolvedFootprints = []
18
+ const missingFootprints = []
19
+
20
+ for (const name of footprintNames || []) {
21
+ const candidates = PcbLibStorageNameResolver.#storageCandidates(
22
+ name,
23
+ sectionKeys
24
+ )
25
+ const storageName = PcbLibStorageNameResolver.#resolveStorageName(
26
+ streams,
27
+ candidates
28
+ )
29
+
30
+ if (!storageName) {
31
+ missingFootprints.push({
32
+ footprintName: name,
33
+ candidates,
34
+ reason: 'no matching footprint Data stream'
35
+ })
36
+ continue
37
+ }
38
+
39
+ resolvedFootprints.push({ name, storageName })
40
+ }
41
+
42
+ return { resolvedFootprints, missingFootprints }
43
+ }
44
+
45
+ /**
46
+ * Parses an optional SectionKeys stream that maps full footprint names to
47
+ * shortened OLE storage names.
48
+ * @param {Uint8Array | undefined} bytes SectionKeys stream bytes.
49
+ * @returns {Record<string, string>}
50
+ */
51
+ static parseSectionKeys(bytes) {
52
+ if (!bytes || bytes.byteLength < 4) {
53
+ return {}
54
+ }
55
+
56
+ const count = PcbLibStorageNameResolver.#readUint32(bytes, 0)
57
+ const entries = {}
58
+ let offset = 4
59
+
60
+ for (let index = 0; index < count; index += 1) {
61
+ const fullName = PcbLibStorageNameResolver.#readStringBlockAt(
62
+ bytes,
63
+ offset
64
+ )
65
+ if (!fullName) break
66
+ offset = fullName.nextOffset
67
+
68
+ const storageName = PcbLibStorageNameResolver.#readStringBlockAt(
69
+ bytes,
70
+ offset
71
+ )
72
+ if (!storageName) break
73
+ offset = storageName.nextOffset
74
+ entries[fullName.text] = storageName.text
75
+ }
76
+
77
+ return entries
78
+ }
79
+
80
+ /**
81
+ * Builds candidate OLE storage names for one declared footprint.
82
+ * @param {string} footprintName Declared footprint name.
83
+ * @param {Record<string, string>} sectionKeys Section-key mapping.
84
+ * @returns {string[]}
85
+ */
86
+ static #storageCandidates(footprintName, sectionKeys) {
87
+ return [
88
+ footprintName,
89
+ PcbLibStorageNameResolver.#sanitizeStorageName(footprintName),
90
+ sectionKeys[footprintName],
91
+ PcbLibStorageNameResolver.#sanitizeStorageName(
92
+ footprintName.slice(0, 31)
93
+ )
94
+ ]
95
+ .filter(Boolean)
96
+ .filter(
97
+ (candidate, index, candidates) =>
98
+ candidates.indexOf(candidate) === index
99
+ )
100
+ }
101
+
102
+ /**
103
+ * Resolves the first candidate with a matching Data stream.
104
+ * @param {Map<string, Uint8Array>} streams PcbLib compound streams.
105
+ * @param {string[]} candidates Candidate storage names.
106
+ * @returns {string}
107
+ */
108
+ static #resolveStorageName(streams, candidates) {
109
+ return (
110
+ candidates.find((candidate) => streams.has(candidate + '/Data')) ||
111
+ ''
112
+ )
113
+ }
114
+
115
+ /**
116
+ * Sanitizes one footprint name for legacy OLE storage lookup.
117
+ * @param {string} name Footprint name.
118
+ * @returns {string}
119
+ */
120
+ static #sanitizeStorageName(name) {
121
+ return String(name || '')
122
+ .replace(/[<>:"/\\|?*\x00-\x1f]/gu, '_')
123
+ .slice(0, 31)
124
+ }
125
+
126
+ /**
127
+ * Reads a little-endian unsigned 32-bit value from one byte array.
128
+ * @param {Uint8Array} bytes Source bytes.
129
+ * @param {number} offset Byte offset.
130
+ * @returns {number}
131
+ */
132
+ static #readUint32(bytes, offset) {
133
+ if (!bytes || offset + 4 > bytes.byteLength) return 0
134
+
135
+ return new DataView(
136
+ bytes.buffer,
137
+ bytes.byteOffset,
138
+ bytes.byteLength
139
+ ).getUint32(offset, true)
140
+ }
141
+
142
+ /**
143
+ * Reads one Pascal-style PCB string block from an offset.
144
+ * @param {Uint8Array} bytes Source bytes.
145
+ * @param {number} offset Byte offset.
146
+ * @returns {{ text: string, nextOffset: number } | null}
147
+ */
148
+ static #readStringBlockAt(bytes, offset) {
149
+ if (!bytes || offset + 5 > bytes.byteLength) return null
150
+
151
+ const byteLength = PcbLibStorageNameResolver.#readUint32(bytes, offset)
152
+ const stringLength = bytes[offset + 4]
153
+ const textOffset = offset + 5
154
+ const nextOffset = offset + 4 + byteLength
155
+
156
+ if (
157
+ byteLength < 1 ||
158
+ textOffset + stringLength > bytes.byteLength ||
159
+ nextOffset > bytes.byteLength
160
+ ) {
161
+ return null
162
+ }
163
+
164
+ return {
165
+ text: new TextDecoder('utf-8').decode(
166
+ bytes.subarray(textOffset, textOffset + stringLength)
167
+ ),
168
+ nextOffset
169
+ }
170
+ }
171
+ }
@@ -7,6 +7,7 @@ import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
7
7
  import { PcbEmbeddedFontExtractor } from './PcbEmbeddedFontExtractor.mjs'
8
8
  import { PcbEmbeddedModelExtractor } from './PcbEmbeddedModelExtractor.mjs'
9
9
  import { PcbExtendedPrimitiveInformationParser } from './PcbExtendedPrimitiveInformationParser.mjs'
10
+ import { PcbLibStorageNameResolver } from './PcbLibStorageNameResolver.mjs'
10
11
  import { NativeStreamInventoryBuilder } from './NativeStreamInventoryBuilder.mjs'
11
12
  import { PcbRawRecordRegistry } from './PcbRawRecordRegistry.mjs'
12
13
  import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
@@ -119,7 +120,7 @@ export class PcbLibStreamExtractor {
119
120
  /**
120
121
  * Extracts all declared footprints from one PcbLib stream map.
121
122
  * @param {Map<string, Uint8Array>} streams
122
- * @returns {{ libraryHeader: Record<string, string>, componentParamsToc: Record<string, object>, sectionKeys: Record<string, string>, footprints: object[], streamNames: string[], diagnostics: Record<string, number> }}
123
+ * @returns {{ libraryHeader: Record<string, string>, componentParamsToc: Record<string, object>, sectionKeys: Record<string, string>, footprints: object[], streamNames: string[], diagnostics: Record<string, number | object[]> }}
123
124
  */
124
125
  static extractFromStreams(streams) {
125
126
  const libraryData = streams.get('Library/Data') || new Uint8Array()
@@ -129,30 +130,24 @@ export class PcbLibStreamExtractor {
129
130
  PcbLibStreamExtractor.#parseComponentParamsToc(
130
131
  streams.get('Library/ComponentParamsTOC/Data')
131
132
  )
132
- const sectionKeys = PcbLibStreamExtractor.#parseSectionKeys(
133
+ const sectionKeys = PcbLibStorageNameResolver.parseSectionKeys(
133
134
  streams.get('SectionKeys')
134
135
  )
135
- const footprints = parsedLibraryData.footprintNames.flatMap((name) => {
136
- const resolvedStorage =
137
- PcbLibStreamExtractor.#resolveFootprintStorageName(
138
- streams,
139
- name,
140
- sectionKeys
141
- )
142
-
143
- if (!resolvedStorage) {
144
- return []
145
- }
146
-
147
- return [
136
+ const storageResolution = PcbLibStorageNameResolver.resolveFootprints(
137
+ streams,
138
+ parsedLibraryData.footprintNames,
139
+ sectionKeys
140
+ )
141
+ const footprints = storageResolution.resolvedFootprints.map(
142
+ ({ name, storageName }) =>
148
143
  PcbLibStreamExtractor.#extractFootprint(
149
144
  streams,
150
145
  name,
151
- resolvedStorage,
146
+ storageName,
152
147
  componentParamsToc[name] || {}
153
148
  )
154
- ]
155
- })
149
+ )
150
+ const missingFootprints = storageResolution.missingFootprints
156
151
  const primitiveCount = footprints.reduce(
157
152
  (sum, footprint) => sum + footprint.primitiveCount,
158
153
  0
@@ -198,8 +193,8 @@ export class PcbLibStreamExtractor {
198
193
  embeddedFontCount: embeddedFonts.fonts.length,
199
194
  embeddedModelCount: embeddedModels.models.length,
200
195
  componentBodyCount: embeddedModels.componentBodies.length,
201
- missingFootprintCount:
202
- parsedLibraryData.footprintNames.length - footprints.length
196
+ missingFootprintCount: missingFootprints.length,
197
+ ...(missingFootprints.length ? { missingFootprints } : {})
203
198
  }
204
199
  }
205
200
  }
@@ -280,68 +275,6 @@ export class PcbLibStreamExtractor {
280
275
  return entries
281
276
  }
282
277
 
283
- /**
284
- * Parses an optional SectionKeys stream that maps full footprint names to
285
- * shortened OLE storage names.
286
- * @param {Uint8Array | undefined} bytes
287
- * @returns {Record<string, string>}
288
- */
289
- static #parseSectionKeys(bytes) {
290
- if (!bytes || bytes.byteLength < 4) {
291
- return {}
292
- }
293
-
294
- const count = PcbLibStreamExtractor.#readUint32(bytes, 0)
295
- const entries = {}
296
- let offset = 4
297
-
298
- for (let index = 0; index < count; index += 1) {
299
- const fullName = PcbLibStreamExtractor.#readStringBlockAt(
300
- bytes,
301
- offset
302
- )
303
- if (!fullName) {
304
- break
305
- }
306
- offset = fullName.nextOffset
307
-
308
- const storageName = PcbLibStreamExtractor.#readStringBlockAt(
309
- bytes,
310
- offset
311
- )
312
- if (!storageName) {
313
- break
314
- }
315
- offset = storageName.nextOffset
316
- entries[fullName.text] = storageName.text
317
- }
318
-
319
- return entries
320
- }
321
-
322
- /**
323
- * Resolves the OLE storage name for one declared footprint.
324
- * @param {Map<string, Uint8Array>} streams
325
- * @param {string} footprintName
326
- * @param {Record<string, string>} sectionKeys
327
- * @returns {string}
328
- */
329
- static #resolveFootprintStorageName(streams, footprintName, sectionKeys) {
330
- const candidates = [
331
- footprintName,
332
- PcbLibStreamExtractor.#sanitizeStorageName(footprintName),
333
- sectionKeys[footprintName],
334
- PcbLibStreamExtractor.#sanitizeStorageName(
335
- footprintName.slice(0, 31)
336
- )
337
- ].filter(Boolean)
338
-
339
- return (
340
- candidates.find((candidate) => streams.has(candidate + '/Data')) ||
341
- ''
342
- )
343
- }
344
-
345
278
  /**
346
279
  * Extracts one footprint storage into normalized primitive lists.
347
280
  * @param {Map<string, Uint8Array>} streams
@@ -1042,17 +975,6 @@ export class PcbLibStreamExtractor {
1042
975
  })
1043
976
  }
1044
977
 
1045
- /**
1046
- * Sanitizes one footprint name for legacy OLE storage lookup.
1047
- * @param {string} name
1048
- * @returns {string}
1049
- */
1050
- static #sanitizeStorageName(name) {
1051
- return String(name || '')
1052
- .replace(/[/:\\]/gu, '_')
1053
- .slice(0, 31)
1054
- }
1055
-
1056
978
  /**
1057
979
  * Reads a little-endian unsigned 32-bit value from one byte array.
1058
980
  * @param {Uint8Array} bytes