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
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { OleConstants } from './OleConstants.mjs'
|
|
6
|
+
|
|
7
|
+
const sectorSize = 512
|
|
8
|
+
const directoryEntrySize = 128
|
|
9
|
+
const regularEntriesPerFatSector = sectorSize / 4
|
|
10
|
+
const rootEntryType = 5
|
|
11
|
+
const storageEntryType = 1
|
|
12
|
+
const streamEntryType = 2
|
|
13
|
+
const emptyEntryType = 0
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Writes compact OLE Compound Document files for generated Altium libraries.
|
|
17
|
+
*/
|
|
18
|
+
export class OleCompoundDocumentWriter {
|
|
19
|
+
/**
|
|
20
|
+
* Writes one compound document from a map of stream paths.
|
|
21
|
+
* @param {{ streams?: Map<string, Uint8Array> | [string, Uint8Array][] }} options Writer options.
|
|
22
|
+
* @returns {Uint8Array}
|
|
23
|
+
*/
|
|
24
|
+
static write(options = {}) {
|
|
25
|
+
const streams = OleCompoundDocumentWriter.#normalizeStreams(
|
|
26
|
+
options.streams
|
|
27
|
+
)
|
|
28
|
+
const entries =
|
|
29
|
+
OleCompoundDocumentWriter.#buildDirectoryEntries(streams)
|
|
30
|
+
const directorySectorCount = Math.ceil(
|
|
31
|
+
(OleCompoundDocumentWriter.#paddedDirectoryEntries(entries).length *
|
|
32
|
+
directoryEntrySize) /
|
|
33
|
+
sectorSize
|
|
34
|
+
)
|
|
35
|
+
const streamSectorCounts = streams.map((stream) =>
|
|
36
|
+
Math.ceil(stream.bytes.byteLength / sectorSize)
|
|
37
|
+
)
|
|
38
|
+
const dataSectorCount =
|
|
39
|
+
directorySectorCount +
|
|
40
|
+
streamSectorCounts.reduce((sum, count) => sum + count, 0)
|
|
41
|
+
const fatSectorCount =
|
|
42
|
+
OleCompoundDocumentWriter.#resolveFatSectorCount(dataSectorCount)
|
|
43
|
+
const totalSectorCount = dataSectorCount + fatSectorCount
|
|
44
|
+
const bytes = new Uint8Array(
|
|
45
|
+
OleConstants.HEADER_BYTE_LENGTH + totalSectorCount * sectorSize
|
|
46
|
+
)
|
|
47
|
+
const dataView = new DataView(bytes.buffer)
|
|
48
|
+
const fatEntries = new Array(totalSectorCount).fill(
|
|
49
|
+
OleConstants.FREE_SECTOR
|
|
50
|
+
)
|
|
51
|
+
const fatSectorStart = dataSectorCount
|
|
52
|
+
|
|
53
|
+
let nextSector = directorySectorCount
|
|
54
|
+
streams.forEach((stream, index) => {
|
|
55
|
+
const sectorCount = streamSectorCounts[index]
|
|
56
|
+
stream.startSector = sectorCount
|
|
57
|
+
? nextSector
|
|
58
|
+
: OleConstants.END_OF_CHAIN
|
|
59
|
+
nextSector += sectorCount
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
OleCompoundDocumentWriter.#writeHeader(
|
|
63
|
+
dataView,
|
|
64
|
+
fatSectorStart,
|
|
65
|
+
fatSectorCount
|
|
66
|
+
)
|
|
67
|
+
OleCompoundDocumentWriter.#writeDirectory(
|
|
68
|
+
bytes,
|
|
69
|
+
fatEntries,
|
|
70
|
+
entries,
|
|
71
|
+
directorySectorCount
|
|
72
|
+
)
|
|
73
|
+
OleCompoundDocumentWriter.#writeStreams(
|
|
74
|
+
bytes,
|
|
75
|
+
fatEntries,
|
|
76
|
+
streams,
|
|
77
|
+
streamSectorCounts
|
|
78
|
+
)
|
|
79
|
+
OleCompoundDocumentWriter.#writeFat(
|
|
80
|
+
bytes,
|
|
81
|
+
dataView,
|
|
82
|
+
fatEntries,
|
|
83
|
+
fatSectorStart,
|
|
84
|
+
fatSectorCount
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return bytes
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Normalizes caller-provided streams into sorted byte entries.
|
|
92
|
+
* @param {Map<string, Uint8Array> | [string, Uint8Array][] | undefined} streams Stream map.
|
|
93
|
+
* @returns {{ path: string, bytes: Uint8Array, startSector: number }[]}
|
|
94
|
+
*/
|
|
95
|
+
static #normalizeStreams(streams) {
|
|
96
|
+
return [...new Map(streams || []).entries()]
|
|
97
|
+
.map(([path, bytes]) => ({
|
|
98
|
+
path: OleCompoundDocumentWriter.#normalizePath(path),
|
|
99
|
+
bytes: bytes instanceof Uint8Array ? bytes : new Uint8Array(0),
|
|
100
|
+
startSector: OleConstants.END_OF_CHAIN
|
|
101
|
+
}))
|
|
102
|
+
.filter((entry) => entry.path)
|
|
103
|
+
.sort((left, right) => left.path.localeCompare(right.path))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normalizes one stream path.
|
|
108
|
+
* @param {any} path Stream path.
|
|
109
|
+
* @returns {string}
|
|
110
|
+
*/
|
|
111
|
+
static #normalizePath(path) {
|
|
112
|
+
return String(path || '')
|
|
113
|
+
.split('/')
|
|
114
|
+
.map((part) => part.trim())
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join('/')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Builds directory entries and a simple right-sibling tree.
|
|
121
|
+
* @param {{ path: string, bytes: Uint8Array }[]} streams Stream entries.
|
|
122
|
+
* @returns {object[]}
|
|
123
|
+
*/
|
|
124
|
+
static #buildDirectoryEntries(streams) {
|
|
125
|
+
const entries = [
|
|
126
|
+
OleCompoundDocumentWriter.#createDirectoryEntry('Root Entry', {
|
|
127
|
+
type: rootEntryType
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
const childrenByParent = new Map([[0, []]])
|
|
131
|
+
const storageIdsByPath = new Map([['', 0]])
|
|
132
|
+
|
|
133
|
+
for (const stream of streams) {
|
|
134
|
+
const parts = stream.path.split('/')
|
|
135
|
+
let parentPath = ''
|
|
136
|
+
let parentId = 0
|
|
137
|
+
|
|
138
|
+
for (const part of parts.slice(0, -1)) {
|
|
139
|
+
const storagePath = parentPath ? parentPath + '/' + part : part
|
|
140
|
+
if (!storageIdsByPath.has(storagePath)) {
|
|
141
|
+
const storageId = entries.length
|
|
142
|
+
storageIdsByPath.set(storagePath, storageId)
|
|
143
|
+
entries.push(
|
|
144
|
+
OleCompoundDocumentWriter.#createDirectoryEntry(part, {
|
|
145
|
+
type: storageEntryType
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
OleCompoundDocumentWriter.#appendChild(
|
|
149
|
+
childrenByParent,
|
|
150
|
+
parentId,
|
|
151
|
+
storageId
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
parentPath = storagePath
|
|
155
|
+
parentId = storageIdsByPath.get(storagePath)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const streamId = entries.length
|
|
159
|
+
entries.push(
|
|
160
|
+
OleCompoundDocumentWriter.#createDirectoryEntry(parts.at(-1), {
|
|
161
|
+
type: streamEntryType,
|
|
162
|
+
stream
|
|
163
|
+
})
|
|
164
|
+
)
|
|
165
|
+
OleCompoundDocumentWriter.#appendChild(
|
|
166
|
+
childrenByParent,
|
|
167
|
+
parentId,
|
|
168
|
+
streamId
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
OleCompoundDocumentWriter.#linkSiblingTrees(entries, childrenByParent)
|
|
173
|
+
|
|
174
|
+
return entries
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates one mutable directory entry.
|
|
179
|
+
* @param {string} name Entry name.
|
|
180
|
+
* @param {{ type: number, stream?: object }} options Entry options.
|
|
181
|
+
* @returns {object}
|
|
182
|
+
*/
|
|
183
|
+
static #createDirectoryEntry(name, options) {
|
|
184
|
+
return {
|
|
185
|
+
name,
|
|
186
|
+
type: options.type,
|
|
187
|
+
leftSiblingId: OleConstants.NO_STREAM,
|
|
188
|
+
rightSiblingId: OleConstants.NO_STREAM,
|
|
189
|
+
childId: OleConstants.NO_STREAM,
|
|
190
|
+
stream: options.stream || null
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Appends a child entry id to the parent lookup table.
|
|
196
|
+
* @param {Map<number, number[]>} childrenByParent Child lookup.
|
|
197
|
+
* @param {number} parentId Parent entry id.
|
|
198
|
+
* @param {number} childId Child entry id.
|
|
199
|
+
* @returns {void}
|
|
200
|
+
*/
|
|
201
|
+
static #appendChild(childrenByParent, parentId, childId) {
|
|
202
|
+
if (!childrenByParent.has(parentId)) {
|
|
203
|
+
childrenByParent.set(parentId, [])
|
|
204
|
+
}
|
|
205
|
+
childrenByParent.get(parentId).push(childId)
|
|
206
|
+
childrenByParent.set(childId, [])
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Links child lists into deterministic right-sibling chains.
|
|
211
|
+
* @param {object[]} entries Directory entries.
|
|
212
|
+
* @param {Map<number, number[]>} childrenByParent Child lookup.
|
|
213
|
+
* @returns {void}
|
|
214
|
+
*/
|
|
215
|
+
static #linkSiblingTrees(entries, childrenByParent) {
|
|
216
|
+
for (const [parentId, childIds] of childrenByParent.entries()) {
|
|
217
|
+
const sortedChildIds = [...childIds].sort((left, right) =>
|
|
218
|
+
entries[left].name.localeCompare(entries[right].name)
|
|
219
|
+
)
|
|
220
|
+
if (!sortedChildIds.length) {
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
entries[parentId].childId = sortedChildIds[0]
|
|
224
|
+
sortedChildIds.slice(0, -1).forEach((childId, index) => {
|
|
225
|
+
entries[childId].rightSiblingId = sortedChildIds[index + 1]
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Pads directory entries to a full sector boundary.
|
|
232
|
+
* @param {object[]} entries Directory entries.
|
|
233
|
+
* @returns {object[]}
|
|
234
|
+
*/
|
|
235
|
+
static #paddedDirectoryEntries(entries) {
|
|
236
|
+
const padded = [...entries]
|
|
237
|
+
while ((padded.length * directoryEntrySize) % sectorSize !== 0) {
|
|
238
|
+
padded.push(
|
|
239
|
+
OleCompoundDocumentWriter.#createDirectoryEntry('', {
|
|
240
|
+
type: emptyEntryType
|
|
241
|
+
})
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
return padded
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Resolves the number of FAT sectors required by this file.
|
|
249
|
+
* @param {number} dataSectorCount Non-FAT sector count.
|
|
250
|
+
* @returns {number}
|
|
251
|
+
*/
|
|
252
|
+
static #resolveFatSectorCount(dataSectorCount) {
|
|
253
|
+
let fatSectorCount = 1
|
|
254
|
+
while (
|
|
255
|
+
dataSectorCount + fatSectorCount >
|
|
256
|
+
fatSectorCount * regularEntriesPerFatSector
|
|
257
|
+
) {
|
|
258
|
+
fatSectorCount += 1
|
|
259
|
+
}
|
|
260
|
+
return fatSectorCount
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Writes the OLE header.
|
|
265
|
+
* @param {DataView} dataView Output view.
|
|
266
|
+
* @param {number} fatSectorStart First FAT sector id.
|
|
267
|
+
* @param {number} fatSectorCount FAT sector count.
|
|
268
|
+
* @returns {void}
|
|
269
|
+
*/
|
|
270
|
+
static #writeHeader(dataView, fatSectorStart, fatSectorCount) {
|
|
271
|
+
OleConstants.HEADER_SIGNATURE.forEach((value, index) => {
|
|
272
|
+
dataView.setUint8(index, value)
|
|
273
|
+
})
|
|
274
|
+
dataView.setUint16(24, 0x003e, true)
|
|
275
|
+
dataView.setUint16(26, 0x0003, true)
|
|
276
|
+
dataView.setUint16(28, 0xfffe, true)
|
|
277
|
+
dataView.setUint16(30, 9, true)
|
|
278
|
+
dataView.setUint16(32, 6, true)
|
|
279
|
+
dataView.setUint32(40, 0, true)
|
|
280
|
+
dataView.setUint32(44, fatSectorCount, true)
|
|
281
|
+
dataView.setInt32(48, 0, true)
|
|
282
|
+
dataView.setUint32(56, 0, true)
|
|
283
|
+
dataView.setInt32(60, OleConstants.END_OF_CHAIN, true)
|
|
284
|
+
dataView.setUint32(64, 0, true)
|
|
285
|
+
dataView.setInt32(68, OleConstants.END_OF_CHAIN, true)
|
|
286
|
+
dataView.setUint32(72, 0, true)
|
|
287
|
+
for (let index = 0; index < 109; index += 1) {
|
|
288
|
+
dataView.setInt32(76 + index * 4, OleConstants.FREE_SECTOR, true)
|
|
289
|
+
}
|
|
290
|
+
for (let index = 0; index < fatSectorCount; index += 1) {
|
|
291
|
+
dataView.setInt32(76 + index * 4, fatSectorStart + index, true)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Writes the directory stream.
|
|
297
|
+
* @param {Uint8Array} bytes Output bytes.
|
|
298
|
+
* @param {number[]} fatEntries FAT entries.
|
|
299
|
+
* @param {object[]} entries Directory entries.
|
|
300
|
+
* @param {number} directorySectorCount Directory sector count.
|
|
301
|
+
* @returns {void}
|
|
302
|
+
*/
|
|
303
|
+
static #writeDirectory(bytes, fatEntries, entries, directorySectorCount) {
|
|
304
|
+
OleCompoundDocumentWriter.#linkFatChain(
|
|
305
|
+
fatEntries,
|
|
306
|
+
0,
|
|
307
|
+
directorySectorCount
|
|
308
|
+
)
|
|
309
|
+
OleCompoundDocumentWriter.#paddedDirectoryEntries(entries).forEach(
|
|
310
|
+
(entry, index) => {
|
|
311
|
+
OleCompoundDocumentWriter.#writeDirectoryEntry(
|
|
312
|
+
bytes,
|
|
313
|
+
index,
|
|
314
|
+
entry
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Writes all regular stream payloads.
|
|
322
|
+
* @param {Uint8Array} bytes Output bytes.
|
|
323
|
+
* @param {number[]} fatEntries FAT entries.
|
|
324
|
+
* @param {object[]} streams Stream entries.
|
|
325
|
+
* @param {number[]} streamSectorCounts Sector counts.
|
|
326
|
+
* @returns {void}
|
|
327
|
+
*/
|
|
328
|
+
static #writeStreams(bytes, fatEntries, streams, streamSectorCounts) {
|
|
329
|
+
streams.forEach((stream, index) => {
|
|
330
|
+
const sectorCount = streamSectorCounts[index]
|
|
331
|
+
if (!sectorCount) {
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
OleCompoundDocumentWriter.#linkFatChain(
|
|
335
|
+
fatEntries,
|
|
336
|
+
stream.startSector,
|
|
337
|
+
sectorCount
|
|
338
|
+
)
|
|
339
|
+
bytes.set(
|
|
340
|
+
stream.bytes,
|
|
341
|
+
OleConstants.HEADER_BYTE_LENGTH +
|
|
342
|
+
stream.startSector * sectorSize
|
|
343
|
+
)
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Links a FAT sector chain.
|
|
349
|
+
* @param {number[]} fatEntries FAT entries.
|
|
350
|
+
* @param {number} startSector Start sector.
|
|
351
|
+
* @param {number} sectorCount Sector count.
|
|
352
|
+
* @returns {void}
|
|
353
|
+
*/
|
|
354
|
+
static #linkFatChain(fatEntries, startSector, sectorCount) {
|
|
355
|
+
for (let offset = 0; offset < sectorCount; offset += 1) {
|
|
356
|
+
fatEntries[startSector + offset] =
|
|
357
|
+
offset === sectorCount - 1
|
|
358
|
+
? OleConstants.END_OF_CHAIN
|
|
359
|
+
: startSector + offset + 1
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Writes FAT sectors.
|
|
365
|
+
* @param {Uint8Array} bytes Output bytes.
|
|
366
|
+
* @param {DataView} dataView Output view.
|
|
367
|
+
* @param {number[]} fatEntries FAT entries.
|
|
368
|
+
* @param {number} fatSectorStart First FAT sector id.
|
|
369
|
+
* @param {number} fatSectorCount FAT sector count.
|
|
370
|
+
* @returns {void}
|
|
371
|
+
*/
|
|
372
|
+
static #writeFat(
|
|
373
|
+
bytes,
|
|
374
|
+
dataView,
|
|
375
|
+
fatEntries,
|
|
376
|
+
fatSectorStart,
|
|
377
|
+
fatSectorCount
|
|
378
|
+
) {
|
|
379
|
+
for (let index = 0; index < fatSectorCount; index += 1) {
|
|
380
|
+
fatEntries[fatSectorStart + index] = OleConstants.FAT_SECTOR
|
|
381
|
+
}
|
|
382
|
+
for (
|
|
383
|
+
let index = 0;
|
|
384
|
+
index < fatSectorCount * regularEntriesPerFatSector;
|
|
385
|
+
index += 1
|
|
386
|
+
) {
|
|
387
|
+
dataView.setInt32(
|
|
388
|
+
OleConstants.HEADER_BYTE_LENGTH +
|
|
389
|
+
fatSectorStart * sectorSize +
|
|
390
|
+
index * 4,
|
|
391
|
+
fatEntries[index] ?? OleConstants.FREE_SECTOR,
|
|
392
|
+
true
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Writes one directory entry.
|
|
399
|
+
* @param {Uint8Array} bytes Output bytes.
|
|
400
|
+
* @param {number} index Directory entry index.
|
|
401
|
+
* @param {object} entry Directory entry.
|
|
402
|
+
* @returns {void}
|
|
403
|
+
*/
|
|
404
|
+
static #writeDirectoryEntry(bytes, index, entry) {
|
|
405
|
+
const offset =
|
|
406
|
+
OleConstants.HEADER_BYTE_LENGTH + index * directoryEntrySize
|
|
407
|
+
const dataView = new DataView(bytes.buffer)
|
|
408
|
+
const encodedName = OleCompoundDocumentWriter.#encodeDirectoryName(
|
|
409
|
+
entry.name
|
|
410
|
+
)
|
|
411
|
+
const nameByteLength = Math.min(encodedName.byteLength, 64)
|
|
412
|
+
|
|
413
|
+
bytes.set(encodedName.slice(0, nameByteLength), offset)
|
|
414
|
+
dataView.setUint16(offset + 64, nameByteLength, true)
|
|
415
|
+
dataView.setUint8(offset + 66, entry.type)
|
|
416
|
+
dataView.setUint8(offset + 67, 1)
|
|
417
|
+
dataView.setInt32(offset + 68, entry.leftSiblingId, true)
|
|
418
|
+
dataView.setInt32(offset + 72, entry.rightSiblingId, true)
|
|
419
|
+
dataView.setInt32(offset + 76, entry.childId, true)
|
|
420
|
+
dataView.setInt32(
|
|
421
|
+
offset + 116,
|
|
422
|
+
entry.stream?.startSector ?? OleConstants.END_OF_CHAIN,
|
|
423
|
+
true
|
|
424
|
+
)
|
|
425
|
+
dataView.setUint32(
|
|
426
|
+
offset + 120,
|
|
427
|
+
entry.stream?.bytes?.byteLength || 0,
|
|
428
|
+
true
|
|
429
|
+
)
|
|
430
|
+
dataView.setUint32(offset + 124, 0, true)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Encodes a directory name as UTF-16LE with a null terminator.
|
|
435
|
+
* @param {string} name Directory entry name.
|
|
436
|
+
* @returns {Uint8Array}
|
|
437
|
+
*/
|
|
438
|
+
static #encodeDirectoryName(name) {
|
|
439
|
+
const characters = Array.from(String(name || '')).slice(0, 31)
|
|
440
|
+
const bytes = new Uint8Array((characters.length + 1) * 2)
|
|
441
|
+
const dataView = new DataView(bytes.buffer)
|
|
442
|
+
|
|
443
|
+
characters.concat('\u0000').forEach((character, index) => {
|
|
444
|
+
dataView.setUint16(index * 2, character.charCodeAt(0), true)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
return bytes
|
|
448
|
+
}
|
|
449
|
+
}
|
package/src/parser.mjs
CHANGED
|
@@ -6,6 +6,14 @@ export { BinaryReader } from './core/BinaryReader.mjs'
|
|
|
6
6
|
export { OleCompoundDocument } from './core/ole/OleCompoundDocument.mjs'
|
|
7
7
|
export { OleConstants } from './core/ole/OleConstants.mjs'
|
|
8
8
|
export { OleDirectoryEntry } from './core/ole/OleDirectoryEntry.mjs'
|
|
9
|
+
export { OleCompoundDocumentWriter } from './core/ole/OleCompoundDocumentWriter.mjs'
|
|
10
|
+
export { AltiumLibraryBatchExporter } from './core/altium/AltiumLibraryBatchExporter.mjs'
|
|
11
|
+
export { AltiumLibraryRecordBuilder } from './core/altium/AltiumLibraryRecordBuilder.mjs'
|
|
12
|
+
export { AltiumPcbLibExporter } from './core/altium/AltiumPcbLibExporter.mjs'
|
|
13
|
+
export { AltiumSchLibExporter } from './core/altium/AltiumSchLibExporter.mjs'
|
|
14
|
+
export { SourceBundleExporter } from './core/altium/SourceBundleExporter.mjs'
|
|
15
|
+
export { SourceComponentBundleNormalizer } from './core/altium/SourceComponentBundleNormalizer.mjs'
|
|
16
|
+
export { SourceComponentClient } from './core/altium/SourceComponentClient.mjs'
|
|
9
17
|
export { AltiumParser } from './core/altium/AltiumParser.mjs'
|
|
10
18
|
export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
|
|
11
19
|
export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
|
|
@@ -56,14 +56,14 @@
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
.schematic-svg {
|
|
59
|
-
--schematic-default-ink-color: #
|
|
60
|
-
--schematic-accent-ink-color: #
|
|
59
|
+
--schematic-default-ink-color: #008aa3;
|
|
60
|
+
--schematic-accent-ink-color: #009bb2;
|
|
61
61
|
--schematic-text-color: #121b22;
|
|
62
62
|
--schematic-sheet-label-color: #405662;
|
|
63
|
-
--schematic-power-color: #
|
|
64
|
-
--schematic-port-color: #
|
|
65
|
-
--schematic-alert-color: #
|
|
66
|
-
--schematic-fill-color: #
|
|
63
|
+
--schematic-power-color: #a44a1b;
|
|
64
|
+
--schematic-port-color: #a44a1b;
|
|
65
|
+
--schematic-alert-color: #c43a68;
|
|
66
|
+
--schematic-fill-color: #f1d8bd;
|
|
67
67
|
--schematic-note-fill-color: #efe4d1;
|
|
68
68
|
--schematic-fill-light-color: #fffaf5;
|
|
69
69
|
--schematic-pin-marker-fill: #edf4f3;
|
package/src/ui/PcbArcUtils.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
|
|
|
10
10
|
export class PcbArcUtils {
|
|
11
11
|
static #FULL_CIRCLE_EPSILON = 0.001
|
|
12
12
|
|
|
13
|
+
static #CROSS_PRODUCT_EPSILON = 1e-9
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* Builds one PCB arc or authored circle as SVG markup.
|
|
15
17
|
* @param {{ x: number, y: number, radius: number, startAngle: number, endAngle: number, width?: number }} arc
|
|
@@ -174,7 +176,7 @@ export class PcbArcUtils {
|
|
|
174
176
|
/**
|
|
175
177
|
* Resolves the short SVG sweep direction for one arc from its authored
|
|
176
178
|
* center and endpoint geometry.
|
|
177
|
-
* @param {{ x1?: number, y1?: number, x2?: number, y2?: number, cx?: number, cy?: number }} segment
|
|
179
|
+
* @param {{ x1?: number, y1?: number, x2?: number, y2?: number, cx?: number, cy?: number, startAngle?: number, endAngle?: number }} segment
|
|
178
180
|
* @returns {0 | 1}
|
|
179
181
|
*/
|
|
180
182
|
static resolveShortSweepFromCenter(segment) {
|
|
@@ -184,6 +186,21 @@ export class PcbArcUtils {
|
|
|
184
186
|
const endDeltaY = Number(segment.y2 || 0) - Number(segment.cy || 0)
|
|
185
187
|
const crossProduct = startDeltaX * endDeltaY - startDeltaY * endDeltaX
|
|
186
188
|
|
|
187
|
-
|
|
189
|
+
if (Math.abs(crossProduct) > PcbArcUtils.#CROSS_PRODUCT_EPSILON) {
|
|
190
|
+
return crossProduct > 0 ? 1 : 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const startAngle = Number(segment.startAngle)
|
|
194
|
+
const endAngle = Number(segment.endAngle)
|
|
195
|
+
if (Number.isFinite(startAngle) && Number.isFinite(endAngle)) {
|
|
196
|
+
// Board-outline segment angles come from board-space arc records;
|
|
197
|
+
// after the endpoints are in SVG space, exact half-circles need
|
|
198
|
+
// the opposite SVG sweep to select the authored side.
|
|
199
|
+
return PcbArcUtils.resolveSweepDelta(startAngle, endAngle) >= 0
|
|
200
|
+
? 0
|
|
201
|
+
: 1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return 1
|
|
188
205
|
}
|
|
189
206
|
}
|