lineadraw 0.9.0 → 0.11.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.
Files changed (36) hide show
  1. package/README.md +154 -20
  2. package/bin/lineadraw.mjs +60 -10
  3. package/dist/bridge.d.ts +1 -0
  4. package/dist/bridge.js +1 -1
  5. package/dist/{chunk-BQCSFVAF.js → chunk-7UFXC7OI.js} +5570 -1174
  6. package/dist/chunk-7UFXC7OI.js.map +1 -0
  7. package/dist/chunk-FDLANLIC.js +1161 -0
  8. package/dist/chunk-FDLANLIC.js.map +1 -0
  9. package/dist/chunk-M3EDRSHW.js +81 -0
  10. package/dist/chunk-M3EDRSHW.js.map +1 -0
  11. package/dist/chunk-MYP7RUYK.js +9 -0
  12. package/dist/chunk-MYP7RUYK.js.map +1 -0
  13. package/dist/{chunk-L2CWZAKW.js → chunk-RNA2RDGL.js} +13 -6
  14. package/dist/chunk-RNA2RDGL.js.map +1 -0
  15. package/dist/dist-56K6CNML.js +188 -0
  16. package/dist/dist-56K6CNML.js.map +1 -0
  17. package/dist/dist-IEN64QSV.js +10 -0
  18. package/dist/dist-IEN64QSV.js.map +1 -0
  19. package/dist/emscripten-module-Q67P5WYC-ITCU5LXL.js +2424 -0
  20. package/dist/emscripten-module-Q67P5WYC-ITCU5LXL.js.map +1 -0
  21. package/dist/{exportDxf-T2RTSTH5.js → exportDxf-6XTXQ4XN.js} +2 -2
  22. package/dist/ffi-SHMEF5YV.js +82 -0
  23. package/dist/ffi-SHMEF5YV.js.map +1 -0
  24. package/dist/module-ES6BEMUI-BJFWC656.js +14 -0
  25. package/dist/module-ES6BEMUI-BJFWC656.js.map +1 -0
  26. package/dist/module-asyncify-2EFITU5U-7F5WZXRA.js +9 -0
  27. package/dist/module-asyncify-2EFITU5U-7F5WZXRA.js.map +1 -0
  28. package/dist/sdk.d.ts +1118 -128
  29. package/dist/sdk.js +742 -620
  30. package/dist/sdk.js.map +1 -1
  31. package/lineadraw.d.ts +666 -44
  32. package/package.json +1 -1
  33. package/templates/README.md +6 -4
  34. package/dist/chunk-BQCSFVAF.js.map +0 -1
  35. package/dist/chunk-L2CWZAKW.js.map +0 -1
  36. /package/dist/{exportDxf-T2RTSTH5.js.map → exportDxf-6XTXQ4XN.js.map} +0 -0
package/dist/sdk.js CHANGED
@@ -1,10 +1,3 @@
1
- import {
2
- BridgeError,
3
- DEFAULT_BRIDGE_PORT,
4
- bridgeFilePath,
5
- connectEditors,
6
- createBridge
7
- } from "./chunk-L2CWZAKW.js";
8
1
  import {
9
2
  ADVANCE_TABLES,
10
3
  ANSI31_BASE_SPACING,
@@ -13,11 +6,11 @@ import {
13
6
  ColorSchema,
14
7
  DEFAULT_DIM_STYLE,
15
8
  DEFAULT_TEXT_STYLE,
9
+ DimStyleOverrideSchema,
16
10
  DimStyleSchema,
17
11
  EXTRACTED_EXTRAS,
18
12
  EXTRACTED_RANGES,
19
13
  FONTS,
20
- HEX_COLOR,
21
14
  HatchPatternSchema,
22
15
  LineTypeSchema,
23
16
  MAX_HATCH_SEGMENTS,
@@ -25,23 +18,36 @@ import {
25
18
  PX_TO_MM,
26
19
  REAL_FACES,
27
20
  REFERENCE_FORMATS,
21
+ TextStyleOverrideSchema,
28
22
  TextStyleSchema,
29
23
  Vec2Schema,
30
24
  aciToHex,
25
+ acquireInputTable,
31
26
  add,
32
27
  angle,
28
+ angleInArc,
33
29
  approximateTextMeasurer,
30
+ arcEndPoint,
31
+ arcFrom3Points,
32
+ arcMidPoint,
33
+ arcStartPoint,
34
+ arcSweep,
34
35
  arcToBeziers,
36
+ arrowLineStart,
35
37
  assertKnownStyleOverrideKeys,
36
38
  blockDefinitionExists,
37
39
  blockPivot,
38
40
  blockScriptId,
41
+ bulgeFrom3Points,
42
+ bulgeToArc,
39
43
  bytesToDataUrl,
44
+ checkNamedInputs,
40
45
  cleanRichText,
41
46
  cleanText,
42
47
  closestPointOnSegment,
43
48
  computeHatchLines,
44
49
  computePatternGeometry,
50
+ curveTolerance,
45
51
  dataUrlBytes,
46
52
  decodeTextEscapes,
47
53
  dimFontFace,
@@ -50,17 +56,21 @@ import {
50
56
  dimensionRawToLabels,
51
57
  dimensionRawToOffset,
52
58
  dist,
59
+ distToArc,
60
+ documentBlocks,
53
61
  documentObjectResolver,
54
62
  dot,
55
- dryRunBlockInputs,
63
+ dryRunBlockPlacement,
56
64
  dryRunObjectResolver,
57
65
  effectiveTextFont,
58
66
  ellipseArcToBeziers,
59
67
  embeddedAssetId,
68
+ emitLoopPath,
60
69
  ensureScriptTranspiler,
61
70
  estimatePatternSegments,
62
71
  executeScriptModule,
63
72
  expandWithBlockDependents,
73
+ findBlockScript,
64
74
  fontFace,
65
75
  fontFromDxfStyle,
66
76
  fromAngle,
@@ -71,16 +81,30 @@ import {
71
81
  getHatchPattern,
72
82
  getLineType,
73
83
  hashAsset,
84
+ hatchArea,
85
+ hatchLoop,
74
86
  hatchPatternKey,
75
87
  hexToRgb,
88
+ hostedClosure,
76
89
  imagePixelSize,
90
+ inputLabel,
91
+ inputPoints,
92
+ inputRefs,
93
+ interpolateClosed,
94
+ interpolateFitPoints,
77
95
  invalidateBlockCache,
96
+ isRequiredInput,
97
+ isValidNurbs,
78
98
  len,
79
99
  lineSegments,
80
100
  lineTypeKey,
81
101
  lineWeightToMm,
82
102
  linearAxis,
83
103
  listBlockMeta,
104
+ loopCorners,
105
+ loopCurves,
106
+ loopPolygon,
107
+ mapInputValues,
84
108
  matApply,
85
109
  matIdentity,
86
110
  matMultiply,
@@ -89,13 +113,19 @@ import {
89
113
  mirrorObject,
90
114
  mtextFontFlags,
91
115
  mtextWidthFactor,
92
- norm,
93
116
  normAngle,
94
117
  normalizeSpans,
118
+ nurbsToBeziers,
119
+ onSandboxReset,
120
+ paintArrowhead,
121
+ paramMismatch,
95
122
  parseFontFace,
123
+ parseInputTable,
96
124
  parseParamsExport,
97
125
  perp,
126
+ pointOnArc,
98
127
  realFace,
128
+ releaseScriptModule,
99
129
  remapBlockInputRefs,
100
130
  renderDashes,
101
131
  resolveDocumentObject,
@@ -105,12 +135,17 @@ import {
105
135
  rgbToHex,
106
136
  richTextBlock,
107
137
  rotateObject,
138
+ sampleNurbs,
108
139
  scale,
109
140
  scaleObjectUniform,
141
+ scanScriptId,
110
142
  scriptTranspilerReady,
143
+ selectionFits,
111
144
  signedArcSweep,
145
+ splineThrough,
112
146
  sub,
113
147
  tableLayout,
148
+ tessellateArc,
114
149
  testBlockDefinition,
115
150
  textArrowSize,
116
151
  textArrowType,
@@ -121,108 +156,26 @@ import {
121
156
  textStyleFor,
122
157
  textWidth,
123
158
  translateObject,
124
- valueParams
125
- } from "./chunk-BQCSFVAF.js";
159
+ valueParams,
160
+ verticesToLoop,
161
+ withEmptyLists
162
+ } from "./chunk-7UFXC7OI.js";
126
163
  import {
127
164
  embeddedPdfFonts
128
165
  } from "./chunk-L4FNH35X.js";
166
+ import {
167
+ BridgeError,
168
+ DEFAULT_BRIDGE_PORT,
169
+ bridgeFilePath,
170
+ connectEditors,
171
+ createBridge
172
+ } from "./chunk-RNA2RDGL.js";
129
173
  import "./chunk-PZ5AY32C.js";
130
174
 
131
175
  // src/document.ts
132
176
  import { readFile, writeFile } from "fs/promises";
133
177
  import { dirname, isAbsolute, resolve } from "path";
134
178
 
135
- // ../core/src/geometry/arcs.ts
136
- var arcSweep = (a) => {
137
- const s = normAngle(a.endAngle - a.startAngle);
138
- return s === 0 ? Math.PI * 2 : s;
139
- };
140
- var pointOnArc = (a, angle2) => ({
141
- x: a.center.x + Math.cos(angle2) * a.radius,
142
- y: a.center.y + Math.sin(angle2) * a.radius
143
- });
144
- var arcStartPoint = (a) => pointOnArc(a, a.startAngle);
145
- var arcEndPoint = (a) => pointOnArc(a, a.endAngle);
146
- var arcMidPoint = (a) => pointOnArc(a, a.startAngle + arcSweep(a) / 2);
147
- var angleInArc = (a, angle2) => normAngle(angle2 - a.startAngle) <= arcSweep(a) + 1e-12;
148
- var bulgeToArc = (a, b, bulge) => {
149
- if (Math.abs(bulge) < 1e-12) return null;
150
- const chord = dist(a, b);
151
- if (chord < 1e-12) return null;
152
- const sweep = 4 * Math.atan(bulge);
153
- const radius = Math.abs(chord / (2 * Math.sin(sweep / 2)));
154
- const m = mid(a, b);
155
- const dir = scale(sub(b, a), 1 / chord);
156
- const h = chord / 2 / Math.tan(sweep / 2);
157
- const center = add(m, scale(perp(dir), h));
158
- const angA = angle(sub(a, center));
159
- const angB = angle(sub(b, center));
160
- return bulge > 0 ? { center, radius, startAngle: angA, endAngle: angB } : { center, radius, startAngle: angB, endAngle: angA };
161
- };
162
- var bulgeFrom3Points = (a, m, b) => {
163
- let turn = angle(sub(b, m)) - angle(sub(m, a));
164
- while (turn > Math.PI) turn -= 2 * Math.PI;
165
- while (turn < -Math.PI) turn += 2 * Math.PI;
166
- return Math.tan(turn / 2);
167
- };
168
- var circleFrom3Points = (p1, p2, p3) => {
169
- const ax = p1.x;
170
- const ay = p1.y;
171
- const bx = p2.x;
172
- const by = p2.y;
173
- const cx = p3.x;
174
- const cy = p3.y;
175
- const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
176
- if (Math.abs(d) < 1e-12) return null;
177
- const a2 = ax * ax + ay * ay;
178
- const b2 = bx * bx + by * by;
179
- const c2 = cx * cx + cy * cy;
180
- const center = {
181
- x: (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / d,
182
- y: (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / d
183
- };
184
- return { center, radius: dist(center, p1) };
185
- };
186
- var arcFrom3Points = (start, on, end) => {
187
- const c = circleFrom3Points(start, on, end);
188
- if (!c) return null;
189
- const angS = angle(sub(start, c.center));
190
- const angO = angle(sub(on, c.center));
191
- const angE = angle(sub(end, c.center));
192
- const ccw = {
193
- center: c.center,
194
- radius: c.radius,
195
- startAngle: angS,
196
- endAngle: angE
197
- };
198
- if (angleInArc(ccw, angO)) return ccw;
199
- return {
200
- center: c.center,
201
- radius: c.radius,
202
- startAngle: angE,
203
- endAngle: angS
204
- };
205
- };
206
- var closestPointOnArc = (a, p) => {
207
- const v = sub(p, a.center);
208
- const ang = angle(v);
209
- if (angleInArc(a, ang)) return pointOnArc(a, ang);
210
- const s = arcStartPoint(a);
211
- const e = arcEndPoint(a);
212
- return dist(p, s) <= dist(p, e) ? s : e;
213
- };
214
- var distToArc = (a, p) => dist(p, closestPointOnArc(a, p));
215
- var tessellateArc = (a, chordTolerance = 0.1) => {
216
- const sweep = arcSweep(a);
217
- const maxStep = a.radius <= chordTolerance ? sweep : 2 * Math.acos(Math.max(-1, 1 - chordTolerance / a.radius));
218
- const n2 = Math.max(2, Math.ceil(sweep / Math.max(maxStep, 1e-3)));
219
- const pts = [];
220
- for (let i = 0; i <= n2; i++) {
221
- pts.push(pointOnArc(a, a.startAngle + sweep * i / n2));
222
- }
223
- return pts;
224
- };
225
-
226
179
  // ../core/src/geometry/ellipse.ts
227
180
  var ellipseSweep = (e) => {
228
181
  const s = normAngle(e.endAngle - e.startAngle);
@@ -340,12 +293,6 @@ var chainLength = (points, closed) => {
340
293
  return sum;
341
294
  };
342
295
  var polylineArea = (points) => polygonArea(verticesToPolygon(points));
343
- var hatchArea = (loops) => {
344
- const areas = loops.map((l) => polygonArea(verticesToPolygon(l.points)));
345
- if (areas.length === 0) return 0;
346
- const holes = areas.slice(1).reduce((a, b) => a + b, 0);
347
- return Math.max(areas[0] - holes, 0);
348
- };
349
296
 
350
297
  // ../core/src/model/document.ts
351
298
  import { z as z2 } from "zod";
@@ -353,11 +300,13 @@ import { z as z2 } from "zod";
353
300
  // ../core/src/model/paperLayout.ts
354
301
  import { z } from "zod";
355
302
  var SHEET_PRESETS = {
303
+ A5: [148, 210],
356
304
  A4: [210, 297],
357
305
  A3: [297, 420],
358
306
  A2: [420, 594],
359
307
  A1: [594, 841],
360
- A0: [841, 1189]
308
+ A0: [841, 1189],
309
+ Letter: [215.9, 279.4]
361
310
  };
362
311
  var resolveSheetSize = (sheet = "A3", defaultOrientation = "landscape") => {
363
312
  if (typeof sheet !== "string" && "width" in sheet)
@@ -408,7 +357,8 @@ var LayerSchema = z2.object({
408
357
  });
409
358
  var DEFAULT_DOCUMENT_NAME = "Untitled project";
410
359
  var DocumentSettingsSchema = z2.object({
411
- units: z2.enum(["mm", "in"]).default("mm")
360
+ units: z2.enum(["mm", "in"]).default("mm"),
361
+ modelScale: z2.number().positive().default(1)
412
362
  });
413
363
  var LineaDocumentSchema = z2.preprocess(
414
364
  migrateDocument,
@@ -425,7 +375,7 @@ var LineaDocumentSchema = z2.preprocess(
425
375
  /** Document-wide settings (`DocumentSettingsSchema`). `units` is
426
376
  * declarative for now: geometry is always stored in mm and nothing
427
377
  * renders in inches yet. */
428
- settings: DocumentSettingsSchema.default({ units: "mm" }),
378
+ settings: DocumentSettingsSchema.default({ units: "mm", modelScale: 1 }),
429
379
  layers: z2.array(LayerSchema).min(1),
430
380
  currentLayerId: z2.string(),
431
381
  /** Array order is draw order (bottom to top).
@@ -659,10 +609,10 @@ var SvgSink = class {
659
609
  transforms.push(
660
610
  `translate(${n(tp.x)} ${n(-tp.y)}) scale(${n(w)} 1) translate(${n(-tp.x)} ${n(tp.y)})`
661
611
  );
662
- const rot2 = transforms.length > 0 ? ` transform="${transforms.join(" ")}"` : "";
612
+ const rot = transforms.length > 0 ? ` transform="${transforms.join(" ")}"` : "";
663
613
  const alpha = t.opacity !== void 0 && t.opacity < 1 ? ` fill-opacity="${n(t.opacity)}"` : "";
664
614
  this.els.push(
665
- `<text x="${n(tp.x)}" y="${n(-tp.y)}" font-size="${n(t.heightMm / def.capHeight * scale2)}" font-family="${def.cssStack}"${face} fill="${t.color}"${alpha} text-anchor="${anchor}"${rot2}>${escapeXml(t.text)}</text>`
615
+ `<text x="${n(tp.x)}" y="${n(-tp.y)}" font-size="${n(t.heightMm / def.capHeight * scale2)}" font-family="${def.cssStack}"${face} fill="${t.color}"${alpha} text-anchor="${anchor}"${rot}>${escapeXml(t.text)}</text>`
666
616
  );
667
617
  }
668
618
  measureText(text, heightMm, font, widthFactor) {
@@ -677,10 +627,10 @@ var SvgSink = class {
677
627
  });
678
628
  const scale2 = Math.hypot(this.m[0], this.m[1]) || 1;
679
629
  const deg = -i.rotation * 180 / Math.PI;
680
- const rot2 = deg !== 0 ? ` transform="rotate(${n(deg)} ${n(topLeft.x)} ${n(-topLeft.y)})"` : "";
630
+ const rot = deg !== 0 ? ` transform="rotate(${n(deg)} ${n(topLeft.x)} ${n(-topLeft.y)})"` : "";
681
631
  const opacity = i.opacity < 1 ? ` opacity="${n(i.opacity)}"` : "";
682
632
  this.els.push(
683
- `<image x="${n(topLeft.x)}" y="${n(-topLeft.y)}" width="${n(i.widthMm * scale2)}" height="${n(i.heightMm * scale2)}" preserveAspectRatio="none"${opacity}${rot2} href="${i.data}"/>`
633
+ `<image x="${n(topLeft.x)}" y="${n(-topLeft.y)}" width="${n(i.widthMm * scale2)}" height="${n(i.heightMm * scale2)}" preserveAspectRatio="none"${opacity}${rot} href="${i.data}"/>`
684
634
  );
685
635
  }
686
636
  pushTransform(m) {
@@ -813,6 +763,22 @@ var paintEllipse = (obj, sink, env) => {
813
763
  sink.stroke(env.style);
814
764
  };
815
765
 
766
+ // ../core/src/paint/painters/spline.ts
767
+ var paintSpline = (obj, sink, env) => {
768
+ sink.beginPath();
769
+ const pieces = nurbsToBeziers(obj);
770
+ if (pieces && pieces.length > 0) {
771
+ sink.moveTo(pieces[0].start);
772
+ for (const b of pieces) sink.bezierTo(b.c1, b.c2, b.end);
773
+ } else {
774
+ const pts = sampleNurbs(obj, curveTolerance(obj));
775
+ if (pts.length < 2) return;
776
+ sink.moveTo(pts[0]);
777
+ for (let i = 1; i < pts.length; i++) sink.lineTo(pts[i]);
778
+ }
779
+ sink.stroke(env.style);
780
+ };
781
+
816
782
  // ../core/src/paint/painters/hatch.ts
817
783
  var SINGLE_FAMILY = {
818
784
  name: "",
@@ -820,7 +786,7 @@ var SINGLE_FAMILY = {
820
786
  };
821
787
  var paintSolid = (obj, sink, env) => {
822
788
  sink.beginPath();
823
- for (const loop of obj.loops) emitVertexPath(sink, loop.points, true);
789
+ for (const loop of obj.loops) emitLoopPath(sink, loop);
824
790
  sink.fill({ color: env.style.color, opacity: env.style.opacity });
825
791
  };
826
792
  var paintGradient = (obj, fill, sink, env) => {
@@ -829,7 +795,7 @@ var paintGradient = (obj, fill, sink, env) => {
829
795
  let maxX = -Infinity;
830
796
  let maxY = -Infinity;
831
797
  for (const loop of obj.loops)
832
- for (const p of loop.points) {
798
+ for (const p of loopPolygon(loop)) {
833
799
  if (p.x < minX) minX = p.x;
834
800
  if (p.y < minY) minY = p.y;
835
801
  if (p.x > maxX) maxX = p.x;
@@ -852,7 +818,7 @@ var paintGradient = (obj, fill, sink, env) => {
852
818
  to = { x: c.x + dir.x * r, y: c.y + dir.y * r };
853
819
  }
854
820
  sink.beginPath();
855
- for (const loop of obj.loops) emitVertexPath(sink, loop.points, true);
821
+ for (const loop of obj.loops) emitLoopPath(sink, loop);
856
822
  sink.fill({
857
823
  color: fill.color1,
858
824
  opacity: env.style.opacity,
@@ -870,7 +836,7 @@ var paintHatch = (obj, sink, env) => {
870
836
  }
871
837
  let lines = env.cache?.hatchLines.get(obj);
872
838
  if (!lines) {
873
- const rings = obj.loops.map((loop) => verticesToPolygon(loop.points));
839
+ const rings = obj.loops.map((loop) => loopPolygon(loop));
874
840
  const s = obj.fill.kind === "pattern" ? obj.fill.scale * obj.scale : obj.scale;
875
841
  const pat = obj.fill.kind === "pattern" ? resolveHatchPattern(obj.fill.name, env.hatchPatterns) : void 0;
876
842
  const estimate = pat ? estimatePatternSegments(rings, pat, s) : estimatePatternSegments(
@@ -912,50 +878,6 @@ var paintHatch = (obj, sink, env) => {
912
878
  });
913
879
  };
914
880
 
915
- // ../core/src/paint/painters/arrows.ts
916
- var paintArrowhead = (sink, tip, toward, size, color, type = "filled", opacity) => {
917
- if (type === "none") return;
918
- const dir = norm(sub(toward, tip));
919
- if (dir.x === 0 && dir.y === 0) return;
920
- switch (type) {
921
- case "filled": {
922
- const base = add(tip, scale(dir, size));
923
- const half = scale(perp(dir), size / 3);
924
- sink.beginPath();
925
- sink.moveTo(tip);
926
- sink.lineTo(add(base, half));
927
- sink.lineTo(sub(base, half));
928
- sink.closePath();
929
- sink.fill({ color, opacity });
930
- break;
931
- }
932
- case "open": {
933
- const base = add(tip, scale(dir, size));
934
- const half = scale(perp(dir), size / 3);
935
- sink.beginPath();
936
- sink.moveTo(add(base, half));
937
- sink.lineTo(tip);
938
- sink.lineTo(sub(base, half));
939
- sink.stroke({ color, widthMm: Math.min(size / 10, 0.25), opacity });
940
- break;
941
- }
942
- case "tick": {
943
- const diag = scale(norm(add(dir, perp(dir))), size / 2);
944
- sink.beginPath();
945
- sink.moveTo(sub(tip, diag));
946
- sink.lineTo(add(tip, diag));
947
- sink.stroke({ color, widthMm: Math.min(size / 8, 0.35), opacity });
948
- break;
949
- }
950
- case "dot": {
951
- sink.beginPath();
952
- sink.arc(tip, size / 4, 0, Math.PI * 2, true);
953
- sink.fill({ color, opacity });
954
- break;
955
- }
956
- }
957
- };
958
-
959
881
  // ../core/src/paint/painters/text.ts
960
882
  var paintText = (obj, sink, env) => {
961
883
  const { font, widthFactor } = effectiveTextFont(obj, env.textStyles);
@@ -971,18 +893,20 @@ var paintText = (obj, sink, env) => {
971
893
  )
972
894
  };
973
895
  const { color, widthMm, opacity } = env.style;
896
+ const arrowSize = textArrowSize(obj, env.textStyles);
897
+ const arrowType = textArrowType(obj, env.textStyles);
974
898
  for (const line of obj.leaderLines) {
975
899
  sink.beginPath();
976
- sink.moveTo(line[0]);
900
+ sink.moveTo(arrowLineStart(line[0], line[1], arrowSize, arrowType));
977
901
  for (let i = 1; i < line.length; i++) sink.lineTo(line[i]);
978
902
  sink.stroke({ color, widthMm, opacity });
979
903
  paintArrowhead(
980
904
  sink,
981
905
  line[0],
982
906
  line[1],
983
- textArrowSize(obj, env.textStyles),
907
+ arrowSize,
984
908
  color,
985
- textArrowType(obj, env.textStyles),
909
+ arrowType,
986
910
  opacity
987
911
  );
988
912
  }
@@ -1110,15 +1034,21 @@ var paintDimension = (obj, sink, env) => {
1110
1034
  dashMm: env.style.dashMm,
1111
1035
  opacity: env.style.opacity
1112
1036
  };
1037
+ const trim = (p, other) => {
1038
+ const arrow = geom.arrows.find(
1039
+ (ar) => dist(ar.tip, p) < 1e-9 && dot(sub(ar.toward, ar.tip), sub(other, p)) > 0
1040
+ );
1041
+ return arrow ? arrowLineStart(p, other, arrow.size, geom.arrowType) : p;
1042
+ };
1113
1043
  sink.beginPath();
1114
- for (const seg of [
1115
- ...geom.extensionLines,
1116
- ...geom.dimLines,
1117
- ...geom.leaders
1118
- ]) {
1044
+ for (const seg of geom.extensionLines) {
1119
1045
  sink.moveTo(seg.a);
1120
1046
  sink.lineTo(seg.b);
1121
1047
  }
1048
+ for (const seg of [...geom.dimLines, ...geom.leaders]) {
1049
+ sink.moveTo(trim(seg.a, seg.b));
1050
+ sink.lineTo(trim(seg.b, seg.a));
1051
+ }
1122
1052
  if (geom.dimArc) {
1123
1053
  const arc = geom.dimArc;
1124
1054
  sink.moveTo(add(arc.center, scale(fromAngle(arc.startAngle), arc.radius)));
@@ -1233,7 +1163,7 @@ var paintChildren = (children, sink, env, parentLayerId) => {
1233
1163
  // Same width-proportional dash rule as resolveStyle.
1234
1164
  dashMm: scaleDash(
1235
1165
  dashForLineType(child.lineType, env.lineTypes),
1236
- (child.lineWidth ?? env.style.widthMm) * (child.lineTypeScale ?? 1)
1166
+ (child.lineWidth ?? env.style.widthMm) * (child.lineTypeScale ?? 1) * (env.lineTypeScale ?? 1)
1237
1167
  ) ?? env.style.dashMm,
1238
1168
  opacity: child.opacity ?? env.style.opacity
1239
1169
  },
@@ -1243,6 +1173,7 @@ var paintChildren = (children, sink, env, parentLayerId) => {
1243
1173
  lineTypes: env.lineTypes,
1244
1174
  hatchPatterns: env.hatchPatterns,
1245
1175
  blockDefinitions: env.blockDefinitions,
1176
+ lineTypeScale: env.lineTypeScale,
1246
1177
  resolveObject: env.resolveObject,
1247
1178
  assets: env.assets,
1248
1179
  // A reference child (linked / embedded file) loads like any other.
@@ -1451,6 +1382,17 @@ var buildEllipse = (obj) => {
1451
1382
  ]
1452
1383
  };
1453
1384
  };
1385
+ var buildSpline = (obj) => {
1386
+ const pts = sampleNurbs(obj, curveTolerance(obj) / 10);
1387
+ const segments = [];
1388
+ for (let i = 1; i < pts.length; i++)
1389
+ segments.push({ a: pts[i - 1], b: pts[i], facet: true });
1390
+ const points = obj.closed || pts.length < 2 ? [] : [
1391
+ { p: pts[0], type: "endpoint" },
1392
+ { p: pts[pts.length - 1], type: "endpoint" }
1393
+ ];
1394
+ return { bounds: boundsOfPoints(pts), points, segments, arcs: [] };
1395
+ };
1454
1396
  var buildHatch = (obj) => {
1455
1397
  let bounds = emptyBounds();
1456
1398
  const points = [];
@@ -1458,12 +1400,11 @@ var buildHatch = (obj) => {
1458
1400
  const arcs = [];
1459
1401
  const rings = [];
1460
1402
  for (const loop of obj.loops) {
1461
- const curves = verticesToCurves(loop.points, true);
1403
+ const curves = loopCurves(loop);
1462
1404
  segments.push(...curves.segments);
1463
1405
  arcs.push(...curves.arcs);
1464
- for (const v of loop.points)
1465
- points.push({ p: { x: v.x, y: v.y }, type: "endpoint" });
1466
- rings.push(verticesToPolygon(loop.points));
1406
+ for (const p of loopCorners(loop)) points.push({ p, type: "endpoint" });
1407
+ rings.push(loopPolygon(loop));
1467
1408
  bounds = unionBounds(bounds, curveBounds(curves.segments, curves.arcs));
1468
1409
  }
1469
1410
  return { bounds, points, segments, arcs, rings };
@@ -1649,6 +1590,8 @@ var buildEntry = (obj, ctx) => {
1649
1590
  }
1650
1591
  case "ellipse":
1651
1592
  return buildEllipse(obj);
1593
+ case "spline":
1594
+ return buildSpline(obj);
1652
1595
  case "hatch":
1653
1596
  return buildHatch(obj);
1654
1597
  case "text":
@@ -1682,10 +1625,8 @@ var buildEntry = (obj, ctx) => {
1682
1625
  };
1683
1626
  }
1684
1627
  case "block": {
1685
- const inputPoints = obj.inputs.filter(
1686
- (v) => typeof v !== "string"
1687
- );
1688
- const points = (inputPoints.length > 0 ? inputPoints : [blockPivot(obj)]).map((p) => ({ p, type: "endpoint" }));
1628
+ const pts = inputPoints(obj.inputs);
1629
+ const points = (pts.length > 0 ? pts : inputRefs(obj.inputs).length > 0 ? [] : [blockPivot(obj)]).map((p) => ({ p, type: "endpoint" }));
1689
1630
  const segments = [];
1690
1631
  const arcs = [];
1691
1632
  const ellipses = [];
@@ -1814,7 +1755,8 @@ var referenceEntry = (obj, ctx) => {
1814
1755
  segments.push({
1815
1756
  a: applyMat(m, kept[0]),
1816
1757
  b: applyMat(m, kept[1]),
1817
- chord: seg.chord
1758
+ chord: seg.chord,
1759
+ facet: seg.facet
1818
1760
  });
1819
1761
  }
1820
1762
  for (const a of e.arcs) {
@@ -2050,174 +1992,6 @@ var mergeStyles = (existing, incoming, same, opts) => {
2050
1992
  return { styles, idByName, created };
2051
1993
  };
2052
1994
 
2053
- // ../core/src/geometry/nurbs.ts
2054
- var nurbsDomain = (c) => {
2055
- const n2 = c.controlPoints.length;
2056
- return [c.knots[c.degree], c.knots[n2]];
2057
- };
2058
- var findSpan = (c, u) => {
2059
- const n2 = c.controlPoints.length;
2060
- const p = c.degree;
2061
- if (u >= c.knots[n2]) return n2 - 1;
2062
- if (u <= c.knots[p]) return p;
2063
- let lo = p;
2064
- let hi = n2;
2065
- let mid2 = lo + hi >> 1;
2066
- while (u < c.knots[mid2] || u >= c.knots[mid2 + 1]) {
2067
- if (u < c.knots[mid2]) hi = mid2;
2068
- else lo = mid2;
2069
- mid2 = lo + hi >> 1;
2070
- }
2071
- return mid2;
2072
- };
2073
- var evaluateNurbs = (c, u) => {
2074
- const p = c.degree;
2075
- const k = findSpan(c, u);
2076
- const d = [];
2077
- for (let j = 0; j <= p; j++) {
2078
- const i = k - p + j;
2079
- const cp = c.controlPoints[i];
2080
- const w2 = c.weights?.[i] ?? 1;
2081
- d.push([cp.x * w2, cp.y * w2, w2]);
2082
- }
2083
- for (let r = 1; r <= p; r++)
2084
- for (let j = p; j >= r; j--) {
2085
- const i = k - p + j;
2086
- const den = c.knots[i + p - r + 1] - c.knots[i];
2087
- const a = den === 0 ? 0 : (u - c.knots[i]) / den;
2088
- d[j] = [
2089
- (1 - a) * d[j - 1][0] + a * d[j][0],
2090
- (1 - a) * d[j - 1][1] + a * d[j][1],
2091
- (1 - a) * d[j - 1][2] + a * d[j][2]
2092
- ];
2093
- }
2094
- const [x, y, w] = d[p];
2095
- return w === 0 ? { x, y } : { x: x / w, y: y / w };
2096
- };
2097
- var isValidNurbs = (c) => c.degree >= 1 && c.controlPoints.length > c.degree && c.knots.length === c.controlPoints.length + c.degree + 1 && (c.weights === void 0 || c.weights.length === c.controlPoints.length) && c.knots.every((v, i) => i === 0 || v >= c.knots[i - 1]);
2098
- var sampleNurbs = (c, tolerance, maxDepth = 10) => {
2099
- const [u0, u1] = nurbsDomain(c);
2100
- const out = [evaluateNurbs(c, u0)];
2101
- const push = (q) => {
2102
- const last = out[out.length - 1];
2103
- if (Math.hypot(q.x - last.x, q.y - last.y) > 1e-9) out.push(q);
2104
- };
2105
- const refine = (a, pa, b, pb, depth) => {
2106
- const m = (a + b) / 2;
2107
- const pm = evaluateNurbs(c, m);
2108
- const chord = { x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 };
2109
- const dev = Math.hypot(pm.x - chord.x, pm.y - chord.y);
2110
- if (depth >= maxDepth || dev <= tolerance) {
2111
- if (dev > tolerance / 4) push(pm);
2112
- push(pb);
2113
- return;
2114
- }
2115
- refine(a, pa, m, pm, depth + 1);
2116
- refine(m, pm, b, pb, depth + 1);
2117
- };
2118
- const spans = [];
2119
- for (let i = c.degree; i < c.controlPoints.length; i++) {
2120
- const a = Math.max(c.knots[i], u0);
2121
- const b = Math.min(c.knots[i + 1], u1);
2122
- if (b > a) spans.push(a, b);
2123
- }
2124
- for (let i = 0; i < spans.length; i += 2) {
2125
- const a = spans[i];
2126
- const b = spans[i + 1];
2127
- const pa = evaluateNurbs(c, a);
2128
- const pb = evaluateNurbs(c, b);
2129
- const m = (a + b) / 2;
2130
- const pm = evaluateNurbs(c, m);
2131
- refine(a, pa, m, pm, 1);
2132
- refine(m, pm, b, pb, 1);
2133
- }
2134
- return dropCollinear(out, tolerance / 10);
2135
- };
2136
- var dropCollinear = (pts, eps) => {
2137
- if (pts.length < 3) return pts;
2138
- const out = [pts[0]];
2139
- for (let i = 1; i < pts.length - 1; i++) {
2140
- const a = out[out.length - 1];
2141
- const b = pts[i + 1];
2142
- const q = pts[i];
2143
- const abx = b.x - a.x;
2144
- const aby = b.y - a.y;
2145
- const len2 = abx * abx + aby * aby;
2146
- const t = len2 === 0 ? 0 : Math.max(
2147
- 0,
2148
- Math.min(1, ((q.x - a.x) * abx + (q.y - a.y) * aby) / len2)
2149
- );
2150
- const dist2 = Math.hypot(q.x - (a.x + t * abx), q.y - (a.y + t * aby));
2151
- if (dist2 > eps) out.push(q);
2152
- }
2153
- out.push(pts[pts.length - 1]);
2154
- return out;
2155
- };
2156
- var interpolateFitPoints = (points, degree = 3) => {
2157
- const n2 = points.length;
2158
- if (n2 < 2) return null;
2159
- const p = Math.min(degree, n2 - 1);
2160
- const u = [0];
2161
- let total2 = 0;
2162
- for (let i = 1; i < n2; i++) {
2163
- total2 += Math.hypot(
2164
- points[i].x - points[i - 1].x,
2165
- points[i].y - points[i - 1].y
2166
- );
2167
- u.push(total2);
2168
- }
2169
- if (total2 === 0) return null;
2170
- for (let i = 0; i < n2; i++) u[i] /= total2;
2171
- const knots = new Array(p + 1).fill(0);
2172
- for (let j = 1; j <= n2 - p - 1; j++) {
2173
- let s = 0;
2174
- for (let i = j; i <= j + p - 1; i++) s += u[i];
2175
- knots.push(s / p);
2176
- }
2177
- knots.push(...new Array(p + 1).fill(1));
2178
- const basisRow = (ui) => {
2179
- const row = new Array(n2).fill(0);
2180
- const probe = {
2181
- degree: p,
2182
- controlPoints: points.map(() => ({ x: 0, y: 0 })),
2183
- knots
2184
- };
2185
- for (let j = 0; j < n2; j++) {
2186
- probe.controlPoints = points.map((_, k) => ({
2187
- x: k === j ? 1 : 0,
2188
- y: 0
2189
- }));
2190
- row[j] = evaluateNurbs(probe, ui).x;
2191
- }
2192
- return row;
2193
- };
2194
- const A = u.map((ui) => basisRow(ui));
2195
- const bx = points.map((q) => q.x);
2196
- const by = points.map((q) => q.y);
2197
- for (let col = 0; col < n2; col++) {
2198
- let pivot = col;
2199
- for (let r = col + 1; r < n2; r++)
2200
- if (Math.abs(A[r][col]) > Math.abs(A[pivot][col])) pivot = r;
2201
- if (Math.abs(A[pivot][col]) < 1e-12) return null;
2202
- [A[col], A[pivot]] = [A[pivot], A[col]];
2203
- [bx[col], bx[pivot]] = [bx[pivot], bx[col]];
2204
- [by[col], by[pivot]] = [by[pivot], by[col]];
2205
- for (let r = 0; r < n2; r++) {
2206
- if (r === col) continue;
2207
- const k = A[r][col] / A[col][col];
2208
- if (k === 0) continue;
2209
- for (let c = col; c < n2; c++) A[r][c] -= k * A[col][c];
2210
- bx[r] -= k * bx[col];
2211
- by[r] -= k * by[col];
2212
- }
2213
- }
2214
- const controlPoints = points.map((_, i) => ({
2215
- x: bx[i] / A[i][i],
2216
- y: by[i] / A[i][i]
2217
- }));
2218
- return { degree: p, controlPoints, knots };
2219
- };
2220
-
2221
1995
  // ../core/src/geometry/band.ts
2222
1996
  var left = (d) => ({ x: -d.y, y: d.x });
2223
1997
  var unit = (a, b) => {
@@ -3191,7 +2965,6 @@ var unitFactor = (insunits) => {
3191
2965
  }
3192
2966
  };
3193
2967
  var IMPORT_LINE_WIDTH_MM = 0.25;
3194
- var SPLINE_TOLERANCE_MM = 0.02;
3195
2968
  var TEXT_TYPES = /* @__PURE__ */ new Set(["TEXT", "MTEXT", "ATTRIB", "ATTDEF"]);
3196
2969
  var DIM_TYPES = /* @__PURE__ */ new Set(["DIMENSION", "LEADER"]);
3197
2970
  var scanTags = (lines) => {
@@ -3401,48 +3174,55 @@ var baseFor = (e, ctx) => {
3401
3174
  hidden: false
3402
3175
  };
3403
3176
  };
3404
- var hatchLoopPoints = (path, f, mirrored) => {
3405
- const points = hatchLoopPointsOcs(path, f);
3406
- return mirrored ? points.map((p) => ({ x: -p.x, y: p.y, bulge: -p.bulge })) : points;
3407
- };
3408
- var HATCH_ELLIPSE_TOLERANCE_MM = 0.1;
3409
- var hatchLoopPointsOcs = (path, f) => {
3410
- if (path.isPolyline) {
3411
- return (path.vertices ?? []).map((v) => ({
3412
- x: v.x * f,
3413
- y: v.y * f,
3414
- bulge: v.bulge ?? 0
3415
- }));
3416
- }
3417
- const points = [];
3418
- const push = (x, y, bulge = 0) => points.push({ x: x * f, y: y * f, bulge });
3177
+ var hatchLoopEdges = (path, f, mirrored) => {
3178
+ const edges = hatchLoopEdgesOcs(path, f);
3179
+ if (!mirrored || edges.length === 0) return edges;
3180
+ const reflected = mirrorObject(
3181
+ {
3182
+ type: "hatch",
3183
+ id: "",
3184
+ layerId: "",
3185
+ loops: [{ edges }],
3186
+ fill: { kind: "solid" },
3187
+ scale: 1
3188
+ },
3189
+ { x: 0, y: 0 },
3190
+ { x: 0, y: 1 }
3191
+ );
3192
+ return reflected.loops[0].edges;
3193
+ };
3194
+ var hatchLoopEdgesOcs = (path, f) => {
3195
+ const pt2 = (p) => ({ x: p.x * f, y: p.y * f });
3196
+ if (path.isPolyline)
3197
+ return verticesToLoop(
3198
+ (path.vertices ?? []).map((v) => ({
3199
+ ...pt2(v),
3200
+ bulge: v.bulge ?? 0
3201
+ }))
3202
+ ).edges;
3203
+ const edges = [];
3419
3204
  for (const edge of path.edges ?? []) {
3420
3205
  switch (edge.kind) {
3421
3206
  case 1: {
3422
- const p = edge.points[0];
3423
- if (p) push(p.x, p.y);
3207
+ const a = edge.points[0];
3208
+ const b = edge.end;
3209
+ if (a && b) edges.push({ kind: "line", a: pt2(a), b: pt2(b) });
3424
3210
  break;
3425
3211
  }
3426
3212
  case 2: {
3427
3213
  const c = edge.points[0];
3428
3214
  if (!c || !(edge.r40 > 0)) break;
3429
- const ccw = edge.ccw !== false;
3430
3215
  const a0 = (edge.startAngle ?? 0) * DEG2RAD;
3431
- let a1 = (edge.endAngle ?? 360) * DEG2RAD;
3432
- if (a1 <= a0 + 1e-12) a1 += Math.PI * 2;
3433
- const sweep = Math.min(a1 - a0, Math.PI * 2);
3434
- const sign = ccw ? 1 : -1;
3435
- const start = sign * a0;
3436
- const pieces = sweep > Math.PI + 1e-9 ? 2 : 1;
3437
- const bulge = sign * Math.tan(sweep / pieces / 4);
3438
- for (let i = 0; i < pieces; i++) {
3439
- const ang = start + sign * sweep * i / pieces;
3440
- push(
3441
- c.x + edge.r40 * Math.cos(ang),
3442
- c.y + edge.r40 * Math.sin(ang),
3443
- bulge
3444
- );
3445
- }
3216
+ const a1 = (edge.endAngle ?? 360) * DEG2RAD;
3217
+ const ccw = edge.ccw !== false;
3218
+ const full = Math.abs(a1 - a0) >= Math.PI * 2 - 1e-9;
3219
+ edges.push({
3220
+ kind: "arc",
3221
+ center: pt2(c),
3222
+ radius: edge.r40 * f,
3223
+ startAngle: ccw ? a0 : -a1,
3224
+ endAngle: full ? ccw ? a0 : -a1 : ccw ? a1 : -a0
3225
+ });
3446
3226
  break;
3447
3227
  }
3448
3228
  case 3: {
@@ -3452,29 +3232,21 @@ var hatchLoopPointsOcs = (path, f) => {
3452
3232
  const ratio = edge.r40 > 0 ? edge.r40 : 1;
3453
3233
  const ccw = edge.ccw !== false;
3454
3234
  const a0 = (edge.startAngle ?? 0) * DEG2RAD;
3455
- let a1 = (edge.endAngle ?? 360) * DEG2RAD;
3456
- if (a1 <= a0 + 1e-12) a1 += Math.PI * 2;
3457
- const sweep = Math.min(a1 - a0, Math.PI * 2);
3458
- const sign = ccw ? 1 : -1;
3459
- const L = Math.hypot(major.x, major.y);
3460
- const tol = HATCH_ELLIPSE_TOLERANCE_MM / f;
3461
- const maxStep = L <= tol ? sweep : 2 * Math.acos(Math.max(-1, 1 - tol / L));
3462
- const n2 = Math.max(8, Math.ceil(sweep / Math.max(maxStep, 1e-3)));
3463
- for (let i = 0; i < n2; i++) {
3464
- const theta = sign * (a0 + sweep * i / n2);
3465
- const t = Math.atan2(Math.sin(theta) / ratio, Math.cos(theta));
3466
- push(
3467
- c.x + major.x * Math.cos(t) - major.y * ratio * Math.sin(t),
3468
- c.y + major.y * Math.cos(t) + major.x * ratio * Math.sin(t)
3469
- );
3470
- }
3235
+ const a1 = (edge.endAngle ?? 360) * DEG2RAD;
3236
+ const full = Math.abs(a1 - a0) >= Math.PI * 2 - 1e-9;
3237
+ const param = (theta) => Math.atan2(Math.sin(theta) / ratio, Math.cos(theta));
3238
+ edges.push({
3239
+ kind: "ellipse",
3240
+ center: pt2(c),
3241
+ majorAxis: pt2(major),
3242
+ ratio: Math.min(1, ratio),
3243
+ startAngle: full ? 0 : param(ccw ? a0 : -a1),
3244
+ endAngle: full ? Math.PI * 2 : param(ccw ? a1 : -a0)
3245
+ });
3471
3246
  break;
3472
3247
  }
3473
3248
  case 4: {
3474
- const control = (edge.points ?? []).map((p) => ({
3475
- x: p.x,
3476
- y: p.y
3477
- }));
3249
+ const control = (edge.points ?? []).map(pt2);
3478
3250
  const weights = edge.weights ?? [];
3479
3251
  const curve = {
3480
3252
  degree: edge.degree ?? 3,
@@ -3482,36 +3254,24 @@ var hatchLoopPointsOcs = (path, f) => {
3482
3254
  knots: edge.knots ?? [],
3483
3255
  ...edge.rational && weights.length === control.length ? { weights } : {}
3484
3256
  };
3485
- const fit = (edge.fitPoints ?? []).map((p) => ({
3486
- x: p.x,
3487
- y: p.y
3488
- }));
3489
- const fitted = isValidNurbs(curve) ? null : interpolateFitPoints(fit);
3490
- const sampled = isValidNurbs(curve) ? sampleNurbs(curve, SPLINE_TOLERANCE_MM / f) : fitted ? sampleNurbs(fitted, SPLINE_TOLERANCE_MM / f) : fit.length >= 2 ? fit : control;
3491
- for (const p of sampled.slice(0, -1)) push(p.x, p.y);
3257
+ const fit = (edge.fitPoints ?? []).map(pt2);
3258
+ const exact = isValidNurbs(curve) ? curve : interpolateFitPoints(fit);
3259
+ if (exact) {
3260
+ edges.push({
3261
+ kind: "spline",
3262
+ ...exact,
3263
+ ...fit.length >= 2 ? { fitPoints: fit } : {}
3264
+ });
3265
+ break;
3266
+ }
3267
+ const poly = fit.length >= 2 ? fit : control;
3268
+ for (let i = 1; i < poly.length; i++)
3269
+ edges.push({ kind: "line", a: poly[i - 1], b: poly[i] });
3492
3270
  break;
3493
3271
  }
3494
3272
  }
3495
3273
  }
3496
- return points.filter(
3497
- (p, i) => i === 0 || Math.abs(p.x - points[i - 1].x) > 1e-9 || Math.abs(p.y - points[i - 1].y) > 1e-9 || points[i - 1].bulge !== 0
3498
- );
3499
- };
3500
- var atLeastThreeVertices = (points) => {
3501
- if (points.length !== 2 || points.every((p) => p.bulge === 0)) return points;
3502
- const out = [];
3503
- for (let i = 0; i < 2; i++) {
3504
- const a = points[i];
3505
- const b = points[1 - i];
3506
- out.push(a);
3507
- const arc = a.bulge === 0 ? null : bulgeToArc(a, b, a.bulge);
3508
- if (!arc) continue;
3509
- const half = Math.tan(Math.atan(a.bulge) / 2);
3510
- const mid2 = arcMidPoint(arc);
3511
- out[out.length - 1] = { ...a, bulge: half };
3512
- out.push({ x: mid2.x, y: mid2.y, bulge: half });
3513
- }
3514
- return out;
3274
+ return edges;
3515
3275
  };
3516
3276
  var convertHatch = (e, ctx) => {
3517
3277
  const anyE = e;
@@ -3519,8 +3279,8 @@ var convertHatch = (e, ctx) => {
3519
3279
  const mirrored = ctx.tags.mirroredHandles.has(String(anyE.handle));
3520
3280
  const flip = mirrored ? -1 : 1;
3521
3281
  const loops = (anyE.boundaryPaths ?? []).map((path) => ({
3522
- points: atLeastThreeVertices(hatchLoopPoints(path, ctx.f, mirrored))
3523
- })).filter((loop) => loop.points.length >= 3);
3282
+ edges: hatchLoopEdges(path, ctx.f, mirrored)
3283
+ })).filter((loop) => loop.edges.length > 0);
3524
3284
  if (loops.length === 0) return [];
3525
3285
  let fill;
3526
3286
  const patternMatch = typeof anyE.patternName === "string" ? getHatchPattern(anyE.patternName) : void 0;
@@ -3984,9 +3744,9 @@ var convertEntityInner = (e, dxf, ctx) => {
3984
3744
  return [
3985
3745
  {
3986
3746
  type: "hatch",
3987
- loops: loops.map((l) => ({
3988
- points: l.map((p) => ({ ...p, bulge: 0 }))
3989
- })),
3747
+ loops: loops.map(
3748
+ (l) => verticesToLoop(l.map((p) => ({ ...p, bulge: 0 })))
3749
+ ),
3990
3750
  fill: { kind: "solid" },
3991
3751
  scale: 1,
3992
3752
  ...base
@@ -4116,32 +3876,46 @@ var convertEntityInner = (e, dxf, ctx) => {
4116
3876
  case "SPLINE": {
4117
3877
  const control = anyE.controlPoints ?? [];
4118
3878
  const weights = ctx.tags.splineWeightsByHandle.get(String(anyE.handle));
3879
+ const fit = (anyE.fitPoints ?? []).map((p) => ({
3880
+ x: p.x * f,
3881
+ y: p.y * f
3882
+ }));
4119
3883
  const curve = {
4120
3884
  degree: anyE.degreeOfSplineCurve ?? 3,
4121
- controlPoints: control.map((p) => ({ x: p.x, y: p.y })),
3885
+ controlPoints: control.map((p) => ({ x: p.x * f, y: p.y * f })),
4122
3886
  knots: anyE.knotValues ?? [],
4123
3887
  ...weights && weights.length === control.length ? { weights } : {}
4124
3888
  };
4125
- const fit = anyE.fitPoints ?? [];
4126
- const fitted = isValidNurbs(curve) ? null : interpolateFitPoints(fit.map((p) => ({ x: p.x, y: p.y })));
4127
- let points;
4128
- if (isValidNurbs(curve)) {
4129
- points = sampleNurbs(curve, SPLINE_TOLERANCE_MM / f);
4130
- bump(ctx.degraded, "SPLINE\u2192polyline");
4131
- } else if (fitted) {
4132
- points = sampleNurbs(fitted, SPLINE_TOLERANCE_MM / f);
4133
- bump(ctx.degraded, "SPLINE\u2192polyline (fit points)");
4134
- } else {
4135
- const raw = fit.length >= 2 ? fit : control;
4136
- if (raw.length < 2) return [];
4137
- points = raw.map((p) => ({ x: p.x, y: p.y }));
4138
- bump(ctx.degraded, "SPLINE\u2192polyline (control points)");
4139
- }
4140
- if (points.length < 2) return [];
3889
+ const tangent = (t) => t && Math.hypot(t.x, t.y) > 1e-12 ? { x: t.x, y: t.y } : void 0;
3890
+ const startTangent = tangent(anyE.startTangent);
3891
+ const endTangent = tangent(anyE.endTangent);
3892
+ const closed = anyE.closed === true;
3893
+ const fitted = isValidNurbs(curve) ? curve : closed ? interpolateClosed(fit) : interpolateFitPoints(fit, 3, {
3894
+ start: startTangent,
3895
+ end: endTangent
3896
+ });
3897
+ if (fitted && isValidNurbs(fitted))
3898
+ return [
3899
+ {
3900
+ type: "spline",
3901
+ degree: fitted.degree,
3902
+ controlPoints: fitted.controlPoints,
3903
+ knots: fitted.knots,
3904
+ ...fitted.weights ? { weights: fitted.weights } : {},
3905
+ ...fit.length >= 2 ? { fitPoints: fit } : {},
3906
+ ...fit.length >= 2 && !closed && startTangent ? { startTangent } : {},
3907
+ ...fit.length >= 2 && !closed && endTangent ? { endTangent } : {},
3908
+ closed,
3909
+ ...base
3910
+ }
3911
+ ];
3912
+ const raw = fit.length >= 2 ? fit : control.map((p) => ({ x: p.x * f, y: p.y * f }));
3913
+ if (raw.length < 2) return [];
3914
+ bump(ctx.degraded, "SPLINE\u2192polyline (control points)");
4141
3915
  return [
4142
3916
  {
4143
3917
  type: "polyline",
4144
- points: points.map((p) => ({ x: p.x * f, y: p.y * f, bulge: 0 })),
3918
+ points: raw.map((p) => ({ x: p.x, y: p.y, bulge: 0 })),
4145
3919
  closed: anyE.closed === true,
4146
3920
  ...base
4147
3921
  }
@@ -4156,7 +3930,9 @@ var convertEntityInner = (e, dxf, ctx) => {
4156
3930
  return [
4157
3931
  {
4158
3932
  type: "hatch",
4159
- loops: [{ points: points.map((p) => ({ ...pt(p, f), bulge: 0 })) }],
3933
+ loops: [
3934
+ verticesToLoop(points.map((p) => ({ ...pt(p, f), bulge: 0 })))
3935
+ ],
4160
3936
  fill: { kind: "solid" },
4161
3937
  scale: 1,
4162
3938
  ...base
@@ -4172,9 +3948,7 @@ var convertEntityInner = (e, dxf, ctx) => {
4172
3948
  {
4173
3949
  type: "hatch",
4174
3950
  loops: [
4175
- {
4176
- points: ordered.map((p) => ({ ...ocs(p), bulge: 0 }))
4177
- }
3951
+ verticesToLoop(ordered.map((p) => ({ ...ocs(p), bulge: 0 })))
4178
3952
  ],
4179
3953
  fill: { kind: "solid" },
4180
3954
  scale: 1,
@@ -4386,7 +4160,7 @@ var convertInsertInner = (e, dxf, ctx, depth, inheritedColor) => {
4386
4160
  const sx = anyE.xScale ?? 1;
4387
4161
  const sy = (anyE.yScale ?? 1) * (mirrored ? -1 : 1);
4388
4162
  const rot0 = (anyE.rotation ?? 0) * DEG2RAD;
4389
- const rot2 = mirrored ? Math.PI - rot0 : rot0;
4163
+ const rot = mirrored ? Math.PI - rot0 : rot0;
4390
4164
  const pos = anyE.position ?? { x: 0, y: 0 };
4391
4165
  const insert = mirrored ? { x: -pos.x * ctx.f, y: pos.y * ctx.f } : pt(pos, ctx.f);
4392
4166
  if (e.type === "INSERT" && depth === 0 && !mirrored && sx > 0 && sy > 0 && Math.abs(sx - sy) < 1e-9) {
@@ -4397,7 +4171,7 @@ var convertInsertInner = (e, dxf, ctx, depth, inheritedColor) => {
4397
4171
  type: "block",
4398
4172
  definitionId: def.id,
4399
4173
  inputs: [insert],
4400
- rotation: rot2,
4174
+ rotation: rot,
4401
4175
  scale: sx,
4402
4176
  params: {},
4403
4177
  ...baseFor(e, ctx)
@@ -4406,8 +4180,8 @@ var convertInsertInner = (e, dxf, ctx, depth, inheritedColor) => {
4406
4180
  }
4407
4181
  if (e.type === "INSERT" && depth === 0) bump(ctx.degraded, "INSERT\u2192exploded");
4408
4182
  const base = pt(block.position ?? { x: 0, y: 0 }, ctx.f);
4409
- const cos = Math.cos(rot2);
4410
- const sin = Math.sin(rot2);
4183
+ const cos = Math.cos(rot);
4184
+ const sin = Math.sin(rot);
4411
4185
  const reflect = sx * sy < 0;
4412
4186
  const meanScale = Math.sqrt(Math.abs(sx * sy)) || 1e-6;
4413
4187
  const lin = (v) => ({
@@ -4460,7 +4234,7 @@ var convertInsertInner = (e, dxf, ctx, depth, inheritedColor) => {
4460
4234
  return t;
4461
4235
  }
4462
4236
  const majorLen = Math.max(ax, ay);
4463
- const ang = rot2 + (ax >= ay ? 0 : Math.PI / 2);
4237
+ const ang = rot + (ax >= ay ? 0 : Math.PI / 2);
4464
4238
  return {
4465
4239
  type: "ellipse",
4466
4240
  center,
@@ -4508,7 +4282,18 @@ var convertInsertInner = (e, dxf, ctx, depth, inheritedColor) => {
4508
4282
  return t;
4509
4283
  case "hatch": {
4510
4284
  t.loops = t.loops.map((loop) => ({
4511
- points: mapBulged(loop.points, true)
4285
+ edges: loop.edges.flatMap((edge) => {
4286
+ const { kind, ...geometry } = edge;
4287
+ const moved = transform({
4288
+ ...geometry,
4289
+ type: kind,
4290
+ layerId: t.layerId
4291
+ });
4292
+ if (!moved || moved.type !== kind) return [];
4293
+ const out = { kind };
4294
+ for (const key of Object.keys(geometry)) out[key] = moved[key];
4295
+ return [out];
4296
+ })
4512
4297
  }));
4513
4298
  if (t.fill.kind === "pattern" || t.fill.kind === "lines") {
4514
4299
  const d = lin({
@@ -4525,9 +4310,15 @@ var convertInsertInner = (e, dxf, ctx, depth, inheritedColor) => {
4525
4310
  }
4526
4311
  return t;
4527
4312
  }
4313
+ case "spline":
4314
+ t.controlPoints = t.controlPoints.map(map);
4315
+ if (t.fitPoints) t.fitPoints = t.fitPoints.map(map);
4316
+ if (t.startTangent) t.startTangent = lin(t.startTangent);
4317
+ if (t.endTangent) t.endTangent = lin(t.endTangent);
4318
+ return t;
4528
4319
  case "text":
4529
4320
  t.position = map(t.position);
4530
- t.rotation += rot2;
4321
+ t.rotation += rot;
4531
4322
  if (meanScale !== 1) {
4532
4323
  const style = t.styleId && ctx.textStyleById.get(t.styleId) || ctx.defaultTextStyle;
4533
4324
  const o = t.styleOverride ?? {};
@@ -4983,7 +4774,10 @@ var contentFromDocument = (doc, refId, bounds = emptyBounds()) => {
4983
4774
  (o) => o.type === "block" ? {
4984
4775
  ...o,
4985
4776
  id: prefix + o.id,
4986
- inputs: o.inputs.map((v) => typeof v === "string" ? prefix + v : v)
4777
+ inputs: mapInputValues(
4778
+ o.inputs,
4779
+ (v) => typeof v === "string" ? prefix + v : v
4780
+ )
4987
4781
  } : { ...o, id: prefix + o.id }
4988
4782
  );
4989
4783
  const byId = new Map(objects.map((o) => [o.id, o]));
@@ -5134,7 +4928,7 @@ var FALLBACK_STYLE = {
5134
4928
  color: "#e8e8e8",
5135
4929
  widthMm: 0.25
5136
4930
  };
5137
- var resolveStyle = (obj, layers, lineTypes) => {
4931
+ var resolveStyle = (obj, layers, lineTypes, lineTypeScale = 1) => {
5138
4932
  const layer = layers.find((l) => l.id === obj.layerId);
5139
4933
  const widthMm = obj.lineWidth ?? layer?.lineWidth ?? FALLBACK_STYLE.widthMm;
5140
4934
  return {
@@ -5142,10 +4936,11 @@ var resolveStyle = (obj, layers, lineTypes) => {
5142
4936
  widthMm,
5143
4937
  // Dash patterns are defined per 1 mm of line width (ISO 128 style —
5144
4938
  // pattern legibility tracks stroke weight), so the resolved lineweight
5145
- // scales them; lineTypeScale (CELTSCALE) multiplies on top.
4939
+ // scales them; lineTypeScale (CELTSCALE) and the model scale multiply
4940
+ // on top.
5146
4941
  dashMm: scaleDash(
5147
4942
  dashForLineType(obj.lineType ?? layer?.lineType, lineTypes),
5148
- widthMm * (obj.lineTypeScale ?? 1)
4943
+ widthMm * (obj.lineTypeScale ?? 1) * lineTypeScale
5149
4944
  ),
5150
4945
  opacity: obj.opacity ?? layer?.opacity
5151
4946
  };
@@ -5162,7 +4957,7 @@ var rebaseSink = (sink, origin) => {
5162
4957
  lineTo: (p) => sink.lineTo(at(p)),
5163
4958
  bezierTo: (c1, c2, end) => sink.bezierTo(at(c1), at(c2), at(end)),
5164
4959
  arc: (center, r, a0, a1, ccw) => sink.arc(at(center), r, a0, a1, ccw),
5165
- ellipse: (center, rx, ry, rot2, a0, a1) => sink.ellipse(at(center), rx, ry, rot2, a0, a1),
4960
+ ellipse: (center, rx, ry, rot, a0, a1) => sink.ellipse(at(center), rx, ry, rot, a0, a1),
5166
4961
  closePath: () => sink.closePath(),
5167
4962
  stroke: (s) => sink.stroke(s),
5168
4963
  fill: (f) => sink.fill(
@@ -5250,7 +5045,8 @@ var paintReference = (obj, sink, env) => {
5250
5045
  const own = resolveStyle(
5251
5046
  child,
5252
5047
  content.layers,
5253
- content.lineTypes
5048
+ content.lineTypes,
5049
+ env.lineTypeScale
5254
5050
  );
5255
5051
  paintObject(child, out, {
5256
5052
  // A highlight paints the whole reference in one style.
@@ -5319,6 +5115,7 @@ var painters = {
5319
5115
  circle: paintCircle,
5320
5116
  arc: paintArc,
5321
5117
  ellipse: paintEllipse,
5118
+ spline: paintSpline,
5322
5119
  hatch: paintHatch,
5323
5120
  text: paintText,
5324
5121
  dimension: paintDimension,
@@ -5344,7 +5141,7 @@ var renderBlockThumbnailSvg = async (script, {
5344
5141
  } = {}) => {
5345
5142
  await ensureScriptTranspiler();
5346
5143
  const defId = getBlockMeta(script).id;
5347
- const inputs = await dryRunBlockInputs(script);
5144
+ const { inputs, params } = await dryRunBlockPlacement(script);
5348
5145
  const defs = [script];
5349
5146
  const present = /* @__PURE__ */ new Set([defId]);
5350
5147
  for (const extra of definitions) {
@@ -5360,7 +5157,7 @@ var renderBlockThumbnailSvg = async (script, {
5360
5157
  definitionId: defId,
5361
5158
  rotation: 0,
5362
5159
  scale: 1,
5363
- params: {},
5160
+ params,
5364
5161
  inputs
5365
5162
  };
5366
5163
  const env = {
@@ -5526,8 +5323,23 @@ var OptionalMetaSchema = z3.object({
5526
5323
  version: z3.string().optional(),
5527
5324
  authors: z3.array(z3.string()).optional(),
5528
5325
  tags: z3.array(z3.string()).optional(),
5529
- inputs: z3.array(z3.string().min(1)).optional()
5326
+ requiresEditor: z3.boolean().optional()
5530
5327
  });
5328
+ var isInputTable = (inputs) => inputs !== void 0 && inputs.length > 0 && typeof inputs[0] !== "string";
5329
+ var parseCommandInputs = (raw, params) => {
5330
+ if (raw === void 0) return void 0;
5331
+ if (Array.isArray(raw) && raw.every((l) => typeof l === "string")) {
5332
+ if (raw.some((l) => l === ""))
5333
+ throw new Error("Invalid command metadata \u2014 inputs: empty label");
5334
+ return raw;
5335
+ }
5336
+ const table = parseInputTable(raw, params);
5337
+ if (table?.some((d) => d.type === "host"))
5338
+ throw new Error(
5339
+ "Invalid command metadata \u2014 inputs: `host` is a block input (a command has no frame to live in)"
5340
+ );
5341
+ return table;
5342
+ };
5531
5343
  var extractCommandMeta = (mod) => {
5532
5344
  if (typeof mod.id !== "string" || mod.id.trim() === "")
5533
5345
  throw new Error("Command script must export a non-empty string `id`");
@@ -5541,14 +5353,17 @@ var extractCommandMeta = (mod) => {
5541
5353
  );
5542
5354
  }
5543
5355
  const params = parseParamsExport(mod.params);
5356
+ const inputs = parseCommandInputs(mod.inputs, params);
5544
5357
  return {
5545
5358
  id: mod.id,
5546
5359
  name: mod.name,
5547
5360
  ...parsed.data,
5361
+ ...inputs ? { inputs } : {},
5548
5362
  ...params.length > 0 ? { params } : {}
5549
5363
  };
5550
5364
  };
5551
5365
  var compileCache = /* @__PURE__ */ new Map();
5366
+ onSandboxReset(() => compileCache.clear());
5552
5367
  var COMPILE_CACHE_MAX = 100;
5553
5368
  var compileCommand = (script) => {
5554
5369
  const cached = compileCache.get(script);
@@ -5557,16 +5372,24 @@ var compileCommand = (script) => {
5557
5372
  compileCache.set(script, cached);
5558
5373
  return cached;
5559
5374
  }
5560
- const mod = executeScriptModule(script);
5561
- const meta = extractCommandMeta(mod);
5562
- const run = mod.run;
5563
- if (typeof run !== "function")
5564
- throw new Error("Command script must export a `run` function");
5375
+ const mod = executeScriptModule(script, "command");
5376
+ let meta;
5377
+ try {
5378
+ meta = extractCommandMeta(mod);
5379
+ if (typeof mod.run !== "function")
5380
+ throw new Error("Command script must export a `run` function");
5381
+ } catch (e) {
5382
+ releaseScriptModule(mod);
5383
+ throw e;
5384
+ }
5565
5385
  if (compileCache.size >= COMPILE_CACHE_MAX) {
5566
5386
  const oldest = compileCache.keys().next().value;
5567
- if (oldest !== void 0) compileCache.delete(oldest);
5387
+ if (oldest !== void 0) {
5388
+ releaseScriptModule(compileCache.get(oldest).mod);
5389
+ compileCache.delete(oldest);
5390
+ }
5568
5391
  }
5569
- const compiled = { meta, run };
5392
+ const compiled = { mod, meta, run: mod.run };
5570
5393
  compileCache.set(script, compiled);
5571
5394
  return compiled;
5572
5395
  };
@@ -5583,16 +5406,11 @@ var getCommandMetaResult = (script) => {
5583
5406
  };
5584
5407
  var listCommandMeta = (scripts) => scripts.map(getCommandMetaResult);
5585
5408
  var getCommandRun = (script) => compileCommand(script).run;
5586
- var ID_EXPORT_RE = /export\s+const\s+id\s*=\s*["']([^"']+)["']/;
5587
5409
  var commandScriptId = (script) => {
5588
5410
  try {
5589
5411
  return compileCommand(script).meta.id;
5590
5412
  } catch {
5591
- const named = ID_EXPORT_RE.exec(script);
5592
- if (named) return named[1];
5593
- const call = script.search(/defineCommand\s*\(|export\s+default\s*\{/);
5594
- if (call === -1) return void 0;
5595
- return /\bid\s*:\s*["']([^"']+)["']/.exec(script.slice(call))?.[1];
5413
+ return scanScriptId(script, /defineCommand\s*\(|export\s+default\s*\{/);
5596
5414
  }
5597
5415
  };
5598
5416
  var mergeCommandModules = (existing, incoming) => mergeScriptModules(existing, incoming, {
@@ -6763,12 +6581,19 @@ var renderModelToSvg = (model, opts = {}) => {
6763
6581
  const origin = far ? { x: bbox.minX, y: bbox.minY } : { x: 0, y: 0 };
6764
6582
  const out = rebaseSink(sink, origin);
6765
6583
  for (const obj of toPaint) {
6766
- const style = resolveStyle(obj, model.layers, model.lineTypes);
6584
+ const lineTypeScale = opts.lineTypeScale ?? 1;
6585
+ const style = resolveStyle(
6586
+ obj,
6587
+ model.layers,
6588
+ model.lineTypes,
6589
+ lineTypeScale
6590
+ );
6767
6591
  sink.beginObject(obj.id, layerName.get(obj.layerId));
6768
6592
  paintObject(obj, out, {
6769
6593
  style: { ...style, color: mapColor(style.color) },
6770
6594
  ...model,
6771
- mapColor
6595
+ mapColor,
6596
+ lineTypeScale
6772
6597
  });
6773
6598
  sink.endObject();
6774
6599
  }
@@ -6779,7 +6604,12 @@ var renderModelToSvg = (model, opts = {}) => {
6779
6604
  maxX: bbox.maxX - origin.x,
6780
6605
  maxY: bbox.maxY - origin.y
6781
6606
  },
6782
- opts.padding
6607
+ // Default: 2% of the frame (at least 5 mm), so strokes and arrowheads
6608
+ // at the extents aren't cut by the image edge.
6609
+ opts.padding ?? Math.max(
6610
+ 5,
6611
+ 0.02 * Math.max(bbox.maxX - bbox.minX, bbox.maxY - bbox.minY)
6612
+ )
6783
6613
  );
6784
6614
  };
6785
6615
  var FAR_MM = 1e7;
@@ -8171,7 +8001,9 @@ var invalidationIds = (op) => {
8171
8001
  switch (op.type) {
8172
8002
  case "add-objects":
8173
8003
  case "update-objects":
8174
- return op.objects.map((o) => o.id);
8004
+ return op.objects.flatMap(
8005
+ (o) => o.type === "block" ? [o.id, ...inputRefs(o.inputs)] : [o.id]
8006
+ );
8175
8007
  case "remove-objects":
8176
8008
  return op.ids;
8177
8009
  case "set-current-layer":
@@ -8351,17 +8183,188 @@ var DocumentKernel = class {
8351
8183
 
8352
8184
  // ../core/src/drawing/localDrawing.ts
8353
8185
  import { z as z6 } from "zod";
8186
+
8187
+ // ../core/src/drawing/inputChecks.ts
8188
+ var DrawingError = class extends Error {
8189
+ code;
8190
+ issues;
8191
+ constructor(code, message, issues) {
8192
+ super(message);
8193
+ this.code = code;
8194
+ if (issues) this.issues = issues;
8195
+ }
8196
+ };
8197
+ var MAX_COORDINATE_MM = 1e9;
8198
+ var STYLE_OVERRIDE_KEYS = {
8199
+ text: TextStyleOverrideSchema.shape,
8200
+ table: TextStyleOverrideSchema.shape,
8201
+ dimension: DimStyleOverrideSchema.shape
8202
+ };
8203
+ var hintFor = (type, key) => {
8204
+ if (type && key in (STYLE_OVERRIDE_KEYS[type] ?? {}))
8205
+ return ` \u2014 style keys go in styleOverride: { ${key}: \u2026 }`;
8206
+ if (type === "layer" && key === "hidden") return " \u2014 use visible: false";
8207
+ if (type === "dimension" && (key === "upVector" || key === "linePoint"))
8208
+ return " \u2014 `offset` is the signed distance of the dimension line";
8209
+ if (type === "dimension" && key === "textOverride")
8210
+ return " \u2014 use labels[i].content (with {value} for the measurement)";
8211
+ return "";
8212
+ };
8213
+ var assertNoUnknownKeys = (sent, parsed, context, type) => {
8214
+ const unknown = Object.keys(sent).filter(
8215
+ (k) => sent[k] !== void 0 && !(k in parsed)
8216
+ );
8217
+ if (unknown.length > 0)
8218
+ throw new DrawingError(
8219
+ "unknownField",
8220
+ `${context}: unknown field(s) ${unknown.map((k) => `"${k}"${hintFor(type, k)}`).join(", ")}`
8221
+ );
8222
+ };
8223
+ var assertSaneNumbers = (value, context) => {
8224
+ const walk = (v, path) => {
8225
+ if (typeof v === "number") {
8226
+ if (Math.abs(v) > MAX_COORDINATE_MM)
8227
+ throw new DrawingError(
8228
+ "outOfRange",
8229
+ `${context}: ${path || "value"} = ${v} is out of range (|value| \u2264 ${MAX_COORDINATE_MM} mm)`
8230
+ );
8231
+ } else if (Array.isArray(v)) v.forEach((x, i) => walk(x, `${path}[${i}]`));
8232
+ else if (v !== null && typeof v === "object")
8233
+ for (const [k, x] of Object.entries(v))
8234
+ walk(x, path ? `${path}.${k}` : k);
8235
+ };
8236
+ walk(value, "");
8237
+ };
8238
+ var knownLineType = (name, sources) => resolveLineType(name, sources.lineTypes) !== void 0 || getLineType(name) !== void 0;
8239
+ var knownFont = (font) => (font.includes(";") ? font.slice(0, font.indexOf(";")) : font) in FONTS;
8240
+ var fontList = Object.keys(FONTS).join(", ");
8241
+ var assertLineType = (name, sources, context) => {
8242
+ if (name !== void 0 && !knownLineType(name, sources))
8243
+ throw new DrawingError(
8244
+ "unknownLineType",
8245
+ `${context}: unknown line type "${name}" (the built-in catalogue or linea_list_line_types)`
8246
+ );
8247
+ };
8248
+ var assertFont = (font, context) => {
8249
+ if (typeof font === "string" && !knownFont(font))
8250
+ throw new DrawingError(
8251
+ "unknownFont",
8252
+ `${context}: unknown font "${font}" (one of ${fontList})`
8253
+ );
8254
+ };
8255
+ var assertObjectFields = (obj, fields, sources, context) => {
8256
+ const o = obj;
8257
+ if (fields.has("lineType"))
8258
+ assertLineType(o.lineType, sources, context);
8259
+ if (fields.has("font")) assertFont(o.font, context);
8260
+ if (fields.has("styleOverride"))
8261
+ assertFont(
8262
+ o.styleOverride?.font,
8263
+ context
8264
+ );
8265
+ if (obj.type === "hatch" && fields.has("fill") && obj.fill.kind === "pattern") {
8266
+ if (!resolveHatchPattern(obj.fill.name, sources.hatchPatterns))
8267
+ throw new DrawingError(
8268
+ "unknownHatchPattern",
8269
+ `${context}: unknown hatch pattern "${obj.fill.name}" (the built-in catalogue or linea_list_hatch_patterns)`
8270
+ );
8271
+ }
8272
+ if (obj.type === "text" && (fields.has("spans") || fields.has("content")))
8273
+ for (const [i, s] of (obj.spans ?? []).entries()) {
8274
+ if (s.end > obj.content.length || s.start >= s.end)
8275
+ throw new DrawingError(
8276
+ "invalidSpan",
8277
+ `${context}: spans[${i}] ${s.start}\u2013${s.end} must satisfy start < end \u2264 ${obj.content.length} (the content's length)`
8278
+ );
8279
+ assertFont(s.font, `${context}: spans[${i}]`);
8280
+ }
8281
+ if (obj.type === "block" && fields.has("params"))
8282
+ assertBlockParams(obj, sources, context);
8283
+ };
8284
+ var assertBlockParams = (obj, sources, context) => {
8285
+ let script;
8286
+ try {
8287
+ script = findBlockScript(sources.blockDefinitions, obj.definitionId);
8288
+ } catch {
8289
+ return;
8290
+ }
8291
+ if (!script) return;
8292
+ const meta = getBlockMetaResult(script);
8293
+ if (!meta.ok) return;
8294
+ const defs = valueParams(meta.meta.params);
8295
+ for (const [name, value] of Object.entries(obj.params ?? {})) {
8296
+ const def = defs.find((d) => d.name === name);
8297
+ if (!def)
8298
+ throw new DrawingError(
8299
+ "invalidParam",
8300
+ `${context}: block "${obj.definitionId}" has no parameter "${name}"${defs.length ? ` (declares ${defs.map((d) => d.name).join(", ")})` : ""}`
8301
+ );
8302
+ const mismatch = paramMismatch(def, value);
8303
+ if (mismatch)
8304
+ throw new DrawingError(
8305
+ "invalidParam",
8306
+ `${context}: parameter "${name}" \u2014 ${mismatch}`
8307
+ );
8308
+ }
8309
+ };
8310
+
8311
+ // ../core/src/drawing/localDrawing.ts
8354
8312
  var toVec = (p) => Vec2Schema.parse(p);
8313
+ var refuseRejected = (report, kind) => {
8314
+ if (report.rejected.length === 0) return;
8315
+ const lines = report.rejected.map(
8316
+ (r, i) => `${i + 1}) ${scanScriptId(r.script, /define(Block|Command)\(|export\s+default\s*\{/) ?? "(no id)"}: ${r.error}`
8317
+ );
8318
+ throw Object.assign(
8319
+ new Error(
8320
+ `${report.rejected.length} ${kind} source(s) rejected, so none were added:
8321
+ ${lines.join("\n")}`
8322
+ ),
8323
+ { code: "moduleRejected" }
8324
+ );
8325
+ };
8326
+ var checkedStyle = (schema, full, sent, context) => {
8327
+ let style;
8328
+ try {
8329
+ style = schema.parse(full);
8330
+ } catch (e) {
8331
+ throw wrapZod(e, context);
8332
+ }
8333
+ assertNoUnknownKeys(sent, style, context);
8334
+ if ("font" in sent) assertFont(sent.font, context);
8335
+ return style;
8336
+ };
8337
+ var assertDataUrl = (dataUrl) => {
8338
+ const m = /^data:([\w.+-]+\/[\w.+-]+)(;[^,]*)?;base64,([A-Za-z0-9+/]*={0,2})$/.exec(
8339
+ dataUrl
8340
+ );
8341
+ if (!m)
8342
+ throw new DrawingError(
8343
+ "invalidAsset",
8344
+ "An asset must be a base64 data URL (data:<type>;base64,...)"
8345
+ );
8346
+ const bytes = Math.floor(m[3].length * 3 / 4) - (m[3].match(/=/g)?.length ?? 0);
8347
+ if (bytes < 16)
8348
+ throw new DrawingError(
8349
+ "invalidAsset",
8350
+ `The ${m[1]} data URL holds only ${bytes} bytes, not a real file`
8351
+ );
8352
+ };
8353
+ var MAX_COPIES = 1e4;
8355
8354
  var wrapZod = (e, context) => {
8356
8355
  if (!(e instanceof z6.ZodError))
8357
8356
  return e instanceof Error ? e : new Error(String(e));
8358
- return new Error(`${context}: ${z6.prettifyError(e)}`);
8357
+ return new DrawingError(
8358
+ "validation",
8359
+ `${context}: ${z6.prettifyError(e)}`,
8360
+ e.issues
8361
+ );
8359
8362
  };
8360
8363
  var PDF_PAGE_GAP_MM = 50;
8361
8364
  var selectPdfLayouts = (layouts, opts, fallback) => {
8362
8365
  const byRef = (ref) => {
8363
8366
  const l = layouts.find((x) => x.name === ref || x.id === ref);
8364
- if (!l) throw new Error(`Unknown layout "${ref}"`);
8367
+ if (!l) throw new DrawingError("unknownLayout", `Unknown layout "${ref}"`);
8365
8368
  return l;
8366
8369
  };
8367
8370
  let selected = opts.layouts !== void 0 ? opts.layouts.map(byRef) : layouts.length > 0 ? [...layouts] : [fallback()];
@@ -8468,11 +8471,6 @@ var kernelHooks = (doc) => {
8468
8471
  revision: () => rev
8469
8472
  };
8470
8473
  };
8471
- var withVisible = (layer) => {
8472
- const { hidden, ...rest } = layer;
8473
- if (hidden === void 0) return layer;
8474
- return { ...rest, visible: !hidden };
8475
- };
8476
8474
  var LocalDrawing = class {
8477
8475
  hooks;
8478
8476
  /** Schema-version migration applied on open; null when none was needed. */
@@ -8528,7 +8526,7 @@ var LocalDrawing = class {
8528
8526
  findObject(id) {
8529
8527
  const doc = this.state;
8530
8528
  const obj = resolveDocumentObject(doc, id);
8531
- if (!obj) throw new Error(`Unknown object "${id}"`);
8529
+ if (!obj) throw new DrawingError("unknownObject", `Unknown object "${id}"`);
8532
8530
  const layout = doc.layouts.find((l) => l.objects.includes(obj));
8533
8531
  return { obj, space: layout ? { layoutId: layout.id } : "model" };
8534
8532
  }
@@ -8610,8 +8608,11 @@ var LocalDrawing = class {
8610
8608
  (l) => l.name.toLowerCase() === layer.name.toLowerCase()
8611
8609
  );
8612
8610
  if (clash)
8613
- throw new Error(`addLayer(): layer "${clash.name}" already exists`);
8614
- const full = LayerSchema.parse(withVisible(layer));
8611
+ throw new DrawingError(
8612
+ "duplicateName",
8613
+ `Layer "${clash.name}" already exists`
8614
+ );
8615
+ const full = this.checkedLayer(layer, layer, "add layer");
8615
8616
  this.applyForward({ type: "add-layer", layer: full });
8616
8617
  return full.id;
8617
8618
  }
@@ -8619,7 +8620,8 @@ var LocalDrawing = class {
8619
8620
  const l = this.state.layers.find(
8620
8621
  (x) => x.id === nameOrId || x.name === nameOrId
8621
8622
  );
8622
- if (!l) throw new Error(`Unknown layer "${nameOrId}"`);
8623
+ if (!l)
8624
+ throw new DrawingError("unknownLayer", `Unknown layer "${nameOrId}"`);
8623
8625
  return l;
8624
8626
  }
8625
8627
  /** Patch a layer (rename, color, lineWidth, visible, locked) by name or id. */
@@ -8630,13 +8632,36 @@ var LocalDrawing = class {
8630
8632
  (l) => l.name.toLowerCase() === patch.name.toLowerCase()
8631
8633
  );
8632
8634
  if (clash)
8633
- throw new Error(`updateLayer(): layer "${clash.name}" already exists`);
8635
+ throw new DrawingError(
8636
+ "duplicateName",
8637
+ `Layer "${clash.name}" already exists`
8638
+ );
8634
8639
  }
8635
8640
  this.applyForward({
8636
8641
  type: "update-layer",
8637
- layer: LayerSchema.parse({ ...cur, ...withVisible(patch), id: cur.id })
8642
+ layer: this.checkedLayer(
8643
+ { ...cur, ...patch, id: cur.id },
8644
+ patch,
8645
+ `update layer "${cur.name}"`
8646
+ )
8638
8647
  });
8639
8648
  }
8649
+ /** A layer parsed from `full`, refusing an empty name, unknown fields
8650
+ * and an unknown line type among what the caller `sent`. */
8651
+ checkedLayer(full, sent, context) {
8652
+ if (typeof sent.name === "string" && sent.name.trim() === "")
8653
+ throw new DrawingError("validation", `${context}: the name is empty`);
8654
+ let layer;
8655
+ try {
8656
+ layer = LayerSchema.parse(full);
8657
+ } catch (e) {
8658
+ throw wrapZod(e, context);
8659
+ }
8660
+ assertNoUnknownKeys(sent, layer, context, "layer");
8661
+ if ("lineType" in sent)
8662
+ assertLineType(layer.lineType, this.nameSources, context);
8663
+ return layer;
8664
+ }
8640
8665
  /** Remove an EMPTY, non-current layer (the kernel refuses otherwise). */
8641
8666
  removeLayer(nameOrId) {
8642
8667
  this.applyForward({
@@ -8671,6 +8696,7 @@ var LocalDrawing = class {
8671
8696
  * Content-keyed: the same data reuses one entry.
8672
8697
  */
8673
8698
  addAsset(dataUrl) {
8699
+ assertDataUrl(dataUrl);
8674
8700
  const id = hashAsset(dataUrl);
8675
8701
  if (this.state.assets[id] !== dataUrl)
8676
8702
  this.applyForward({ type: "set-asset", id, dataUrl });
@@ -8778,25 +8804,32 @@ var LocalDrawing = class {
8778
8804
  // (case-insensitive) on add and rename.
8779
8805
  resolveStyle(styles, nameOrId, what) {
8780
8806
  const s = styles.find((x) => x.id === nameOrId || x.name === nameOrId);
8781
- if (!s) throw new Error(`Unknown ${what} "${nameOrId}"`);
8807
+ if (!s)
8808
+ throw new DrawingError("unknownStyle", `Unknown ${what} "${nameOrId}"`);
8782
8809
  return s;
8783
8810
  }
8784
- assertStyleName(styles, name, context, except) {
8811
+ assertStyleName(styles, name, except) {
8785
8812
  const clash = styles.find(
8786
8813
  (s) => s.id !== except && s.name.toLowerCase() === name.toLowerCase()
8787
8814
  );
8788
8815
  if (clash)
8789
- throw new Error(`${context}: style "${clash.name}" already exists`);
8816
+ throw new DrawingError(
8817
+ "duplicateName",
8818
+ `Style "${clash.name}" already exists`
8819
+ );
8790
8820
  }
8791
8821
  /** Layouts are addressed by name everywhere (`layout` on the object
8792
8822
  * tools, remove, reorder, render) — a duplicate would silently resolve to
8793
8823
  * the first match. */
8794
- assertLayoutName(name, context, except) {
8824
+ assertLayoutName(name, except) {
8795
8825
  const clash = this.state.layouts.find(
8796
8826
  (l) => l.id !== except && l.name.toLowerCase() === name.toLowerCase()
8797
8827
  );
8798
8828
  if (clash)
8799
- throw new Error(`${context}: layout "${clash.name}" already exists`);
8829
+ throw new DrawingError(
8830
+ "duplicateName",
8831
+ `Layout "${clash.name}" already exists`
8832
+ );
8800
8833
  }
8801
8834
  /** Objects reference styles by id, but callers often pass the NAME:
8802
8835
  * resolve it (text/table → text styles, dimension → dim styles). An
@@ -8808,7 +8841,8 @@ var LocalDrawing = class {
8808
8841
  if (!styles) return {};
8809
8842
  const s = styles.find((x) => x.id === obj.styleId) ?? styles.find((x) => x.name === obj.styleId);
8810
8843
  if (!s)
8811
- throw new Error(
8844
+ throw new DrawingError(
8845
+ "unknownStyle",
8812
8846
  `${context}: unknown ${obj.type === "dimension" ? "dim" : "text"} style "${obj.styleId}"`
8813
8847
  );
8814
8848
  return { styleId: s.id };
@@ -8822,8 +8856,8 @@ var LocalDrawing = class {
8822
8856
  /** Create a text style (unique name; unset fields take the defaults);
8823
8857
  * returns its id. */
8824
8858
  addTextStyle(props) {
8825
- this.assertStyleName(this.state.textStyles, props.name, "addTextStyle()");
8826
- const style = TextStyleSchema.parse(props);
8859
+ this.assertStyleName(this.state.textStyles, props.name);
8860
+ const style = checkedStyle(TextStyleSchema, props, props, "add text style");
8827
8861
  this.applyForward({ type: "add-text-style", style });
8828
8862
  return style.id;
8829
8863
  }
@@ -8834,15 +8868,15 @@ var LocalDrawing = class {
8834
8868
  "text style"
8835
8869
  );
8836
8870
  if (patch.name !== void 0)
8837
- this.assertStyleName(
8838
- this.state.textStyles,
8839
- patch.name,
8840
- "updateTextStyle()",
8841
- cur.id
8842
- );
8871
+ this.assertStyleName(this.state.textStyles, patch.name, cur.id);
8843
8872
  this.applyForward({
8844
8873
  type: "update-text-style",
8845
- style: TextStyleSchema.parse({ ...cur, ...patch, id: cur.id })
8874
+ style: checkedStyle(
8875
+ TextStyleSchema,
8876
+ { ...cur, ...patch, id: cur.id },
8877
+ patch,
8878
+ `update text style "${cur.name}"`
8879
+ )
8846
8880
  });
8847
8881
  }
8848
8882
  // --- line types / hatch patterns (name-keyed definitions) -----------------
@@ -8861,7 +8895,8 @@ var LocalDrawing = class {
8861
8895
  const cur = this.state.lineTypes.find(
8862
8896
  (t) => lineTypeKey(t.name) === lineTypeKey(name)
8863
8897
  );
8864
- if (!cur) throw new Error(`Unknown line type "${name}"`);
8898
+ if (!cur)
8899
+ throw new DrawingError("unknownLineType", `Unknown line type "${name}"`);
8865
8900
  const next = LineTypeSchema.parse({ ...cur, ...patch });
8866
8901
  if (lineTypeKey(next.name) !== lineTypeKey(cur.name)) {
8867
8902
  this.transaction(() => {
@@ -8888,7 +8923,11 @@ var LocalDrawing = class {
8888
8923
  const cur = this.state.hatchPatterns.find(
8889
8924
  (p) => hatchPatternKey(p.name) === hatchPatternKey(name)
8890
8925
  );
8891
- if (!cur) throw new Error(`Unknown hatch pattern "${name}"`);
8926
+ if (!cur)
8927
+ throw new DrawingError(
8928
+ "unknownHatchPattern",
8929
+ `Unknown hatch pattern "${name}"`
8930
+ );
8892
8931
  const next = HatchPatternSchema.parse({ ...cur, ...patch });
8893
8932
  if (hatchPatternKey(next.name) !== hatchPatternKey(cur.name)) {
8894
8933
  this.transaction(() => {
@@ -8911,23 +8950,23 @@ var LocalDrawing = class {
8911
8950
  /** Create a dimension style (unique name; unset fields take the
8912
8951
  * defaults); returns its id. */
8913
8952
  addDimStyle(props) {
8914
- this.assertStyleName(this.state.dimStyles, props.name, "addDimStyle()");
8915
- const style = DimStyleSchema.parse(props);
8953
+ this.assertStyleName(this.state.dimStyles, props.name);
8954
+ const style = checkedStyle(DimStyleSchema, props, props, "add dim style");
8916
8955
  this.applyForward({ type: "add-dim-style", style });
8917
8956
  return style.id;
8918
8957
  }
8919
8958
  updateDimStyle(nameOrId, patch) {
8920
8959
  const cur = this.resolveStyle(this.state.dimStyles, nameOrId, "dim style");
8921
8960
  if (patch.name !== void 0)
8922
- this.assertStyleName(
8923
- this.state.dimStyles,
8924
- patch.name,
8925
- "updateDimStyle()",
8926
- cur.id
8927
- );
8961
+ this.assertStyleName(this.state.dimStyles, patch.name, cur.id);
8928
8962
  this.applyForward({
8929
8963
  type: "update-dim-style",
8930
- style: DimStyleSchema.parse({ ...cur, ...patch, id: cur.id })
8964
+ style: checkedStyle(
8965
+ DimStyleSchema,
8966
+ { ...cur, ...patch, id: cur.id },
8967
+ patch,
8968
+ `update dim style "${cur.name}"`
8969
+ )
8931
8970
  });
8932
8971
  }
8933
8972
  /** Remove a dimension style no dimension uses (the kernel refuses
@@ -9003,6 +9042,7 @@ var LocalDrawing = class {
9003
9042
  async addBlockDefinitions(sources) {
9004
9043
  await ensureScriptTranspiler();
9005
9044
  const report = mergeBlockModules(this.state.blockDefinitions, sources);
9045
+ refuseRejected(report, "block definition");
9006
9046
  this.applyForwardAll(
9007
9047
  diffModuleOps(
9008
9048
  this.state.blockDefinitions,
@@ -9015,6 +9055,7 @@ var LocalDrawing = class {
9015
9055
  async addUserCommands(sources) {
9016
9056
  await ensureScriptTranspiler();
9017
9057
  const report = mergeCommandModules(this.state.commands, sources);
9058
+ refuseRejected(report, "user command");
9018
9059
  this.applyForwardAll(
9019
9060
  diffModuleOps(this.state.commands, report.merged, "commands")
9020
9061
  );
@@ -9104,7 +9145,6 @@ var LocalDrawing = class {
9104
9145
  };
9105
9146
  for (const plan of plans) {
9106
9147
  const r = plan.kind === "block" ? await this.addBlockDefinitions(plan.sources) : await this.addUserCommands(plan.sources);
9107
- if (r.rejected.length > 0) throw new Error(r.rejected[0].error);
9108
9148
  report.merged = r.merged;
9109
9149
  report.added.push(...r.added);
9110
9150
  report.replaced.push(...r.replaced);
@@ -9118,7 +9158,8 @@ var LocalDrawing = class {
9118
9158
  const source = this.state.blockDefinitions.find(
9119
9159
  (s) => listBlockMeta([s]).some((r) => r.ok && r.meta.id === id)
9120
9160
  );
9121
- if (source === void 0) throw new Error(`Unknown block "${id}"`);
9161
+ if (source === void 0)
9162
+ throw new DrawingError("unknownBlock", `Unknown block "${id}"`);
9122
9163
  this.applyForward({ type: "remove-block-definition", source });
9123
9164
  }
9124
9165
  /** Remove a command module by its exported id (unknown id → error). */
@@ -9126,7 +9167,8 @@ var LocalDrawing = class {
9126
9167
  const source = this.state.commands.find(
9127
9168
  (s) => listCommandMeta([s]).some((r) => r.ok && r.meta.id === id)
9128
9169
  );
9129
- if (source === void 0) throw new Error(`Unknown command "${id}"`);
9170
+ if (source === void 0)
9171
+ throw new DrawingError("unknownCommand", `Unknown command "${id}"`);
9130
9172
  this.applyForward({ type: "remove-document-command", source });
9131
9173
  }
9132
9174
  /**
@@ -9202,7 +9244,7 @@ var LocalDrawing = class {
9202
9244
  * to the model extents. Returns the layout id.
9203
9245
  */
9204
9246
  addLayout(name, opts = {}) {
9205
- this.assertLayoutName(name, "addLayout()");
9247
+ this.assertLayoutName(name);
9206
9248
  const layout = this.makeLayout(name, opts);
9207
9249
  this.applyForward({ type: "add-layout", layout, index: opts.index });
9208
9250
  return layout.id;
@@ -9225,8 +9267,7 @@ var LocalDrawing = class {
9225
9267
  /** Patch a layout's name / sheet (preset or mm) / print palette. */
9226
9268
  updateLayout(nameOrId, patch) {
9227
9269
  const layout = this.resolveLayout(nameOrId);
9228
- if (patch.name !== void 0)
9229
- this.assertLayoutName(patch.name, "updateLayout()", layout.id);
9270
+ if (patch.name !== void 0) this.assertLayoutName(patch.name, layout.id);
9230
9271
  const { sheet, ...rest } = patch;
9231
9272
  this.applyForward({
9232
9273
  type: "update-layout-meta",
@@ -9236,6 +9277,14 @@ var LocalDrawing = class {
9236
9277
  });
9237
9278
  }
9238
9279
  // --- object mutations ----------------------------------------------------
9280
+ /** What names in a new or changed object must resolve against. */
9281
+ get nameSources() {
9282
+ return {
9283
+ lineTypes: this.state.lineTypes,
9284
+ hatchPatterns: this.state.hatchPatterns,
9285
+ blockDefinitions: this.state.blockDefinitions
9286
+ };
9287
+ }
9239
9288
  normalize(input, index) {
9240
9289
  const { layer, layerId, id, ...rest } = input;
9241
9290
  const context = `add(): objects[${index ?? 0}] (${String(input.type ?? "?")})`;
@@ -9248,9 +9297,16 @@ var LocalDrawing = class {
9248
9297
  layerId: this.resolveLayerId(layer, layerId),
9249
9298
  id: id ?? crypto.randomUUID()
9250
9299
  };
9251
- return ModelObjectSchema.parse(
9252
- raw.type === "dimension" ? dimensionRawToLabels(dimensionRawToOffset(raw)) : raw
9300
+ const obj = ModelObjectSchema.parse(raw);
9301
+ assertNoUnknownKeys(raw, obj, context, obj.type);
9302
+ assertSaneNumbers(obj, context);
9303
+ assertObjectFields(
9304
+ obj,
9305
+ new Set(Object.keys(rest)),
9306
+ this.nameSources,
9307
+ context
9253
9308
  );
9309
+ return obj;
9254
9310
  } catch (e) {
9255
9311
  throw wrapZod(
9256
9312
  e,
@@ -9282,18 +9338,25 @@ var LocalDrawing = class {
9282
9338
  `update(): "${id}" (${cur.type})`
9283
9339
  ) : {};
9284
9340
  const staleSpans = cur.type === "text" && "content" in rest && !("spans" in rest) ? { spans: void 0 } : {};
9341
+ const context = `update(): "${id}" (${cur.type})`;
9285
9342
  try {
9286
- return {
9287
- space,
9288
- obj: ModelObjectSchema.parse({
9289
- ...cur,
9290
- ...rest,
9291
- ...staleSpans,
9292
- ...styleRef,
9293
- ...resolvedLayerId ? { layerId: resolvedLayerId } : {},
9294
- id
9295
- })
9296
- };
9343
+ const obj = ModelObjectSchema.parse({
9344
+ ...cur,
9345
+ ...rest,
9346
+ ...staleSpans,
9347
+ ...styleRef,
9348
+ ...resolvedLayerId ? { layerId: resolvedLayerId } : {},
9349
+ id
9350
+ });
9351
+ assertNoUnknownKeys(rest, obj, context, cur.type);
9352
+ assertSaneNumbers(rest, context);
9353
+ assertObjectFields(
9354
+ obj,
9355
+ new Set(Object.keys(rest)),
9356
+ this.nameSources,
9357
+ context
9358
+ );
9359
+ return { space, obj };
9297
9360
  } catch (e) {
9298
9361
  throw wrapZod(e, `update(): "${id}" (${cur.type})`);
9299
9362
  }
@@ -9312,7 +9375,12 @@ var LocalDrawing = class {
9312
9375
  /** Delete objects by id (unknown ids are skipped); returns how many went. */
9313
9376
  remove(ids) {
9314
9377
  const entries = [];
9315
- for (const id of ids) {
9378
+ const all = hostedClosure(
9379
+ ids,
9380
+ this.state.blockDefinitions,
9381
+ documentBlocks(this.state)
9382
+ );
9383
+ for (const id of all) {
9316
9384
  try {
9317
9385
  entries.push({ space: this.findObject(id).space, item: id });
9318
9386
  } catch {
@@ -9397,22 +9465,50 @@ var LocalDrawing = class {
9397
9465
  mirror(ids, a, b) {
9398
9466
  const pa = toVec(a);
9399
9467
  const pb = toVec(b);
9468
+ if (pa.x === pb.x && pa.y === pb.y)
9469
+ throw new DrawingError(
9470
+ "validation",
9471
+ "Mirror needs two different points for its axis"
9472
+ );
9400
9473
  this.transformEach(ids, (o) => mirrorObject(o, pa, pb));
9401
9474
  }
9402
9475
  /** Duplicate objects (into their own space) displaced by `delta`. */
9403
9476
  copy(ids, delta) {
9404
- if (ids.length === 0) return [];
9477
+ return this.copySteps(ids, [toVec(delta)]);
9478
+ }
9479
+ /**
9480
+ * Linear array: `count` TOTAL instances (including the originals) stepped
9481
+ * by `delta`. Returns the new ids, one batch per step, in one undo step.
9482
+ */
9483
+ array(ids, count, delta) {
9484
+ if (!Number.isInteger(count) || count < 1)
9485
+ throw new Error(`Invalid array count ${count}`);
9405
9486
  const d = toVec(delta);
9406
- const idMap = new Map(ids.map((id) => [id, crypto.randomUUID()]));
9407
- const clones = ids.map((id) => {
9408
- const { obj, space } = this.findObject(id);
9409
- return {
9487
+ const steps = Array.from({ length: count - 1 }, (_, i) => ({
9488
+ x: d.x * (i + 1),
9489
+ y: d.y * (i + 1)
9490
+ }));
9491
+ return this.copySteps(ids, steps);
9492
+ }
9493
+ /** One displaced copy of `ids` per step, applied as ONE add op per space
9494
+ * (an op per step re-copied the whole space each time: quadratic). */
9495
+ copySteps(ids, steps) {
9496
+ if (ids.length === 0 || steps.length === 0) return [];
9497
+ const total2 = ids.length * steps.length;
9498
+ if (total2 > MAX_COPIES)
9499
+ throw new Error(
9500
+ `That would create ${total2} objects; one copy or array makes at most ${MAX_COPIES}.`
9501
+ );
9502
+ const sources = ids.map((id) => this.findObject(id));
9503
+ const clones = steps.flatMap((d) => {
9504
+ const idMap = new Map(ids.map((id) => [id, crypto.randomUUID()]));
9505
+ return sources.map(({ obj, space }) => ({
9410
9506
  space,
9411
9507
  obj: ModelObjectSchema.parse({
9412
9508
  ...remapBlockInputRefs(translateObject(obj, d), idMap),
9413
- id: idMap.get(id)
9509
+ id: idMap.get(obj.id)
9414
9510
  })
9415
- };
9511
+ }));
9416
9512
  });
9417
9513
  this.applyBySpace(clones, (space, objects) => ({
9418
9514
  type: "add-objects",
@@ -9421,21 +9517,6 @@ var LocalDrawing = class {
9421
9517
  }));
9422
9518
  return clones.map((c) => c.obj.id);
9423
9519
  }
9424
- /**
9425
- * Linear array: `count` TOTAL instances (including the originals) stepped
9426
- * by `delta`. Returns the new ids, one batch per step, in one undo step.
9427
- */
9428
- array(ids, count, delta) {
9429
- if (!Number.isInteger(count) || count < 1)
9430
- throw new Error(`Invalid array count ${count}`);
9431
- const d = toVec(delta);
9432
- return this.transaction(() => {
9433
- const created = [];
9434
- for (let i = 1; i < count; i++)
9435
- created.push(...this.copy(ids, { x: d.x * i, y: d.y * i }));
9436
- return created;
9437
- });
9438
- }
9439
9520
  // --- reads ---------------------------------------------------------------
9440
9521
  get measurer() {
9441
9522
  return this.hooks.measure ?? approximateTextMeasurer;
@@ -9599,7 +9680,8 @@ var LocalDrawing = class {
9599
9680
  const layout = this.state.layouts.find(
9600
9681
  (l) => l.name === nameOrId || l.id === nameOrId
9601
9682
  );
9602
- if (!layout) throw new Error(`Unknown layout "${nameOrId}"`);
9683
+ if (!layout)
9684
+ throw new DrawingError("unknownLayout", `Unknown layout "${nameOrId}"`);
9603
9685
  return layout;
9604
9686
  }
9605
9687
  /** Render model space (or a paper layout via `layout`) to an SVG string. */
@@ -9610,11 +9692,18 @@ var LocalDrawing = class {
9610
9692
  this.paintContext(),
9611
9693
  opts
9612
9694
  );
9613
- return renderModelToSvg(this.paintContext(), opts);
9695
+ return renderModelToSvg(this.paintContext(), {
9696
+ lineTypeScale: this.document.settings.modelScale,
9697
+ // Model lineweights are true mm: on a building-sized drawing they
9698
+ // rasterize sub-pixel and read as a blank sheet. Keep every stroke
9699
+ // ≥ ~1.5 px at the output width (PNG) or a 1024 px view (SVG).
9700
+ minStrokeFrac: 1.5 / (opts.width ?? 1024),
9701
+ ...opts
9702
+ });
9614
9703
  }
9615
9704
  /** Export the document to a DXF string. */
9616
9705
  async toDxf() {
9617
- const { buildDxf } = await import("./exportDxf-T2RTSTH5.js");
9706
+ const { buildDxf } = await import("./exportDxf-6XTXQ4XN.js");
9618
9707
  return buildDxf(this.state, this.measurer).content;
9619
9708
  }
9620
9709
  /** Export the document to DWG bytes (the DXF content, wasm-transcoded).
@@ -9623,7 +9712,7 @@ var LocalDrawing = class {
9623
9712
  * exploded to plain geometry (DWG_DXF_OPTIONS — the transcoder's DWG
9624
9713
  * output for them is not accepted by AutoCAD). */
9625
9714
  async toDwg(opts) {
9626
- const { buildDxf, DWG_DXF_OPTIONS } = await import("./exportDxf-T2RTSTH5.js");
9715
+ const { buildDxf, DWG_DXF_OPTIONS } = await import("./exportDxf-6XTXQ4XN.js");
9627
9716
  const dxf = buildDxf(this.state, this.measurer, DWG_DXF_OPTIONS).content;
9628
9717
  return dxfToDwg(dxf, opts?.version ?? "AC1027");
9629
9718
  }
@@ -9642,46 +9731,6 @@ var makeContext = (opts, inputs, params) => {
9642
9731
  params
9643
9732
  };
9644
9733
  };
9645
- var paramMismatch = (def, value) => {
9646
- switch (def.type) {
9647
- case "number":
9648
- if (typeof value !== "number" || !Number.isFinite(value))
9649
- return "expected a number";
9650
- if (def.integer && !Number.isInteger(value))
9651
- return "expected a whole number";
9652
- if (def.min !== void 0 && value < def.min)
9653
- return `below the minimum ${def.min}`;
9654
- if (def.max !== void 0 && value > def.max)
9655
- return `above the maximum ${def.max}`;
9656
- return null;
9657
- case "string":
9658
- case "textStyle":
9659
- case "dimStyle":
9660
- case "font":
9661
- case "lineType":
9662
- case "hatchPattern":
9663
- return typeof value === "string" ? null : "expected a string";
9664
- case "boolean":
9665
- return typeof value === "boolean" ? null : "expected true or false";
9666
- case "numberList": {
9667
- if (!Array.isArray(value) || !value.every((v) => typeof v === "number" && Number.isFinite(v)))
9668
- return "expected an array of numbers";
9669
- const low = value.find((v) => def.min !== void 0 && v < def.min);
9670
- if (low !== void 0) return `${low} is below the minimum ${def.min}`;
9671
- const high = value.find((v) => def.max !== void 0 && v > def.max);
9672
- if (high !== void 0) return `${high} is above the maximum ${def.max}`;
9673
- return null;
9674
- }
9675
- case "color":
9676
- return typeof value === "string" && HEX_COLOR.test(value) ? null : 'expected a "#rrggbb" color';
9677
- case "stringList":
9678
- return Array.isArray(value) && value.every((v) => typeof v === "string") ? null : "expected an array of strings";
9679
- case "enum":
9680
- return def.options.some((o) => o.value === value) ? null : `expected one of ${def.options.map((o) => JSON.stringify(o.value)).join(", ")}`;
9681
- default:
9682
- return "a label carries no value";
9683
- }
9684
- };
9685
9734
  var resolveCommandParams = (meta, given = {}) => {
9686
9735
  const defs = valueParams(meta.params ?? []);
9687
9736
  const params = /* @__PURE__ */ Object.create(null);
@@ -9699,8 +9748,57 @@ var resolveCommandParams = (meta, given = {}) => {
9699
9748
  }
9700
9749
  return params;
9701
9750
  };
9751
+ var surfacePickers = (editor, document) => {
9752
+ let selectionUsed = false;
9753
+ const fromSelection = (def) => {
9754
+ if (selectionUsed) return null;
9755
+ const selected = editor.state().selectedIds.map((id) => document.getObject(id)).filter((o) => o !== void 0);
9756
+ if (!selectionFits(def, selected)) return null;
9757
+ selectionUsed = true;
9758
+ return selected.map((o) => o.id);
9759
+ };
9760
+ return {
9761
+ point: (def, ref) => editor.pickPoint({ label: inputLabel(def), referencePoint: ref }),
9762
+ points: (def, ref) => editor.pickPoints({ ...def, referencePoint: ref }),
9763
+ object: async (def) => fromSelection({ types: def.types, min: 1, max: 1 })?.[0] ?? editor.pickObject({ label: inputLabel(def), types: def.types }),
9764
+ objects: async (def) => fromSelection(def) ?? editor.pickObjects(def)
9765
+ };
9766
+ };
9767
+ var acquireNamedCommandInputs = async (meta, table, opts) => {
9768
+ const { inputs, missing } = checkNamedInputs(
9769
+ meta.name,
9770
+ table,
9771
+ opts.inputs,
9772
+ (id) => opts.document.getObject(id)
9773
+ );
9774
+ if (!opts.editor) {
9775
+ const required = missing.filter(isRequiredInput);
9776
+ if (required.length > 0)
9777
+ throw new Error(
9778
+ `${meta.name} needs an editor for: ${required.map((d) => d.name).join(", ")} \u2014 supply inputs`
9779
+ );
9780
+ return withEmptyLists(table, inputs);
9781
+ }
9782
+ if (missing.length === 0) return inputs;
9783
+ const picked = await acquireInputTable(
9784
+ missing,
9785
+ surfacePickers(opts.editor, opts.document)
9786
+ );
9787
+ const out = {};
9788
+ for (const def of table) {
9789
+ const v = inputs[def.name] ?? picked[def.name];
9790
+ if (v !== void 0) out[def.name] = v;
9791
+ }
9792
+ return out;
9793
+ };
9702
9794
  var acquireCommandInputs = async (meta, opts) => {
9795
+ if (isInputTable(meta.inputs))
9796
+ return acquireNamedCommandInputs(meta, meta.inputs, opts);
9703
9797
  const labels = meta.inputs ?? [];
9798
+ if (opts.inputs !== void 0 && !Array.isArray(opts.inputs))
9799
+ throw new Error(
9800
+ `${meta.name}: inputs are positional (${labels.join(", ") || "none declared"}) \u2014 pass an array`
9801
+ );
9704
9802
  const given = (opts.inputs ?? []).map((p, i) => {
9705
9803
  try {
9706
9804
  return Vec2Schema.parse(p);
@@ -9732,11 +9830,29 @@ var runCommandModule = async (source, opts) => {
9732
9830
  const result = getCommandMetaResult(source);
9733
9831
  if (!result.ok) throw new Error(`Command failed to compile: ${result.error}`);
9734
9832
  const run = getCommandRun(source);
9833
+ if (result.meta.requiresEditor && !opts.editor)
9834
+ throw new Error(
9835
+ `${result.meta.name} needs the editor (it asks during the run) \u2014 run it from Linea`
9836
+ );
9735
9837
  const params = resolveCommandParams(result.meta, opts.params);
9736
9838
  const inputs = await acquireCommandInputs(result.meta, opts);
9737
- return opts.document.transaction(
9738
- () => run(makeContext(opts, inputs, params)),
9739
- result.meta.name
9839
+ try {
9840
+ return await opts.document.transaction(
9841
+ () => run(makeContext(opts, inputs, params)),
9842
+ result.meta.name
9843
+ );
9844
+ } catch (e) {
9845
+ throw opts.editor ? e : namedEditorError(result.meta.name, e);
9846
+ }
9847
+ };
9848
+ var namedEditorError = (name, e) => {
9849
+ const m = e instanceof TypeError ? /(?:reading|property) '(pickPoint|pickPoints|pickObject|pickObjects|prompt|ask|toast|state|setSelection|setActiveSpace|zoom|listCommands|runCommand)'/.exec(
9850
+ e.message
9851
+ ) : null;
9852
+ if (!m) return e;
9853
+ return new Error(
9854
+ `${name} used editor.${m[1]} but runs without an editor here \u2014 check \`editor\` before using it, or declare \`requiresEditor: true\``,
9855
+ { cause: e }
9740
9856
  );
9741
9857
  };
9742
9858
  var runDocumentCommand = async (id, opts) => {
@@ -9922,7 +10038,10 @@ var Drawing = class extends LocalDrawing {
9922
10038
  this.paintContext(),
9923
10039
  opts
9924
10040
  );
9925
- return renderModelToPng(this.paintContext(), opts);
10041
+ return renderModelToPng(this.paintContext(), {
10042
+ lineTypeScale: this.document.settings.modelScale,
10043
+ ...opts
10044
+ });
9926
10045
  }
9927
10046
  /** Multi-page PDF of the layouts (default: all) with the embedded fonts. */
9928
10047
  async renderPdf(opts = {}) {
@@ -9982,7 +10101,8 @@ var Drawing = class extends LocalDrawing {
9982
10101
  }
9983
10102
  // --- scripts ---------------------------------------------------------------
9984
10103
  /** Run a command module source (`defineCommand`) against this drawing as
9985
- * ONE undo step, headlessly: declared `inputs` must be supplied; declared
10104
+ * ONE undo step, headlessly: declared `inputs` must be supplied (points
10105
+ * in label order, or a record by name for an inputs table); declared
9986
10106
  * `params` take their defaults unless given by name (unknown names and
9987
10107
  * wrong types throw). Resolves with the script's return value. */
9988
10108
  runScript(source, inputs, params) {
@@ -10033,10 +10153,12 @@ export {
10033
10153
  ensureScriptTranspiler,
10034
10154
  getBlockMetaResult,
10035
10155
  getCommandMetaResult,
10156
+ hatchLoop,
10036
10157
  newDrawing,
10037
10158
  openDrawing,
10038
10159
  parseScale,
10039
10160
  renderBlockThumbnailSvg,
10161
+ splineThrough,
10040
10162
  testBlockDefinition
10041
10163
  };
10042
10164
  //# sourceMappingURL=sdk.js.map