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.
- package/AGENTS.md +67 -0
- package/COMMERCIAL-LICENSE.md +20 -0
- package/CONTRIBUTING.md +19 -0
- package/LICENSE +22 -0
- package/LICENSES/CC-BY-SA-4.0.txt +170 -0
- package/LICENSES/GPL-3.0-or-later.txt +232 -0
- package/NOTICE.md +32 -0
- package/README.md +116 -0
- package/docs/api.md +73 -0
- package/docs/model-format.md +36 -0
- package/docs/testing.md +25 -0
- package/examples/README.md +47 -0
- package/examples/arduino-uno/PcbThreeSceneRenderer.mjs +635 -0
- package/examples/arduino-uno/SvgViewportController.mjs +306 -0
- package/examples/arduino-uno/example.mjs +480 -0
- package/examples/arduino-uno/index.html +163 -0
- package/examples/arduino-uno/styles.css +552 -0
- package/examples/server.mjs +212 -0
- package/package.json +53 -0
- package/spec/library-scope.md +32 -0
- package/src/core/BinaryReader.mjs +127 -0
- package/src/core/altium/AltiumLayoutParser.mjs +485 -0
- package/src/core/altium/AltiumParser.mjs +1007 -0
- package/src/core/altium/AsciiRecordParser.mjs +151 -0
- package/src/core/altium/ParserUtils.mjs +173 -0
- package/src/core/altium/PcbBinaryPrimitiveParser.mjs +424 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +505 -0
- package/src/core/altium/PcbModelParser.mjs +336 -0
- package/src/core/altium/PcbOutlineRasterizer.mjs +852 -0
- package/src/core/altium/PcbOutlineRecovery.mjs +957 -0
- package/src/core/altium/PcbStreamExtractor.mjs +210 -0
- package/src/core/altium/PrintableTextDecoder.mjs +156 -0
- package/src/core/altium/SchematicAnnotationParser.mjs +220 -0
- package/src/core/altium/SchematicBusEntryParser.mjs +48 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +47 -0
- package/src/core/altium/SchematicImageParser.mjs +173 -0
- package/src/core/altium/SchematicJunctionParser.mjs +43 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +564 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +351 -0
- package/src/core/altium/SchematicPinParser.mjs +767 -0
- package/src/core/altium/SchematicPrimitiveParser.mjs +716 -0
- package/src/core/altium/SchematicSheetParser.mjs +241 -0
- package/src/core/altium/SchematicSheetStyleResolver.mjs +46 -0
- package/src/core/altium/SchematicStandaloneCalloutNormalizer.mjs +592 -0
- package/src/core/altium/SchematicTextParser.mjs +708 -0
- package/src/core/altium/SchematicTextPostProcessor.mjs +801 -0
- package/src/core/ole/OleCompoundDocument.mjs +439 -0
- package/src/core/ole/OleConstants.mjs +64 -0
- package/src/core/ole/OleDirectoryEntry.mjs +95 -0
- package/src/index.mjs +7 -0
- package/src/parser.mjs +21 -0
- package/src/renderers.mjs +15 -0
- package/src/scene3d.mjs +9 -0
- package/src/styles/altium-renderers.css +358 -0
- package/src/ui/BomTableRenderer.mjs +46 -0
- package/src/ui/PcbArcUtils.mjs +189 -0
- package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +808 -0
- package/src/ui/PcbFootprintPrimitiveSelector.mjs +128 -0
- package/src/ui/PcbScene3dBuilder.mjs +742 -0
- package/src/ui/PcbScene3dModelRegistry.mjs +309 -0
- package/src/ui/PcbScene3dPackages.mjs +137 -0
- package/src/ui/PcbScene3dScenePreparator.mjs +36 -0
- package/src/ui/PcbScene3dSummaryRenderer.mjs +65 -0
- package/src/ui/PcbSvgRenderer.mjs +906 -0
- package/src/ui/SchematicColorResolver.mjs +132 -0
- package/src/ui/SchematicContentLayout.mjs +661 -0
- package/src/ui/SchematicDirectiveRenderer.mjs +184 -0
- package/src/ui/SchematicImageRenderer.mjs +135 -0
- package/src/ui/SchematicJunctionRenderer.mjs +381 -0
- package/src/ui/SchematicNoteRenderer.mjs +427 -0
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +173 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +495 -0
- package/src/ui/SchematicPortRenderer.mjs +558 -0
- package/src/ui/SchematicPowerPortRenderer.mjs +574 -0
- package/src/ui/SchematicRegionRenderer.mjs +94 -0
- package/src/ui/SchematicShapeRenderer.mjs +398 -0
- package/src/ui/SchematicSheetChromeRenderer.mjs +1025 -0
- package/src/ui/SchematicSheetSymbolRenderer.mjs +228 -0
- package/src/ui/SchematicSvgRenderer.mjs +756 -0
- package/src/ui/SchematicSvgUtils.mjs +182 -0
- package/src/ui/SchematicTypography.mjs +204 -0
- package/src/workers/altium-parser.worker.mjs +29 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared SVG and markup formatting helpers.
|
|
7
|
+
*/
|
|
8
|
+
export class SchematicSvgUtils {
|
|
9
|
+
/**
|
|
10
|
+
* Escapes user-facing markup.
|
|
11
|
+
* @param {string} value
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
static escapeHtml(value) {
|
|
15
|
+
return String(value)
|
|
16
|
+
.replaceAll('&', '&')
|
|
17
|
+
.replaceAll('<', '<')
|
|
18
|
+
.replaceAll('>', '>')
|
|
19
|
+
.replaceAll('"', '"')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Formats a concise numeric attribute.
|
|
24
|
+
* @param {number} value
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
static formatNumber(value) {
|
|
28
|
+
return Number(value).toFixed(2).replace(/\.00$/, '')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Converts bottom-left schematic coordinates into SVG coordinates.
|
|
33
|
+
* @param {number} sheetHeight
|
|
34
|
+
* @param {number} value
|
|
35
|
+
* @returns {number}
|
|
36
|
+
*/
|
|
37
|
+
static projectSchematicY(sheetHeight, value) {
|
|
38
|
+
return Number(sheetHeight) - Number(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates one escaped SVG text element.
|
|
43
|
+
* @param {string} className
|
|
44
|
+
* @param {number} x
|
|
45
|
+
* @param {number} y
|
|
46
|
+
* @param {string} text
|
|
47
|
+
* @param {string} color
|
|
48
|
+
* @param {'start' | 'end' | 'middle'} anchor
|
|
49
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, segments?: { text: string, overline?: boolean }[] }} [options]
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
static createSvgText(className, x, y, text, color, anchor, options = {}) {
|
|
53
|
+
const hasSegments = Array.isArray(options.segments)
|
|
54
|
+
? options.segments.some((segment) => segment?.text)
|
|
55
|
+
: false
|
|
56
|
+
const hasPlainText =
|
|
57
|
+
text !== undefined && text !== null && String(text).length > 0
|
|
58
|
+
if (!hasSegments && !hasPlainText) return ''
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
'<text class="' +
|
|
62
|
+
SchematicSvgUtils.escapeHtml(className) +
|
|
63
|
+
'" x="' +
|
|
64
|
+
SchematicSvgUtils.formatNumber(x) +
|
|
65
|
+
'" y="' +
|
|
66
|
+
SchematicSvgUtils.formatNumber(y) +
|
|
67
|
+
'" fill="' +
|
|
68
|
+
SchematicSvgUtils.escapeHtml(color) +
|
|
69
|
+
'" text-anchor="' +
|
|
70
|
+
SchematicSvgUtils.escapeHtml(anchor) +
|
|
71
|
+
'"' +
|
|
72
|
+
SchematicSvgUtils.#buildSvgTextStyleAttributes(x, y, options) +
|
|
73
|
+
'>' +
|
|
74
|
+
SchematicSvgUtils.#buildSvgTextContent(text, options.segments) +
|
|
75
|
+
'</text>'
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns only the trailing file segment for footer display.
|
|
81
|
+
* @param {string | undefined} fileName
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
static basename(fileName) {
|
|
85
|
+
if (!fileName) return ''
|
|
86
|
+
const parts = String(fileName).split(/[\\/]/)
|
|
87
|
+
return parts.at(-1) || ''
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Formats the current date like Altium's default title block.
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
static buildCurrentDateValue() {
|
|
95
|
+
const today = new Date()
|
|
96
|
+
const month = String(today.getMonth() + 1)
|
|
97
|
+
const day = String(today.getDate()).padStart(2, '0')
|
|
98
|
+
return month + '/' + day + '/' + today.getFullYear()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates optional inline SVG text attributes for typography and rotation.
|
|
103
|
+
* @param {number} x
|
|
104
|
+
* @param {number} y
|
|
105
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }} options
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
static #buildSvgTextStyleAttributes(x, y, options) {
|
|
109
|
+
let attributes = ''
|
|
110
|
+
|
|
111
|
+
if (options.fontSize) {
|
|
112
|
+
attributes +=
|
|
113
|
+
' font-size="' +
|
|
114
|
+
SchematicSvgUtils.escapeHtml(
|
|
115
|
+
SchematicSvgUtils.formatNumber(options.fontSize)
|
|
116
|
+
) +
|
|
117
|
+
'"'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (options.fontFamily) {
|
|
121
|
+
attributes +=
|
|
122
|
+
' font-family="' +
|
|
123
|
+
SchematicSvgUtils.escapeHtml(options.fontFamily) +
|
|
124
|
+
'"'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (options.fontWeight) {
|
|
128
|
+
attributes +=
|
|
129
|
+
' font-weight="' +
|
|
130
|
+
SchematicSvgUtils.escapeHtml(String(options.fontWeight)) +
|
|
131
|
+
'"'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (options.rotation) {
|
|
135
|
+
attributes +=
|
|
136
|
+
' transform="rotate(' +
|
|
137
|
+
SchematicSvgUtils.escapeHtml(
|
|
138
|
+
SchematicSvgUtils.formatNumber(options.rotation)
|
|
139
|
+
) +
|
|
140
|
+
' ' +
|
|
141
|
+
SchematicSvgUtils.escapeHtml(
|
|
142
|
+
SchematicSvgUtils.formatNumber(x)
|
|
143
|
+
) +
|
|
144
|
+
' ' +
|
|
145
|
+
SchematicSvgUtils.escapeHtml(
|
|
146
|
+
SchematicSvgUtils.formatNumber(y)
|
|
147
|
+
) +
|
|
148
|
+
')"'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return attributes
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Builds escaped text or segmented tspan markup for one SVG text element.
|
|
156
|
+
* @param {string} text
|
|
157
|
+
* @param {{ text: string, overline?: boolean }[] | undefined} segments
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
160
|
+
static #buildSvgTextContent(text, segments) {
|
|
161
|
+
if (
|
|
162
|
+
Array.isArray(segments) &&
|
|
163
|
+
segments.some((segment) => segment?.text)
|
|
164
|
+
) {
|
|
165
|
+
return segments
|
|
166
|
+
.filter((segment) => segment?.text)
|
|
167
|
+
.map(
|
|
168
|
+
(segment) =>
|
|
169
|
+
'<tspan text-decoration="' +
|
|
170
|
+
SchematicSvgUtils.escapeHtml(
|
|
171
|
+
segment.overline ? 'overline' : 'none'
|
|
172
|
+
) +
|
|
173
|
+
'">' +
|
|
174
|
+
SchematicSvgUtils.escapeHtml(segment.text) +
|
|
175
|
+
'</tspan>'
|
|
176
|
+
)
|
|
177
|
+
.join('')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return SchematicSvgUtils.escapeHtml(text)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared typography helpers for synthetic schematic SVG labels.
|
|
7
|
+
*/
|
|
8
|
+
export class SchematicTypography {
|
|
9
|
+
/**
|
|
10
|
+
* Returns true when the schematic already contains a visible designator text
|
|
11
|
+
* close to one component origin.
|
|
12
|
+
* @param {{ x?: number, y?: number }} component
|
|
13
|
+
* @param {{ x: number, y: number, name?: string }[]} texts
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
static hasNearbyVisibleDesignatorText(component, texts) {
|
|
17
|
+
return texts.some(
|
|
18
|
+
(text) =>
|
|
19
|
+
String(text.name || '')
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase() === 'designator' &&
|
|
22
|
+
Math.abs(Number(text.x) - Number(component.x)) <= 80 &&
|
|
23
|
+
Math.abs(Number(text.y) - Number(component.y)) <= 80
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds the default font options used for synthetic schematic labels.
|
|
29
|
+
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean }> }} sheet
|
|
30
|
+
* @returns {{ fontSize: number, fontFamily: string, fontWeight: number }}
|
|
31
|
+
*/
|
|
32
|
+
static buildDefaultSchematicFontOptions(sheet) {
|
|
33
|
+
const font = sheet?.fonts?.['1'] || {
|
|
34
|
+
size: 10,
|
|
35
|
+
family: 'Times New Roman',
|
|
36
|
+
bold: false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
fontSize: SchematicTypography.#toSvgFontSize(font.size),
|
|
41
|
+
fontFamily: font.family || 'Times New Roman',
|
|
42
|
+
fontWeight: font.bold ? 700 : 400
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Builds default font options with the viewer-wide one-point reduction
|
|
48
|
+
* already applied.
|
|
49
|
+
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean }> }} sheet
|
|
50
|
+
* @returns {{ fontSize: number | undefined, fontFamily: string, fontWeight: number }}
|
|
51
|
+
*/
|
|
52
|
+
static buildViewerSchematicFontOptions(sheet) {
|
|
53
|
+
return SchematicTypography.withViewerFontSize(
|
|
54
|
+
SchematicTypography.buildDefaultSchematicFontOptions(sheet)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Builds render options for one schematic text label, including the signed
|
|
60
|
+
* SVG rotation derived from the original Altium orientation and mirrored
|
|
61
|
+
* source state.
|
|
62
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, sourceOrientation?: number, isMirrored?: boolean }} text
|
|
63
|
+
* @returns {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }}
|
|
64
|
+
*/
|
|
65
|
+
static buildSchematicTextRenderOptions(text) {
|
|
66
|
+
return {
|
|
67
|
+
fontSize: SchematicTypography.resolveViewerFontSize(text.fontSize),
|
|
68
|
+
fontFamily: text.fontFamily,
|
|
69
|
+
fontWeight: text.fontWeight,
|
|
70
|
+
rotation: SchematicTypography.#resolveSignedTextRotation(
|
|
71
|
+
text.rotation,
|
|
72
|
+
text.sourceOrientation,
|
|
73
|
+
text.isMirrored
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Applies the viewer-wide one-point text reduction to one option bag.
|
|
80
|
+
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }} options
|
|
81
|
+
* @returns {{ fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number }}
|
|
82
|
+
*/
|
|
83
|
+
static withViewerFontSize(options) {
|
|
84
|
+
return {
|
|
85
|
+
...options,
|
|
86
|
+
fontSize: SchematicTypography.resolveViewerFontSize(
|
|
87
|
+
options?.fontSize
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns the one-point-smaller font size used for viewer text.
|
|
94
|
+
* @param {number | undefined} size
|
|
95
|
+
* @returns {number | undefined}
|
|
96
|
+
*/
|
|
97
|
+
static resolveViewerFontSize(size) {
|
|
98
|
+
const numericSize = Number(size)
|
|
99
|
+
|
|
100
|
+
if (!Number.isFinite(numericSize) || numericSize <= 0) {
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Math.max(numericSize - 1, 1)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Collects number-only owner groups whose top pin numbers should rotate
|
|
109
|
+
* along the vertical pin axis.
|
|
110
|
+
* @param {{ ownerIndex?: string, orientation: 'left' | 'right' | 'top' | 'bottom', labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[]} pins
|
|
111
|
+
* @returns {Set<string>}
|
|
112
|
+
*/
|
|
113
|
+
static collectRotatedVerticalNumberOwners(pins) {
|
|
114
|
+
const ownerPins = new Map()
|
|
115
|
+
|
|
116
|
+
for (const pin of pins) {
|
|
117
|
+
const ownerIndex = String(pin.ownerIndex || '').trim()
|
|
118
|
+
if (!ownerIndex) continue
|
|
119
|
+
if (!ownerPins.has(ownerIndex)) ownerPins.set(ownerIndex, [])
|
|
120
|
+
ownerPins.get(ownerIndex).push(pin)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return new Set(
|
|
124
|
+
[...ownerPins.entries()]
|
|
125
|
+
.filter(([, groupedPins]) => {
|
|
126
|
+
const hasTopPin = groupedPins.some(
|
|
127
|
+
(pin) => pin.orientation === 'top'
|
|
128
|
+
)
|
|
129
|
+
const hasHorizontalPins = groupedPins.some(
|
|
130
|
+
(pin) =>
|
|
131
|
+
pin.orientation === 'left' ||
|
|
132
|
+
pin.orientation === 'right'
|
|
133
|
+
)
|
|
134
|
+
return (
|
|
135
|
+
groupedPins.length >= 4 &&
|
|
136
|
+
hasTopPin &&
|
|
137
|
+
hasHorizontalPins &&
|
|
138
|
+
groupedPins.every(
|
|
139
|
+
(pin) =>
|
|
140
|
+
(pin.labelMode || 'name-and-number') ===
|
|
141
|
+
'number-only'
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
})
|
|
145
|
+
.map(([ownerIndex]) => ownerIndex)
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Collects owner/text pairs that already expose explicit pin-name labels as
|
|
151
|
+
* free text primitives, so the pin renderer can avoid duplicating them.
|
|
152
|
+
* @param {{ ownerIndex?: string, recordType?: string, text?: string }[]} texts
|
|
153
|
+
* @returns {Set<string>}
|
|
154
|
+
*/
|
|
155
|
+
static collectExplicitOwnerPinNameLabels(texts) {
|
|
156
|
+
return new Set(
|
|
157
|
+
texts
|
|
158
|
+
.filter(
|
|
159
|
+
(text) =>
|
|
160
|
+
text &&
|
|
161
|
+
text.recordType === '4' &&
|
|
162
|
+
String(text.ownerIndex || '').trim() &&
|
|
163
|
+
String(text.text || '').trim()
|
|
164
|
+
)
|
|
165
|
+
.map(
|
|
166
|
+
(text) =>
|
|
167
|
+
String(text.ownerIndex || '').trim() +
|
|
168
|
+
'::' +
|
|
169
|
+
String(text.text || '').trim()
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Converts Altium point sizes into SVG font units.
|
|
176
|
+
* @param {number} size
|
|
177
|
+
* @returns {number}
|
|
178
|
+
*/
|
|
179
|
+
static #toSvgFontSize(size) {
|
|
180
|
+
return Number(size || 10)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolves the signed SVG rotation for one schematic text label.
|
|
185
|
+
* @param {number | undefined} rotation
|
|
186
|
+
* @param {number | undefined} sourceOrientation
|
|
187
|
+
* @param {boolean | undefined} isMirrored
|
|
188
|
+
* @returns {number}
|
|
189
|
+
*/
|
|
190
|
+
static #resolveSignedTextRotation(rotation, sourceOrientation, isMirrored) {
|
|
191
|
+
const normalizedRotation = Number(rotation || 0)
|
|
192
|
+
|
|
193
|
+
if (!normalizedRotation) {
|
|
194
|
+
return 0
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const signedRotation =
|
|
198
|
+
Number(sourceOrientation || 0) === 3
|
|
199
|
+
? normalizedRotation
|
|
200
|
+
: -normalizedRotation
|
|
201
|
+
|
|
202
|
+
return isMirrored ? -signedRotation : signedRotation
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { AltiumParser } from '../core/altium/AltiumParser.mjs'
|
|
6
|
+
|
|
7
|
+
self.addEventListener('message', (event) => {
|
|
8
|
+
const payload = event?.data || {}
|
|
9
|
+
if (payload.type !== 'parse:file') return
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const documentModel = AltiumParser.parseArrayBuffer(
|
|
13
|
+
String(payload.fileName || 'document'),
|
|
14
|
+
payload.buffer
|
|
15
|
+
)
|
|
16
|
+
self.postMessage({
|
|
17
|
+
type: 'parser:success',
|
|
18
|
+
requestId: String(payload.requestId || ''),
|
|
19
|
+
documentModel
|
|
20
|
+
})
|
|
21
|
+
} catch (error) {
|
|
22
|
+
self.postMessage({
|
|
23
|
+
type: 'parser:error',
|
|
24
|
+
requestId: String(payload.requestId || ''),
|
|
25
|
+
message:
|
|
26
|
+
error instanceof Error ? error.message : 'Parser worker failed.'
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
})
|