altium-toolkit 0.1.20 → 0.1.21

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.
@@ -0,0 +1,93 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { AsciiRecordParser } from './AsciiRecordParser.mjs'
6
+ import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
7
+ import { OleConstants } from '../ole/OleConstants.mjs'
8
+
9
+ /**
10
+ * Extracts stream-scoped printable schematic content from OLE-backed SchDoc
11
+ * containers.
12
+ */
13
+ export class SchematicStreamExtractor {
14
+ /**
15
+ * Returns true when one buffer starts with the OLE compound-document
16
+ * signature.
17
+ * @param {ArrayBuffer} arrayBuffer
18
+ * @returns {boolean}
19
+ */
20
+ static isCompoundDocument(arrayBuffer) {
21
+ const bytes = new Uint8Array(
22
+ arrayBuffer,
23
+ 0,
24
+ Math.min(
25
+ arrayBuffer.byteLength,
26
+ OleConstants.HEADER_SIGNATURE.length
27
+ )
28
+ )
29
+
30
+ if (bytes.byteLength < OleConstants.HEADER_SIGNATURE.length) {
31
+ return false
32
+ }
33
+
34
+ return OleConstants.HEADER_SIGNATURE.every(
35
+ (value, index) => bytes[index] === value
36
+ )
37
+ }
38
+
39
+ /**
40
+ * Extracts schematic records from the logical `FileHeader` stream.
41
+ * @param {ArrayBuffer} arrayBuffer
42
+ * @returns {{ records: Array<{ raw: string, fields: Record<string, string | string[]>, sourceStream: string }>, streamNames: string[] } | null}
43
+ */
44
+ static extractFromArrayBuffer(arrayBuffer) {
45
+ if (!SchematicStreamExtractor.isCompoundDocument(arrayBuffer)) {
46
+ return null
47
+ }
48
+
49
+ let compoundDocument
50
+
51
+ try {
52
+ compoundDocument = OleCompoundDocument.fromArrayBuffer(arrayBuffer)
53
+ } catch {
54
+ return null
55
+ }
56
+
57
+ let fileHeaderBytes
58
+
59
+ try {
60
+ fileHeaderBytes = compoundDocument.getStream('FileHeader')
61
+ } catch {
62
+ return null
63
+ }
64
+
65
+ const records = AsciiRecordParser.parse(
66
+ SchematicStreamExtractor.#toArrayBuffer(fileHeaderBytes)
67
+ ).map((record) => ({
68
+ ...record,
69
+ sourceStream: 'FileHeader'
70
+ }))
71
+
72
+ if (!records.length) {
73
+ return null
74
+ }
75
+
76
+ return {
77
+ records,
78
+ streamNames: compoundDocument.listStreams()
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Returns an ArrayBuffer view over one byte slice.
84
+ * @param {Uint8Array} bytes
85
+ * @returns {ArrayBuffer}
86
+ */
87
+ static #toArrayBuffer(bytes) {
88
+ return bytes.buffer.slice(
89
+ bytes.byteOffset,
90
+ bytes.byteOffset + bytes.byteLength
91
+ )
92
+ }
93
+ }
@@ -136,13 +136,25 @@ export class SchematicTextPostProcessor {
136
136
  * @param {{ x1: number, y1: number, x2: number, y2: number, ownerIndex?: string }[]} lines
137
137
  * @param {{ x: number, y: number, ownerIndex: string, length: number, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
138
138
  * @param {{ x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down' }[]} ports
139
+ * @param {{ x: number, y: number, width: number, height: number, ownerIndex?: string }[]} rectangles
139
140
  * @returns {{ x: number, y: number, text: string, name?: string, ownerIndex?: string, recordType?: string, rotation?: number, anchor?: 'start' | 'middle' | 'end' }[]}
140
141
  */
141
- static anchorComponentTextsFromOwnerBounds(texts, lines, pins, ports = []) {
142
+ static anchorComponentTextsFromOwnerBounds(
143
+ texts,
144
+ lines,
145
+ pins,
146
+ ports = [],
147
+ rectangles = []
148
+ ) {
142
149
  const ownerBounds = SchematicTextPostProcessor.#buildOwnerBounds(
143
150
  lines,
144
151
  pins
145
152
  )
153
+ const ownerBodyBounds = SchematicTextPostProcessor.#buildOwnerBounds(
154
+ lines,
155
+ pins,
156
+ rectangles
157
+ )
146
158
  const ownerPinCounts =
147
159
  SchematicTextPostProcessor.#buildOwnerPinCounts(pins)
148
160
  const ownerPinOrientations =
@@ -164,14 +176,11 @@ export class SchematicTextPostProcessor {
164
176
  return text
165
177
  }
166
178
 
167
- const paddedText = SchematicTextPostProcessor.#isDesignatorText(
168
- text
169
- )
170
- ? SchematicTextPostProcessor.#padDesignatorAboveOwner(
171
- text,
172
- bounds
173
- )
174
- : text
179
+ const paddedText =
180
+ SchematicTextPostProcessor.#padDesignatorAboveOwner(
181
+ text,
182
+ ownerBodyBounds.get(text.ownerIndex) || bounds
183
+ )
175
184
  const ownerPinCount = ownerPinCounts.get(text.ownerIndex) || 0
176
185
 
177
186
  if (text.y > bounds.maxY) {
@@ -290,12 +299,13 @@ export class SchematicTextPostProcessor {
290
299
  }
291
300
 
292
301
  /**
293
- * Builds per-owner primitive bounds from drawable lines and pins.
302
+ * Builds per-owner primitive bounds from drawable lines, pins, and bodies.
294
303
  * @param {{ x1: number, y1: number, x2: number, y2: number, ownerIndex?: string }[]} lines
295
304
  * @param {{ x: number, y: number, ownerIndex: string }[]} pins
305
+ * @param {{ x: number, y: number, width: number, height: number, ownerIndex?: string }[]} rectangles
296
306
  * @returns {Map<string, { minX: number, minY: number, maxX: number, maxY: number }>}
297
307
  */
298
- static #buildOwnerBounds(lines, pins) {
308
+ static #buildOwnerBounds(lines, pins, rectangles = []) {
299
309
  const ownerBounds = new Map()
300
310
 
301
311
  for (const line of lines) {
@@ -325,6 +335,24 @@ export class SchematicTextPostProcessor {
325
335
  )
326
336
  }
327
337
 
338
+ for (const rectangle of rectangles) {
339
+ if (!rectangle.ownerIndex) {
340
+ continue
341
+ }
342
+
343
+ SchematicTextPostProcessor.#extendBounds(
344
+ ownerBounds,
345
+ rectangle.ownerIndex,
346
+ [
347
+ { x: rectangle.x, y: rectangle.y },
348
+ {
349
+ x: rectangle.x + rectangle.width,
350
+ y: rectangle.y + rectangle.height
351
+ }
352
+ ]
353
+ )
354
+ }
355
+
328
356
  return ownerBounds
329
357
  }
330
358
 
@@ -506,7 +534,7 @@ export class SchematicTextPostProcessor {
506
534
  * @returns {{ x: number, y: number }}
507
535
  */
508
536
  static #padDesignatorAboveOwner(text, bounds) {
509
- if (text.y <= bounds.maxY) {
537
+ if (text.y < bounds.maxY - 1 || text.y >= bounds.maxY + 4) {
510
538
  return text
511
539
  }
512
540