circuitjson-toolkit 1.0.16 → 1.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 +15 -0
- package/NOTICE.md +31 -0
- package/README.md +267 -107
- package/docs/api.md +501 -96
- package/docs/capabilities.md +70 -0
- package/docs/migration/behaviors.md +45 -0
- package/docs/migration/parser.md +60 -0
- package/docs/migration/renderers.md +515 -0
- package/docs/migration/root.md +740 -0
- package/docs/migration.md +120 -0
- package/docs/model-format.md +175 -57
- package/docs/provenance.md +206 -0
- package/docs/release-notes-v1.1.0.md +154 -0
- package/docs/testing.md +117 -7
- package/package.json +31 -5
- package/spec/api-baseline-v1.0.17.json +1 -0
- package/spec/baseline-provenance-v1.0.17.json +7 -0
- package/spec/circuitjson-schema-snapshot.json +321 -0
- package/spec/circuitjson-schema-source.json +28 -0
- package/spec/feature-preservation.json +1 -0
- package/spec/library-scope.md +27 -20
- package/src/capabilities.mjs +1 -0
- package/src/core/ArchiveEntryPath.mjs +93 -0
- package/src/core/ArchiveLimits.mjs +31 -0
- package/src/core/ArchiveLimitsValidator.mjs +107 -0
- package/src/core/AsyncInputOwnership.mjs +56 -0
- package/src/core/AttachedValueLimits.mjs +67 -0
- package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
- package/src/core/CircuitJsonDocument.mjs +19 -61
- package/src/core/CircuitJsonElementTypes.mjs +10 -0
- package/src/core/CircuitJsonElementValidator.mjs +98 -847
- package/src/core/CircuitJsonIndexer.mjs +274 -194
- package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
- package/src/core/CircuitJsonParser.mjs +75 -13
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
- package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
- package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
- package/src/core/CircuitJsonUnitParsers.mjs +101 -0
- package/src/core/CircuitJsonUnits.mjs +13 -87
- package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
- package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
- package/src/core/CircuitJsonValidationUnits.mjs +6 -0
- package/src/core/ManufacturingService.mjs +323 -0
- package/src/core/Parser.mjs +343 -0
- package/src/core/ParserOptions.mjs +333 -0
- package/src/core/PcbBoundsSelectionModel.mjs +55 -19
- package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
- package/src/core/PcbInteractionIndex.mjs +368 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
- package/src/core/ProjectAsyncInputOwner.mjs +70 -0
- package/src/core/ProjectLoader.mjs +975 -0
- package/src/core/SimulationService.mjs +790 -0
- package/src/core/ToolkitCapabilities.mjs +130 -0
- package/src/core/ZipArchiveInspector.mjs +649 -0
- package/src/core/context/BinaryDataSnapshot.mjs +217 -0
- package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
- package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
- package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
- package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
- package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
- package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
- package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
- package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
- package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
- package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
- package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
- package/src/core/context/PcbSpatialIndex.mjs +701 -0
- package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
- package/src/core/context/StructuredDataSnapshot.mjs +683 -0
- package/src/core/contracts/DocumentResult.mjs +198 -0
- package/src/core/contracts/ProjectResult.mjs +96 -0
- package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
- package/src/core/contracts/ToolkitAsset.mjs +493 -0
- package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
- package/src/core/contracts/ToolkitError.mjs +176 -0
- package/src/core/contracts/ToolkitProgress.mjs +89 -0
- package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
- package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
- package/src/core/query/CircuitTraversal.mjs +343 -0
- package/src/core/query/ComponentGrouping.mjs +275 -0
- package/src/core/query/QueryNetlistBuilder.mjs +306 -0
- package/src/core/query/QueryService.mjs +435 -0
- package/src/core/query/RegexPattern.mjs +75 -0
- package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
- package/src/core/rendering/CanonicalBomRows.mjs +92 -0
- package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
- package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
- package/src/core/rendering/PcbRenderPlan.mjs +429 -0
- package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
- package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
- package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
- package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
- package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
- package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
- package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
- package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
- package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
- package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
- package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
- package/src/core/scene3d/Scene3dOptions.mjs +363 -0
- package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
- package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
- package/src/core/worker/ParserWorkerClient.mjs +997 -0
- package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
- package/src/core/worker/WorkerRequestData.mjs +623 -0
- package/src/core/worker/WorkerResponseData.mjs +905 -0
- package/src/extensions.mjs +37 -0
- package/src/index.mjs +14 -9
- package/src/interaction.mjs +2 -0
- package/src/manufacturing.mjs +1 -0
- package/src/parser.mjs +12 -2
- package/src/project.mjs +5 -0
- package/src/query.mjs +1 -0
- package/src/renderers.mjs +3 -29
- package/src/scene3d.mjs +3 -0
- package/src/simulation.mjs +1 -0
- package/src/styles/renderers.css +24 -0
- package/src/testing/ToolkitContractFixtures.mjs +124 -0
- package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
- package/src/testing/runToolkitContract.mjs +705 -0
- package/src/testing.mjs +3 -0
- package/src/ui/BomTableRenderer.mjs +304 -0
- package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
- package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
- package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
- package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
- package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
- package/src/ui/PcbSvgRenderer.mjs +41 -0
- package/src/ui/SafeSvgPaint.mjs +26 -0
- package/src/ui/SafeXmlText.mjs +60 -0
- package/src/ui/SchematicGeometryBounds.mjs +540 -0
- package/src/ui/SchematicSvgRenderer.mjs +110 -0
- package/src/ui/SchematicTableGeometry.mjs +319 -0
- package/src/ui/SchematicTextAnchor.mjs +55 -0
- package/src/ui/SchematicTextBounds.mjs +98 -0
- package/src/workers/parser.worker.mjs +59 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
2
|
+
import { SchematicTableGeometry } from './SchematicTableGeometry.mjs'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Renders schematic table frames and cells from element-array metadata.
|
|
@@ -11,7 +12,7 @@ export class CircuitJsonSchematicTableSvgRenderer {
|
|
|
11
12
|
* @returns {string}
|
|
12
13
|
*/
|
|
13
14
|
static render(index, formatters) {
|
|
14
|
-
const tables =
|
|
15
|
+
const tables = SchematicTableGeometry.models(index)
|
|
15
16
|
const tableMarkup = [...tables.values()]
|
|
16
17
|
.map((table) =>
|
|
17
18
|
CircuitJsonSchematicTableSvgRenderer.#tableElement(
|
|
@@ -38,62 +39,6 @@ export class CircuitJsonSchematicTableSvgRenderer {
|
|
|
38
39
|
: ''
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
/**
|
|
42
|
-
* Builds table models keyed by table id.
|
|
43
|
-
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
44
|
-
* @returns {Map<string, object>}
|
|
45
|
-
*/
|
|
46
|
-
static #tableModels(index) {
|
|
47
|
-
const tables = new Map()
|
|
48
|
-
for (const table of CircuitJsonSchematicTableSvgRenderer.#all(
|
|
49
|
-
index,
|
|
50
|
-
'schematic_table'
|
|
51
|
-
)) {
|
|
52
|
-
const model =
|
|
53
|
-
CircuitJsonSchematicTableSvgRenderer.#tableModel(table)
|
|
54
|
-
if (model) tables.set(model.id, model)
|
|
55
|
-
}
|
|
56
|
-
return tables
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Builds one schematic table model.
|
|
61
|
-
* @param {object} element Table element.
|
|
62
|
-
* @returns {object | null}
|
|
63
|
-
*/
|
|
64
|
-
static #tableModel(element) {
|
|
65
|
-
const id = String(element.schematic_table_id || '').trim()
|
|
66
|
-
const columns = CircuitJsonSchematicTableSvgRenderer.#lengths(
|
|
67
|
-
element.column_widths || element.columnWidths || element.columns
|
|
68
|
-
)
|
|
69
|
-
const rows = CircuitJsonSchematicTableSvgRenderer.#lengths(
|
|
70
|
-
element.row_heights || element.rowHeights || element.rows
|
|
71
|
-
)
|
|
72
|
-
const gridWidth = CircuitJsonSchematicTableSvgRenderer.#sum(columns)
|
|
73
|
-
const gridHeight = CircuitJsonSchematicTableSvgRenderer.#sum(rows)
|
|
74
|
-
const rect = CircuitJsonSchematicTableSvgRenderer.#rect(
|
|
75
|
-
element,
|
|
76
|
-
gridWidth,
|
|
77
|
-
gridHeight
|
|
78
|
-
)
|
|
79
|
-
if (!id || !rect) return null
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
id,
|
|
83
|
-
element,
|
|
84
|
-
...rect,
|
|
85
|
-
columns,
|
|
86
|
-
rows,
|
|
87
|
-
columnOffsets:
|
|
88
|
-
CircuitJsonSchematicTableSvgRenderer.#offsets(columns),
|
|
89
|
-
rowOffsets: CircuitJsonSchematicTableSvgRenderer.#offsets(rows),
|
|
90
|
-
padding: CircuitJsonUnits.length(element.cell_padding, 0),
|
|
91
|
-
borderWidth:
|
|
92
|
-
CircuitJsonUnits.optionalLength(element.border_width) ??
|
|
93
|
-
CircuitJsonUnits.optionalLength(element.borderWidth)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
42
|
/**
|
|
98
43
|
* Renders one table frame.
|
|
99
44
|
* @param {object} table Table model.
|
|
@@ -122,16 +67,9 @@ export class CircuitJsonSchematicTableSvgRenderer {
|
|
|
122
67
|
* @returns {string}
|
|
123
68
|
*/
|
|
124
69
|
static #tableCellElement(element, table, formatters) {
|
|
125
|
-
const rect =
|
|
126
|
-
element,
|
|
127
|
-
table
|
|
128
|
-
)
|
|
70
|
+
const rect = SchematicTableGeometry.cellRect(element, table)
|
|
129
71
|
if (!rect) return ''
|
|
130
|
-
const text =
|
|
131
|
-
element,
|
|
132
|
-
table,
|
|
133
|
-
rect
|
|
134
|
-
)
|
|
72
|
+
const text = SchematicTableGeometry.textPosition(element, table, rect)
|
|
135
73
|
const fontSize = CircuitJsonUnits.optionalLength(element.font_size)
|
|
136
74
|
|
|
137
75
|
return (
|
|
@@ -164,106 +102,6 @@ export class CircuitJsonSchematicTableSvgRenderer {
|
|
|
164
102
|
)
|
|
165
103
|
}
|
|
166
104
|
|
|
167
|
-
/**
|
|
168
|
-
* Resolves one cell rectangle.
|
|
169
|
-
* @param {object} element Cell element.
|
|
170
|
-
* @param {object | undefined} table Parent table model.
|
|
171
|
-
* @returns {object | null}
|
|
172
|
-
*/
|
|
173
|
-
static #cellRect(element, table) {
|
|
174
|
-
if (table?.columns?.length && table?.rows?.length) {
|
|
175
|
-
return CircuitJsonSchematicTableSvgRenderer.#gridCellRect(
|
|
176
|
-
element,
|
|
177
|
-
table
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
return CircuitJsonSchematicTableSvgRenderer.#rect(element)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Resolves one grid cell rectangle.
|
|
185
|
-
* @param {object} element Cell element.
|
|
186
|
-
* @param {object} table Parent table model.
|
|
187
|
-
* @returns {object | null}
|
|
188
|
-
*/
|
|
189
|
-
static #gridCellRect(element, table) {
|
|
190
|
-
const row = CircuitJsonSchematicTableSvgRenderer.#index(
|
|
191
|
-
element.row ?? element.row_index ?? element.rowIndex
|
|
192
|
-
)
|
|
193
|
-
const column = CircuitJsonSchematicTableSvgRenderer.#index(
|
|
194
|
-
element.column ??
|
|
195
|
-
element.col ??
|
|
196
|
-
element.column_index ??
|
|
197
|
-
element.columnIndex
|
|
198
|
-
)
|
|
199
|
-
if (row === null || column === null) return null
|
|
200
|
-
const rowSpan = CircuitJsonSchematicTableSvgRenderer.#span(
|
|
201
|
-
element.row_span ?? element.rowSpan
|
|
202
|
-
)
|
|
203
|
-
const columnSpan = CircuitJsonSchematicTableSvgRenderer.#span(
|
|
204
|
-
element.col_span ?? element.column_span ?? element.colSpan
|
|
205
|
-
)
|
|
206
|
-
const width = CircuitJsonSchematicTableSvgRenderer.#sum(
|
|
207
|
-
table.columns.slice(column, column + columnSpan)
|
|
208
|
-
)
|
|
209
|
-
const height = CircuitJsonSchematicTableSvgRenderer.#sum(
|
|
210
|
-
table.rows.slice(row, row + rowSpan)
|
|
211
|
-
)
|
|
212
|
-
if (width <= 0 || height <= 0) return null
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
x: table.x + (table.columnOffsets[column] || 0),
|
|
216
|
-
y: table.y + (table.rowOffsets[row] || 0),
|
|
217
|
-
width,
|
|
218
|
-
height
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Resolves one text position inside a cell.
|
|
224
|
-
* @param {object} element Cell element.
|
|
225
|
-
* @param {object | undefined} table Parent table model.
|
|
226
|
-
* @param {object} rect Cell rectangle.
|
|
227
|
-
* @returns {{ x: number, y: number, anchor: string, baseline: string }}
|
|
228
|
-
*/
|
|
229
|
-
static #textPosition(element, table, rect) {
|
|
230
|
-
const padding = Number(table?.padding || 0)
|
|
231
|
-
const horizontal = String(
|
|
232
|
-
element.horizontal_align || element.horizontalAlign || ''
|
|
233
|
-
).toLowerCase()
|
|
234
|
-
const vertical = String(
|
|
235
|
-
element.vertical_align || element.verticalAlign || ''
|
|
236
|
-
).toLowerCase()
|
|
237
|
-
const x =
|
|
238
|
-
horizontal === 'left' || horizontal === 'start'
|
|
239
|
-
? rect.x + padding
|
|
240
|
-
: horizontal === 'right' || horizontal === 'end'
|
|
241
|
-
? rect.x + rect.width - padding
|
|
242
|
-
: rect.x + rect.width / 2
|
|
243
|
-
const y =
|
|
244
|
-
vertical === 'top'
|
|
245
|
-
? rect.y + padding
|
|
246
|
-
: vertical === 'bottom'
|
|
247
|
-
? rect.y + rect.height - padding
|
|
248
|
-
: rect.y + rect.height / 2
|
|
249
|
-
return {
|
|
250
|
-
x,
|
|
251
|
-
y,
|
|
252
|
-
anchor:
|
|
253
|
-
horizontal === 'left' || horizontal === 'start'
|
|
254
|
-
? 'start'
|
|
255
|
-
: horizontal === 'right' || horizontal === 'end'
|
|
256
|
-
? 'end'
|
|
257
|
-
: 'middle',
|
|
258
|
-
baseline:
|
|
259
|
-
vertical === 'top'
|
|
260
|
-
? 'hanging'
|
|
261
|
-
: vertical === 'bottom'
|
|
262
|
-
? 'text-after-edge'
|
|
263
|
-
: 'central'
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
105
|
/**
|
|
268
106
|
* Builds SVG rect attributes.
|
|
269
107
|
* @param {object} rect Rect model.
|
|
@@ -301,132 +139,6 @@ export class CircuitJsonSchematicTableSvgRenderer {
|
|
|
301
139
|
: ' font-size="' + formatters.formatNumber(fontSize) + '"'
|
|
302
140
|
}
|
|
303
141
|
|
|
304
|
-
/**
|
|
305
|
-
* Resolves a rectangle from center/size or anchor metadata.
|
|
306
|
-
* @param {object} element Element row.
|
|
307
|
-
* @param {number} [fallbackWidth] Fallback width.
|
|
308
|
-
* @param {number} [fallbackHeight] Fallback height.
|
|
309
|
-
* @returns {object | null}
|
|
310
|
-
*/
|
|
311
|
-
static #rect(element, fallbackWidth = 0, fallbackHeight = 0) {
|
|
312
|
-
const size = CircuitJsonSchematicTableSvgRenderer.#size(
|
|
313
|
-
element,
|
|
314
|
-
fallbackWidth,
|
|
315
|
-
fallbackHeight
|
|
316
|
-
)
|
|
317
|
-
const anchor =
|
|
318
|
-
CircuitJsonUnits.optionalPoint(element.anchor_position) || null
|
|
319
|
-
if (size && anchor) {
|
|
320
|
-
return CircuitJsonSchematicTableSvgRenderer.#anchoredRect(
|
|
321
|
-
anchor,
|
|
322
|
-
size,
|
|
323
|
-
element.anchor
|
|
324
|
-
)
|
|
325
|
-
}
|
|
326
|
-
const center = CircuitJsonUnits.optionalPoint(element.center || element)
|
|
327
|
-
if (!center || !size) return null
|
|
328
|
-
return {
|
|
329
|
-
x: center.x - size.width / 2,
|
|
330
|
-
y: center.y - size.height / 2,
|
|
331
|
-
...size
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Resolves a width/height pair.
|
|
337
|
-
* @param {object} element Element row.
|
|
338
|
-
* @param {number} fallbackWidth Fallback width.
|
|
339
|
-
* @param {number} fallbackHeight Fallback height.
|
|
340
|
-
* @returns {{ width: number, height: number } | null}
|
|
341
|
-
*/
|
|
342
|
-
static #size(element, fallbackWidth, fallbackHeight) {
|
|
343
|
-
const size = CircuitJsonUnits.optionalSize(element.size || element)
|
|
344
|
-
const width = size?.width ?? fallbackWidth
|
|
345
|
-
const height = size?.height ?? fallbackHeight
|
|
346
|
-
if (width <= 0 || height <= 0) return null
|
|
347
|
-
return { width, height }
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Builds an anchored rectangle.
|
|
352
|
-
* @param {{ x: number, y: number }} point Anchor point.
|
|
353
|
-
* @param {{ width: number, height: number }} size Size.
|
|
354
|
-
* @param {unknown} anchor Anchor name.
|
|
355
|
-
* @returns {object}
|
|
356
|
-
*/
|
|
357
|
-
static #anchoredRect(point, size, anchor) {
|
|
358
|
-
const text = String(anchor || 'top_left').toLowerCase()
|
|
359
|
-
const x = text.includes('right')
|
|
360
|
-
? point.x - size.width
|
|
361
|
-
: text.includes('center')
|
|
362
|
-
? point.x - size.width / 2
|
|
363
|
-
: point.x
|
|
364
|
-
const y = text.includes('bottom')
|
|
365
|
-
? point.y - size.height
|
|
366
|
-
: text.includes('middle') || text === 'center'
|
|
367
|
-
? point.y - size.height / 2
|
|
368
|
-
: point.y
|
|
369
|
-
return { x, y, ...size }
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Resolves an array of positive lengths.
|
|
374
|
-
* @param {unknown} value Length array candidate.
|
|
375
|
-
* @returns {number[]}
|
|
376
|
-
*/
|
|
377
|
-
static #lengths(value) {
|
|
378
|
-
return (Array.isArray(value) ? value : [])
|
|
379
|
-
.map((entry) =>
|
|
380
|
-
CircuitJsonUnits.optionalLength(
|
|
381
|
-
entry?.width ?? entry?.height ?? entry
|
|
382
|
-
)
|
|
383
|
-
)
|
|
384
|
-
.filter((entry) => entry !== null && entry > 0)
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Builds cumulative offsets for a length list.
|
|
389
|
-
* @param {number[]} values Length values.
|
|
390
|
-
* @returns {number[]}
|
|
391
|
-
*/
|
|
392
|
-
static #offsets(values) {
|
|
393
|
-
let total = 0
|
|
394
|
-
return values.map((value) => {
|
|
395
|
-
const offset = total
|
|
396
|
-
total += value
|
|
397
|
-
return offset
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Sums numeric values.
|
|
403
|
-
* @param {number[]} values Values.
|
|
404
|
-
* @returns {number}
|
|
405
|
-
*/
|
|
406
|
-
static #sum(values) {
|
|
407
|
-
return values.reduce((total, value) => total + value, 0)
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Resolves a zero-based row or column index.
|
|
412
|
-
* @param {unknown} value Index candidate.
|
|
413
|
-
* @returns {number | null}
|
|
414
|
-
*/
|
|
415
|
-
static #index(value) {
|
|
416
|
-
const number = Number(value)
|
|
417
|
-
return Number.isInteger(number) && number >= 0 ? number : null
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Resolves a positive row or column span.
|
|
422
|
-
* @param {unknown} value Span candidate.
|
|
423
|
-
* @returns {number}
|
|
424
|
-
*/
|
|
425
|
-
static #span(value) {
|
|
426
|
-
const number = Number(value)
|
|
427
|
-
return Number.isInteger(number) && number > 0 ? number : 1
|
|
428
|
-
}
|
|
429
|
-
|
|
430
142
|
/**
|
|
431
143
|
* Returns indexed element rows.
|
|
432
144
|
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { PcbRenderPlan } from '../core/rendering/PcbRenderPlan.mjs'
|
|
2
|
+
import { CircuitJsonPcbSvgRenderer } from './CircuitJsonPcbSvgRenderer.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Renders canonical CircuitJSON PCB documents from one reusable plan.
|
|
6
|
+
*/
|
|
7
|
+
export class PcbSvgRenderer {
|
|
8
|
+
/**
|
|
9
|
+
* Renders one combined PCB SVG.
|
|
10
|
+
* @param {unknown} document DocumentResult, CircuitJSON model, or context.
|
|
11
|
+
* @param {Record<string, any>} [options] Canonical render options.
|
|
12
|
+
* @returns {string} SVG markup.
|
|
13
|
+
*/
|
|
14
|
+
static render(document, options = {}) {
|
|
15
|
+
const plan = PcbRenderPlan.prepare(document, options)
|
|
16
|
+
return CircuitJsonPcbSvgRenderer.renderPlan(plan)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Renders every selected PCB layer from one primitive preparation.
|
|
21
|
+
* @param {unknown} document DocumentResult, CircuitJSON model, or context.
|
|
22
|
+
* @param {Record<string, any>} [options] Canonical render options.
|
|
23
|
+
* @returns {{ schema: 'ecad-toolkit.render-set.v1', items: object[], diagnostics: object[], statistics: Record<string, number> }} Render set.
|
|
24
|
+
*/
|
|
25
|
+
static renderLayers(document, options = {}) {
|
|
26
|
+
const plan = PcbRenderPlan.prepare(document, options)
|
|
27
|
+
return {
|
|
28
|
+
schema: 'ecad-toolkit.render-set.v1',
|
|
29
|
+
items: plan.layers.map((layer) => ({
|
|
30
|
+
id: layer.id,
|
|
31
|
+
side: plan.side,
|
|
32
|
+
layerIds: [layer.id],
|
|
33
|
+
svg: CircuitJsonPcbSvgRenderer.renderPlan(plan, {
|
|
34
|
+
layerIds: [layer.id]
|
|
35
|
+
})
|
|
36
|
+
})),
|
|
37
|
+
diagnostics: structuredClone(plan.diagnostics),
|
|
38
|
+
statistics: { ...plan.statistics }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const NUMERIC_COLOR =
|
|
2
|
+
/^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\(\s*[-+.%0-9,\s/]+\)$/iu
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes literal SVG paint without admitting resource URL contexts.
|
|
6
|
+
*/
|
|
7
|
+
export class SafeSvgPaint {
|
|
8
|
+
/**
|
|
9
|
+
* Returns a bounded literal CSS/SVG color or an empty string.
|
|
10
|
+
* @param {unknown} value Paint candidate.
|
|
11
|
+
* @returns {string} Safe literal paint.
|
|
12
|
+
*/
|
|
13
|
+
static color(value) {
|
|
14
|
+
const text = String(value ?? '').trim()
|
|
15
|
+
if (!text || text.length > 128) return ''
|
|
16
|
+
if (
|
|
17
|
+
/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/iu.test(
|
|
18
|
+
text
|
|
19
|
+
)
|
|
20
|
+
) {
|
|
21
|
+
return text
|
|
22
|
+
}
|
|
23
|
+
if (/^[a-z]{3,24}$/iu.test(text)) return text
|
|
24
|
+
return NUMERIC_COLOR.test(text) ? text : ''
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes and escapes untrusted scalar data for deterministic XML output.
|
|
3
|
+
*/
|
|
4
|
+
export class SafeXmlText {
|
|
5
|
+
/**
|
|
6
|
+
* Escapes XML text and attribute data, including quotation marks.
|
|
7
|
+
* @param {unknown} value Scalar value.
|
|
8
|
+
* @returns {string} Well-formed escaped XML.
|
|
9
|
+
*/
|
|
10
|
+
static escape(value) {
|
|
11
|
+
return SafeXmlText.#escape(value, true)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Escapes XML character data while leaving quotation marks literal.
|
|
16
|
+
* @param {unknown} value Scalar value.
|
|
17
|
+
* @returns {string} Well-formed escaped XML text.
|
|
18
|
+
*/
|
|
19
|
+
static escapeText(value) {
|
|
20
|
+
return SafeXmlText.#escape(value, false)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Escapes allowed characters and replaces invalid XML scalars.
|
|
25
|
+
* @param {unknown} value Scalar value.
|
|
26
|
+
* @param {boolean} quote Whether quotation marks require escaping.
|
|
27
|
+
* @returns {string} Safe XML data.
|
|
28
|
+
*/
|
|
29
|
+
static #escape(value, quote) {
|
|
30
|
+
let result = ''
|
|
31
|
+
for (const candidate of String(value ?? '')) {
|
|
32
|
+
const character = SafeXmlText.#isAllowed(candidate)
|
|
33
|
+
? candidate
|
|
34
|
+
: '\ufffd'
|
|
35
|
+
if (character === '&') result += '&'
|
|
36
|
+
else if (character === '<') result += '<'
|
|
37
|
+
else if (character === '>') result += '>'
|
|
38
|
+
else if (quote && character === '"') result += '"'
|
|
39
|
+
else result += character
|
|
40
|
+
}
|
|
41
|
+
return result
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns whether one Unicode scalar belongs to the XML 1.0 Char set.
|
|
46
|
+
* @param {string} character One iterated character.
|
|
47
|
+
* @returns {boolean} Whether XML permits the character.
|
|
48
|
+
*/
|
|
49
|
+
static #isAllowed(character) {
|
|
50
|
+
const codePoint = character.codePointAt(0)
|
|
51
|
+
return (
|
|
52
|
+
codePoint === 0x09 ||
|
|
53
|
+
codePoint === 0x0a ||
|
|
54
|
+
codePoint === 0x0d ||
|
|
55
|
+
(codePoint >= 0x20 && codePoint <= 0xd7ff) ||
|
|
56
|
+
(codePoint >= 0xe000 && codePoint <= 0xfffd) ||
|
|
57
|
+
(codePoint >= 0x10000 && codePoint <= 0x10ffff)
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|