altium-toolkit 0.1.0

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 (82) hide show
  1. package/AGENTS.md +67 -0
  2. package/COMMERCIAL-LICENSE.md +20 -0
  3. package/CONTRIBUTING.md +19 -0
  4. package/LICENSE +22 -0
  5. package/LICENSES/CC-BY-SA-4.0.txt +170 -0
  6. package/LICENSES/GPL-3.0-or-later.txt +232 -0
  7. package/NOTICE.md +32 -0
  8. package/README.md +116 -0
  9. package/docs/api.md +73 -0
  10. package/docs/model-format.md +36 -0
  11. package/docs/testing.md +25 -0
  12. package/examples/README.md +47 -0
  13. package/examples/arduino-uno/PcbThreeSceneRenderer.mjs +635 -0
  14. package/examples/arduino-uno/SvgViewportController.mjs +306 -0
  15. package/examples/arduino-uno/example.mjs +480 -0
  16. package/examples/arduino-uno/index.html +163 -0
  17. package/examples/arduino-uno/styles.css +552 -0
  18. package/examples/server.mjs +212 -0
  19. package/package.json +53 -0
  20. package/spec/library-scope.md +32 -0
  21. package/src/core/BinaryReader.mjs +127 -0
  22. package/src/core/altium/AltiumLayoutParser.mjs +485 -0
  23. package/src/core/altium/AltiumParser.mjs +1007 -0
  24. package/src/core/altium/AsciiRecordParser.mjs +151 -0
  25. package/src/core/altium/ParserUtils.mjs +173 -0
  26. package/src/core/altium/PcbBinaryPrimitiveParser.mjs +424 -0
  27. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +505 -0
  28. package/src/core/altium/PcbModelParser.mjs +336 -0
  29. package/src/core/altium/PcbOutlineRasterizer.mjs +852 -0
  30. package/src/core/altium/PcbOutlineRecovery.mjs +957 -0
  31. package/src/core/altium/PcbStreamExtractor.mjs +210 -0
  32. package/src/core/altium/PrintableTextDecoder.mjs +156 -0
  33. package/src/core/altium/SchematicAnnotationParser.mjs +220 -0
  34. package/src/core/altium/SchematicBusEntryParser.mjs +48 -0
  35. package/src/core/altium/SchematicDirectiveParser.mjs +47 -0
  36. package/src/core/altium/SchematicImageParser.mjs +173 -0
  37. package/src/core/altium/SchematicJunctionParser.mjs +43 -0
  38. package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +564 -0
  39. package/src/core/altium/SchematicNetlistBuilder.mjs +351 -0
  40. package/src/core/altium/SchematicPinParser.mjs +767 -0
  41. package/src/core/altium/SchematicPrimitiveParser.mjs +716 -0
  42. package/src/core/altium/SchematicSheetParser.mjs +241 -0
  43. package/src/core/altium/SchematicSheetStyleResolver.mjs +46 -0
  44. package/src/core/altium/SchematicStandaloneCalloutNormalizer.mjs +592 -0
  45. package/src/core/altium/SchematicTextParser.mjs +708 -0
  46. package/src/core/altium/SchematicTextPostProcessor.mjs +801 -0
  47. package/src/core/ole/OleCompoundDocument.mjs +439 -0
  48. package/src/core/ole/OleConstants.mjs +64 -0
  49. package/src/core/ole/OleDirectoryEntry.mjs +95 -0
  50. package/src/index.mjs +7 -0
  51. package/src/parser.mjs +21 -0
  52. package/src/renderers.mjs +15 -0
  53. package/src/scene3d.mjs +9 -0
  54. package/src/styles/altium-renderers.css +358 -0
  55. package/src/ui/BomTableRenderer.mjs +46 -0
  56. package/src/ui/PcbArcUtils.mjs +189 -0
  57. package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +808 -0
  58. package/src/ui/PcbFootprintPrimitiveSelector.mjs +128 -0
  59. package/src/ui/PcbScene3dBuilder.mjs +742 -0
  60. package/src/ui/PcbScene3dModelRegistry.mjs +309 -0
  61. package/src/ui/PcbScene3dPackages.mjs +137 -0
  62. package/src/ui/PcbScene3dScenePreparator.mjs +36 -0
  63. package/src/ui/PcbScene3dSummaryRenderer.mjs +65 -0
  64. package/src/ui/PcbSvgRenderer.mjs +906 -0
  65. package/src/ui/SchematicColorResolver.mjs +132 -0
  66. package/src/ui/SchematicContentLayout.mjs +661 -0
  67. package/src/ui/SchematicDirectiveRenderer.mjs +184 -0
  68. package/src/ui/SchematicImageRenderer.mjs +135 -0
  69. package/src/ui/SchematicJunctionRenderer.mjs +381 -0
  70. package/src/ui/SchematicNoteRenderer.mjs +427 -0
  71. package/src/ui/SchematicOwnerPinLabelLayout.mjs +173 -0
  72. package/src/ui/SchematicPinSvgRenderer.mjs +495 -0
  73. package/src/ui/SchematicPortRenderer.mjs +558 -0
  74. package/src/ui/SchematicPowerPortRenderer.mjs +574 -0
  75. package/src/ui/SchematicRegionRenderer.mjs +94 -0
  76. package/src/ui/SchematicShapeRenderer.mjs +398 -0
  77. package/src/ui/SchematicSheetChromeRenderer.mjs +1025 -0
  78. package/src/ui/SchematicSheetSymbolRenderer.mjs +228 -0
  79. package/src/ui/SchematicSvgRenderer.mjs +756 -0
  80. package/src/ui/SchematicSvgUtils.mjs +182 -0
  81. package/src/ui/SchematicTypography.mjs +204 -0
  82. package/src/workers/altium-parser.worker.mjs +29 -0
@@ -0,0 +1,439 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { BinaryReader } from '../BinaryReader.mjs'
6
+ import { OleConstants } from './OleConstants.mjs'
7
+ import { OleDirectoryEntry } from './OleDirectoryEntry.mjs'
8
+
9
+ /**
10
+ * Reads OLE Compound Document streams.
11
+ */
12
+ export class OleCompoundDocument {
13
+ #directoryEntries
14
+
15
+ #fatEntries
16
+
17
+ #header
18
+
19
+ #miniFatEntries
20
+
21
+ #miniStreamBytes
22
+
23
+ #reader
24
+
25
+ #streamPaths
26
+
27
+ /**
28
+ * @param {ArrayBuffer} arrayBuffer
29
+ */
30
+ constructor(arrayBuffer) {
31
+ this.#reader = new BinaryReader(arrayBuffer)
32
+ this.#header = this.#parseHeader()
33
+ this.#fatEntries = this.#parseFatEntries()
34
+ this.#directoryEntries = this.#parseDirectoryEntries()
35
+ this.#miniFatEntries = this.#parseMiniFatEntries()
36
+ this.#miniStreamBytes = this.#parseMiniStreamBytes()
37
+ this.#streamPaths = this.#collectStreamPaths()
38
+ }
39
+
40
+ /**
41
+ * Creates one compound document reader from raw bytes.
42
+ * @param {ArrayBuffer} arrayBuffer
43
+ * @returns {OleCompoundDocument}
44
+ */
45
+ static fromArrayBuffer(arrayBuffer) {
46
+ return new OleCompoundDocument(arrayBuffer)
47
+ }
48
+
49
+ /**
50
+ * Returns the sector size in bytes.
51
+ * @returns {number}
52
+ */
53
+ get sectorByteLength() {
54
+ return this.#header.sectorByteLength
55
+ }
56
+
57
+ /**
58
+ * Returns the mini-sector size in bytes.
59
+ * @returns {number}
60
+ */
61
+ get miniSectorByteLength() {
62
+ return this.#header.miniSectorByteLength
63
+ }
64
+
65
+ /**
66
+ * Lists all stream paths.
67
+ * @returns {string[]}
68
+ */
69
+ listStreams() {
70
+ return [...this.#streamPaths.keys()].sort((left, right) =>
71
+ left.localeCompare(right)
72
+ )
73
+ }
74
+
75
+ /**
76
+ * Resolves one stream by path or unique leaf name.
77
+ * @param {string} name
78
+ * @returns {Uint8Array}
79
+ */
80
+ getStream(name) {
81
+ const directMatch = this.#streamPaths.get(name)
82
+ if (directMatch) {
83
+ return this.#readEntryStream(directMatch)
84
+ }
85
+
86
+ const leafMatches = [...this.#streamPaths.entries()].filter(
87
+ ([path]) => path.split('/').at(-1) === name
88
+ )
89
+
90
+ if (!leafMatches.length) {
91
+ throw new Error('OLE stream not found: ' + name)
92
+ }
93
+
94
+ if (leafMatches.length > 1) {
95
+ throw new Error('OLE stream name is ambiguous: ' + name)
96
+ }
97
+
98
+ return this.#readEntryStream(leafMatches[0][1])
99
+ }
100
+
101
+ /**
102
+ * Parses and validates the OLE header.
103
+ * @returns {{ sectorByteLength: number, miniSectorByteLength: number, numberOfFatSectors: number, firstDirectorySector: number, miniStreamCutoff: number, firstMiniFatSector: number, numberOfMiniFatSectors: number, firstDifatSector: number, numberOfDifatSectors: number, difatEntries: number[] }}
104
+ */
105
+ #parseHeader() {
106
+ const signature = [
107
+ ...this.#reader.readBytes(0, OleConstants.HEADER_SIGNATURE.length)
108
+ ]
109
+
110
+ if (
111
+ signature.some(
112
+ (value, index) => value !== OleConstants.HEADER_SIGNATURE[index]
113
+ )
114
+ ) {
115
+ throw new Error('Invalid OLE header signature.')
116
+ }
117
+
118
+ const sectorByteLength = 2 ** this.#reader.readUint16(30)
119
+ const miniSectorByteLength = 2 ** this.#reader.readUint16(32)
120
+ const difatEntries = []
121
+
122
+ for (let index = 0; index < 109; index += 1) {
123
+ difatEntries.push(this.#reader.readInt32(76 + index * 4))
124
+ }
125
+
126
+ return {
127
+ sectorByteLength,
128
+ miniSectorByteLength,
129
+ numberOfFatSectors: this.#reader.readUint32(44),
130
+ firstDirectorySector: this.#reader.readInt32(48),
131
+ miniStreamCutoff: this.#reader.readUint32(56),
132
+ firstMiniFatSector: this.#reader.readInt32(60),
133
+ numberOfMiniFatSectors: this.#reader.readUint32(64),
134
+ firstDifatSector: this.#reader.readInt32(68),
135
+ numberOfDifatSectors: this.#reader.readUint32(72),
136
+ difatEntries
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Parses all FAT entries.
142
+ * @returns {number[]}
143
+ */
144
+ #parseFatEntries() {
145
+ const fatSectorIds = this.#collectFatSectorIds()
146
+ const entries = []
147
+ const intsPerSector = this.#header.sectorByteLength / 4
148
+
149
+ for (const sectorId of fatSectorIds) {
150
+ const sectorBytes = this.#readSectorBytes(sectorId)
151
+ const reader = new BinaryReader(sectorBytes.buffer)
152
+
153
+ for (let index = 0; index < intsPerSector; index += 1) {
154
+ entries.push(reader.readInt32(index * 4))
155
+ }
156
+ }
157
+
158
+ return entries
159
+ }
160
+
161
+ /**
162
+ * Collects all FAT sector identifiers from the header and optional DIFAT
163
+ * sectors.
164
+ * @returns {number[]}
165
+ */
166
+ #collectFatSectorIds() {
167
+ const fatSectorIds = this.#header.difatEntries.filter(
168
+ (sectorId) => sectorId >= 0
169
+ )
170
+
171
+ if (
172
+ this.#header.firstDifatSector < 0 ||
173
+ this.#header.numberOfDifatSectors === 0
174
+ ) {
175
+ return fatSectorIds.slice(0, this.#header.numberOfFatSectors)
176
+ }
177
+
178
+ const intsPerSector = this.#header.sectorByteLength / 4
179
+ let currentSectorId = this.#header.firstDifatSector
180
+
181
+ for (
182
+ let difatIndex = 0;
183
+ difatIndex < this.#header.numberOfDifatSectors &&
184
+ currentSectorId >= 0;
185
+ difatIndex += 1
186
+ ) {
187
+ const sectorReader = new BinaryReader(
188
+ this.#readSectorBytes(currentSectorId).buffer
189
+ )
190
+
191
+ for (let index = 0; index < intsPerSector - 1; index += 1) {
192
+ const fatSectorId = sectorReader.readInt32(index * 4)
193
+ if (fatSectorId >= 0) {
194
+ fatSectorIds.push(fatSectorId)
195
+ }
196
+ }
197
+
198
+ currentSectorId = sectorReader.readInt32((intsPerSector - 1) * 4)
199
+ }
200
+
201
+ return fatSectorIds.slice(0, this.#header.numberOfFatSectors)
202
+ }
203
+
204
+ /**
205
+ * Parses all directory entries reachable from the directory stream chain.
206
+ * @returns {OleDirectoryEntry[]}
207
+ */
208
+ #parseDirectoryEntries() {
209
+ const directoryChain = this.#readRegularChain(
210
+ this.#header.firstDirectorySector
211
+ )
212
+ const entryCount =
213
+ directoryChain.length / OleConstants.DIRECTORY_ENTRY_BYTE_LENGTH
214
+ const entries = []
215
+
216
+ for (let index = 0; index < entryCount; index += 1) {
217
+ const offset = index * OleConstants.DIRECTORY_ENTRY_BYTE_LENGTH
218
+ entries.push(
219
+ OleDirectoryEntry.fromBytes(
220
+ directoryChain.slice(
221
+ offset,
222
+ offset + OleConstants.DIRECTORY_ENTRY_BYTE_LENGTH
223
+ ),
224
+ index
225
+ )
226
+ )
227
+ }
228
+
229
+ return entries
230
+ }
231
+
232
+ /**
233
+ * Parses all mini FAT entries.
234
+ * @returns {number[]}
235
+ */
236
+ #parseMiniFatEntries() {
237
+ if (
238
+ this.#header.firstMiniFatSector < 0 ||
239
+ this.#header.numberOfMiniFatSectors === 0
240
+ ) {
241
+ return []
242
+ }
243
+
244
+ const chain = this.#readRegularChain(this.#header.firstMiniFatSector)
245
+ const reader = new BinaryReader(chain.buffer)
246
+ const entryCount = chain.length / 4
247
+ const entries = []
248
+
249
+ for (let index = 0; index < entryCount; index += 1) {
250
+ entries.push(reader.readInt32(index * 4))
251
+ }
252
+
253
+ return entries
254
+ }
255
+
256
+ /**
257
+ * Reads the root mini-stream bytes.
258
+ * @returns {Uint8Array}
259
+ */
260
+ #parseMiniStreamBytes() {
261
+ const rootEntry = this.#directoryEntries.find((entry) =>
262
+ entry.isRootStorage()
263
+ )
264
+ if (!rootEntry || rootEntry.startSector < 0 || !rootEntry.streamSize) {
265
+ return new Uint8Array(0)
266
+ }
267
+
268
+ return this.#readRegularChain(rootEntry.startSector).slice(
269
+ 0,
270
+ rootEntry.streamSize
271
+ )
272
+ }
273
+
274
+ /**
275
+ * Builds a path map for all reachable stream entries.
276
+ * @returns {Map<string, OleDirectoryEntry>}
277
+ */
278
+ #collectStreamPaths() {
279
+ const rootEntry = this.#directoryEntries.find((entry) =>
280
+ entry.isRootStorage()
281
+ )
282
+ const paths = new Map()
283
+
284
+ if (!rootEntry || rootEntry.childId < 0) {
285
+ return paths
286
+ }
287
+
288
+ this.#walkDirectoryTree(rootEntry.childId, '', paths)
289
+
290
+ return paths
291
+ }
292
+
293
+ /**
294
+ * Traverses one directory sibling tree.
295
+ * @param {number} entryId
296
+ * @param {string} basePath
297
+ * @param {Map<string, OleDirectoryEntry>} paths
298
+ */
299
+ #walkDirectoryTree(entryId, basePath, paths) {
300
+ if (entryId < 0) {
301
+ return
302
+ }
303
+
304
+ const entry = this.#directoryEntries[entryId]
305
+ if (!entry) {
306
+ return
307
+ }
308
+
309
+ this.#walkDirectoryTree(entry.leftSiblingId, basePath, paths)
310
+
311
+ const path = basePath ? basePath + '/' + entry.name : entry.name
312
+
313
+ if (entry.isStream()) {
314
+ paths.set(path, entry)
315
+ }
316
+
317
+ if (entry.isStorage() && !entry.isRootStorage() && entry.childId >= 0) {
318
+ this.#walkDirectoryTree(entry.childId, path, paths)
319
+ }
320
+
321
+ this.#walkDirectoryTree(entry.rightSiblingId, basePath, paths)
322
+ }
323
+
324
+ /**
325
+ * Reads one stream entry using the standard or mini FAT.
326
+ * @param {OleDirectoryEntry} entry
327
+ * @returns {Uint8Array}
328
+ */
329
+ #readEntryStream(entry) {
330
+ if (entry.streamSize < this.#header.miniStreamCutoff) {
331
+ return this.#readMiniStream(entry)
332
+ }
333
+
334
+ return this.#readRegularChain(entry.startSector).slice(
335
+ 0,
336
+ entry.streamSize
337
+ )
338
+ }
339
+
340
+ /**
341
+ * Reads one standard-sector chain.
342
+ * @param {number} startSectorId
343
+ * @returns {Uint8Array}
344
+ */
345
+ #readRegularChain(startSectorId) {
346
+ return this.#concatenateSectors(
347
+ this.#readSectorChain(startSectorId, this.#fatEntries),
348
+ this.#header.sectorByteLength,
349
+ (sectorId) => this.#readSectorBytes(sectorId)
350
+ )
351
+ }
352
+
353
+ /**
354
+ * Reads one mini-sector-backed stream.
355
+ * @param {OleDirectoryEntry} entry
356
+ * @returns {Uint8Array}
357
+ */
358
+ #readMiniStream(entry) {
359
+ const sectorIds = this.#readSectorChain(
360
+ entry.startSector,
361
+ this.#miniFatEntries
362
+ )
363
+ const bytes = this.#concatenateSectors(
364
+ sectorIds,
365
+ this.#header.miniSectorByteLength,
366
+ (miniSectorId) =>
367
+ this.#miniStreamBytes.slice(
368
+ miniSectorId * this.#header.miniSectorByteLength,
369
+ (miniSectorId + 1) * this.#header.miniSectorByteLength
370
+ )
371
+ )
372
+
373
+ return bytes.slice(0, entry.streamSize)
374
+ }
375
+
376
+ /**
377
+ * Reads one chain of sector identifiers.
378
+ * @param {number} startSectorId
379
+ * @param {number[]} entries
380
+ * @returns {number[]}
381
+ */
382
+ #readSectorChain(startSectorId, entries) {
383
+ const sectorIds = []
384
+ const visited = new Set()
385
+ let currentSectorId = startSectorId
386
+
387
+ while (currentSectorId >= 0) {
388
+ if (visited.has(currentSectorId)) {
389
+ throw new Error(
390
+ 'OLE sector chain loop detected at sector ' +
391
+ currentSectorId +
392
+ '.'
393
+ )
394
+ }
395
+
396
+ visited.add(currentSectorId)
397
+ sectorIds.push(currentSectorId)
398
+
399
+ const nextSectorId = entries[currentSectorId]
400
+ if (nextSectorId === OleConstants.END_OF_CHAIN) {
401
+ break
402
+ }
403
+
404
+ currentSectorId = nextSectorId
405
+ }
406
+
407
+ return sectorIds
408
+ }
409
+
410
+ /**
411
+ * Concatenates sector bytes from one chain.
412
+ * @param {number[]} sectorIds
413
+ * @param {number} sectorByteLength
414
+ * @param {(sectorId: number) => Uint8Array} byteResolver
415
+ * @returns {Uint8Array}
416
+ */
417
+ #concatenateSectors(sectorIds, sectorByteLength, byteResolver) {
418
+ const bytes = new Uint8Array(sectorIds.length * sectorByteLength)
419
+
420
+ for (let index = 0; index < sectorIds.length; index += 1) {
421
+ bytes.set(byteResolver(sectorIds[index]), index * sectorByteLength)
422
+ }
423
+
424
+ return bytes
425
+ }
426
+
427
+ /**
428
+ * Reads one regular sector.
429
+ * @param {number} sectorId
430
+ * @returns {Uint8Array}
431
+ */
432
+ #readSectorBytes(sectorId) {
433
+ const offset =
434
+ OleConstants.HEADER_BYTE_LENGTH +
435
+ sectorId * this.#header.sectorByteLength
436
+
437
+ return this.#reader.readBytes(offset, this.#header.sectorByteLength)
438
+ }
439
+ }
@@ -0,0 +1,64 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Shared OLE Compound Document constants.
7
+ */
8
+ export class OleConstants {
9
+ /**
10
+ * @returns {number}
11
+ */
12
+ static get HEADER_BYTE_LENGTH() {
13
+ return 512
14
+ }
15
+
16
+ /**
17
+ * @returns {number}
18
+ */
19
+ static get DIRECTORY_ENTRY_BYTE_LENGTH() {
20
+ return 128
21
+ }
22
+
23
+ /**
24
+ * @returns {number[]}
25
+ */
26
+ static get HEADER_SIGNATURE() {
27
+ return [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]
28
+ }
29
+
30
+ /**
31
+ * @returns {number}
32
+ */
33
+ static get FREE_SECTOR() {
34
+ return -1
35
+ }
36
+
37
+ /**
38
+ * @returns {number}
39
+ */
40
+ static get END_OF_CHAIN() {
41
+ return -2
42
+ }
43
+
44
+ /**
45
+ * @returns {number}
46
+ */
47
+ static get FAT_SECTOR() {
48
+ return -3
49
+ }
50
+
51
+ /**
52
+ * @returns {number}
53
+ */
54
+ static get DIFAT_SECTOR() {
55
+ return -4
56
+ }
57
+
58
+ /**
59
+ * @returns {number}
60
+ */
61
+ static get NO_STREAM() {
62
+ return -1
63
+ }
64
+ }
@@ -0,0 +1,95 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { BinaryReader } from '../BinaryReader.mjs'
6
+ import { OleConstants } from './OleConstants.mjs'
7
+
8
+ /**
9
+ * Represents one decoded OLE directory entry.
10
+ */
11
+ export class OleDirectoryEntry {
12
+ /**
13
+ * @param {{ id: number, name: string, type: number, leftSiblingId: number, rightSiblingId: number, childId: number, startSector: number, streamSize: number }} options
14
+ */
15
+ constructor(options) {
16
+ this.id = options.id
17
+ this.name = options.name
18
+ this.type = options.type
19
+ this.leftSiblingId = options.leftSiblingId
20
+ this.rightSiblingId = options.rightSiblingId
21
+ this.childId = options.childId
22
+ this.startSector = options.startSector
23
+ this.streamSize = options.streamSize
24
+ }
25
+
26
+ /**
27
+ * Decodes one raw directory-entry buffer.
28
+ * @param {Uint8Array} bytes
29
+ * @param {number} id
30
+ * @returns {OleDirectoryEntry}
31
+ */
32
+ static fromBytes(bytes, id) {
33
+ const reader = new BinaryReader(
34
+ bytes.buffer.slice(
35
+ bytes.byteOffset,
36
+ bytes.byteOffset + bytes.byteLength
37
+ )
38
+ )
39
+ const nameByteLength = Math.max(
40
+ Math.min(reader.readUint16(64), 64) - 2,
41
+ 0
42
+ )
43
+ const name = new TextDecoder('utf-16le')
44
+ .decode(reader.readBytes(0, nameByteLength))
45
+ .replace(/\u0000+$/g, '')
46
+
47
+ return new OleDirectoryEntry({
48
+ id,
49
+ name,
50
+ type: reader.readUint8(66),
51
+ leftSiblingId: reader.readInt32(68),
52
+ rightSiblingId: reader.readInt32(72),
53
+ childId: reader.readInt32(76),
54
+ startSector: reader.readInt32(116),
55
+ streamSize: reader.readUint64(120)
56
+ })
57
+ }
58
+
59
+ /**
60
+ * Returns true when this entry stores stream bytes.
61
+ * @returns {boolean}
62
+ */
63
+ isStream() {
64
+ return this.type === 2
65
+ }
66
+
67
+ /**
68
+ * Returns true when this entry is the root storage.
69
+ * @returns {boolean}
70
+ */
71
+ isRootStorage() {
72
+ return this.type === 5
73
+ }
74
+
75
+ /**
76
+ * Returns true when this entry is a non-root storage.
77
+ * @returns {boolean}
78
+ */
79
+ isStorage() {
80
+ return this.type === 1 || this.type === 5
81
+ }
82
+
83
+ /**
84
+ * Returns true when the entry has no useful payload.
85
+ * @returns {boolean}
86
+ */
87
+ isEmpty() {
88
+ return (
89
+ !this.name &&
90
+ this.type === 0 &&
91
+ this.startSector === OleConstants.NO_STREAM &&
92
+ this.streamSize === 0
93
+ )
94
+ }
95
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ export * from './parser.mjs'
6
+ export * from './renderers.mjs'
7
+ export * from './scene3d.mjs'
package/src/parser.mjs ADDED
@@ -0,0 +1,21 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ export { BinaryReader } from './core/BinaryReader.mjs'
6
+ export { OleCompoundDocument } from './core/ole/OleCompoundDocument.mjs'
7
+ export { OleConstants } from './core/ole/OleConstants.mjs'
8
+ export { OleDirectoryEntry } from './core/ole/OleDirectoryEntry.mjs'
9
+ export { AltiumParser } from './core/altium/AltiumParser.mjs'
10
+ export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
11
+ export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
12
+ export { ParserUtils } from './core/altium/ParserUtils.mjs'
13
+ export { PcbBinaryPrimitiveParser } from './core/altium/PcbBinaryPrimitiveParser.mjs'
14
+ export { PcbEmbeddedModelExtractor } from './core/altium/PcbEmbeddedModelExtractor.mjs'
15
+ export { PcbModelParser } from './core/altium/PcbModelParser.mjs'
16
+ export { PcbOutlineRasterizer } from './core/altium/PcbOutlineRasterizer.mjs'
17
+ export { PcbOutlineRecovery } from './core/altium/PcbOutlineRecovery.mjs'
18
+ export { PcbStreamExtractor } from './core/altium/PcbStreamExtractor.mjs'
19
+ export { PrintableTextDecoder } from './core/altium/PrintableTextDecoder.mjs'
20
+ export { SchematicMultipartOwnerMatcher } from './core/altium/SchematicMultipartOwnerMatcher.mjs'
21
+ export { SchematicStandaloneCalloutNormalizer } from './core/altium/SchematicStandaloneCalloutNormalizer.mjs'
@@ -0,0 +1,15 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ export { BomTableRenderer } from './ui/BomTableRenderer.mjs'
6
+ export { PcbArcUtils } from './ui/PcbArcUtils.mjs'
7
+ export { PcbEdgeFacingGlyphNormalizer } from './ui/PcbEdgeFacingGlyphNormalizer.mjs'
8
+ export { PcbFootprintPrimitiveSelector } from './ui/PcbFootprintPrimitiveSelector.mjs'
9
+ export { PcbSvgRenderer } from './ui/PcbSvgRenderer.mjs'
10
+ export { SchematicColorResolver } from './ui/SchematicColorResolver.mjs'
11
+ export { SchematicContentLayout } from './ui/SchematicContentLayout.mjs'
12
+ export { SchematicOwnerPinLabelLayout } from './ui/SchematicOwnerPinLabelLayout.mjs'
13
+ export { SchematicSvgRenderer } from './ui/SchematicSvgRenderer.mjs'
14
+ export { SchematicSvgUtils } from './ui/SchematicSvgUtils.mjs'
15
+ export { SchematicTypography } from './ui/SchematicTypography.mjs'
@@ -0,0 +1,9 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ export { PcbScene3dBuilder } from './ui/PcbScene3dBuilder.mjs'
6
+ export { PcbScene3dModelRegistry } from './ui/PcbScene3dModelRegistry.mjs'
7
+ export { PcbScene3dPackages } from './ui/PcbScene3dPackages.mjs'
8
+ export { PcbScene3dScenePreparator } from './ui/PcbScene3dScenePreparator.mjs'
9
+ export { PcbScene3dSummaryRenderer } from './ui/PcbScene3dSummaryRenderer.mjs'