circuitjson-toolkit 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -6
- package/docs/api.md +21 -6
- package/docs/model-format.md +46 -8
- package/docs/release-notes-v1.2.0.md +33 -0
- package/docs/release-notes-v1.2.1.md +26 -0
- package/package.json +3 -1
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +71 -7
- package/src/core/CircuitJsonToolkitElementSchema.mjs +127 -0
- package/src/core/CircuitJsonUpstreamValidator.mjs +10 -5
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +1 -1
- package/src/core/context/StructuredDataSnapshot.mjs +1 -1
- package/src/core/worker/WorkerRequestData.mjs +402 -56
- package/src/core/worker/WorkerResultShape.mjs +222 -0
package/README.md
CHANGED
|
@@ -49,6 +49,18 @@ theme fallback, and generic component and symbol bodies explicitly use the
|
|
|
49
49
|
ECAD Forge schematic palette. See the
|
|
50
50
|
[1.1.2 release notes](docs/release-notes-v1.1.2.md).
|
|
51
51
|
|
|
52
|
+
Version 1.2.0 extends the canonical PCB contract with validated source text
|
|
53
|
+
fidelity, rotated drilled-pad bounds, and rounded-pad corner radii. Worker and
|
|
54
|
+
metadata ownership traversals now support deeply nested, valid ECAD documents
|
|
55
|
+
up to 256 levels. See the
|
|
56
|
+
[1.2.0 release notes](docs/release-notes-v1.2.0.md).
|
|
57
|
+
|
|
58
|
+
Version 1.2.1 keeps large canonical documents and multi-document projects
|
|
59
|
+
inside explicit worker budgets. Selected extensions may contain up to four
|
|
60
|
+
million structured items, while exact project envelopes receive independent
|
|
61
|
+
per-document, project-metadata, aggregate-byte, and repeated-work limits. See
|
|
62
|
+
the [1.2.1 release notes](docs/release-notes-v1.2.1.md).
|
|
63
|
+
|
|
52
64
|
Before 1.1.0:
|
|
53
65
|
|
|
54
66
|
```js
|
|
@@ -82,6 +94,8 @@ const model = document.model
|
|
|
82
94
|
defaults
|
|
83
95
|
- Asset-backed `schematic_image` rows and hierarchical
|
|
84
96
|
`schematic_sheet_symbol` rows with shared bounds and SVG behavior
|
|
97
|
+
- Validated PCB text source-fidelity fields for independent font dimensions,
|
|
98
|
+
exact nine-position anchors, visibility, layer provenance, and native type
|
|
85
99
|
- Reusable exact PCB interaction and spatial indexes
|
|
86
100
|
- Query, manufacturing, and injected simulation services
|
|
87
101
|
- Data-only, millimeter-based, right-handed Z-up PCB 3D scenes
|
|
@@ -92,7 +106,7 @@ const model = document.model
|
|
|
92
106
|
- Strict `ecad-toolkit.worker.v1` parsing/project protocol with progress,
|
|
93
107
|
cancellation, and controlled buffer transfer
|
|
94
108
|
- One-pass ownership for selected source extensions, with a separate 128 MiB /
|
|
95
|
-
|
|
109
|
+
4,000,000-item bound and exact direct/worker result parity
|
|
96
110
|
- Machine-readable capability inventory and packed downstream conformance
|
|
97
111
|
harness
|
|
98
112
|
- Explicit `/extensions` surface retaining every previous specialized API
|
|
@@ -193,11 +207,14 @@ falls back to direct execution only when worker construction is unavailable;
|
|
|
193
207
|
explicit worker and runtime failures remain visible.
|
|
194
208
|
|
|
195
209
|
Selected source-native extensions are captured once into an immutable owned
|
|
196
|
-
snapshot. Their separate 128 MiB payload and
|
|
197
|
-
realistic renderer/model graphs
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
210
|
+
snapshot. Their separate 128 MiB payload and 4,000,000-item ceilings permit
|
|
211
|
+
realistic renderer/model graphs. Canonical standalone documents remain within
|
|
212
|
+
a 250 MB result ceiling, while exact multi-document project envelopes use a
|
|
213
|
+
256 MiB aggregate ceiling plus independent 250 MB per-document and metadata
|
|
214
|
+
ceilings. Item and repeated-alias work budgets remain separately bounded; an
|
|
215
|
+
over-limit extension or result fails visibly instead of being silently
|
|
216
|
+
truncated. Binary extension values remain byte-backed and return defensive
|
|
217
|
+
copies instead of expanding into JavaScript number arrays.
|
|
201
218
|
When extensions are disabled, native documents and projects return the exact
|
|
202
219
|
empty map `{}`. Bounded ZIP consumers can preflight local/central filenames and
|
|
203
220
|
CRC32/size metadata with
|
|
@@ -310,6 +327,8 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
|
|
|
310
327
|
- [1.1.0 release notes](docs/release-notes-v1.1.0.md)
|
|
311
328
|
- [1.1.1 release notes](docs/release-notes-v1.1.1.md)
|
|
312
329
|
- [1.1.2 release notes](docs/release-notes-v1.1.2.md)
|
|
330
|
+
- [1.2.0 release notes](docs/release-notes-v1.2.0.md)
|
|
331
|
+
- [1.2.1 release notes](docs/release-notes-v1.2.1.md)
|
|
313
332
|
- [Library scope](spec/library-scope.md)
|
|
314
333
|
|
|
315
334
|
## Package scope
|
package/docs/api.md
CHANGED
|
@@ -53,12 +53,13 @@ make the common shape smaller.
|
|
|
53
53
|
|
|
54
54
|
Validated source documents own selected extension values as one deeply
|
|
55
55
|
immutable, descriptor-safe snapshot. This uses a distinct 128 MiB payload and
|
|
56
|
-
|
|
56
|
+
4,000,000-item ceiling so large native renderer graphs do not inherit the
|
|
57
57
|
compact metadata limit. The shared worker protocol applies the same ownership
|
|
58
|
-
boundary after structured cloning
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
boundary after structured cloning. Canonical standalone documents retain a
|
|
59
|
+
250 MB byte ceiling; exact multi-document projects use a 256 MiB aggregate
|
|
60
|
+
ceiling with independent 250 MB document and project-metadata limits. Binary
|
|
61
|
+
values preserve their `ArrayBuffer`/typed-view shape behind defensive copy
|
|
62
|
+
access, so payload bytes do not inflate into one JavaScript number per byte.
|
|
62
63
|
|
|
63
64
|
### Errors
|
|
64
65
|
|
|
@@ -144,6 +145,15 @@ The canonical union is the pinned upstream schema plus the source-neutral
|
|
|
144
145
|
snapshot, `canonicalElementTypes()` for all accepted types, and
|
|
145
146
|
`extensionElementTypes()` for the toolkit-owned additions.
|
|
146
147
|
|
|
148
|
+
Pinned PCB text rows may also retain the validated source-fidelity fields
|
|
149
|
+
`font_width`, `font_height`, `stroke_width`, `source_anchor_alignment`,
|
|
150
|
+
`is_hidden`, `source_layer`, `source_type`, and `source_text_kind`. Board note
|
|
151
|
+
rotation uses `ccw_rotation`; fabrication note mirroring uses `is_mirrored`.
|
|
152
|
+
Canonical upstream properties remain authoritative when they exist. In
|
|
153
|
+
particular, `pcb_note_text.anchor_alignment` keeps the narrower upstream enum,
|
|
154
|
+
while `source_anchor_alignment` can preserve the exact nine-position source
|
|
155
|
+
anchor for lossless rendering.
|
|
156
|
+
|
|
147
157
|
The projection covers legacy schematic table row/column/span geometry, PCB
|
|
148
158
|
artwork `points`/`width` paths, pad-clearance diagnostic relations, courtyard
|
|
149
159
|
line/path/polygon forms, outer-layer aliases, and retained stroke dash fields.
|
|
@@ -157,7 +167,12 @@ rendering and interaction consumers. Polygon `pad_outline` points determine
|
|
|
157
167
|
rotation-local outer width and height; pill drill width, height, diameter, and
|
|
158
168
|
board-space rotation remain distinct from outer-pad rotation. Legal
|
|
159
169
|
`outer_width`, `outer_height`, `rect_ccw_rotation`, and `hole_ccw_rotation`
|
|
160
|
-
variants are preserved.
|
|
170
|
+
variants are preserved. The returned geometry includes the parsed rectangular
|
|
171
|
+
`cornerRadius`. Rotated rectangular bounds use the exact visible
|
|
172
|
+
rounded-rectangle support dimensions, with the effective radius clamped from
|
|
173
|
+
zero through half the smaller outer dimension. A zero radius therefore retains
|
|
174
|
+
sharp-rectangle bounds, while a half-minor-dimension radius has the same support
|
|
175
|
+
extent as a pill. Import this retained source-neutral helper from
|
|
161
176
|
`circuitjson-toolkit/extensions`.
|
|
162
177
|
|
|
163
178
|
Packed release checks reject any missing or additional root export.
|
package/docs/model-format.md
CHANGED
|
@@ -102,15 +102,19 @@ must not duplicate the CircuitJSON model or keep a renamed renderer model.
|
|
|
102
102
|
Default parsing excludes raw/base64/full-payload graphs.
|
|
103
103
|
|
|
104
104
|
When a caller explicitly selects a native extension, the toolkit captures and
|
|
105
|
-
freezes that graph once under a distinct ceiling of
|
|
105
|
+
freezes that graph once under a distinct ceiling of 4,000,000 structured items
|
|
106
106
|
and 128 MiB of string or binary content. The ceiling is shared across the
|
|
107
|
-
selected namespace
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
selected namespace and applies equally after worker transfer. Canonical
|
|
108
|
+
standalone documents allow the extension plus envelope overhead within five
|
|
109
|
+
million result values and 250 MB. Exact project envelopes allow eight million
|
|
110
|
+
aggregate values and 256 MiB, while each document stays within five million
|
|
111
|
+
values and 250 MB and non-document project metadata stays within two million
|
|
112
|
+
values and 250 MB. Reused graphs are charged independently to each local scope,
|
|
113
|
+
and repeated accounting work is itself bounded by the aggregate project
|
|
114
|
+
budget. Oversized graphs are rejected; they are never truncated or partially
|
|
115
|
+
retained. Binary extension values remain byte-backed with their common
|
|
116
|
+
buffer/view type and are exposed as defensive copies, preserving mutation
|
|
117
|
+
isolation without expanding bytes into plain numeric arrays.
|
|
114
118
|
|
|
115
119
|
Supported pre-union CircuitJSON aliases may be projected through
|
|
116
120
|
`CircuitJsonDocument.normalizeModel()`. The copy-on-write form preserves exact
|
|
@@ -119,6 +123,40 @@ validation/proof boundary. Table cell geometry, PCB artwork routes,
|
|
|
119
123
|
pad-clearance relations, courtyards, layer aliases, and stroke dashes are
|
|
120
124
|
derived structurally and never from a source filename or fixture identity.
|
|
121
125
|
|
|
126
|
+
### PCB text source fidelity
|
|
127
|
+
|
|
128
|
+
`pcb_note_text`, `pcb_fabrication_note_text`, and `pcb_silkscreen_text` retain
|
|
129
|
+
canonical upstream fields first. Source formats with independent width and
|
|
130
|
+
height, edge-center anchors, native layer names, or hidden-text state may add:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
{
|
|
134
|
+
type: 'pcb_note_text',
|
|
135
|
+
pcb_note_text_id: 'board_text_1',
|
|
136
|
+
text: 'BOARD MARK',
|
|
137
|
+
anchor_position: { x: 12.5, y: 4.25 },
|
|
138
|
+
layer: 'bottom',
|
|
139
|
+
ccw_rotation: 28,
|
|
140
|
+
font_size: 1.2,
|
|
141
|
+
font_width: 0.8,
|
|
142
|
+
font_height: 1.2,
|
|
143
|
+
stroke_width: 0.12,
|
|
144
|
+
anchor_alignment: 'center',
|
|
145
|
+
source_anchor_alignment: 'center_left',
|
|
146
|
+
is_mirrored_from_top_view: true,
|
|
147
|
+
is_hidden: false,
|
|
148
|
+
source_layer: 'B.SilkS',
|
|
149
|
+
source_type: 'gr_text'
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The extension fields are validated and preserved through immutable document
|
|
154
|
+
preparation. Lengths accept the same numeric millimeter or unit-suffixed style
|
|
155
|
+
as canonical dimensions. `source_anchor_alignment` accepts all nine standard
|
|
156
|
+
anchor positions. The narrower upstream anchor on board and fabrication notes
|
|
157
|
+
must remain `center` or a corner; renderers use the source anchor when they
|
|
158
|
+
need the exact edge-center placement.
|
|
159
|
+
|
|
122
160
|
## Assets and diagnostics
|
|
123
161
|
|
|
124
162
|
Assets have exact `id`, `kind`, `name`, `mediaType`, `byteLength`, `data`, and
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# circuitjson-toolkit 1.2.0
|
|
2
|
+
|
|
3
|
+
## Canonical PCB fidelity
|
|
4
|
+
|
|
5
|
+
This minor release expands the shared CircuitJSON contract used by the Gerber,
|
|
6
|
+
Altium, KiCad, and 3D viewer packages. Existing canonical fields remain
|
|
7
|
+
authoritative, while source formats can retain exact rendering information in
|
|
8
|
+
validated, source-neutral extension fields.
|
|
9
|
+
|
|
10
|
+
### API additions
|
|
11
|
+
|
|
12
|
+
- `pcb_note_text`, `pcb_fabrication_note_text`, and `pcb_silkscreen_text` may
|
|
13
|
+
retain independent `font_width` and `font_height`, `stroke_width`, the exact
|
|
14
|
+
nine-position `source_anchor_alignment`, `is_hidden`, `source_layer`,
|
|
15
|
+
`source_type`, and `source_text_kind`.
|
|
16
|
+
- Board-note `ccw_rotation` and fabrication-note `is_mirrored` are validated
|
|
17
|
+
when present, so downstream renderers can consume the original orientation
|
|
18
|
+
without source-format adapters.
|
|
19
|
+
- `CircuitJsonPcbHolePrimitiveModel.build()` now returns `cornerRadius` and
|
|
20
|
+
computes board-space bounds for rotated rectangles and pills instead of
|
|
21
|
+
treating their local width and height as axis-aligned.
|
|
22
|
+
|
|
23
|
+
### Behavior and performance
|
|
24
|
+
|
|
25
|
+
- Structured metadata snapshots and worker request graphs accept valid nesting
|
|
26
|
+
up to 256 levels, matching the deeper native data graphs emitted by ECAD
|
|
27
|
+
parsers while retaining the existing item and byte limits.
|
|
28
|
+
- The new fields pass through the existing immutable document ownership and
|
|
29
|
+
validation boundaries; no viewer or host-app workaround is required.
|
|
30
|
+
|
|
31
|
+
No existing public class, method, package subpath, parameter, or document
|
|
32
|
+
envelope is removed in this release. Consumers that exhaustively validate PCB
|
|
33
|
+
text or primitive return objects should accept the additive fields above.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# circuitjson-toolkit 1.2.1
|
|
2
|
+
|
|
3
|
+
## Large canonical worker results
|
|
4
|
+
|
|
5
|
+
This patch release keeps valid high-fidelity documents and multi-document
|
|
6
|
+
projects inside explicit, format-neutral worker limits:
|
|
7
|
+
|
|
8
|
+
- Selected native extensions may contain up to 4,000,000 structured items and
|
|
9
|
+
128 MiB of string or binary content.
|
|
10
|
+
- A canonical standalone document may contain up to 5,000,000 result values
|
|
11
|
+
within the existing 250 MB byte ceiling.
|
|
12
|
+
- An exact canonical project may contain up to 8,000,000 aggregate values and
|
|
13
|
+
256 MiB, while every document retains its own 5,000,000-value and 250 MB
|
|
14
|
+
limits.
|
|
15
|
+
- Non-document project metadata retains the generic 2,000,000-value and 250 MB
|
|
16
|
+
limits.
|
|
17
|
+
|
|
18
|
+
Elevated budgets require the complete canonical document or project envelope;
|
|
19
|
+
a schema string alone does not change generic result limits. Reused object
|
|
20
|
+
graphs are charged independently to document and project-metadata scopes in a
|
|
21
|
+
property-order-independent way. Repeated alias-accounting work is also capped
|
|
22
|
+
by the project aggregate budget, keeping transport time bounded.
|
|
23
|
+
|
|
24
|
+
No public class, method, package subpath, parameter, or result field is removed
|
|
25
|
+
or renamed. Oversized results continue to fail visibly rather than being
|
|
26
|
+
truncated.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuitjson-toolkit",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"circuitjson",
|
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
"docs/release-notes-v1.1.0.md",
|
|
54
54
|
"docs/release-notes-v1.1.1.md",
|
|
55
55
|
"docs/release-notes-v1.1.2.md",
|
|
56
|
+
"docs/release-notes-v1.2.0.md",
|
|
57
|
+
"docs/release-notes-v1.2.1.md",
|
|
56
58
|
"docs/testing.md",
|
|
57
59
|
"spec",
|
|
58
60
|
"LICENSE",
|
|
@@ -29,19 +29,25 @@ export class CircuitJsonPcbHolePrimitiveModel {
|
|
|
29
29
|
shape,
|
|
30
30
|
size
|
|
31
31
|
)
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
const cornerRadius = CircuitJsonUnits.length(
|
|
33
|
+
element.rect_border_radius ?? element.corner_radius,
|
|
34
|
+
0
|
|
35
|
+
)
|
|
36
|
+
const bounds = CircuitJsonPcbHolePrimitiveModel.#outerBounds(
|
|
37
|
+
center,
|
|
38
|
+
size,
|
|
39
|
+
shape,
|
|
40
|
+
rotation,
|
|
41
|
+
cornerRadius,
|
|
42
|
+
points
|
|
43
|
+
)
|
|
39
44
|
|
|
40
45
|
return {
|
|
41
46
|
shape,
|
|
42
47
|
width: size.width,
|
|
43
48
|
height: size.height,
|
|
44
49
|
diameter: Math.max(size.width, size.height),
|
|
50
|
+
cornerRadius,
|
|
45
51
|
holeShape: hole.shape,
|
|
46
52
|
holeDiameter: hole.diameter,
|
|
47
53
|
holeWidth: hole.width,
|
|
@@ -123,6 +129,64 @@ export class CircuitJsonPcbHolePrimitiveModel {
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Resolves the board-space axis-aligned bounds of the visible outer shape.
|
|
134
|
+
* @param {{ x: number, y: number }} center Shape center.
|
|
135
|
+
* @param {{ width: number, height: number }} size Local shape size.
|
|
136
|
+
* @param {'circle' | 'pill' | 'polygon' | 'rect'} shape Shape kind.
|
|
137
|
+
* @param {number} rotation Counter-clockwise rotation in degrees.
|
|
138
|
+
* @param {number} cornerRadius Parsed rectangular corner radius.
|
|
139
|
+
* @param {{ x: number, y: number }[]} points Polygon points.
|
|
140
|
+
* @returns {object} Board-space axis-aligned bounds.
|
|
141
|
+
*/
|
|
142
|
+
static #outerBounds(center, size, shape, rotation, cornerRadius, points) {
|
|
143
|
+
if (points.length) {
|
|
144
|
+
return CircuitJsonPcbPrimitiveGeometry.pointsBounds(points)
|
|
145
|
+
}
|
|
146
|
+
if (shape === 'circle' || rotation === 0) {
|
|
147
|
+
return CircuitJsonPcbPrimitiveGeometry.centerBounds(
|
|
148
|
+
center,
|
|
149
|
+
size.width,
|
|
150
|
+
size.height
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const radians = (rotation * Math.PI) / 180
|
|
155
|
+
if (shape === 'pill') {
|
|
156
|
+
const minor = Math.min(size.width, size.height)
|
|
157
|
+
const lineLength = Math.max(size.width, size.height) - minor
|
|
158
|
+
const axisRadians =
|
|
159
|
+
radians + (size.height > size.width ? Math.PI / 2 : 0)
|
|
160
|
+
const width = Math.abs(Math.cos(axisRadians)) * lineLength + minor
|
|
161
|
+
const height = Math.abs(Math.sin(axisRadians)) * lineLength + minor
|
|
162
|
+
return CircuitJsonPcbPrimitiveGeometry.centerBounds(
|
|
163
|
+
center,
|
|
164
|
+
width,
|
|
165
|
+
height
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const radius = Math.max(
|
|
170
|
+
0,
|
|
171
|
+
Math.min(cornerRadius, Math.min(size.width, size.height) / 2)
|
|
172
|
+
)
|
|
173
|
+
const innerWidth = size.width - radius * 2
|
|
174
|
+
const innerHeight = size.height - radius * 2
|
|
175
|
+
const width =
|
|
176
|
+
Math.abs(Math.cos(radians)) * innerWidth +
|
|
177
|
+
Math.abs(Math.sin(radians)) * innerHeight +
|
|
178
|
+
radius * 2
|
|
179
|
+
const height =
|
|
180
|
+
Math.abs(Math.sin(radians)) * innerWidth +
|
|
181
|
+
Math.abs(Math.cos(radians)) * innerHeight +
|
|
182
|
+
radius * 2
|
|
183
|
+
return CircuitJsonPcbPrimitiveGeometry.centerBounds(
|
|
184
|
+
center,
|
|
185
|
+
width,
|
|
186
|
+
height
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
126
190
|
/**
|
|
127
191
|
* Resolves polygonal outer pad points.
|
|
128
192
|
* @param {object} element Drilled PCB element.
|
|
@@ -1,7 +1,25 @@
|
|
|
1
|
+
import { optionalAngle, optionalLength } from './CircuitJsonUnitParsers.mjs'
|
|
2
|
+
|
|
1
3
|
const TOOLKIT_ELEMENT_TYPES = new Set([
|
|
2
4
|
'schematic_image',
|
|
3
5
|
'schematic_sheet_symbol'
|
|
4
6
|
])
|
|
7
|
+
const PCB_TEXT_EXTENSION_TYPES = new Set([
|
|
8
|
+
'pcb_note_text',
|
|
9
|
+
'pcb_fabrication_note_text',
|
|
10
|
+
'pcb_silkscreen_text'
|
|
11
|
+
])
|
|
12
|
+
const PCB_TEXT_ANCHOR_ALIGNMENTS = new Set([
|
|
13
|
+
'top_left',
|
|
14
|
+
'top_center',
|
|
15
|
+
'top_right',
|
|
16
|
+
'center_left',
|
|
17
|
+
'center',
|
|
18
|
+
'center_right',
|
|
19
|
+
'bottom_left',
|
|
20
|
+
'bottom_center',
|
|
21
|
+
'bottom_right'
|
|
22
|
+
])
|
|
5
23
|
|
|
6
24
|
/**
|
|
7
25
|
* Validates canonical toolkit element types that are newer than the pinned
|
|
@@ -45,6 +63,22 @@ export class CircuitJsonToolkitElementSchema {
|
|
|
45
63
|
]
|
|
46
64
|
}
|
|
47
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Validates source-fidelity fields retained on pinned upstream elements.
|
|
68
|
+
* @param {Record<string, any>} value Element value.
|
|
69
|
+
* @param {string} type Element type.
|
|
70
|
+
* @param {string} [location] Human-readable location suffix.
|
|
71
|
+
* @returns {string[]} Empty on success or one validation error.
|
|
72
|
+
*/
|
|
73
|
+
static validateExtensions(value, type, location = '') {
|
|
74
|
+
if (!PCB_TEXT_EXTENSION_TYPES.has(type)) return []
|
|
75
|
+
return CircuitJsonToolkitElementSchema.#pcbTextExtensions(value, type)
|
|
76
|
+
? []
|
|
77
|
+
: [
|
|
78
|
+
`CircuitJSON element ${type}${location} does not match the canonical toolkit extension schema.`
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
/**
|
|
49
83
|
* Validates an asset-backed schematic image.
|
|
50
84
|
* @param {Record<string, any>} value Image element.
|
|
@@ -156,6 +190,68 @@ export class CircuitJsonToolkitElementSchema {
|
|
|
156
190
|
)
|
|
157
191
|
}
|
|
158
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Validates exact PCB text dimensions, alignment, visibility, and source provenance.
|
|
195
|
+
* @param {Record<string, any>} value Text element.
|
|
196
|
+
* @param {string} type Text element type.
|
|
197
|
+
* @returns {boolean} Whether the extension fields match their contract.
|
|
198
|
+
*/
|
|
199
|
+
static #pcbTextExtensions(value, type) {
|
|
200
|
+
for (const field of ['font_width', 'font_height']) {
|
|
201
|
+
if (
|
|
202
|
+
value[field] !== undefined &&
|
|
203
|
+
!CircuitJsonToolkitElementSchema.#positiveLength(value[field])
|
|
204
|
+
) {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (
|
|
209
|
+
value.stroke_width !== undefined &&
|
|
210
|
+
!CircuitJsonToolkitElementSchema.#nonNegativeLength(
|
|
211
|
+
value.stroke_width
|
|
212
|
+
)
|
|
213
|
+
) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
for (const field of [
|
|
217
|
+
'source_layer',
|
|
218
|
+
'source_type',
|
|
219
|
+
'source_text_kind'
|
|
220
|
+
]) {
|
|
221
|
+
if (
|
|
222
|
+
value[field] !== undefined &&
|
|
223
|
+
!CircuitJsonToolkitElementSchema.#requiredString(value[field])
|
|
224
|
+
) {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
value.source_anchor_alignment !== undefined &&
|
|
230
|
+
!PCB_TEXT_ANCHOR_ALIGNMENTS.has(value.source_anchor_alignment)
|
|
231
|
+
) {
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
if (
|
|
235
|
+
value.is_hidden !== undefined &&
|
|
236
|
+
typeof value.is_hidden !== 'boolean'
|
|
237
|
+
) {
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
type === 'pcb_note_text' &&
|
|
242
|
+
value.ccw_rotation !== undefined &&
|
|
243
|
+
(!CircuitJsonToolkitElementSchema.#unitScalar(value.ccw_rotation) ||
|
|
244
|
+
optionalAngle(value.ccw_rotation) === null)
|
|
245
|
+
) {
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
return !(
|
|
249
|
+
type === 'pcb_fabrication_note_text' &&
|
|
250
|
+
value.is_mirrored !== undefined &&
|
|
251
|
+
typeof value.is_mirrored !== 'boolean'
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
159
255
|
/**
|
|
160
256
|
* Validates a required non-empty string.
|
|
161
257
|
* @param {unknown} value Candidate.
|
|
@@ -183,6 +279,37 @@ export class CircuitJsonToolkitElementSchema {
|
|
|
183
279
|
return CircuitJsonToolkitElementSchema.#number(value) && value > 0
|
|
184
280
|
}
|
|
185
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Validates a positive CircuitJSON length with optional unit suffix.
|
|
284
|
+
* @param {unknown} value Candidate.
|
|
285
|
+
* @returns {boolean} Whether the length is positive.
|
|
286
|
+
*/
|
|
287
|
+
static #positiveLength(value) {
|
|
288
|
+
if (!CircuitJsonToolkitElementSchema.#unitScalar(value)) return false
|
|
289
|
+
const length = optionalLength(value)
|
|
290
|
+
return length !== null && length > 0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Validates a non-negative CircuitJSON length with optional unit suffix.
|
|
295
|
+
* @param {unknown} value Candidate.
|
|
296
|
+
* @returns {boolean} Whether the length is non-negative.
|
|
297
|
+
*/
|
|
298
|
+
static #nonNegativeLength(value) {
|
|
299
|
+
if (!CircuitJsonToolkitElementSchema.#unitScalar(value)) return false
|
|
300
|
+
const length = optionalLength(value)
|
|
301
|
+
return length !== null && length >= 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Returns whether a unit field is a primitive parser input.
|
|
306
|
+
* @param {unknown} value Candidate.
|
|
307
|
+
* @returns {boolean} Whether the value can be parsed without coercion.
|
|
308
|
+
*/
|
|
309
|
+
static #unitScalar(value) {
|
|
310
|
+
return typeof value === 'number' || typeof value === 'string'
|
|
311
|
+
}
|
|
312
|
+
|
|
186
313
|
/**
|
|
187
314
|
* Validates one finite point.
|
|
188
315
|
* @param {unknown} value Candidate.
|
|
@@ -31,11 +31,16 @@ export class CircuitJsonUpstreamValidator {
|
|
|
31
31
|
type === 'source_net'
|
|
32
32
|
? CircuitJsonUpstreamValidator.#sourceNet(value)
|
|
33
33
|
: CircuitJsonUpstreamValidator.#matches(schema, value)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
if (!matches) {
|
|
35
|
+
return [
|
|
36
|
+
`CircuitJSON element ${type}${location} does not match the pinned upstream schema.`
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
return CircuitJsonToolkitElementSchema.validateExtensions(
|
|
40
|
+
value,
|
|
41
|
+
type,
|
|
42
|
+
location
|
|
43
|
+
)
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -13,7 +13,7 @@ const METADATA_BUDGETS = new WeakMap()
|
|
|
13
13
|
const EXTENSION_METADATA_LIMITS = Object.freeze({
|
|
14
14
|
label: 'Canonical extension data',
|
|
15
15
|
maxBytes: 128 * 1024 * 1024,
|
|
16
|
-
maxItems:
|
|
16
|
+
maxItems: 4_000_000,
|
|
17
17
|
preserveBinary: true
|
|
18
18
|
})
|
|
19
19
|
const ASSET_SCALAR_FIELDS = new Set([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BinaryDataSnapshot } from './BinaryDataSnapshot.mjs'
|
|
2
2
|
|
|
3
|
-
const METADATA_MAX_DEPTH =
|
|
3
|
+
const METADATA_MAX_DEPTH = 256
|
|
4
4
|
const METADATA_MAX_ITEMS = 100_000
|
|
5
5
|
const UNBOUNDED_METADATA_BYTES = Number.MAX_SAFE_INTEGER
|
|
6
6
|
const DATE_GET_TIME = Date.prototype.getTime
|