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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
2
|
+
import { SafeXmlText } from './SafeXmlText.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Renders every standard CircuitJSON schematic debug-object variant.
|
|
6
|
+
*/
|
|
7
|
+
export class CircuitJsonSchematicDebugRenderer {
|
|
8
|
+
/**
|
|
9
|
+
* Renders one debug object or an empty string for invalid geometry.
|
|
10
|
+
* @param {object} element Debug element.
|
|
11
|
+
* @returns {string} Debug SVG group.
|
|
12
|
+
*/
|
|
13
|
+
static render(element) {
|
|
14
|
+
if (
|
|
15
|
+
element.shape === undefined ||
|
|
16
|
+
element.legacy_shape_omitted === true
|
|
17
|
+
) {
|
|
18
|
+
return CircuitJsonSchematicDebugRenderer.#legacyRect(element)
|
|
19
|
+
}
|
|
20
|
+
const shape = String(element.shape || 'rect')
|
|
21
|
+
const geometry = CircuitJsonSchematicDebugRenderer.#geometry(
|
|
22
|
+
element,
|
|
23
|
+
shape
|
|
24
|
+
)
|
|
25
|
+
if (!geometry) return ''
|
|
26
|
+
const label = String(element.label ?? element.message ?? '').trim()
|
|
27
|
+
return (
|
|
28
|
+
'<g class="schematic-debug-object" data-schematic-debug-object-id="' +
|
|
29
|
+
CircuitJsonSchematicDebugRenderer.#escape(
|
|
30
|
+
element.schematic_debug_object_id || ''
|
|
31
|
+
) +
|
|
32
|
+
'"><title>' +
|
|
33
|
+
CircuitJsonSchematicDebugRenderer.#escape(label) +
|
|
34
|
+
'</title>' +
|
|
35
|
+
geometry.markup +
|
|
36
|
+
'<text x="' +
|
|
37
|
+
CircuitJsonSchematicDebugRenderer.#number(geometry.label.x) +
|
|
38
|
+
'" y="' +
|
|
39
|
+
CircuitJsonSchematicDebugRenderer.#number(geometry.label.y) +
|
|
40
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
41
|
+
CircuitJsonSchematicDebugRenderer.#escape(label) +
|
|
42
|
+
'</text></g>'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Preserves the original unqualified rectangle markup byte-for-byte.
|
|
48
|
+
* @param {object} element Legacy debug element.
|
|
49
|
+
* @returns {string} Legacy SVG group.
|
|
50
|
+
*/
|
|
51
|
+
static #legacyRect(element) {
|
|
52
|
+
const center = CircuitJsonUnits.optionalPoint(element.center)
|
|
53
|
+
const size = CircuitJsonUnits.optionalSize(element.size || element)
|
|
54
|
+
if (!center || !size) return ''
|
|
55
|
+
const message = String(element.message || '').trim()
|
|
56
|
+
return (
|
|
57
|
+
'<g class="schematic-debug-object" data-schematic-debug-object-id="' +
|
|
58
|
+
CircuitJsonSchematicDebugRenderer.#escape(
|
|
59
|
+
element.schematic_debug_object_id || ''
|
|
60
|
+
) +
|
|
61
|
+
'"><title>' +
|
|
62
|
+
CircuitJsonSchematicDebugRenderer.#escape(message) +
|
|
63
|
+
'</title><rect x="' +
|
|
64
|
+
CircuitJsonSchematicDebugRenderer.#number(
|
|
65
|
+
center.x - size.width / 2
|
|
66
|
+
) +
|
|
67
|
+
'" y="' +
|
|
68
|
+
CircuitJsonSchematicDebugRenderer.#number(
|
|
69
|
+
center.y - size.height / 2
|
|
70
|
+
) +
|
|
71
|
+
'" width="' +
|
|
72
|
+
CircuitJsonSchematicDebugRenderer.#number(size.width) +
|
|
73
|
+
'" height="' +
|
|
74
|
+
CircuitJsonSchematicDebugRenderer.#number(size.height) +
|
|
75
|
+
'"></rect><text x="' +
|
|
76
|
+
CircuitJsonSchematicDebugRenderer.#number(center.x) +
|
|
77
|
+
'" y="' +
|
|
78
|
+
CircuitJsonSchematicDebugRenderer.#number(center.y) +
|
|
79
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
80
|
+
CircuitJsonSchematicDebugRenderer.#escape(message) +
|
|
81
|
+
'</text></g>'
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Builds variant geometry and its label anchor.
|
|
87
|
+
* @param {object} element Debug element.
|
|
88
|
+
* @param {string} shape Standard shape discriminant.
|
|
89
|
+
* @returns {{ markup: string, label: { x: number, y: number } } | null} Geometry.
|
|
90
|
+
*/
|
|
91
|
+
static #geometry(element, shape) {
|
|
92
|
+
if (shape === 'line') {
|
|
93
|
+
const start = CircuitJsonUnits.optionalPoint(element.start)
|
|
94
|
+
const end = CircuitJsonUnits.optionalPoint(element.end)
|
|
95
|
+
if (!start || !end) return null
|
|
96
|
+
return {
|
|
97
|
+
markup:
|
|
98
|
+
'<line class="schematic-debug-object__line" x1="' +
|
|
99
|
+
CircuitJsonSchematicDebugRenderer.#number(start.x) +
|
|
100
|
+
'" y1="' +
|
|
101
|
+
CircuitJsonSchematicDebugRenderer.#number(start.y) +
|
|
102
|
+
'" x2="' +
|
|
103
|
+
CircuitJsonSchematicDebugRenderer.#number(end.x) +
|
|
104
|
+
'" y2="' +
|
|
105
|
+
CircuitJsonSchematicDebugRenderer.#number(end.y) +
|
|
106
|
+
'"></line>',
|
|
107
|
+
label: { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const center = CircuitJsonUnits.optionalPoint(element.center)
|
|
111
|
+
if (!center) return null
|
|
112
|
+
if (shape === 'point') {
|
|
113
|
+
return {
|
|
114
|
+
markup:
|
|
115
|
+
'<circle class="schematic-debug-object__point" cx="' +
|
|
116
|
+
CircuitJsonSchematicDebugRenderer.#number(center.x) +
|
|
117
|
+
'" cy="' +
|
|
118
|
+
CircuitJsonSchematicDebugRenderer.#number(center.y) +
|
|
119
|
+
'" r="0.25"></circle>',
|
|
120
|
+
label: center
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const size = CircuitJsonUnits.optionalSize(element.size || element)
|
|
124
|
+
if (!size) return null
|
|
125
|
+
return {
|
|
126
|
+
markup:
|
|
127
|
+
'<rect class="schematic-debug-object__rect" x="' +
|
|
128
|
+
CircuitJsonSchematicDebugRenderer.#number(
|
|
129
|
+
center.x - size.width / 2
|
|
130
|
+
) +
|
|
131
|
+
'" y="' +
|
|
132
|
+
CircuitJsonSchematicDebugRenderer.#number(
|
|
133
|
+
center.y - size.height / 2
|
|
134
|
+
) +
|
|
135
|
+
'" width="' +
|
|
136
|
+
CircuitJsonSchematicDebugRenderer.#number(size.width) +
|
|
137
|
+
'" height="' +
|
|
138
|
+
CircuitJsonSchematicDebugRenderer.#number(size.height) +
|
|
139
|
+
'"></rect>',
|
|
140
|
+
label: center
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Formats one finite SVG number.
|
|
146
|
+
* @param {unknown} value Number candidate.
|
|
147
|
+
* @returns {string} SVG number.
|
|
148
|
+
*/
|
|
149
|
+
static #number(value) {
|
|
150
|
+
const number = Number(value)
|
|
151
|
+
return Number.isFinite(number)
|
|
152
|
+
? Number(number.toFixed(6)).toString()
|
|
153
|
+
: '0'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Escapes XML text and attribute data.
|
|
158
|
+
* @param {unknown} value Text candidate.
|
|
159
|
+
* @returns {string} Escaped text.
|
|
160
|
+
*/
|
|
161
|
+
static #escape(value) {
|
|
162
|
+
return SafeXmlText.escape(value)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { BinaryDataSnapshot } from '../core/context/BinaryDataSnapshot.mjs'
|
|
2
|
+
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
3
|
+
import { SafeXmlText } from './SafeXmlText.mjs'
|
|
4
|
+
|
|
5
|
+
const IMAGE_MEDIA_TYPES = new Set([
|
|
6
|
+
'image/bmp',
|
|
7
|
+
'image/gif',
|
|
8
|
+
'image/jpeg',
|
|
9
|
+
'image/png',
|
|
10
|
+
'image/webp'
|
|
11
|
+
])
|
|
12
|
+
const BASE64_ALPHABET =
|
|
13
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
14
|
+
|
|
15
|
+
/** Renders canonical asset-backed schematic images into deterministic SVG. */
|
|
16
|
+
export class CircuitJsonSchematicImageSvgRenderer {
|
|
17
|
+
/**
|
|
18
|
+
* Renders every resolvable canonical image in authored order.
|
|
19
|
+
* @param {object[]} images Canonical schematic image rows.
|
|
20
|
+
* @param {object[]} assets Canonical document assets.
|
|
21
|
+
* @returns {string} SVG image group or an empty string.
|
|
22
|
+
*/
|
|
23
|
+
static render(images, assets) {
|
|
24
|
+
const byId = CircuitJsonSchematicImageSvgRenderer.#assetIndex(
|
|
25
|
+
assets || []
|
|
26
|
+
)
|
|
27
|
+
const markup = (images || [])
|
|
28
|
+
.map((image, index) => ({ image, index }))
|
|
29
|
+
.sort(
|
|
30
|
+
(left, right) =>
|
|
31
|
+
CircuitJsonSchematicImageSvgRenderer.#order(left.image) -
|
|
32
|
+
CircuitJsonSchematicImageSvgRenderer.#order(
|
|
33
|
+
right.image
|
|
34
|
+
) || left.index - right.index
|
|
35
|
+
)
|
|
36
|
+
.map(({ image }) =>
|
|
37
|
+
CircuitJsonSchematicImageSvgRenderer.#image(
|
|
38
|
+
image,
|
|
39
|
+
byId.get(String(image.asset_id || ''))
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
return markup.length
|
|
44
|
+
? '<g class="schematic-images">' + markup.join('') + '</g>'
|
|
45
|
+
: ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds an exact asset-id index and marks duplicate ids ambiguous.
|
|
50
|
+
* @param {object[]} assets Canonical assets.
|
|
51
|
+
* @returns {Map<string, object | null>} Assets by exact id.
|
|
52
|
+
*/
|
|
53
|
+
static #assetIndex(assets) {
|
|
54
|
+
const byId = new Map()
|
|
55
|
+
for (const asset of assets) {
|
|
56
|
+
const id = String(asset?.id || '')
|
|
57
|
+
if (!id) continue
|
|
58
|
+
byId.set(id, byId.has(id) ? null : asset)
|
|
59
|
+
}
|
|
60
|
+
return byId
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Renders one image when its exact payload is available.
|
|
65
|
+
* @param {object} image Canonical image row.
|
|
66
|
+
* @param {object | null | undefined} asset Resolved asset.
|
|
67
|
+
* @returns {string} SVG image markup or an empty string.
|
|
68
|
+
*/
|
|
69
|
+
static #image(image, asset) {
|
|
70
|
+
if (!asset || asset.kind !== 'schematic-image') return ''
|
|
71
|
+
const mediaType = String(asset.mediaType || '').toLowerCase()
|
|
72
|
+
if (!IMAGE_MEDIA_TYPES.has(mediaType)) return ''
|
|
73
|
+
const bytes = CircuitJsonSchematicImageSvgRenderer.#assetBytes(asset)
|
|
74
|
+
if (!bytes?.byteLength) return ''
|
|
75
|
+
const center = CircuitJsonUnits.optionalPoint(image.center)
|
|
76
|
+
const size = CircuitJsonUnits.optionalSize(image.size)
|
|
77
|
+
if (!center || !size || size.width <= 0 || size.height <= 0) return ''
|
|
78
|
+
const rotation = CircuitJsonUnits.angle(image.rotation, 0)
|
|
79
|
+
const opacity = CircuitJsonSchematicImageSvgRenderer.#opacity(
|
|
80
|
+
image.opacity
|
|
81
|
+
)
|
|
82
|
+
const attributes = [
|
|
83
|
+
'class="schematic-image"',
|
|
84
|
+
'data-schematic-image-id="' +
|
|
85
|
+
SafeXmlText.escape(image.schematic_image_id || '') +
|
|
86
|
+
'"',
|
|
87
|
+
'data-asset-id="' + SafeXmlText.escape(image.asset_id || '') + '"',
|
|
88
|
+
'x="' +
|
|
89
|
+
CircuitJsonSchematicImageSvgRenderer.#number(
|
|
90
|
+
center.x - size.width / 2
|
|
91
|
+
) +
|
|
92
|
+
'"',
|
|
93
|
+
'y="' +
|
|
94
|
+
CircuitJsonSchematicImageSvgRenderer.#number(
|
|
95
|
+
center.y - size.height / 2
|
|
96
|
+
) +
|
|
97
|
+
'"',
|
|
98
|
+
'width="' +
|
|
99
|
+
CircuitJsonSchematicImageSvgRenderer.#number(size.width) +
|
|
100
|
+
'"',
|
|
101
|
+
'height="' +
|
|
102
|
+
CircuitJsonSchematicImageSvgRenderer.#number(size.height) +
|
|
103
|
+
'"',
|
|
104
|
+
'href="data:' +
|
|
105
|
+
mediaType +
|
|
106
|
+
';base64,' +
|
|
107
|
+
CircuitJsonSchematicImageSvgRenderer.#base64(bytes) +
|
|
108
|
+
'"',
|
|
109
|
+
'preserveAspectRatio="' +
|
|
110
|
+
(image.preserve_aspect_ratio === false
|
|
111
|
+
? 'none'
|
|
112
|
+
: 'xMidYMid meet') +
|
|
113
|
+
'"'
|
|
114
|
+
]
|
|
115
|
+
if (rotation) {
|
|
116
|
+
attributes.push(
|
|
117
|
+
'transform="rotate(' +
|
|
118
|
+
CircuitJsonSchematicImageSvgRenderer.#number(rotation) +
|
|
119
|
+
' ' +
|
|
120
|
+
CircuitJsonSchematicImageSvgRenderer.#number(center.x) +
|
|
121
|
+
' ' +
|
|
122
|
+
CircuitJsonSchematicImageSvgRenderer.#number(center.y) +
|
|
123
|
+
')"'
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
if (opacity !== null) {
|
|
127
|
+
attributes.push(
|
|
128
|
+
'opacity="' +
|
|
129
|
+
CircuitJsonSchematicImageSvgRenderer.#number(opacity) +
|
|
130
|
+
'"'
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
return '<image ' + attributes.join(' ') + '></image>'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reads one exact binary asset range without accepting string payloads.
|
|
138
|
+
* @param {object} asset Canonical asset.
|
|
139
|
+
* @returns {Uint8Array | null} Isolated bytes.
|
|
140
|
+
*/
|
|
141
|
+
static #assetBytes(asset) {
|
|
142
|
+
let data
|
|
143
|
+
try {
|
|
144
|
+
data = asset.data
|
|
145
|
+
} catch {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
const range = BinaryDataSnapshot.describe(data)
|
|
149
|
+
return range ? BinaryDataSnapshot.copyBytes(data, range) : null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Encodes exact bytes without Node-only globals.
|
|
154
|
+
* @param {Uint8Array} bytes Binary bytes.
|
|
155
|
+
* @returns {string} RFC 4648 base64 text.
|
|
156
|
+
*/
|
|
157
|
+
static #base64(bytes) {
|
|
158
|
+
let result = ''
|
|
159
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
160
|
+
const first = bytes[index]
|
|
161
|
+
const hasSecond = index + 1 < bytes.length
|
|
162
|
+
const hasThird = index + 2 < bytes.length
|
|
163
|
+
const second = hasSecond ? bytes[index + 1] : 0
|
|
164
|
+
const third = hasThird ? bytes[index + 2] : 0
|
|
165
|
+
const bits = (first << 16) | (second << 8) | third
|
|
166
|
+
result += BASE64_ALPHABET[(bits >>> 18) & 63]
|
|
167
|
+
result += BASE64_ALPHABET[(bits >>> 12) & 63]
|
|
168
|
+
result += hasSecond ? BASE64_ALPHABET[(bits >>> 6) & 63] : '='
|
|
169
|
+
result += hasThird ? BASE64_ALPHABET[bits & 63] : '='
|
|
170
|
+
}
|
|
171
|
+
return result
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolves a valid optional opacity.
|
|
176
|
+
* @param {unknown} value Candidate.
|
|
177
|
+
* @returns {number | null} Opacity or null.
|
|
178
|
+
*/
|
|
179
|
+
static #opacity(value) {
|
|
180
|
+
return typeof value === 'number' &&
|
|
181
|
+
Number.isFinite(value) &&
|
|
182
|
+
value >= 0 &&
|
|
183
|
+
value <= 1
|
|
184
|
+
? value
|
|
185
|
+
: null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Resolves an authored render order.
|
|
190
|
+
* @param {object} image Image row.
|
|
191
|
+
* @returns {number} Sort value.
|
|
192
|
+
*/
|
|
193
|
+
static #order(image) {
|
|
194
|
+
return Number.isSafeInteger(image?.render_order)
|
|
195
|
+
? image.render_order
|
|
196
|
+
: Number.MAX_SAFE_INTEGER
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Formats one deterministic SVG number.
|
|
201
|
+
* @param {number} value Number value.
|
|
202
|
+
* @returns {string} SVG number.
|
|
203
|
+
*/
|
|
204
|
+
static #number(value) {
|
|
205
|
+
return Number(Number(value).toFixed(6)).toString()
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Object.freeze(CircuitJsonSchematicImageSvgRenderer.prototype)
|
|
210
|
+
Object.freeze(CircuitJsonSchematicImageSvgRenderer)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
2
|
+
import { CircuitJsonSchematicSvgPrimitiveAttributes } from './CircuitJsonSchematicSvgPrimitiveAttributes.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Renders standard trace edges while preserving legacy line aliases.
|
|
6
|
+
*/
|
|
7
|
+
export class CircuitJsonSchematicLineRenderer {
|
|
8
|
+
/**
|
|
9
|
+
* Renders all usable standard edges or one legacy fallback segment.
|
|
10
|
+
* @param {object} element Line or trace element.
|
|
11
|
+
* @returns {string[]} SVG line elements.
|
|
12
|
+
*/
|
|
13
|
+
static render(element) {
|
|
14
|
+
if (
|
|
15
|
+
element.type === 'schematic_trace' &&
|
|
16
|
+
Array.isArray(element.edges)
|
|
17
|
+
) {
|
|
18
|
+
const edges = element.edges
|
|
19
|
+
.map((edge) =>
|
|
20
|
+
CircuitJsonSchematicLineRenderer.#segment(
|
|
21
|
+
element,
|
|
22
|
+
edge?.from,
|
|
23
|
+
edge?.to
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
if (edges.length) return edges
|
|
28
|
+
}
|
|
29
|
+
const fallback = CircuitJsonSchematicLineRenderer.#segment(element)
|
|
30
|
+
return fallback ? [fallback] : []
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders one line segment from explicit endpoints or legacy aliases.
|
|
35
|
+
* @param {object} element Source element.
|
|
36
|
+
* @param {unknown} [startValue] Standard edge start.
|
|
37
|
+
* @param {unknown} [endValue] Standard edge end.
|
|
38
|
+
* @returns {string} SVG line or empty string.
|
|
39
|
+
*/
|
|
40
|
+
static #segment(element, startValue, endValue) {
|
|
41
|
+
const start = CircuitJsonUnits.optionalPoint(
|
|
42
|
+
startValue !== undefined
|
|
43
|
+
? startValue
|
|
44
|
+
: {
|
|
45
|
+
x: element.x1 ?? element.start?.x,
|
|
46
|
+
y: element.y1 ?? element.start?.y
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
const end = CircuitJsonUnits.optionalPoint(
|
|
50
|
+
endValue !== undefined
|
|
51
|
+
? endValue
|
|
52
|
+
: {
|
|
53
|
+
x: element.x2 ?? element.end?.x,
|
|
54
|
+
y: element.y2 ?? element.end?.y
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
if (!start || !end) return ''
|
|
58
|
+
return (
|
|
59
|
+
'<line class="schematic-wire" x1="' +
|
|
60
|
+
CircuitJsonSchematicLineRenderer.#number(start.x) +
|
|
61
|
+
'" y1="' +
|
|
62
|
+
CircuitJsonSchematicLineRenderer.#number(start.y) +
|
|
63
|
+
'" x2="' +
|
|
64
|
+
CircuitJsonSchematicLineRenderer.#number(end.x) +
|
|
65
|
+
'" y2="' +
|
|
66
|
+
CircuitJsonSchematicLineRenderer.#number(end.y) +
|
|
67
|
+
'"' +
|
|
68
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
|
|
69
|
+
fill: false
|
|
70
|
+
}) +
|
|
71
|
+
'></line>'
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Formats one finite SVG number.
|
|
77
|
+
* @param {unknown} value Number candidate.
|
|
78
|
+
* @returns {string} SVG number.
|
|
79
|
+
*/
|
|
80
|
+
static #number(value) {
|
|
81
|
+
const number = Number(value)
|
|
82
|
+
return Number.isFinite(number)
|
|
83
|
+
? Number(number.toFixed(6)).toString()
|
|
84
|
+
: '0'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
2
|
+
import { CircuitJsonSchematicSvgPrimitiveAttributes } from './CircuitJsonSchematicSvgPrimitiveAttributes.mjs'
|
|
3
|
+
import { SafeXmlText } from './SafeXmlText.mjs'
|
|
4
|
+
|
|
5
|
+
/** Renders hierarchical child-sheet symbols without treating them as pages. */
|
|
6
|
+
export class CircuitJsonSchematicSheetSymbolSvgRenderer {
|
|
7
|
+
/**
|
|
8
|
+
* Renders child-sheet symbols in their authored order.
|
|
9
|
+
* @param {object[]} symbols Canonical sheet-symbol rows.
|
|
10
|
+
* @returns {string} SVG group or an empty string.
|
|
11
|
+
*/
|
|
12
|
+
static render(symbols) {
|
|
13
|
+
const markup = (symbols || [])
|
|
14
|
+
.map((symbol, index) => ({ symbol, index }))
|
|
15
|
+
.sort(
|
|
16
|
+
(left, right) =>
|
|
17
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#order(
|
|
18
|
+
left.symbol
|
|
19
|
+
) -
|
|
20
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#order(
|
|
21
|
+
right.symbol
|
|
22
|
+
) || left.index - right.index
|
|
23
|
+
)
|
|
24
|
+
.map(({ symbol }) =>
|
|
25
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#symbol(symbol)
|
|
26
|
+
)
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
return markup.length
|
|
29
|
+
? '<g class="schematic-sheet-symbols">' + markup.join('') + '</g>'
|
|
30
|
+
: ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders one child-sheet box and label.
|
|
35
|
+
* @param {object} symbol Sheet-symbol row.
|
|
36
|
+
* @returns {string} SVG markup or an empty string.
|
|
37
|
+
*/
|
|
38
|
+
static #symbol(symbol) {
|
|
39
|
+
const center = CircuitJsonUnits.optionalPoint(symbol.center)
|
|
40
|
+
const size = CircuitJsonUnits.optionalSize(symbol)
|
|
41
|
+
if (!center || !size || size.width <= 0 || size.height <= 0) return ''
|
|
42
|
+
const sourceFileName = String(symbol.source_file_name || '')
|
|
43
|
+
return (
|
|
44
|
+
'<g class="schematic-sheet-symbol" data-schematic-sheet-symbol-id="' +
|
|
45
|
+
SafeXmlText.escape(symbol.schematic_sheet_symbol_id || '') +
|
|
46
|
+
'"' +
|
|
47
|
+
(sourceFileName
|
|
48
|
+
? ' data-source-file-name="' +
|
|
49
|
+
SafeXmlText.escape(sourceFileName) +
|
|
50
|
+
'"'
|
|
51
|
+
: '') +
|
|
52
|
+
'><rect class="schematic-sheet-symbol__body" x="' +
|
|
53
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#number(
|
|
54
|
+
center.x - size.width / 2
|
|
55
|
+
) +
|
|
56
|
+
'" y="' +
|
|
57
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#number(
|
|
58
|
+
center.y - size.height / 2
|
|
59
|
+
) +
|
|
60
|
+
'" width="' +
|
|
61
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#number(size.width) +
|
|
62
|
+
'" height="' +
|
|
63
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#number(size.height) +
|
|
64
|
+
'"' +
|
|
65
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.attributes(symbol) +
|
|
66
|
+
'></rect><text class="schematic-sheet-symbol__label" x="' +
|
|
67
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#number(center.x) +
|
|
68
|
+
'" y="' +
|
|
69
|
+
CircuitJsonSchematicSheetSymbolSvgRenderer.#number(center.y) +
|
|
70
|
+
'" text-anchor="middle" dominant-baseline="central">' +
|
|
71
|
+
SafeXmlText.escapeText(symbol.name || '') +
|
|
72
|
+
'</text></g>'
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolves one authored render order.
|
|
78
|
+
* @param {object} symbol Sheet-symbol row.
|
|
79
|
+
* @returns {number} Sort value.
|
|
80
|
+
*/
|
|
81
|
+
static #order(symbol) {
|
|
82
|
+
return Number.isSafeInteger(symbol?.render_order)
|
|
83
|
+
? symbol.render_order
|
|
84
|
+
: Number.MAX_SAFE_INTEGER
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Formats one deterministic SVG number.
|
|
89
|
+
* @param {number} value Number value.
|
|
90
|
+
* @returns {string} SVG number.
|
|
91
|
+
*/
|
|
92
|
+
static #number(value) {
|
|
93
|
+
return Number(Number(value).toFixed(6)).toString()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Object.freeze(CircuitJsonSchematicSheetSymbolSvgRenderer.prototype)
|
|
98
|
+
Object.freeze(CircuitJsonSchematicSheetSymbolSvgRenderer)
|