circuitjson-toolkit 1.0.3 → 1.0.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/AGENTS.md +5 -3
- package/README.md +21 -2
- package/docs/api.md +50 -4
- package/docs/model-format.md +21 -3
- package/package.json +3 -2
- package/spec/library-scope.md +4 -1
- package/src/core/CircuitJsonBomBuilder.mjs +143 -0
- package/src/core/CircuitJsonDocument.mjs +46 -13
- package/src/core/CircuitJsonElementValidator.mjs +990 -0
- package/src/core/CircuitJsonIndexer.mjs +773 -4
- package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- package/src/core/CircuitJsonParser.mjs +22 -6
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
- package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
- package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
- package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
- package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
- package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
- package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +481 -0
- package/src/core/CircuitJsonUnits.mjs +133 -8
- package/src/core/PcbBoundsSelectionModel.mjs +250 -0
- package/src/core/PcbCandidateSelectionModel.mjs +77 -0
- package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
- package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
- package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
- package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
- package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
- package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
- package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
- package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
- package/src/core/spice/SpiceSimulationService.mjs +92 -0
- package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
- package/src/index.mjs +8 -0
- package/src/renderers.mjs +29 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
import { CircuitJsonIndexer } from '../core/CircuitJsonIndexer.mjs'
|
|
2
|
+
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
3
|
+
import { CircuitJsonSchematicSvgArcPath } from './CircuitJsonSchematicSvgArcPath.mjs'
|
|
4
|
+
import { CircuitJsonSchematicSvgPortMetadata } from './CircuitJsonSchematicSvgPortMetadata.mjs'
|
|
5
|
+
import { CircuitJsonSchematicSvgPrimitiveAttributes } from './CircuitJsonSchematicSvgPrimitiveAttributes.mjs'
|
|
6
|
+
import { CircuitJsonSchematicTableSvgRenderer } from './CircuitJsonSchematicTableSvgRenderer.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders standards-shaped schematic element arrays into app-compatible SVG.
|
|
10
|
+
*/
|
|
11
|
+
export class CircuitJsonSchematicSvgRenderer {
|
|
12
|
+
/**
|
|
13
|
+
* Renders one schematic document into SVG markup.
|
|
14
|
+
* @param {object | object[]} documentModel Parsed document model.
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
static render(documentModel) {
|
|
18
|
+
const elements =
|
|
19
|
+
CircuitJsonSchematicSvgRenderer.#elements(documentModel)
|
|
20
|
+
const index = CircuitJsonIndexer.index(elements)
|
|
21
|
+
const bounds = CircuitJsonSchematicSvgRenderer.#bounds(index)
|
|
22
|
+
const viewBox = CircuitJsonSchematicSvgRenderer.#viewBox(bounds)
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
'<svg class="schematic-svg schematic-svg--circuitjson" xmlns="http://www.w3.org/2000/svg" role="img" viewBox="' +
|
|
26
|
+
CircuitJsonSchematicSvgRenderer.#formatViewBox(viewBox) +
|
|
27
|
+
'">' +
|
|
28
|
+
CircuitJsonSchematicSvgRenderer.#renderSheet(bounds) +
|
|
29
|
+
CircuitJsonSchematicSvgRenderer.#renderComponents(index) +
|
|
30
|
+
CircuitJsonSchematicSvgRenderer.#renderSymbols(index) +
|
|
31
|
+
CircuitJsonSchematicSvgRenderer.#renderGroups(index) +
|
|
32
|
+
CircuitJsonSchematicSvgRenderer.#renderLines(index) +
|
|
33
|
+
CircuitJsonSchematicSvgRenderer.#renderShapes(index) +
|
|
34
|
+
CircuitJsonSchematicSvgRenderer.#renderPorts(index) +
|
|
35
|
+
CircuitJsonSchematicSvgRenderer.#renderTables(index) +
|
|
36
|
+
CircuitJsonSchematicSvgRenderer.#renderTexts(index) +
|
|
37
|
+
CircuitJsonSchematicSvgRenderer.#renderProbes(index) +
|
|
38
|
+
CircuitJsonSchematicSvgRenderer.#renderDebugObjects(index) +
|
|
39
|
+
CircuitJsonSchematicSvgRenderer.#renderDiagnostics(index) +
|
|
40
|
+
'</svg>'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns element rows from an array or wrapper object.
|
|
46
|
+
* @param {object | object[]} documentModel Parsed document model.
|
|
47
|
+
* @returns {object[]}
|
|
48
|
+
*/
|
|
49
|
+
static #elements(documentModel) {
|
|
50
|
+
if (Array.isArray(documentModel)) return documentModel
|
|
51
|
+
if (Array.isArray(documentModel?.elements))
|
|
52
|
+
return documentModel.elements
|
|
53
|
+
if (Array.isArray(documentModel?.circuitJson)) {
|
|
54
|
+
return documentModel.circuitJson
|
|
55
|
+
}
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Renders the schematic sheet backdrop.
|
|
61
|
+
* @param {object} bounds Sheet bounds.
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
static #renderSheet(bounds) {
|
|
65
|
+
return (
|
|
66
|
+
'<rect class="sheet-backdrop schematic-sheet" x="' +
|
|
67
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(bounds.minX) +
|
|
68
|
+
'" y="' +
|
|
69
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(bounds.minY) +
|
|
70
|
+
'" width="' +
|
|
71
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(bounds.width) +
|
|
72
|
+
'" height="' +
|
|
73
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(bounds.height) +
|
|
74
|
+
'"></rect>'
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Renders schematic component boxes.
|
|
80
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
static #renderComponents(index) {
|
|
84
|
+
const sourceNames = CircuitJsonSchematicSvgRenderer.#sourceNames(index)
|
|
85
|
+
const components = CircuitJsonSchematicSvgRenderer.#all(
|
|
86
|
+
index,
|
|
87
|
+
'schematic_component'
|
|
88
|
+
).map((component, componentIndex) => {
|
|
89
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(
|
|
90
|
+
component
|
|
91
|
+
) || {
|
|
92
|
+
x: 0,
|
|
93
|
+
y: 0
|
|
94
|
+
}
|
|
95
|
+
const size = CircuitJsonUnits.optionalSize(component.size) || {
|
|
96
|
+
width: 4,
|
|
97
|
+
height: 3
|
|
98
|
+
}
|
|
99
|
+
const key =
|
|
100
|
+
sourceNames.get(String(component.source_component_id || '')) ||
|
|
101
|
+
String(component.name || 'U' + (componentIndex + 1))
|
|
102
|
+
const x = center.x - size.width / 2
|
|
103
|
+
const y = center.y - size.height / 2
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
'<g class="schematic-component" data-component-key="' +
|
|
107
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(key) +
|
|
108
|
+
'" data-schematic-component-id="' +
|
|
109
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
110
|
+
component.schematic_component_id || ''
|
|
111
|
+
) +
|
|
112
|
+
'">' +
|
|
113
|
+
'<rect class="schematic-component__body" x="' +
|
|
114
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(x) +
|
|
115
|
+
'" y="' +
|
|
116
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(y) +
|
|
117
|
+
'" width="' +
|
|
118
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.width) +
|
|
119
|
+
'" height="' +
|
|
120
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.height) +
|
|
121
|
+
'"></rect><text class="schematic-component__label" x="' +
|
|
122
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
123
|
+
'" y="' +
|
|
124
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
125
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
126
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(key) +
|
|
127
|
+
'</text></g>'
|
|
128
|
+
)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return components.length
|
|
132
|
+
? '<g class="schematic-components">' + components.join('') + '</g>'
|
|
133
|
+
: ''
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Renders reusable schematic symbol rows.
|
|
138
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
static #renderSymbols(index) {
|
|
142
|
+
const sourceNames = CircuitJsonSchematicSvgRenderer.#sourceNames(index)
|
|
143
|
+
const symbols = CircuitJsonSchematicSvgRenderer.#all(
|
|
144
|
+
index,
|
|
145
|
+
'schematic_symbol'
|
|
146
|
+
)
|
|
147
|
+
.map((symbol, symbolIndex) =>
|
|
148
|
+
CircuitJsonSchematicSvgRenderer.#symbolElement(
|
|
149
|
+
symbol,
|
|
150
|
+
symbolIndex,
|
|
151
|
+
sourceNames
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
return symbols.length
|
|
156
|
+
? '<g class="schematic-symbols">' + symbols.join('') + '</g>'
|
|
157
|
+
: ''
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Renders one schematic symbol.
|
|
162
|
+
* @param {object} symbol Symbol row.
|
|
163
|
+
* @param {number} symbolIndex Symbol index.
|
|
164
|
+
* @param {Map<string, string>} sourceNames Source display names.
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
static #symbolElement(symbol, symbolIndex, sourceNames) {
|
|
168
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(symbol)
|
|
169
|
+
const size = CircuitJsonSchematicSvgRenderer.#size(symbol)
|
|
170
|
+
if (!center || !size) return ''
|
|
171
|
+
const key =
|
|
172
|
+
sourceNames.get(String(symbol.source_component_id || '')) ||
|
|
173
|
+
String(symbol.name || 'SYM' + (symbolIndex + 1))
|
|
174
|
+
const x = center.x - size.width / 2
|
|
175
|
+
const y = center.y - size.height / 2
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
'<g class="schematic-symbol" data-component-key="' +
|
|
179
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(key) +
|
|
180
|
+
'" data-schematic-symbol-id="' +
|
|
181
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
182
|
+
symbol.schematic_symbol_id || ''
|
|
183
|
+
) +
|
|
184
|
+
'"><rect class="schematic-symbol__body" x="' +
|
|
185
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(x) +
|
|
186
|
+
'" y="' +
|
|
187
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(y) +
|
|
188
|
+
'" width="' +
|
|
189
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.width) +
|
|
190
|
+
'" height="' +
|
|
191
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.height) +
|
|
192
|
+
'"></rect><text class="schematic-symbol__label" x="' +
|
|
193
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
194
|
+
'" y="' +
|
|
195
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
196
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
197
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(key) +
|
|
198
|
+
'</text></g>'
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Renders schematic grouping bounds.
|
|
204
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
static #renderGroups(index) {
|
|
208
|
+
const groups = CircuitJsonSchematicSvgRenderer.#all(
|
|
209
|
+
index,
|
|
210
|
+
'schematic_group'
|
|
211
|
+
)
|
|
212
|
+
.map((element) =>
|
|
213
|
+
CircuitJsonSchematicSvgRenderer.#groupElement(element)
|
|
214
|
+
)
|
|
215
|
+
.filter(Boolean)
|
|
216
|
+
return groups.length
|
|
217
|
+
? '<g class="schematic-groups">' + groups.join('') + '</g>'
|
|
218
|
+
: ''
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Renders one schematic group.
|
|
223
|
+
* @param {object} element Group element.
|
|
224
|
+
* @returns {string}
|
|
225
|
+
*/
|
|
226
|
+
static #groupElement(element) {
|
|
227
|
+
const rect = CircuitJsonSchematicSvgRenderer.#rectAttributes(element)
|
|
228
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
229
|
+
if (!rect || !center) return ''
|
|
230
|
+
const label = String(element.name || element.schematic_group_id || '')
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
'<g class="schematic-group" data-schematic-group-id="' +
|
|
234
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
235
|
+
element.schematic_group_id || ''
|
|
236
|
+
) +
|
|
237
|
+
'"><rect class="schematic-group__bounds" ' +
|
|
238
|
+
rect +
|
|
239
|
+
'></rect><text class="schematic-group__label" x="' +
|
|
240
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
241
|
+
'" y="' +
|
|
242
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
243
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
244
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(label) +
|
|
245
|
+
'</text></g>'
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Renders schematic line and trace elements.
|
|
251
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
252
|
+
* @returns {string}
|
|
253
|
+
*/
|
|
254
|
+
static #renderLines(index) {
|
|
255
|
+
const lines = ['schematic_line', 'schematic_trace']
|
|
256
|
+
.flatMap((type) =>
|
|
257
|
+
CircuitJsonSchematicSvgRenderer.#all(index, type)
|
|
258
|
+
)
|
|
259
|
+
.map((element) =>
|
|
260
|
+
CircuitJsonSchematicSvgRenderer.#lineElement(element)
|
|
261
|
+
)
|
|
262
|
+
.filter(Boolean)
|
|
263
|
+
return lines.length
|
|
264
|
+
? '<g class="schematic-wires">' + lines.join('') + '</g>'
|
|
265
|
+
: ''
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Renders one schematic line.
|
|
270
|
+
* @param {object} element Source element.
|
|
271
|
+
* @returns {string}
|
|
272
|
+
*/
|
|
273
|
+
static #lineElement(element) {
|
|
274
|
+
const start = CircuitJsonSchematicSvgRenderer.#point({
|
|
275
|
+
x: element.x1 ?? element.start?.x,
|
|
276
|
+
y: element.y1 ?? element.start?.y
|
|
277
|
+
})
|
|
278
|
+
const end = CircuitJsonSchematicSvgRenderer.#point({
|
|
279
|
+
x: element.x2 ?? element.end?.x,
|
|
280
|
+
y: element.y2 ?? element.end?.y
|
|
281
|
+
})
|
|
282
|
+
if (!start || !end) return ''
|
|
283
|
+
return (
|
|
284
|
+
'<line class="schematic-wire" x1="' +
|
|
285
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(start.x) +
|
|
286
|
+
'" y1="' +
|
|
287
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(start.y) +
|
|
288
|
+
'" x2="' +
|
|
289
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(end.x) +
|
|
290
|
+
'" y2="' +
|
|
291
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(end.y) +
|
|
292
|
+
'"' +
|
|
293
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
|
|
294
|
+
fill: false
|
|
295
|
+
}) +
|
|
296
|
+
'></line>'
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Renders schematic rectangle and circle shapes.
|
|
302
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
303
|
+
* @returns {string}
|
|
304
|
+
*/
|
|
305
|
+
static #renderShapes(index) {
|
|
306
|
+
const rects = ['schematic_rect', 'schematic_box']
|
|
307
|
+
.flatMap((type) =>
|
|
308
|
+
CircuitJsonSchematicSvgRenderer.#all(index, type)
|
|
309
|
+
)
|
|
310
|
+
.map((element) =>
|
|
311
|
+
CircuitJsonSchematicSvgRenderer.#rectElement(element)
|
|
312
|
+
)
|
|
313
|
+
.filter(Boolean)
|
|
314
|
+
const circles = CircuitJsonSchematicSvgRenderer.#all(
|
|
315
|
+
index,
|
|
316
|
+
'schematic_circle'
|
|
317
|
+
)
|
|
318
|
+
.map((element) =>
|
|
319
|
+
CircuitJsonSchematicSvgRenderer.#circleElement(element)
|
|
320
|
+
)
|
|
321
|
+
.filter(Boolean)
|
|
322
|
+
const arcs = CircuitJsonSchematicSvgRenderer.#all(
|
|
323
|
+
index,
|
|
324
|
+
'schematic_arc'
|
|
325
|
+
)
|
|
326
|
+
.map((element) =>
|
|
327
|
+
CircuitJsonSchematicSvgRenderer.#arcElement(element)
|
|
328
|
+
)
|
|
329
|
+
.filter(Boolean)
|
|
330
|
+
const paths = CircuitJsonSchematicSvgRenderer.#all(
|
|
331
|
+
index,
|
|
332
|
+
'schematic_path'
|
|
333
|
+
)
|
|
334
|
+
.map((element) =>
|
|
335
|
+
CircuitJsonSchematicSvgRenderer.#pathElement(element)
|
|
336
|
+
)
|
|
337
|
+
.filter(Boolean)
|
|
338
|
+
const markup = [...rects, ...circles, ...arcs, ...paths]
|
|
339
|
+
return markup.length
|
|
340
|
+
? '<g class="schematic-shapes">' + markup.join('') + '</g>'
|
|
341
|
+
: ''
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Renders one schematic rectangle.
|
|
346
|
+
* @param {object} element Rectangle element.
|
|
347
|
+
* @returns {string}
|
|
348
|
+
*/
|
|
349
|
+
static #rectElement(element) {
|
|
350
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
351
|
+
const size = CircuitJsonUnits.optionalSize(element.size || element)
|
|
352
|
+
if (!center || !size) return ''
|
|
353
|
+
return (
|
|
354
|
+
'<rect class="schematic-shape schematic-shape--rect" x="' +
|
|
355
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(
|
|
356
|
+
center.x - size.width / 2
|
|
357
|
+
) +
|
|
358
|
+
'" y="' +
|
|
359
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(
|
|
360
|
+
center.y - size.height / 2
|
|
361
|
+
) +
|
|
362
|
+
'" width="' +
|
|
363
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.width) +
|
|
364
|
+
'" height="' +
|
|
365
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.height) +
|
|
366
|
+
'"' +
|
|
367
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element) +
|
|
368
|
+
'></rect>'
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Renders one schematic circle.
|
|
374
|
+
* @param {object} element Circle element.
|
|
375
|
+
* @returns {string}
|
|
376
|
+
*/
|
|
377
|
+
static #circleElement(element) {
|
|
378
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
379
|
+
const radius = CircuitJsonUnits.optionalLength(element.radius)
|
|
380
|
+
if (!center || radius === null) return ''
|
|
381
|
+
return (
|
|
382
|
+
'<circle class="schematic-shape schematic-shape--circle" cx="' +
|
|
383
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
384
|
+
'" cy="' +
|
|
385
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
386
|
+
'" r="' +
|
|
387
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(radius) +
|
|
388
|
+
'"' +
|
|
389
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element) +
|
|
390
|
+
'></circle>'
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Renders one schematic arc.
|
|
396
|
+
* @param {object} element Arc element.
|
|
397
|
+
* @returns {string}
|
|
398
|
+
*/
|
|
399
|
+
static #arcElement(element) {
|
|
400
|
+
const threePointPath = CircuitJsonSchematicSvgArcPath.fromThreePoints(
|
|
401
|
+
CircuitJsonSchematicSvgRenderer.#point(element.start),
|
|
402
|
+
CircuitJsonSchematicSvgRenderer.#point(element.mid),
|
|
403
|
+
CircuitJsonSchematicSvgRenderer.#point(element.end)
|
|
404
|
+
)
|
|
405
|
+
if (threePointPath) {
|
|
406
|
+
return (
|
|
407
|
+
'<path class="schematic-shape schematic-shape--arc" d="' +
|
|
408
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(threePointPath) +
|
|
409
|
+
'"' +
|
|
410
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
|
|
411
|
+
fill: false
|
|
412
|
+
}) +
|
|
413
|
+
'></path>'
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
418
|
+
const radius = CircuitJsonUnits.optionalLength(element.radius)
|
|
419
|
+
if (!center || radius === null) return ''
|
|
420
|
+
const startAngle = CircuitJsonUnits.angle(
|
|
421
|
+
element.start_angle ?? element.startAngle,
|
|
422
|
+
0
|
|
423
|
+
)
|
|
424
|
+
const endAngle = CircuitJsonUnits.angle(
|
|
425
|
+
element.end_angle ?? element.endAngle,
|
|
426
|
+
360
|
|
427
|
+
)
|
|
428
|
+
const start = CircuitJsonSchematicSvgRenderer.#polarPoint(
|
|
429
|
+
center,
|
|
430
|
+
radius,
|
|
431
|
+
startAngle
|
|
432
|
+
)
|
|
433
|
+
const end = CircuitJsonSchematicSvgRenderer.#polarPoint(
|
|
434
|
+
center,
|
|
435
|
+
radius,
|
|
436
|
+
endAngle
|
|
437
|
+
)
|
|
438
|
+
const largeArc = Math.abs(endAngle - startAngle) > 180 ? 1 : 0
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
'<path class="schematic-shape schematic-shape--arc" d="M ' +
|
|
442
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(start.x) +
|
|
443
|
+
' ' +
|
|
444
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(start.y) +
|
|
445
|
+
' A ' +
|
|
446
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(radius) +
|
|
447
|
+
' ' +
|
|
448
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(radius) +
|
|
449
|
+
' 0 ' +
|
|
450
|
+
largeArc +
|
|
451
|
+
' 1 ' +
|
|
452
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(end.x) +
|
|
453
|
+
' ' +
|
|
454
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(end.y) +
|
|
455
|
+
'"' +
|
|
456
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
|
|
457
|
+
fill: false
|
|
458
|
+
}) +
|
|
459
|
+
'></path>'
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Renders one schematic polyline path.
|
|
465
|
+
* @param {object} element Path element.
|
|
466
|
+
* @returns {string}
|
|
467
|
+
*/
|
|
468
|
+
static #pathElement(element) {
|
|
469
|
+
const points = CircuitJsonSchematicSvgRenderer.#points(element)
|
|
470
|
+
if (points.length < 2) return ''
|
|
471
|
+
const tag = element?.is_filled === true ? 'polygon' : 'polyline'
|
|
472
|
+
return (
|
|
473
|
+
'<' +
|
|
474
|
+
tag +
|
|
475
|
+
' class="schematic-shape schematic-shape--path" points="' +
|
|
476
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
477
|
+
CircuitJsonSchematicSvgRenderer.#pointsAttribute(points)
|
|
478
|
+
) +
|
|
479
|
+
'"' +
|
|
480
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element) +
|
|
481
|
+
'></' +
|
|
482
|
+
tag +
|
|
483
|
+
'>'
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Renders schematic ports.
|
|
489
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
490
|
+
* @returns {string}
|
|
491
|
+
*/
|
|
492
|
+
static #renderPorts(index) {
|
|
493
|
+
const sourcePorts = CircuitJsonSchematicSvgPortMetadata.sourcePorts(
|
|
494
|
+
CircuitJsonSchematicSvgRenderer.#all(index, 'source_port')
|
|
495
|
+
)
|
|
496
|
+
const ports = CircuitJsonSchematicSvgRenderer.#all(
|
|
497
|
+
index,
|
|
498
|
+
'schematic_port'
|
|
499
|
+
)
|
|
500
|
+
.map((element) =>
|
|
501
|
+
CircuitJsonSchematicSvgRenderer.#portElement(
|
|
502
|
+
element,
|
|
503
|
+
sourcePorts
|
|
504
|
+
)
|
|
505
|
+
)
|
|
506
|
+
.filter(Boolean)
|
|
507
|
+
return ports.length
|
|
508
|
+
? '<g class="schematic-ports">' + ports.join('') + '</g>'
|
|
509
|
+
: ''
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Renders one schematic port marker.
|
|
514
|
+
* @param {object} element Port element.
|
|
515
|
+
* @param {Map<string, object>} sourcePorts Source port lookup.
|
|
516
|
+
* @returns {string}
|
|
517
|
+
*/
|
|
518
|
+
static #portElement(element, sourcePorts) {
|
|
519
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
520
|
+
if (!center) return ''
|
|
521
|
+
const sourcePortId = String(element.source_port_id || '').trim()
|
|
522
|
+
const sourcePort = sourcePorts.get(sourcePortId) || null
|
|
523
|
+
const label = CircuitJsonSchematicSvgPortMetadata.label(
|
|
524
|
+
element,
|
|
525
|
+
sourcePort
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
return (
|
|
529
|
+
'<g ' +
|
|
530
|
+
CircuitJsonSchematicSvgPortMetadata.attributes(
|
|
531
|
+
element,
|
|
532
|
+
sourcePort
|
|
533
|
+
) +
|
|
534
|
+
'><circle class="schematic-port__terminal" cx="' +
|
|
535
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
536
|
+
'" cy="' +
|
|
537
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
538
|
+
'" r="0.35"></circle><text class="schematic-port__label" x="' +
|
|
539
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x + 0.7) +
|
|
540
|
+
'" y="' +
|
|
541
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
542
|
+
'" dominant-baseline="central">' +
|
|
543
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(label) +
|
|
544
|
+
'</text></g>'
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Renders schematic tables and cells.
|
|
550
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
551
|
+
* @returns {string}
|
|
552
|
+
*/
|
|
553
|
+
static #renderTables(index) {
|
|
554
|
+
return CircuitJsonSchematicTableSvgRenderer.render(index, {
|
|
555
|
+
escapeHtml: CircuitJsonSchematicSvgRenderer.#escapeHtml,
|
|
556
|
+
formatNumber: CircuitJsonSchematicSvgRenderer.#formatNumber
|
|
557
|
+
})
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Renders schematic text and net labels.
|
|
562
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
563
|
+
* @returns {string}
|
|
564
|
+
*/
|
|
565
|
+
static #renderTexts(index) {
|
|
566
|
+
const texts = ['schematic_text', 'schematic_net_label']
|
|
567
|
+
.flatMap((type) =>
|
|
568
|
+
CircuitJsonSchematicSvgRenderer.#all(index, type)
|
|
569
|
+
)
|
|
570
|
+
.map((element) =>
|
|
571
|
+
CircuitJsonSchematicSvgRenderer.#textElement(element)
|
|
572
|
+
)
|
|
573
|
+
.filter(Boolean)
|
|
574
|
+
return texts.length
|
|
575
|
+
? '<g class="schematic-texts">' + texts.join('') + '</g>'
|
|
576
|
+
: ''
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Renders one schematic text row.
|
|
581
|
+
* @param {object} element Text element.
|
|
582
|
+
* @returns {string}
|
|
583
|
+
*/
|
|
584
|
+
static #textElement(element) {
|
|
585
|
+
const point =
|
|
586
|
+
CircuitJsonSchematicSvgRenderer.#point(element.anchor_position) ||
|
|
587
|
+
CircuitJsonSchematicSvgRenderer.#point(element.position) ||
|
|
588
|
+
CircuitJsonSchematicSvgRenderer.#center(element)
|
|
589
|
+
if (!point) return ''
|
|
590
|
+
const fontSize = CircuitJsonUnits.optionalLength(element.font_size)
|
|
591
|
+
const rotation = CircuitJsonUnits.angle(
|
|
592
|
+
element.ccw_rotation ?? element.rotation,
|
|
593
|
+
0
|
|
594
|
+
)
|
|
595
|
+
const attributes = [
|
|
596
|
+
'class="schematic-text"',
|
|
597
|
+
'x="' +
|
|
598
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(point.x) +
|
|
599
|
+
'"',
|
|
600
|
+
'y="' + CircuitJsonSchematicSvgRenderer.#formatNumber(point.y) + '"'
|
|
601
|
+
]
|
|
602
|
+
|
|
603
|
+
if (fontSize !== null) {
|
|
604
|
+
attributes.push(
|
|
605
|
+
'font-size="' +
|
|
606
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(fontSize) +
|
|
607
|
+
'"'
|
|
608
|
+
)
|
|
609
|
+
}
|
|
610
|
+
if (rotation) {
|
|
611
|
+
attributes.push(
|
|
612
|
+
'transform="rotate(' +
|
|
613
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(rotation) +
|
|
614
|
+
' ' +
|
|
615
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(point.x) +
|
|
616
|
+
' ' +
|
|
617
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(point.y) +
|
|
618
|
+
')"'
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return (
|
|
623
|
+
'<text ' +
|
|
624
|
+
attributes.join(' ') +
|
|
625
|
+
'>' +
|
|
626
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
627
|
+
element.text || element.name || ''
|
|
628
|
+
) +
|
|
629
|
+
'</text>'
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Renders schematic probe markers.
|
|
635
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
636
|
+
* @returns {string}
|
|
637
|
+
*/
|
|
638
|
+
static #renderProbes(index) {
|
|
639
|
+
const probes = ['schematic_voltage_probe']
|
|
640
|
+
.flatMap((type) =>
|
|
641
|
+
CircuitJsonSchematicSvgRenderer.#all(index, type)
|
|
642
|
+
)
|
|
643
|
+
.map((element) =>
|
|
644
|
+
CircuitJsonSchematicSvgRenderer.#probeElement(element)
|
|
645
|
+
)
|
|
646
|
+
.filter(Boolean)
|
|
647
|
+
return probes.length
|
|
648
|
+
? '<g class="schematic-probes">' + probes.join('') + '</g>'
|
|
649
|
+
: ''
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Renders one schematic probe.
|
|
654
|
+
* @param {object} element Probe element.
|
|
655
|
+
* @returns {string}
|
|
656
|
+
*/
|
|
657
|
+
static #probeElement(element) {
|
|
658
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
659
|
+
if (!center) return ''
|
|
660
|
+
const label = String(element.name || element.label || '').trim()
|
|
661
|
+
return (
|
|
662
|
+
'<g class="schematic-probe" data-schematic-probe-id="' +
|
|
663
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
664
|
+
CircuitJsonIndexer.getElementId(element)
|
|
665
|
+
) +
|
|
666
|
+
'"><circle cx="' +
|
|
667
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
668
|
+
'" cy="' +
|
|
669
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
670
|
+
'" r="0.45"></circle><text x="' +
|
|
671
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x + 0.65) +
|
|
672
|
+
'" y="' +
|
|
673
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
674
|
+
'" dominant-baseline="central">' +
|
|
675
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(label) +
|
|
676
|
+
'</text></g>'
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Renders schematic debug objects.
|
|
682
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
683
|
+
* @returns {string}
|
|
684
|
+
*/
|
|
685
|
+
static #renderDebugObjects(index) {
|
|
686
|
+
const objects = CircuitJsonSchematicSvgRenderer.#all(
|
|
687
|
+
index,
|
|
688
|
+
'schematic_debug_object'
|
|
689
|
+
)
|
|
690
|
+
.map((element) =>
|
|
691
|
+
CircuitJsonSchematicSvgRenderer.#debugObjectElement(element)
|
|
692
|
+
)
|
|
693
|
+
.filter(Boolean)
|
|
694
|
+
return objects.length
|
|
695
|
+
? '<g class="schematic-debug-objects">' + objects.join('') + '</g>'
|
|
696
|
+
: ''
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Renders one schematic debug object.
|
|
701
|
+
* @param {object} element Debug element.
|
|
702
|
+
* @returns {string}
|
|
703
|
+
*/
|
|
704
|
+
static #debugObjectElement(element) {
|
|
705
|
+
const rect = CircuitJsonSchematicSvgRenderer.#rectAttributes(element)
|
|
706
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
707
|
+
if (!rect || !center) return ''
|
|
708
|
+
const message = String(element.message || '').trim()
|
|
709
|
+
|
|
710
|
+
return (
|
|
711
|
+
'<g class="schematic-debug-object" data-schematic-debug-object-id="' +
|
|
712
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
713
|
+
element.schematic_debug_object_id || ''
|
|
714
|
+
) +
|
|
715
|
+
'"><title>' +
|
|
716
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(message) +
|
|
717
|
+
'</title><rect ' +
|
|
718
|
+
rect +
|
|
719
|
+
'></rect><text x="' +
|
|
720
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
721
|
+
'" y="' +
|
|
722
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
723
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
724
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(message) +
|
|
725
|
+
'</text></g>'
|
|
726
|
+
)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Renders schematic warning and error markers.
|
|
731
|
+
* @param {{ elements: object[] }} index Element index.
|
|
732
|
+
* @returns {string}
|
|
733
|
+
*/
|
|
734
|
+
static #renderDiagnostics(index) {
|
|
735
|
+
const diagnostics = (index.elements || [])
|
|
736
|
+
.filter((element) =>
|
|
737
|
+
/(?:error|warning)/iu.test(String(element?.type || ''))
|
|
738
|
+
)
|
|
739
|
+
.map((element) =>
|
|
740
|
+
CircuitJsonSchematicSvgRenderer.#diagnosticElement(element)
|
|
741
|
+
)
|
|
742
|
+
.filter(Boolean)
|
|
743
|
+
return diagnostics.length
|
|
744
|
+
? '<g class="schematic-diagnostics">' +
|
|
745
|
+
diagnostics.join('') +
|
|
746
|
+
'</g>'
|
|
747
|
+
: ''
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Renders one schematic diagnostic marker.
|
|
752
|
+
* @param {object} element Diagnostic row.
|
|
753
|
+
* @returns {string}
|
|
754
|
+
*/
|
|
755
|
+
static #diagnosticElement(element) {
|
|
756
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element) || {
|
|
757
|
+
x: 0,
|
|
758
|
+
y: 0
|
|
759
|
+
}
|
|
760
|
+
const severity = String(element.warning_type ? 'warning' : 'error')
|
|
761
|
+
return (
|
|
762
|
+
'<g class="schematic-diagnostic-marker schematic-diagnostic-marker--' +
|
|
763
|
+
severity +
|
|
764
|
+
'" data-schematic-diagnostic-id="' +
|
|
765
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
766
|
+
CircuitJsonIndexer.getElementId(element)
|
|
767
|
+
) +
|
|
768
|
+
'"><title>' +
|
|
769
|
+
CircuitJsonSchematicSvgRenderer.#escapeHtml(
|
|
770
|
+
element.message ||
|
|
771
|
+
element.error_type ||
|
|
772
|
+
element.warning_type ||
|
|
773
|
+
''
|
|
774
|
+
) +
|
|
775
|
+
'</title><circle cx="' +
|
|
776
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
|
|
777
|
+
'" cy="' +
|
|
778
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
|
|
779
|
+
'" r="0.45"></circle></g>'
|
|
780
|
+
)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Builds source component display names.
|
|
785
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
786
|
+
* @returns {Map<string, string>}
|
|
787
|
+
*/
|
|
788
|
+
static #sourceNames(index) {
|
|
789
|
+
return new Map(
|
|
790
|
+
CircuitJsonSchematicSvgRenderer.#all(index, 'source_component').map(
|
|
791
|
+
(element) => [
|
|
792
|
+
String(element.source_component_id || '').trim(),
|
|
793
|
+
String(element.name || element.source_component_id || '')
|
|
794
|
+
]
|
|
795
|
+
)
|
|
796
|
+
)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Resolves schematic bounds.
|
|
801
|
+
* @param {{ elements: object[], elementsByType: Map<string, object[]> }} index Element index.
|
|
802
|
+
* @returns {{ minX: number, minY: number, maxX: number, maxY: number, width: number, height: number }}
|
|
803
|
+
*/
|
|
804
|
+
static #bounds(index) {
|
|
805
|
+
const sheet = CircuitJsonSchematicSvgRenderer.#all(
|
|
806
|
+
index,
|
|
807
|
+
'schematic_sheet'
|
|
808
|
+
)[0]
|
|
809
|
+
const width = CircuitJsonUnits.optionalLength(sheet?.width)
|
|
810
|
+
const height = CircuitJsonUnits.optionalLength(sheet?.height)
|
|
811
|
+
if (width !== null && height !== null) {
|
|
812
|
+
return {
|
|
813
|
+
minX: 0,
|
|
814
|
+
minY: 0,
|
|
815
|
+
maxX: width,
|
|
816
|
+
maxY: height,
|
|
817
|
+
width,
|
|
818
|
+
height
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
minX: -10,
|
|
823
|
+
minY: -10,
|
|
824
|
+
maxX: 10,
|
|
825
|
+
maxY: 10,
|
|
826
|
+
width: 20,
|
|
827
|
+
height: 20
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Builds a padded SVG viewBox.
|
|
833
|
+
* @param {object} bounds Bounds.
|
|
834
|
+
* @returns {object}
|
|
835
|
+
*/
|
|
836
|
+
static #viewBox(bounds) {
|
|
837
|
+
const padding = Math.max(bounds.width, bounds.height, 1) * 0.06
|
|
838
|
+
return {
|
|
839
|
+
minX: bounds.minX - padding,
|
|
840
|
+
minY: bounds.minY - padding,
|
|
841
|
+
width: bounds.width + padding * 2,
|
|
842
|
+
height: bounds.height + padding * 2
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Returns indexed elements by type.
|
|
848
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
849
|
+
* @param {string} type Element type.
|
|
850
|
+
* @returns {object[]}
|
|
851
|
+
*/
|
|
852
|
+
static #all(index, type) {
|
|
853
|
+
return index.elementsByType.get(type) || []
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Resolves an element center.
|
|
858
|
+
* @param {object} element Element row.
|
|
859
|
+
* @returns {{ x: number, y: number } | null}
|
|
860
|
+
*/
|
|
861
|
+
static #center(element) {
|
|
862
|
+
return CircuitJsonSchematicSvgRenderer.#point(
|
|
863
|
+
element?.center || element
|
|
864
|
+
)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Resolves an element size.
|
|
869
|
+
* @param {object} element Element row.
|
|
870
|
+
* @returns {{ width: number, height: number } | null}
|
|
871
|
+
*/
|
|
872
|
+
static #size(element) {
|
|
873
|
+
return CircuitJsonUnits.optionalSize(element?.size || element)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Resolves a point.
|
|
878
|
+
* @param {object | null | undefined} value Point candidate.
|
|
879
|
+
* @returns {{ x: number, y: number } | null}
|
|
880
|
+
*/
|
|
881
|
+
static #point(value) {
|
|
882
|
+
return CircuitJsonUnits.optionalPoint(value)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Resolves path points from common element fields.
|
|
887
|
+
* @param {object} element Element row.
|
|
888
|
+
* @returns {{ x: number, y: number }[]}
|
|
889
|
+
*/
|
|
890
|
+
static #points(element) {
|
|
891
|
+
const points = Array.isArray(element?.points)
|
|
892
|
+
? element.points
|
|
893
|
+
: Array.isArray(element?.path)
|
|
894
|
+
? element.path
|
|
895
|
+
: []
|
|
896
|
+
return points
|
|
897
|
+
.map((point) => CircuitJsonSchematicSvgRenderer.#point(point))
|
|
898
|
+
.filter(Boolean)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Builds SVG rect attributes for a center/size element.
|
|
903
|
+
* @param {object} element Element row.
|
|
904
|
+
* @returns {string}
|
|
905
|
+
*/
|
|
906
|
+
static #rectAttributes(element) {
|
|
907
|
+
const center = CircuitJsonSchematicSvgRenderer.#center(element)
|
|
908
|
+
const size = CircuitJsonSchematicSvgRenderer.#size(element)
|
|
909
|
+
if (!center || !size) return ''
|
|
910
|
+
return (
|
|
911
|
+
'x="' +
|
|
912
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(
|
|
913
|
+
center.x - size.width / 2
|
|
914
|
+
) +
|
|
915
|
+
'" y="' +
|
|
916
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(
|
|
917
|
+
center.y - size.height / 2
|
|
918
|
+
) +
|
|
919
|
+
'" width="' +
|
|
920
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.width) +
|
|
921
|
+
'" height="' +
|
|
922
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(size.height) +
|
|
923
|
+
'"'
|
|
924
|
+
)
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Resolves one point on a circle.
|
|
929
|
+
* @param {{ x: number, y: number }} center Circle center.
|
|
930
|
+
* @param {number} radius Circle radius.
|
|
931
|
+
* @param {number} angle Angle in degrees.
|
|
932
|
+
* @returns {{ x: number, y: number }}
|
|
933
|
+
*/
|
|
934
|
+
static #polarPoint(center, radius, angle) {
|
|
935
|
+
const radians = (angle * Math.PI) / 180
|
|
936
|
+
return {
|
|
937
|
+
x: center.x + Math.cos(radians) * radius,
|
|
938
|
+
y: center.y + Math.sin(radians) * radius
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Builds a polyline point string.
|
|
944
|
+
* @param {{ x: number, y: number }[]} points Points.
|
|
945
|
+
* @returns {string}
|
|
946
|
+
*/
|
|
947
|
+
static #pointsAttribute(points) {
|
|
948
|
+
return points
|
|
949
|
+
.map(
|
|
950
|
+
(point) =>
|
|
951
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(point.x) +
|
|
952
|
+
',' +
|
|
953
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(point.y)
|
|
954
|
+
)
|
|
955
|
+
.join(' ')
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Formats a viewBox record.
|
|
960
|
+
* @param {{ minX: number, minY: number, width: number, height: number }} viewBox ViewBox record.
|
|
961
|
+
* @returns {string}
|
|
962
|
+
*/
|
|
963
|
+
static #formatViewBox(viewBox) {
|
|
964
|
+
return [viewBox.minX, viewBox.minY, viewBox.width, viewBox.height]
|
|
965
|
+
.map((value) =>
|
|
966
|
+
CircuitJsonSchematicSvgRenderer.#formatNumber(value)
|
|
967
|
+
)
|
|
968
|
+
.join(' ')
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Formats one SVG number.
|
|
973
|
+
* @param {number} value Number.
|
|
974
|
+
* @returns {string}
|
|
975
|
+
*/
|
|
976
|
+
static #formatNumber(value) {
|
|
977
|
+
const number = Number(value)
|
|
978
|
+
if (!Number.isFinite(number)) return '0'
|
|
979
|
+
return Number(number.toFixed(6)).toString()
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Escapes markup text.
|
|
984
|
+
* @param {unknown} value Raw value.
|
|
985
|
+
* @returns {string}
|
|
986
|
+
*/
|
|
987
|
+
static #escapeHtml(value) {
|
|
988
|
+
return String(value ?? '')
|
|
989
|
+
.replaceAll('&', '&')
|
|
990
|
+
.replaceAll('<', '<')
|
|
991
|
+
.replaceAll('>', '>')
|
|
992
|
+
.replaceAll('"', '"')
|
|
993
|
+
}
|
|
994
|
+
}
|