tsdraft 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/drawing.js CHANGED
@@ -1,16 +1,53 @@
1
+ import { holdCall, readCall, recordCalls } from "./callsite.js";
1
2
  import { fmt, written } from "./format.js";
2
3
  import { layerAuditKey, layerKey } from "./layers.js";
3
4
  import { BUILT_IN_LINETYPES, builtInLinetype } from "./linetypes.js";
4
- import { arcProblem, layerColorProblem, layerFlagProblem, layerLinetypeProblem, layerStateProblem, lineweightProblem, linetypeNameProblem, linetypeProblem, linetypeScaleProblem, magnitudeProblem, layerNameProblem, layerTwinProblem, R12_ONLY, sizeProblem, textProblem, undefinedLinetypeProblem, } from "./rules.js";
5
+ import { DrawingError } from "./lint.js";
6
+ import { arcProblem, duplicateIdProblem, idKey, idProblem, layerColorProblem, layerFlagProblem, layerLinetypeProblem, layerStateProblem, lineweightProblem, linetypeNameProblem, linetypeProblem, linetypeScaleProblem, magnitudeProblem, layerNameProblem, layerTwinProblem, R12_ONLY, sizeProblem, textProblem, undefinedLinetypeProblem, } from "./rules.js";
5
7
  const UNITS = ["in", "ft", "mm", "cm", "m"];
6
8
  const DXF_VERSIONS = ["R12", "R2018"];
9
+ /** A broken rule, thrown inside a builder method and turned into a `DrawingError` on its way out. */
10
+ class Problem {
11
+ rule;
12
+ message;
13
+ constructor(rule, message) {
14
+ this.rule = rule;
15
+ this.message = message;
16
+ }
17
+ }
18
+ function refuse(rule, message) {
19
+ throw new Problem(rule, message);
20
+ }
21
+ /**
22
+ * What a builder method throws for something thrown inside it: for a `Problem`, a `DrawingError`
23
+ * whose one diagnostic names the caller's call of `method` and what the call was `about`.
24
+ */
25
+ function located(thrown, method, about) {
26
+ if (!(thrown instanceof Problem))
27
+ return thrown;
28
+ const call = readCall(holdCall(method));
29
+ const diagnostic = {
30
+ rule: thrown.rule,
31
+ severity: "error",
32
+ message: thrown.message,
33
+ ...about,
34
+ ...(call ? { call } : {}),
35
+ };
36
+ return new DrawingError([diagnostic], thrown.message);
37
+ }
38
+ /** Whether a tool that runs drawing files has asked every builder to record calls. */
39
+ function recordsCalls() {
40
+ return globalThis.TSDRAFT_RECORD_CALLS === true;
41
+ }
7
42
  /**
8
43
  * Builds a drawing. Entities go on the current layer, which starts as "0".
9
44
  * `layer(name, opts, draw)` scopes everything drawn inside `draw` to that layer.
10
45
  *
11
46
  * Mistakes written as literals, such as a layer name the DXF version does not
12
47
  * allow or a negative radius, are compile errors. The same checks run again
13
- * when the code runs, for values the compiler cannot see.
48
+ * when the code runs, for values the compiler cannot see: `layer`, `linetype`
49
+ * and the entity methods then throw a `DrawingError` whose diagnostic has the
50
+ * rule, the entity's `id` and the `call` that broke it.
14
51
  */
15
52
  export class DrawingBuilder {
16
53
  units;
@@ -25,6 +62,10 @@ export class DrawingBuilder {
25
62
  /** Seeded with the built-in linetypes, whose twins AUDIT reports as it reports any other. */
26
63
  #linetypesAudited = new Map(BUILT_IN_LINETYPES.map((name) => [layerAuditKey(name), name]));
27
64
  #entities = [];
65
+ /** The first spelling of each entity id, by `idKey`. */
66
+ #ids = new Map();
67
+ /** The call that drew each entity, when calls are recorded. */
68
+ #calls;
28
69
  #current = "0";
29
70
  constructor(options = {}) {
30
71
  const units = options.units ?? "in";
@@ -47,6 +88,7 @@ export class DrawingBuilder {
47
88
  this.units = units;
48
89
  this.dxf = dxf;
49
90
  this.linetypeScale = scale ?? 1;
91
+ this.#calls = (options.recordCalls ?? recordsCalls()) ? [] : undefined;
50
92
  }
51
93
  /**
52
94
  * Defines a linetype, or updates the one of the same name. `dashed` and
@@ -54,9 +96,17 @@ export class DrawingBuilder {
54
96
  * tsdraft exports the linetypes plans use, such as `DASHED` and `HIDDEN`.
55
97
  */
56
98
  linetype(linetype) {
57
- if (this.dxf === "R12")
58
- throw new Error(R12_ONLY.linetypes);
59
- this.#define(linetype);
99
+ const definition = linetype;
100
+ try {
101
+ if (this.dxf === "R12")
102
+ refuse("linetype", R12_ONLY.linetypes);
103
+ this.#define(definition);
104
+ }
105
+ catch (thrown) {
106
+ const name = definition?.name;
107
+ const about = typeof name === "string" ? { linetype: name } : {};
108
+ throw located(thrown, DrawingBuilder.prototype.linetype, about);
109
+ }
60
110
  return this;
61
111
  }
62
112
  /**
@@ -67,23 +117,41 @@ export class DrawingBuilder {
67
117
  layer(name, options = {}, draw) {
68
118
  // The parameter type is the error message when a literal name is wrong, so read it as a string.
69
119
  const requested = name;
70
- const fail = (problem) => {
71
- throw new Error(`Layer ${JSON.stringify(requested)}: ${problem}`);
72
- };
120
+ let layer;
121
+ try {
122
+ layer = this.#layer(requested, options);
123
+ }
124
+ catch (thrown) {
125
+ throw located(thrown, DrawingBuilder.prototype.layer, { layer: requested });
126
+ }
127
+ if (draw) {
128
+ const previous = this.#current;
129
+ this.#current = layer.name;
130
+ try {
131
+ draw();
132
+ }
133
+ finally {
134
+ this.#current = previous;
135
+ }
136
+ }
137
+ return this;
138
+ }
139
+ /** Checks a layer and stores it. */
140
+ #layer(requested, o) {
141
+ const fail = (rule, problem) => refuse(rule, `Layer ${JSON.stringify(requested)}: ${problem}`);
73
142
  const nameProblem = layerNameProblem(requested, this.dxf);
74
143
  if (nameProblem)
75
- fail(nameProblem);
144
+ fail("layer-name", nameProblem);
76
145
  const key = layerKey(requested);
77
146
  const existing = this.#layers.get(key);
78
147
  const auditKey = layerAuditKey(requested);
79
148
  const twinProblem = layerTwinProblem(requested, this.#audited.get(auditKey) ?? requested);
80
149
  if (twinProblem)
81
- fail(twinProblem);
82
- const color = options.color ?? existing?.color ?? 7;
150
+ fail("duplicate-layer", twinProblem);
151
+ const color = o.color ?? existing?.color ?? 7;
83
152
  const colorProblem = layerColorProblem(color);
84
153
  if (colorProblem)
85
- fail(colorProblem);
86
- const o = options;
154
+ fail("layer-color", colorProblem);
87
155
  const layer = { name: existing?.name ?? requested, color };
88
156
  const set = (field, value, plain) => {
89
157
  const kept = value === undefined ? existing?.[field] : value;
@@ -94,80 +162,81 @@ export class DrawingBuilder {
94
162
  let linetype;
95
163
  if (o.linetype !== undefined) {
96
164
  if (r12 && !isBuiltIn(o.linetype, "Continuous"))
97
- fail(R12_ONLY.linetypes);
165
+ fail("linetype", R12_ONLY.linetypes);
98
166
  linetype = this.#linetypeName(o.linetype, fail);
99
167
  const problem = layerLinetypeProblem(linetype);
100
168
  if (problem)
101
- fail(problem);
169
+ fail("linetype", problem);
102
170
  }
103
171
  set("linetype", linetype === undefined ? undefined : (builtInLinetype(linetype) ?? linetype), "Continuous");
104
172
  if (o.lineweight !== undefined) {
105
173
  const problem = lineweightProblem(o.lineweight, "layer");
106
174
  if (problem)
107
- fail(problem);
175
+ fail("lineweight", problem);
108
176
  if (r12 && o.lineweight !== "Default")
109
- fail(R12_ONLY.lineweights);
177
+ fail("lineweight", R12_ONLY.lineweights);
110
178
  }
111
179
  set("lineweight", o.lineweight, "Default");
112
180
  for (const state of ["off", "frozen", "locked", "plot"]) {
113
181
  const value = o[state];
114
182
  const flagProblem = layerFlagProblem(state, value);
115
183
  if (flagProblem)
116
- fail(flagProblem);
117
- if (r12 && value !== undefined && value !== (state === "plot"))
118
- fail(R12_ONLY.layerStates);
184
+ fail("layer-state", flagProblem);
185
+ if (r12 && value !== undefined && value !== (state === "plot")) {
186
+ fail("layer-state", R12_ONLY.layerStates);
187
+ }
119
188
  set(state, value, state === "plot");
120
189
  }
121
190
  const stateProblem = layerStateProblem(layer.name, layer.frozen);
122
191
  if (stateProblem)
123
- fail(stateProblem);
192
+ fail("layer-state", stateProblem);
124
193
  this.#layers.set(key, layer);
125
194
  if (!this.#audited.has(auditKey))
126
195
  this.#audited.set(auditKey, layer.name);
127
- if (draw) {
128
- const previous = this.#current;
129
- this.#current = layer.name;
130
- try {
131
- draw();
132
- }
133
- finally {
134
- this.#current = previous;
135
- }
136
- }
137
- return this;
196
+ return layer;
138
197
  }
139
198
  line(from, to, options = {}) {
140
- return this.#add({ kind: "line", layer: this.#current, from: pt(from), to: pt(to) }, options);
199
+ return this.#add(DrawingBuilder.prototype.line, options, () => ({
200
+ kind: "line",
201
+ layer: this.#current,
202
+ from: pt(from),
203
+ to: pt(to),
204
+ }));
141
205
  }
142
206
  polyline(points, closed = false, options = {}) {
143
- if (points.length < 2)
144
- throw new Error("A polyline needs at least 2 points");
145
- return this.#add({ kind: "polyline", layer: this.#current, points: points.map(pt), closed }, options);
207
+ return this.#add(DrawingBuilder.prototype.polyline, options, () => {
208
+ if (points.length < 2)
209
+ refuse("geometry", "A polyline needs at least 2 points");
210
+ return { kind: "polyline", layer: this.#current, points: points.map(pt), closed };
211
+ });
146
212
  }
147
213
  /** Closed rectangle from its lower-left corner. */
148
214
  rect(corner, width, height, options = {}) {
149
- const [x, y] = pt(corner);
150
- const w = positive(width, "width");
151
- const h = positive(height, "height");
152
- // A side can still vanish in the file: a corner and the one across from it may round together.
153
- if (written(x + w) === written(x))
154
- throw new Error(lostSide("width", "x", x));
155
- if (written(y + h) === written(y))
156
- throw new Error(lostSide("height", "y", y));
157
- return this.polyline([
158
- [x, y],
159
- [x + w, y],
160
- [x + w, y + h],
161
- [x, y + h],
162
- ], true, options);
215
+ return this.#add(DrawingBuilder.prototype.rect, options, () => {
216
+ const [x, y] = pt(corner);
217
+ const w = positive(width, "width");
218
+ const h = positive(height, "height");
219
+ // A side can still vanish in the file: a corner and the one across from it may round together.
220
+ if (written(x + w) === written(x))
221
+ refuse("geometry", lostSide("width", "x", x));
222
+ if (written(y + h) === written(y))
223
+ refuse("geometry", lostSide("height", "y", y));
224
+ const corners = [
225
+ [x, y],
226
+ [x + w, y],
227
+ [x + w, y + h],
228
+ [x, y + h],
229
+ ];
230
+ return { kind: "polyline", layer: this.#current, points: corners.map(pt), closed: true };
231
+ });
163
232
  }
164
233
  circle(center, radius, options = {}) {
165
- return this.#add({
234
+ return this.#add(DrawingBuilder.prototype.circle, options, () => ({
166
235
  kind: "circle",
167
236
  layer: this.#current,
168
237
  center: pt(center),
169
238
  radius: positive(radius, "radius"),
170
- }, options);
239
+ }));
171
240
  }
172
241
  /**
173
242
  * Arc from `startAngle` counterclockwise to `endAngle`. Angles are stored
@@ -179,61 +248,93 @@ export class DrawingBuilder {
179
248
  * arc of no length, so it throws: draw a circle instead.
180
249
  */
181
250
  arc(center, radius, startAngle, endAngle, options = {}) {
182
- const start = turn(num(startAngle, "startAngle"));
183
- const end = turn(num(endAngle, "endAngle"));
184
- if (start === end)
185
- throw new Error("An arc's start and end angles must differ");
186
- const problem = arcProblem(start, end);
187
- if (problem)
188
- throw new Error(problem);
189
- return this.#add({
190
- kind: "arc",
191
- layer: this.#current,
192
- center: pt(center),
193
- radius: positive(radius, "radius"),
194
- startAngle: start,
195
- endAngle: end,
196
- }, options);
251
+ return this.#add(DrawingBuilder.prototype.arc, options, () => {
252
+ const start = turn(num(startAngle, "startAngle"));
253
+ const end = turn(num(endAngle, "endAngle"));
254
+ if (start === end)
255
+ refuse("geometry", "An arc's start and end angles must differ");
256
+ const problem = arcProblem(start, end);
257
+ if (problem)
258
+ refuse("geometry", problem);
259
+ return {
260
+ kind: "arc",
261
+ layer: this.#current,
262
+ center: pt(center),
263
+ radius: positive(radius, "radius"),
264
+ startAngle: start,
265
+ endAngle: end,
266
+ };
267
+ });
197
268
  }
198
269
  text(at, value, options = {}) {
199
- const problem = textProblem(value);
200
- if (problem)
201
- throw new Error(problem);
202
- return this.#add({
203
- kind: "text",
204
- layer: this.#current,
205
- at: pt(at),
206
- value,
207
- height: positive(options.height ?? 0.125, "height"),
208
- rotation: kept(options.rotation ?? 0, "rotation"),
209
- }, options);
270
+ return this.#add(DrawingBuilder.prototype.text, options, () => {
271
+ const problem = textProblem(value);
272
+ if (problem)
273
+ refuse("text", problem);
274
+ return {
275
+ kind: "text",
276
+ layer: this.#current,
277
+ at: pt(at),
278
+ value,
279
+ height: positive(options.height ?? 0.125, "height"),
280
+ rotation: kept(options.rotation ?? 0, "rotation"),
281
+ };
282
+ });
210
283
  }
211
284
  build() {
285
+ const entities = [...this.#entities];
286
+ if (this.#calls)
287
+ recordCalls(entities, [...this.#calls]);
212
288
  return {
289
+ format: 0,
213
290
  units: this.units,
214
291
  dxf: this.dxf,
215
292
  layers: [...this.#layers.values()],
216
- entities: [...this.#entities],
293
+ entities,
217
294
  linetypes: [...this.#linetypes.values()],
218
295
  linetypeScale: this.linetypeScale,
219
296
  };
220
297
  }
221
- /** `options` is an `EntityOptions`, whose type parameters differ from method to method. */
222
- #add(entity, options) {
223
- this.#entities.push({ ...entity, ...this.#style(options) });
298
+ /**
299
+ * Checks the entity that `make` gives and the `EntityOptions` it was drawn with, whose type
300
+ * parameters differ from method to method, and stores it. `method` is the builder method the
301
+ * caller called, which a diagnostic names the call of.
302
+ */
303
+ #add(method, options, make) {
304
+ const o = options;
305
+ const id = o.id;
306
+ try {
307
+ const key = id === undefined ? undefined : this.#idKey(id);
308
+ const named = id === undefined ? {} : { id: id };
309
+ this.#entities.push({ ...make(), ...named, ...this.#style(o) });
310
+ if (key !== undefined)
311
+ this.#ids.set(key, id);
312
+ }
313
+ catch (thrown) {
314
+ throw located(thrown, method, typeof id === "string" ? { id } : {});
315
+ }
316
+ this.#calls?.push(holdCall(method));
224
317
  return this;
225
318
  }
319
+ /** Checks an id the drawing does not have yet, and returns the key it is kept by. */
320
+ #idKey(id) {
321
+ const problem = idProblem(id);
322
+ if (problem)
323
+ refuse("id", problem);
324
+ const key = idKey(id);
325
+ const first = this.#ids.get(key);
326
+ if (first !== undefined)
327
+ refuse("duplicate-id", duplicateIdProblem(id, first));
328
+ return key;
329
+ }
226
330
  /** The linetype and lineweight an entity is stored with. */
227
331
  #style(options) {
228
- const fail = (problem) => {
229
- throw new Error(problem);
230
- };
231
332
  const style = {};
232
333
  if (options.linetype !== undefined) {
233
334
  if (this.dxf === "R12" && !isBuiltIn(options.linetype, "ByLayer")) {
234
- fail(R12_ONLY.entityStyle);
335
+ refuse("linetype", R12_ONLY.entityStyle);
235
336
  }
236
- const name = this.#linetypeName(options.linetype, fail);
337
+ const name = this.#linetypeName(options.linetype, refuse);
237
338
  const builtIn = builtInLinetype(name);
238
339
  if (builtIn !== "ByLayer")
239
340
  style.linetype = builtIn ?? name;
@@ -242,9 +343,9 @@ export class DrawingBuilder {
242
343
  if (weight !== undefined) {
243
344
  const problem = lineweightProblem(weight, "entity");
244
345
  if (problem)
245
- fail(problem);
346
+ refuse("lineweight", problem);
246
347
  if (this.dxf === "R12" && weight !== "ByLayer")
247
- fail(R12_ONLY.entityStyle);
348
+ refuse("lineweight", R12_ONLY.entityStyle);
248
349
  if (weight !== "ByLayer")
249
350
  style.lineweight = weight;
250
351
  }
@@ -258,14 +359,17 @@ export class DrawingBuilder {
258
359
  if (typeof choice === "object" && choice !== null) {
259
360
  return this.#define(choice, true);
260
361
  }
261
- if (typeof choice !== "string")
262
- return fail("A linetype is a name or a LinetypeDefinition");
362
+ if (typeof choice !== "string") {
363
+ return fail("undefined-linetype", "A linetype is a name or a LinetypeDefinition");
364
+ }
263
365
  const problem = linetypeNameProblem(choice);
264
366
  if (problem)
265
- fail(`Linetype ${JSON.stringify(choice)}: ${problem}`);
367
+ fail("undefined-linetype", `Linetype ${JSON.stringify(choice)}: ${problem}`);
266
368
  const known = builtInLinetype(choice) ?? this.#linetypes.get(layerKey(choice))?.name;
267
- if (known === undefined)
268
- fail(`${undefinedLinetypeProblem(choice)}. Define it with linetype() first`);
369
+ if (known === undefined) {
370
+ const absent = `${undefinedLinetypeProblem(choice)}. Define it with linetype() first`;
371
+ fail("undefined-linetype", absent);
372
+ }
269
373
  return known;
270
374
  }
271
375
  /**
@@ -277,7 +381,7 @@ export class DrawingBuilder {
277
381
  const label = `Linetype ${JSON.stringify(definition.name)}`;
278
382
  const problem = linetypeProblem(definition.name, definition.description ?? "", definition.pattern);
279
383
  if (problem)
280
- throw new Error(`${label}: ${problem}`);
384
+ refuse("linetype", `${label}: ${problem}`);
281
385
  // linetypeProblem has checked the types.
282
386
  const name = definition.name;
283
387
  const description = (definition.description ?? "");
@@ -286,14 +390,14 @@ export class DrawingBuilder {
286
390
  const auditKey = layerAuditKey(name);
287
391
  const twin = layerTwinProblem(name, this.#linetypesAudited.get(auditKey) ?? name, "linetype");
288
392
  if (twin)
289
- throw new Error(`${label}: ${twin}`);
393
+ refuse("duplicate-linetype", `${label}: ${twin}`);
290
394
  const existing = this.#linetypes.get(key);
291
395
  if (inline && existing) {
292
396
  const same = existing.description === description &&
293
397
  existing.pattern.length === lengths.length &&
294
398
  existing.pattern.every((length, i) => length === lengths[i]);
295
399
  if (!same) {
296
- throw new Error(`${label}: the drawing has a linetype of this name with another pattern or description. Pass its name, or change it with linetype()`);
400
+ refuse("linetype", `${label}: the drawing has a linetype of this name with another pattern or description. Pass its name, or change it with linetype()`);
297
401
  }
298
402
  return existing.name;
299
403
  }
@@ -327,17 +431,17 @@ function turn(degrees) {
327
431
  }
328
432
  function num(value, label) {
329
433
  if (!Number.isFinite(value))
330
- throw new Error(`${label} must be a finite number`);
434
+ refuse("geometry", `${label} must be a finite number`);
331
435
  return value;
332
436
  }
333
437
  /** Takes `unknown` because a `Positive<N>` parameter is a message string when the literal is wrong. */
334
438
  function positive(value, label) {
335
439
  if (typeof value !== "number" || !Number.isFinite(value)) {
336
- throw new Error(`${label} must be a finite number`);
440
+ return refuse("geometry", `${label} must be a finite number`);
337
441
  }
338
442
  const problem = sizeProblem(value);
339
443
  if (problem)
340
- throw new Error(`${label}: ${problem}`);
444
+ refuse("geometry", `${label}: ${problem}`);
341
445
  return value;
342
446
  }
343
447
  function lostSide(size, axis, at) {
@@ -347,7 +451,7 @@ function lostSide(size, axis, at) {
347
451
  function kept(value, label) {
348
452
  const problem = magnitudeProblem(num(value, label));
349
453
  if (problem)
350
- throw new Error(`${label}: ${problem}`);
454
+ refuse("geometry", `${label}: ${problem}`);
351
455
  return value;
352
456
  }
353
457
  function pt(point) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
+ export type { CallSite } from "./callsite.ts";
1
2
  export { createDrawing, DrawingBuilder, sweep, type Arc, type Circle, type Drawing, type DrawingOptions, type DxfVersion, type Entity, type EntityOptions, type Layer, type LayerOptions, type Line, type Linetype, type LinetypeChoice, type LinetypeDefinition, type Lineweight, type Point, type Polyline, type Text, type TextOptions, type Units, } from "./drawing.ts";
2
3
  export { toDXF, type DxfOptions } from "./dxf.ts";
4
+ export { fromJSON, toJSON } from "./json.ts";
3
5
  export { DrawingError, lint, type Diagnostic, type LintOptions, type LintRule } from "./lint.ts";
4
6
  export { CENTER, DASHDOT, DASHED, DOT, HIDDEN, LINEWEIGHTS, PHANTOM } from "./linetypes.ts";
5
- export type { Aci, Description, LayerColor, LayerLinetypeName, LayerName, LineweightOf, LinetypeName, NewLinetypeName, Pattern, Positive, } from "./rules.ts";
7
+ export { fromPaper, type PaperScale, type PaperUnit } from "./paper.ts";
8
+ export type { Aci, Description, EntityId, LayerColor, LayerLinetypeName, LayerName, LineweightOf, LinetypeName, NewLinetypeName, PaperLength, Pattern, Positive, Ratio, } from "./rules.ts";
6
9
  export { toSVG, type SvgOptions } from "./svg.ts";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export { createDrawing, DrawingBuilder, sweep, } from "./drawing.js";
2
2
  export { toDXF } from "./dxf.js";
3
+ export { fromJSON, toJSON } from "./json.js";
3
4
  export { DrawingError, lint } from "./lint.js";
4
5
  export { CENTER, DASHDOT, DASHED, DOT, HIDDEN, LINEWEIGHTS, PHANTOM } from "./linetypes.js";
6
+ export { fromPaper } from "./paper.js";
5
7
  export { toSVG } from "./svg.js";
package/dist/json.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { Drawing } from "./drawing.ts";
2
+ /**
3
+ * Writes the drawing as canonical JSON: no white space, the keys of every object in code-unit
4
+ * order, and each number in the shortest form that reads back as the same number, with `-0`
5
+ * for negative zero. Two drawings that are equal give the same string, whatever order their
6
+ * keys were set in. A field that is `undefined` is left out.
7
+ *
8
+ * The drawing is written as it is and is not checked: `fromJSON` checks it when it is read.
9
+ * Throws when the drawing holds a value JSON cannot, such as `NaN`.
10
+ */
11
+ export declare function toJSON(drawing: Drawing): string;
12
+ /**
13
+ * Reads a drawing from JSON, as a string or as the value `JSON.parse` gives, which is returned
14
+ * as the drawing when it passes.
15
+ *
16
+ * It refuses a format version it does not know, a field or an entity kind it does not know, a
17
+ * missing field and a value of the wrong type, and then every error `lint` finds, so a drawing
18
+ * that was read has passed each check the builder makes. It throws a `DrawingError` whose
19
+ * diagnostics name each problem, with the rule `"format"` for the ones `lint` does not look for.
20
+ */
21
+ export declare function fromJSON(json: unknown): Drawing;