altium-toolkit 0.1.0 → 0.1.16
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/README.md +24 -6
- package/docs/api.md +42 -4
- package/docs/model-format.md +95 -5
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +553 -0
- package/docs/testing.md +7 -2
- package/package.json +21 -2
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumParser.mjs +22 -325
- package/src/core/altium/NormalizedModelSchema.mjs +28 -0
- package/src/core/altium/PcbArcPrimitiveParser.mjs +87 -0
- package/src/core/altium/PcbBinaryPrimitiveParser.mjs +43 -370
- package/src/core/altium/PcbBoardRegionSemanticsParser.mjs +477 -0
- package/src/core/altium/PcbComponentAnnotationNormalizer.mjs +290 -0
- package/src/core/altium/PcbComponentBodyPlacementNormalizer.mjs +52 -0
- package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +109 -0
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +484 -0
- package/src/core/altium/PcbFillPrimitiveParser.mjs +84 -0
- package/src/core/altium/PcbFontMetricsParser.mjs +308 -0
- package/src/core/altium/PcbGeometryFlipper.mjs +244 -0
- package/src/core/altium/PcbLayerIdCodec.mjs +136 -0
- package/src/core/altium/PcbLibModelParser.mjs +202 -0
- package/src/core/altium/PcbLibStreamExtractor.mjs +968 -0
- package/src/core/altium/PcbModelParser.mjs +618 -66
- package/src/core/altium/PcbOutlineRecovery.mjs +4 -112
- package/src/core/altium/PcbPadPrimitiveParser.mjs +347 -0
- package/src/core/altium/PcbPadShapeCodec.mjs +158 -0
- package/src/core/altium/PcbPadStackParser.mjs +903 -0
- package/src/core/altium/PcbPrimitiveOwnershipIndexParser.mjs +60 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +212 -0
- package/src/core/altium/PcbPrimitiveRecordSlicer.mjs +243 -0
- package/src/core/altium/PcbRawRecordRegistry.mjs +831 -0
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +317 -0
- package/src/core/altium/PcbRuleParser.mjs +587 -0
- package/src/core/altium/PcbStreamExtractor.mjs +127 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +537 -0
- package/src/core/altium/PcbTrackPrimitiveParser.mjs +87 -0
- package/src/core/altium/PcbViaPrimitiveParser.mjs +88 -0
- package/src/core/altium/PcbViaStackParser.mjs +548 -0
- package/src/core/altium/PcbWideStringTableParser.mjs +108 -0
- package/src/core/altium/PrjPcbModelParser.mjs +797 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +355 -0
- package/src/parser.mjs +13 -0
- package/src/renderers.mjs +5 -0
- package/src/styles/altium-renderers.css +11 -6
- package/src/ui/PcbCopperPrimitiveSplitter.mjs +113 -0
- package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +6 -5
- package/src/ui/PcbEmbeddedFontFaceRenderer.mjs +126 -0
- package/src/ui/PcbFootprintPrimitiveSelector.mjs +27 -6
- package/src/ui/PcbRegionPrimitiveRenderer.mjs +243 -0
- package/src/ui/PcbSideResolvedRenderModel.mjs +336 -0
- package/src/ui/PcbSvgRenderer.mjs +101 -109
- package/src/ui/PcbTextPrimitiveRenderer.mjs +252 -0
- package/src/ui/SchematicSheetChromeRenderer.mjs +2 -93
- package/src/ui/SchematicSheetZoneRenderer.mjs +104 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Renders recovered PCB text primitives.
|
|
9
|
+
*/
|
|
10
|
+
export class PcbTextPrimitiveRenderer {
|
|
11
|
+
/**
|
|
12
|
+
* Selects texts that belong to the requested board-side composite.
|
|
13
|
+
* @param {{ layerId: number, name: string }[]} primitiveLayers
|
|
14
|
+
* @param {{ text: string, x: number, y: number, height?: number, rotation?: number, layerId?: number, visible?: boolean }[]} texts
|
|
15
|
+
* @param {'top' | 'bottom'} [side]
|
|
16
|
+
* @returns {{ text: string, x: number, y: number, height?: number, rotation?: number, layerId?: number, visible?: boolean }[]}
|
|
17
|
+
*/
|
|
18
|
+
static select(primitiveLayers, texts, side = 'top') {
|
|
19
|
+
const layerIds = PcbTextPrimitiveRenderer.#resolveLayerIds(
|
|
20
|
+
primitiveLayers || [],
|
|
21
|
+
side
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return (texts || []).filter((text) => {
|
|
25
|
+
const layerId = Number(text?.layerId)
|
|
26
|
+
return (
|
|
27
|
+
text?.visible !== false &&
|
|
28
|
+
String(text?.text || '').trim() &&
|
|
29
|
+
!PcbTextPrimitiveRenderer.#isPlaceholderText(text) &&
|
|
30
|
+
Number.isInteger(layerId) &&
|
|
31
|
+
layerIds.has(layerId)
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Renders selected PCB texts into SVG markup.
|
|
38
|
+
* @param {{ text: string, x: number, y: number, height?: number, rotation?: number, layerId?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string }[]} texts
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
static render(texts) {
|
|
42
|
+
return (texts || [])
|
|
43
|
+
.map((text) => PcbTextPrimitiveRenderer.#renderText(text))
|
|
44
|
+
.join('')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Renders one PCB text primitive.
|
|
49
|
+
* @param {{ text: string, x: number, y: number, height?: number, rotation?: number, layerId?: number, fontFamily?: string, fontWeight?: number, fontStyle?: string }} text
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
static #renderText(text) {
|
|
53
|
+
const fontSize = Math.max(Number(text.height || 0), 8)
|
|
54
|
+
const rotation = Number(text.rotation || 0)
|
|
55
|
+
const lines = String(text.text || '')
|
|
56
|
+
.replace(/\r\n?/gu, '\n')
|
|
57
|
+
.split('\n')
|
|
58
|
+
.filter((line) => line.length > 0)
|
|
59
|
+
const content = lines.length
|
|
60
|
+
? PcbTextPrimitiveRenderer.#renderTextLines(lines, fontSize)
|
|
61
|
+
: SchematicSvgUtils.escapeHtml(String(text.text || ''))
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
'<text class="pcb-text pcb-text--layer-' +
|
|
65
|
+
SchematicSvgUtils.escapeHtml(String(Number(text.layerId || 0))) +
|
|
66
|
+
'" transform="translate(' +
|
|
67
|
+
SchematicSvgUtils.formatNumber(Number(text.x || 0)) +
|
|
68
|
+
' ' +
|
|
69
|
+
SchematicSvgUtils.formatNumber(Number(text.y || 0)) +
|
|
70
|
+
') rotate(' +
|
|
71
|
+
SchematicSvgUtils.formatNumber(rotation) +
|
|
72
|
+
')" font-size="' +
|
|
73
|
+
SchematicSvgUtils.formatNumber(fontSize) +
|
|
74
|
+
'"' +
|
|
75
|
+
PcbTextPrimitiveRenderer.#renderFontAttributes(text) +
|
|
76
|
+
' text-anchor="start" dominant-baseline="alphabetic">' +
|
|
77
|
+
content +
|
|
78
|
+
'</text>'
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Renders optional SVG font attributes for TrueType text primitives.
|
|
84
|
+
* @param {{ fontFamily?: string, fontWeight?: number, fontStyle?: string }} text
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
static #renderFontAttributes(text) {
|
|
88
|
+
let attributes = ''
|
|
89
|
+
|
|
90
|
+
if (text.fontFamily && text.fontFamily !== 'Stroke') {
|
|
91
|
+
attributes +=
|
|
92
|
+
' font-family="' +
|
|
93
|
+
SchematicSvgUtils.escapeHtml(text.fontFamily) +
|
|
94
|
+
'"'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (text.fontWeight) {
|
|
98
|
+
attributes +=
|
|
99
|
+
' font-weight="' +
|
|
100
|
+
SchematicSvgUtils.escapeHtml(String(text.fontWeight)) +
|
|
101
|
+
'"'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (text.fontStyle && text.fontStyle !== 'normal') {
|
|
105
|
+
attributes +=
|
|
106
|
+
' font-style="' +
|
|
107
|
+
SchematicSvgUtils.escapeHtml(text.fontStyle) +
|
|
108
|
+
'"'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return attributes
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Renders one or more text lines with SVG tspans.
|
|
116
|
+
* @param {string[]} lines
|
|
117
|
+
* @param {number} fontSize
|
|
118
|
+
* @returns {string}
|
|
119
|
+
*/
|
|
120
|
+
static #renderTextLines(lines, fontSize) {
|
|
121
|
+
if (lines.length === 1) {
|
|
122
|
+
return SchematicSvgUtils.escapeHtml(lines[0])
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return lines
|
|
126
|
+
.map(
|
|
127
|
+
(line, index) =>
|
|
128
|
+
'<tspan x="0" dy="' +
|
|
129
|
+
SchematicSvgUtils.formatNumber(index === 0 ? 0 : fontSize) +
|
|
130
|
+
'">' +
|
|
131
|
+
SchematicSvgUtils.escapeHtml(line) +
|
|
132
|
+
'</tspan>'
|
|
133
|
+
)
|
|
134
|
+
.join('')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolves candidate text layer ids from layer names, falling back to
|
|
139
|
+
* standard Altium layer ids when legacy layer metadata is absent.
|
|
140
|
+
* @param {{ layerId: number, name: string }[]} primitiveLayers
|
|
141
|
+
* @param {'top' | 'bottom'} side
|
|
142
|
+
* @returns {Set<number>}
|
|
143
|
+
*/
|
|
144
|
+
static #resolveLayerIds(primitiveLayers, side) {
|
|
145
|
+
const matchers = PcbTextPrimitiveRenderer.#resolveLayerMatchers(side)
|
|
146
|
+
const layerIds = new Set(
|
|
147
|
+
primitiveLayers
|
|
148
|
+
.filter((layer) =>
|
|
149
|
+
matchers.some((matchesLayerName) =>
|
|
150
|
+
matchesLayerName(layer.name)
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
.map((layer) => Number(layer.layerId))
|
|
154
|
+
.filter((layerId) => Number.isInteger(layerId))
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (layerIds.size) {
|
|
158
|
+
return layerIds
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return new Set(
|
|
162
|
+
side === 'bottom' ? [32, 34, 36, 38, 73] : [1, 33, 35, 37, 73]
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolves side-specific layer-name matchers.
|
|
168
|
+
* @param {'top' | 'bottom'} side
|
|
169
|
+
* @returns {((layerName: string) => boolean)[]}
|
|
170
|
+
*/
|
|
171
|
+
static #resolveLayerMatchers(side) {
|
|
172
|
+
if (side === 'bottom') {
|
|
173
|
+
return [
|
|
174
|
+
(layerName) =>
|
|
175
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
176
|
+
layerName,
|
|
177
|
+
'BOTTOM OVERLAY'
|
|
178
|
+
),
|
|
179
|
+
(layerName) =>
|
|
180
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
181
|
+
layerName,
|
|
182
|
+
'BOTTOM SOLDER'
|
|
183
|
+
),
|
|
184
|
+
(layerName) =>
|
|
185
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
186
|
+
layerName,
|
|
187
|
+
'BOTTOM PASTE'
|
|
188
|
+
),
|
|
189
|
+
(layerName) =>
|
|
190
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
191
|
+
layerName,
|
|
192
|
+
'L4_BOT'
|
|
193
|
+
),
|
|
194
|
+
(layerName) =>
|
|
195
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
196
|
+
layerName,
|
|
197
|
+
'DRILL DRAWING'
|
|
198
|
+
)
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return [
|
|
203
|
+
(layerName) =>
|
|
204
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
205
|
+
layerName,
|
|
206
|
+
'TOP OVERLAY'
|
|
207
|
+
),
|
|
208
|
+
(layerName) =>
|
|
209
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
210
|
+
layerName,
|
|
211
|
+
'TOP SOLDER'
|
|
212
|
+
),
|
|
213
|
+
(layerName) =>
|
|
214
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
215
|
+
layerName,
|
|
216
|
+
'TOP PASTE'
|
|
217
|
+
),
|
|
218
|
+
(layerName) =>
|
|
219
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
220
|
+
layerName,
|
|
221
|
+
'L1_TOP'
|
|
222
|
+
),
|
|
223
|
+
(layerName) =>
|
|
224
|
+
PcbTextPrimitiveRenderer.#includesLayerName(
|
|
225
|
+
layerName,
|
|
226
|
+
'DRILL DRAWING'
|
|
227
|
+
)
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns true when a layer name contains the target token.
|
|
233
|
+
* @param {string} layerName
|
|
234
|
+
* @param {string} needle
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
237
|
+
static #includesLayerName(layerName, needle) {
|
|
238
|
+
return String(layerName || '')
|
|
239
|
+
.trim()
|
|
240
|
+
.toUpperCase()
|
|
241
|
+
.includes(needle)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Returns true for unresolved Altium component annotation placeholders.
|
|
246
|
+
* @param {{ isPlaceholder?: boolean }} text
|
|
247
|
+
* @returns {boolean}
|
|
248
|
+
*/
|
|
249
|
+
static #isPlaceholderText(text) {
|
|
250
|
+
return text?.isPlaceholder === true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
|
|
6
6
|
import { SchematicColorResolver } from './SchematicColorResolver.mjs'
|
|
7
|
+
import { SchematicSheetZoneRenderer } from './SchematicSheetZoneRenderer.mjs'
|
|
7
8
|
|
|
8
9
|
const {
|
|
9
10
|
basename,
|
|
@@ -27,7 +28,7 @@ export class SchematicSheetChromeRenderer {
|
|
|
27
28
|
*/
|
|
28
29
|
static buildMarkup(width, height, sheet, fileName) {
|
|
29
30
|
const margin = Math.max(Number(sheet?.marginWidth || 20), 10)
|
|
30
|
-
let markup =
|
|
31
|
+
let markup = SchematicSheetZoneRenderer.buildMarkup(
|
|
31
32
|
width,
|
|
32
33
|
height,
|
|
33
34
|
margin,
|
|
@@ -915,98 +916,6 @@ export class SchematicSheetChromeRenderer {
|
|
|
915
916
|
}
|
|
916
917
|
}
|
|
917
918
|
|
|
918
|
-
/**
|
|
919
|
-
* Builds the border zone labels around the sheet frame.
|
|
920
|
-
* @param {number} width
|
|
921
|
-
* @param {number} height
|
|
922
|
-
* @param {number} margin
|
|
923
|
-
* @param {{ borderOn?: boolean, xZones?: number, yZones?: number }} sheet
|
|
924
|
-
* @returns {string}
|
|
925
|
-
*/
|
|
926
|
-
static #buildSheetZoneMarkup(width, height, margin, sheet) {
|
|
927
|
-
if (!sheet?.borderOn) return ''
|
|
928
|
-
|
|
929
|
-
const xZones = Math.max(Number(sheet?.xZones || 0), 1)
|
|
930
|
-
const yZones = Math.max(Number(sheet?.yZones || 0), 1)
|
|
931
|
-
const innerWidth = Math.max(width - margin * 2, 10)
|
|
932
|
-
const innerHeight = Math.max(height - margin * 2, 10)
|
|
933
|
-
const separator = (x1, y1, x2, y2) =>
|
|
934
|
-
'<line class="sheet-zone-separator" x1="' +
|
|
935
|
-
formatNumber(x1) +
|
|
936
|
-
'" y1="' +
|
|
937
|
-
formatNumber(y1) +
|
|
938
|
-
'" x2="' +
|
|
939
|
-
formatNumber(x2) +
|
|
940
|
-
'" y2="' +
|
|
941
|
-
formatNumber(y2) +
|
|
942
|
-
'" />'
|
|
943
|
-
let markup = ''
|
|
944
|
-
|
|
945
|
-
for (let index = 1; index < xZones; index += 1) {
|
|
946
|
-
const x = margin + (innerWidth * index) / xZones
|
|
947
|
-
|
|
948
|
-
markup +=
|
|
949
|
-
separator(x, 0, x, margin) +
|
|
950
|
-
separator(x, height - margin, x, height)
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
for (let index = 0; index < xZones; index += 1) {
|
|
954
|
-
const label = String(index + 1)
|
|
955
|
-
const x = margin + (innerWidth * (index + 0.5)) / xZones
|
|
956
|
-
|
|
957
|
-
markup +=
|
|
958
|
-
createSvgText(
|
|
959
|
-
'sheet-zone-label',
|
|
960
|
-
x,
|
|
961
|
-
margin - 6,
|
|
962
|
-
label,
|
|
963
|
-
'var(--schematic-text-color)',
|
|
964
|
-
'middle'
|
|
965
|
-
) +
|
|
966
|
-
createSvgText(
|
|
967
|
-
'sheet-zone-label',
|
|
968
|
-
x,
|
|
969
|
-
height - 4,
|
|
970
|
-
label,
|
|
971
|
-
'var(--schematic-text-color)',
|
|
972
|
-
'middle'
|
|
973
|
-
)
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
for (let index = 1; index < yZones; index += 1) {
|
|
977
|
-
const y = margin + (innerHeight * index) / yZones
|
|
978
|
-
|
|
979
|
-
markup +=
|
|
980
|
-
separator(0, y, margin, y) +
|
|
981
|
-
separator(width - margin, y, width, y)
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
for (let index = 0; index < yZones; index += 1) {
|
|
985
|
-
const label = String.fromCharCode(65 + index)
|
|
986
|
-
const y = margin + (innerHeight * (index + 0.5)) / yZones
|
|
987
|
-
|
|
988
|
-
markup +=
|
|
989
|
-
createSvgText(
|
|
990
|
-
'sheet-zone-label',
|
|
991
|
-
8,
|
|
992
|
-
y + 2,
|
|
993
|
-
label,
|
|
994
|
-
'var(--schematic-text-color)',
|
|
995
|
-
'middle'
|
|
996
|
-
) +
|
|
997
|
-
createSvgText(
|
|
998
|
-
'sheet-zone-label',
|
|
999
|
-
width - 8,
|
|
1000
|
-
y + 2,
|
|
1001
|
-
label,
|
|
1002
|
-
'var(--schematic-text-color)',
|
|
1003
|
-
'middle'
|
|
1004
|
-
)
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
return markup
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
919
|
/**
|
|
1011
920
|
* Formats the sheet numbering shown in the title block.
|
|
1012
921
|
* @param {{ sheetNumber?: string, sheetTotal?: string }} titleBlock
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
|
|
6
|
+
|
|
7
|
+
const { createSvgText, formatNumber } = SchematicSvgUtils
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders schematic border zone labels and separators.
|
|
11
|
+
*/
|
|
12
|
+
export class SchematicSheetZoneRenderer {
|
|
13
|
+
/**
|
|
14
|
+
* Builds the border zone labels around the sheet frame.
|
|
15
|
+
* @param {number} width
|
|
16
|
+
* @param {number} height
|
|
17
|
+
* @param {number} margin
|
|
18
|
+
* @param {{ borderOn?: boolean, xZones?: number, yZones?: number }} sheet
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
static buildMarkup(width, height, margin, sheet) {
|
|
22
|
+
if (!sheet?.borderOn) return ''
|
|
23
|
+
|
|
24
|
+
const xZones = Math.max(Number(sheet?.xZones || 0), 1)
|
|
25
|
+
const yZones = Math.max(Number(sheet?.yZones || 0), 1)
|
|
26
|
+
const innerWidth = Math.max(width - margin * 2, 10)
|
|
27
|
+
const innerHeight = Math.max(height - margin * 2, 10)
|
|
28
|
+
const separator = (x1, y1, x2, y2) =>
|
|
29
|
+
'<line class="sheet-zone-separator" x1="' +
|
|
30
|
+
formatNumber(x1) +
|
|
31
|
+
'" y1="' +
|
|
32
|
+
formatNumber(y1) +
|
|
33
|
+
'" x2="' +
|
|
34
|
+
formatNumber(x2) +
|
|
35
|
+
'" y2="' +
|
|
36
|
+
formatNumber(y2) +
|
|
37
|
+
'" />'
|
|
38
|
+
let markup = ''
|
|
39
|
+
|
|
40
|
+
for (let index = 1; index < xZones; index += 1) {
|
|
41
|
+
const x = margin + (innerWidth * index) / xZones
|
|
42
|
+
|
|
43
|
+
markup +=
|
|
44
|
+
separator(x, 0, x, margin) +
|
|
45
|
+
separator(x, height - margin, x, height)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (let index = 0; index < xZones; index += 1) {
|
|
49
|
+
const label = String(index + 1)
|
|
50
|
+
const x = margin + (innerWidth * (index + 0.5)) / xZones
|
|
51
|
+
|
|
52
|
+
markup +=
|
|
53
|
+
createSvgText(
|
|
54
|
+
'sheet-zone-label',
|
|
55
|
+
x,
|
|
56
|
+
margin - 6,
|
|
57
|
+
label,
|
|
58
|
+
'var(--schematic-text-color)',
|
|
59
|
+
'middle'
|
|
60
|
+
) +
|
|
61
|
+
createSvgText(
|
|
62
|
+
'sheet-zone-label',
|
|
63
|
+
x,
|
|
64
|
+
height - 4,
|
|
65
|
+
label,
|
|
66
|
+
'var(--schematic-text-color)',
|
|
67
|
+
'middle'
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (let index = 1; index < yZones; index += 1) {
|
|
72
|
+
const y = margin + (innerHeight * index) / yZones
|
|
73
|
+
|
|
74
|
+
markup +=
|
|
75
|
+
separator(0, y, margin, y) +
|
|
76
|
+
separator(width - margin, y, width, y)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (let index = 0; index < yZones; index += 1) {
|
|
80
|
+
const label = String.fromCharCode(65 + index)
|
|
81
|
+
const y = margin + (innerHeight * (index + 0.5)) / yZones
|
|
82
|
+
|
|
83
|
+
markup +=
|
|
84
|
+
createSvgText(
|
|
85
|
+
'sheet-zone-label',
|
|
86
|
+
8,
|
|
87
|
+
y + 2,
|
|
88
|
+
label,
|
|
89
|
+
'var(--schematic-text-color)',
|
|
90
|
+
'middle'
|
|
91
|
+
) +
|
|
92
|
+
createSvgText(
|
|
93
|
+
'sheet-zone-label',
|
|
94
|
+
width - 8,
|
|
95
|
+
y + 2,
|
|
96
|
+
label,
|
|
97
|
+
'var(--schematic-text-color)',
|
|
98
|
+
'middle'
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return markup
|
|
103
|
+
}
|
|
104
|
+
}
|