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
|
@@ -11,8 +11,12 @@ export class CircuitJsonSchematicSvgArcPath {
|
|
|
11
11
|
*/
|
|
12
12
|
static fromThreePoints(start, mid, end) {
|
|
13
13
|
if (!start || !mid || !end) return ''
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const geometry = CircuitJsonSchematicSvgArcPath.#geometry(
|
|
15
|
+
start,
|
|
16
|
+
mid,
|
|
17
|
+
end
|
|
18
|
+
)
|
|
19
|
+
if (!geometry) {
|
|
16
20
|
return (
|
|
17
21
|
'M ' +
|
|
18
22
|
CircuitJsonSchematicSvgArcPath.#formatPoint(start) +
|
|
@@ -20,6 +24,69 @@ export class CircuitJsonSchematicSvgArcPath {
|
|
|
20
24
|
CircuitJsonSchematicSvgArcPath.#formatPoint(end)
|
|
21
25
|
)
|
|
22
26
|
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
'M ' +
|
|
30
|
+
CircuitJsonSchematicSvgArcPath.#formatPoint(start) +
|
|
31
|
+
' A ' +
|
|
32
|
+
CircuitJsonSchematicSvgArcPath.#formatNumber(
|
|
33
|
+
geometry.circle.radius
|
|
34
|
+
) +
|
|
35
|
+
' ' +
|
|
36
|
+
CircuitJsonSchematicSvgArcPath.#formatNumber(
|
|
37
|
+
geometry.circle.radius
|
|
38
|
+
) +
|
|
39
|
+
' 0 ' +
|
|
40
|
+
geometry.largeArc +
|
|
41
|
+
' ' +
|
|
42
|
+
geometry.sweep +
|
|
43
|
+
' ' +
|
|
44
|
+
CircuitJsonSchematicSvgArcPath.#formatPoint(end)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the exact axis-aligned bounds of the rendered three-point arc.
|
|
50
|
+
* @param {{ x: number, y: number } | null} start Start point.
|
|
51
|
+
* @param {{ x: number, y: number } | null} mid Midpoint on the arc.
|
|
52
|
+
* @param {{ x: number, y: number } | null} end End point.
|
|
53
|
+
* @returns {{ minX: number, minY: number, maxX: number, maxY: number } | null} Arc bounds.
|
|
54
|
+
*/
|
|
55
|
+
static boundsFromThreePoints(start, mid, end) {
|
|
56
|
+
if (!start || !mid || !end) return null
|
|
57
|
+
const geometry = CircuitJsonSchematicSvgArcPath.#geometry(
|
|
58
|
+
start,
|
|
59
|
+
mid,
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
if (!geometry) {
|
|
63
|
+
return CircuitJsonSchematicSvgArcPath.#bounds([start, end])
|
|
64
|
+
}
|
|
65
|
+
const points = [start, end]
|
|
66
|
+
for (const angle of [0, Math.PI / 2, Math.PI, (Math.PI * 3) / 2]) {
|
|
67
|
+
if (
|
|
68
|
+
!CircuitJsonSchematicSvgArcPath.#containsAngle(geometry, angle)
|
|
69
|
+
) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
points.push({
|
|
73
|
+
x: geometry.circle.x + Math.cos(angle) * geometry.circle.radius,
|
|
74
|
+
y: geometry.circle.y + Math.sin(angle) * geometry.circle.radius
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
return CircuitJsonSchematicSvgArcPath.#bounds(points)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves the shared circle and directed sweep through three points.
|
|
82
|
+
* @param {{ x: number, y: number }} start Start point.
|
|
83
|
+
* @param {{ x: number, y: number }} mid Midpoint on the arc.
|
|
84
|
+
* @param {{ x: number, y: number }} end End point.
|
|
85
|
+
* @returns {{ circle: { x: number, y: number, radius: number }, startAngle: number, endAngle: number, sweep: 0 | 1, span: number, largeArc: 0 | 1 } | null} Arc geometry.
|
|
86
|
+
*/
|
|
87
|
+
static #geometry(start, mid, end) {
|
|
88
|
+
const circle = CircuitJsonSchematicSvgArcPath.#circle(start, mid, end)
|
|
89
|
+
if (!circle) return null
|
|
23
90
|
const startAngle = CircuitJsonSchematicSvgArcPath.#angle(circle, start)
|
|
24
91
|
const midAngle = CircuitJsonSchematicSvgArcPath.#angle(circle, mid)
|
|
25
92
|
const endAngle = CircuitJsonSchematicSvgArcPath.#angle(circle, end)
|
|
@@ -39,22 +106,55 @@ export class CircuitJsonSchematicSvgArcPath {
|
|
|
39
106
|
endAngle,
|
|
40
107
|
startAngle
|
|
41
108
|
)
|
|
42
|
-
|
|
109
|
+
return {
|
|
110
|
+
circle,
|
|
111
|
+
startAngle,
|
|
112
|
+
endAngle,
|
|
113
|
+
sweep,
|
|
114
|
+
span,
|
|
115
|
+
largeArc: span > Math.PI ? 1 : 0
|
|
116
|
+
}
|
|
117
|
+
}
|
|
43
118
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Returns whether one cardinal angle lies on the directed arc sweep.
|
|
121
|
+
* @param {{ startAngle: number, sweep: 0 | 1, span: number }} geometry Arc geometry.
|
|
122
|
+
* @param {number} angle Candidate angle.
|
|
123
|
+
* @returns {boolean} Whether the angle is visible.
|
|
124
|
+
*/
|
|
125
|
+
static #containsAngle(geometry, angle) {
|
|
126
|
+
const delta =
|
|
127
|
+
geometry.sweep === 1
|
|
128
|
+
? CircuitJsonSchematicSvgArcPath.#positiveDelta(
|
|
129
|
+
geometry.startAngle,
|
|
130
|
+
angle
|
|
131
|
+
)
|
|
132
|
+
: CircuitJsonSchematicSvgArcPath.#positiveDelta(
|
|
133
|
+
angle,
|
|
134
|
+
geometry.startAngle
|
|
135
|
+
)
|
|
136
|
+
return delta <= geometry.span + 0.000001
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Builds finite bounds around a non-empty point list.
|
|
141
|
+
* @param {{ x: number, y: number }[]} points Points to include.
|
|
142
|
+
* @returns {{ minX: number, minY: number, maxX: number, maxY: number }} Bounds.
|
|
143
|
+
*/
|
|
144
|
+
static #bounds(points) {
|
|
145
|
+
const bounds = {
|
|
146
|
+
minX: Infinity,
|
|
147
|
+
minY: Infinity,
|
|
148
|
+
maxX: -Infinity,
|
|
149
|
+
maxY: -Infinity
|
|
150
|
+
}
|
|
151
|
+
for (const point of points) {
|
|
152
|
+
bounds.minX = Math.min(bounds.minX, point.x)
|
|
153
|
+
bounds.minY = Math.min(bounds.minY, point.y)
|
|
154
|
+
bounds.maxX = Math.max(bounds.maxX, point.x)
|
|
155
|
+
bounds.maxY = Math.max(bounds.maxY, point.y)
|
|
156
|
+
}
|
|
157
|
+
return bounds
|
|
58
158
|
}
|
|
59
159
|
|
|
60
160
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SafeXmlText } from './SafeXmlText.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Resolves schematic port display metadata from linked source ports.
|
|
3
5
|
*/
|
|
@@ -26,10 +28,10 @@ export class CircuitJsonSchematicSvgPortMetadata {
|
|
|
26
28
|
*/
|
|
27
29
|
static attributes(element, sourcePort) {
|
|
28
30
|
const sourcePortId = String(element.source_port_id || '').trim()
|
|
29
|
-
const pinNumber = CircuitJsonSchematicSvgPortMetadata.#firstText(
|
|
31
|
+
const pinNumber = CircuitJsonSchematicSvgPortMetadata.#firstText([
|
|
30
32
|
element.pin_number,
|
|
31
33
|
sourcePort?.pin_number
|
|
32
|
-
)
|
|
34
|
+
])
|
|
33
35
|
const direction = String(
|
|
34
36
|
element.facing_direction || element.direction || ''
|
|
35
37
|
).trim()
|
|
@@ -69,34 +71,83 @@ export class CircuitJsonSchematicSvgPortMetadata {
|
|
|
69
71
|
* Resolves a visible schematic port label.
|
|
70
72
|
* @param {object} element Schematic port row.
|
|
71
73
|
* @param {object | null} sourcePort Linked source port row.
|
|
74
|
+
* @param {WeakMap<object, string> | null} [hintCache] Render-scoped hint cache.
|
|
72
75
|
* @returns {string}
|
|
73
76
|
*/
|
|
74
|
-
static label(element, sourcePort) {
|
|
75
|
-
|
|
77
|
+
static label(element, sourcePort, hintCache = null) {
|
|
78
|
+
const direct = CircuitJsonSchematicSvgPortMetadata.#firstText([
|
|
76
79
|
element.name,
|
|
77
80
|
element.label,
|
|
78
81
|
sourcePort?.name,
|
|
79
82
|
sourcePort?.label,
|
|
80
83
|
sourcePort?.pin_label,
|
|
81
84
|
element.pin_number,
|
|
82
|
-
sourcePort?.pin_number
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
sourcePort?.pin_number
|
|
86
|
+
])
|
|
87
|
+
return direct
|
|
88
|
+
? direct
|
|
89
|
+
: CircuitJsonSchematicSvgPortMetadata.#sourceHint(
|
|
90
|
+
sourcePort,
|
|
91
|
+
hintCache
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves one source-port hint fallback within a single render snapshot.
|
|
97
|
+
* @param {object | null} sourcePort Source-port row.
|
|
98
|
+
* @param {WeakMap<object, string> | null} hintCache Render-scoped cache.
|
|
99
|
+
* @returns {string} First non-empty hint.
|
|
100
|
+
*/
|
|
101
|
+
static #sourceHint(sourcePort, hintCache) {
|
|
102
|
+
if (!sourcePort || typeof sourcePort !== 'object') return ''
|
|
103
|
+
if (hintCache?.has(sourcePort)) {
|
|
104
|
+
return hintCache.get(sourcePort)
|
|
105
|
+
}
|
|
106
|
+
const hints = Array.isArray(sourcePort.port_hints)
|
|
107
|
+
? sourcePort.port_hints
|
|
108
|
+
: []
|
|
109
|
+
const hint = CircuitJsonSchematicSvgPortMetadata.#firstText(hints)
|
|
110
|
+
hintCache?.set(sourcePort, hint)
|
|
111
|
+
return hint
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolves the first non-empty text and whether evaluated values are immutable.
|
|
116
|
+
* @param {unknown[]} values Candidate values.
|
|
117
|
+
* @returns {{ text: string, immutable: boolean }} Text metadata.
|
|
118
|
+
*/
|
|
119
|
+
static #firstTextResult(values) {
|
|
120
|
+
let immutable = true
|
|
121
|
+
for (const value of values) {
|
|
122
|
+
immutable =
|
|
123
|
+
immutable &&
|
|
124
|
+
CircuitJsonSchematicSvgPortMetadata.#isImmutableScalar(value)
|
|
125
|
+
const text = String(value ?? '').trim()
|
|
126
|
+
if (text) return { text, immutable }
|
|
127
|
+
}
|
|
128
|
+
return { text: '', immutable }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns whether a label value is immutable primitive data.
|
|
133
|
+
* @param {unknown} value Label value.
|
|
134
|
+
* @returns {boolean} Whether the value can participate in memoization.
|
|
135
|
+
*/
|
|
136
|
+
static #isImmutableScalar(value) {
|
|
137
|
+
return (
|
|
138
|
+
value === null ||
|
|
139
|
+
value === undefined ||
|
|
140
|
+
['string', 'number', 'boolean', 'bigint'].includes(typeof value)
|
|
86
141
|
)
|
|
87
142
|
}
|
|
88
143
|
|
|
89
144
|
/**
|
|
90
145
|
* Resolves the first non-empty display text value.
|
|
91
|
-
* @param {
|
|
146
|
+
* @param {unknown[]} values Candidate values.
|
|
92
147
|
* @returns {string}
|
|
93
148
|
*/
|
|
94
|
-
static #firstText(
|
|
95
|
-
return (
|
|
96
|
-
values
|
|
97
|
-
.map((value) => String(value ?? '').trim())
|
|
98
|
-
.find((value) => value) || ''
|
|
99
|
-
)
|
|
149
|
+
static #firstText(values) {
|
|
150
|
+
return CircuitJsonSchematicSvgPortMetadata.#firstTextResult(values).text
|
|
100
151
|
}
|
|
101
152
|
|
|
102
153
|
/**
|
|
@@ -105,10 +156,6 @@ export class CircuitJsonSchematicSvgPortMetadata {
|
|
|
105
156
|
* @returns {string}
|
|
106
157
|
*/
|
|
107
158
|
static #escapeHtml(value) {
|
|
108
|
-
return
|
|
109
|
-
.replaceAll('&', '&')
|
|
110
|
-
.replaceAll('<', '<')
|
|
111
|
-
.replaceAll('>', '>')
|
|
112
|
-
.replaceAll('"', '"')
|
|
159
|
+
return SafeXmlText.escape(value)
|
|
113
160
|
}
|
|
114
161
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SafeXmlText } from './SafeXmlText.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Builds SVG drawing attributes for schematic primitive rows.
|
|
3
5
|
*/
|
|
@@ -21,6 +23,8 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
|
|
|
21
23
|
)
|
|
22
24
|
const dashArray =
|
|
23
25
|
CircuitJsonSchematicSvgPrimitiveAttributes.#dashArray(element)
|
|
26
|
+
const lineCap =
|
|
27
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.#lineCap(element)
|
|
24
28
|
|
|
25
29
|
if (strokeWidth !== null) {
|
|
26
30
|
attributes.push(
|
|
@@ -31,20 +35,22 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
|
|
|
31
35
|
'"'
|
|
32
36
|
)
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
const safeStroke = SafeSvgPaint.color(strokeColor)
|
|
39
|
+
const safeFill = SafeSvgPaint.color(fillColor)
|
|
40
|
+
if (safeStroke) {
|
|
35
41
|
attributes.push(
|
|
36
42
|
'stroke="' +
|
|
37
43
|
CircuitJsonSchematicSvgPrimitiveAttributes.#escapeHtml(
|
|
38
|
-
|
|
44
|
+
safeStroke
|
|
39
45
|
) +
|
|
40
46
|
'"'
|
|
41
47
|
)
|
|
42
48
|
}
|
|
43
|
-
if (options.fill !== false &&
|
|
49
|
+
if (options.fill !== false && safeFill) {
|
|
44
50
|
attributes.push(
|
|
45
51
|
'fill="' +
|
|
46
52
|
CircuitJsonSchematicSvgPrimitiveAttributes.#escapeHtml(
|
|
47
|
-
|
|
53
|
+
safeFill
|
|
48
54
|
) +
|
|
49
55
|
'"'
|
|
50
56
|
)
|
|
@@ -58,6 +64,9 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
|
|
|
58
64
|
'"'
|
|
59
65
|
)
|
|
60
66
|
}
|
|
67
|
+
if (lineCap) {
|
|
68
|
+
attributes.push('stroke-linecap="' + lineCap + '"')
|
|
69
|
+
}
|
|
61
70
|
|
|
62
71
|
return attributes.length ? ' ' + attributes.join(' ') : ''
|
|
63
72
|
}
|
|
@@ -87,6 +96,22 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
|
|
|
87
96
|
* @returns {string}
|
|
88
97
|
*/
|
|
89
98
|
static #dashArray(element) {
|
|
99
|
+
const dashLength = CircuitJsonSchematicSvgPrimitiveAttributes.#number(
|
|
100
|
+
element?.dash_length ?? element?.dashLength
|
|
101
|
+
)
|
|
102
|
+
const dashGap = CircuitJsonSchematicSvgPrimitiveAttributes.#number(
|
|
103
|
+
element?.dash_gap ?? element?.dashGap
|
|
104
|
+
)
|
|
105
|
+
if (element?.is_dashed && dashLength !== null) {
|
|
106
|
+
return [dashLength, dashGap ?? dashLength]
|
|
107
|
+
.map((entry) =>
|
|
108
|
+
CircuitJsonSchematicSvgPrimitiveAttributes.#formatNumber(
|
|
109
|
+
entry
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
.join(' ')
|
|
113
|
+
}
|
|
114
|
+
|
|
90
115
|
const value =
|
|
91
116
|
element?.stroke_dasharray ??
|
|
92
117
|
element?.strokeDasharray ??
|
|
@@ -106,6 +131,20 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
|
|
|
106
131
|
return element?.is_dashed ? '0.4 0.25' : ''
|
|
107
132
|
}
|
|
108
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Resolves one safe SVG stroke line-cap value.
|
|
136
|
+
* @param {object} element CircuitJSON element row.
|
|
137
|
+
* @returns {'butt' | 'round' | 'square' | ''}
|
|
138
|
+
*/
|
|
139
|
+
static #lineCap(element) {
|
|
140
|
+
const value = String(
|
|
141
|
+
element?.stroke_linecap ?? element?.strokeLinecap ?? ''
|
|
142
|
+
)
|
|
143
|
+
.trim()
|
|
144
|
+
.toLowerCase()
|
|
145
|
+
return ['butt', 'round', 'square'].includes(value) ? value : ''
|
|
146
|
+
}
|
|
147
|
+
|
|
109
148
|
/**
|
|
110
149
|
* Formats one SVG number.
|
|
111
150
|
* @param {number} value Number value.
|
|
@@ -121,10 +160,7 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
|
|
|
121
160
|
* @returns {string}
|
|
122
161
|
*/
|
|
123
162
|
static #escapeHtml(value) {
|
|
124
|
-
return
|
|
125
|
-
.replaceAll('&', '&')
|
|
126
|
-
.replaceAll('<', '<')
|
|
127
|
-
.replaceAll('>', '>')
|
|
128
|
-
.replaceAll('"', '"')
|
|
163
|
+
return SafeXmlText.escape(value)
|
|
129
164
|
}
|
|
130
165
|
}
|
|
166
|
+
import { SafeSvgPaint } from './SafeSvgPaint.mjs'
|