dwf-viewer 0.5.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 (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +235 -0
  3. package/NOTICE +10 -0
  4. package/PRODUCTION_3D_NOTES.md +48 -0
  5. package/README.md +203 -0
  6. package/dist/format/document.d.ts +186 -0
  7. package/dist/format/document.js +9 -0
  8. package/dist/format/dwf.d.ts +4 -0
  9. package/dist/format/dwf.js +372 -0
  10. package/dist/format/dwfx.d.ts +6 -0
  11. package/dist/format/dwfx.js +425 -0
  12. package/dist/format/emodelMetadata.d.ts +10 -0
  13. package/dist/format/emodelMetadata.js +368 -0
  14. package/dist/format/inflate.d.ts +4 -0
  15. package/dist/format/inflate.js +28 -0
  16. package/dist/format/opc.d.ts +28 -0
  17. package/dist/format/opc.js +85 -0
  18. package/dist/format/open.d.ts +3 -0
  19. package/dist/format/open.js +69 -0
  20. package/dist/format/types.d.ts +61 -0
  21. package/dist/format/types.js +6 -0
  22. package/dist/format/util.d.ts +18 -0
  23. package/dist/format/util.js +324 -0
  24. package/dist/format/w2dBinary.d.ts +19 -0
  25. package/dist/format/w2dBinary.js +629 -0
  26. package/dist/format/w2dText.d.ts +13 -0
  27. package/dist/format/w2dText.js +166 -0
  28. package/dist/format/w3d.d.ts +8 -0
  29. package/dist/format/w3d.js +826 -0
  30. package/dist/format/zip.d.ts +30 -0
  31. package/dist/format/zip.js +141 -0
  32. package/dist/index.d.ts +12 -0
  33. package/dist/index.js +9 -0
  34. package/dist/render/PageRenderer.d.ts +27 -0
  35. package/dist/render/PageRenderer.js +92 -0
  36. package/dist/render/ThreeJsSceneAdapter.d.ts +29 -0
  37. package/dist/render/ThreeJsSceneAdapter.js +52 -0
  38. package/dist/render/ThreeW3dRenderer.d.ts +24 -0
  39. package/dist/render/ThreeW3dRenderer.js +372 -0
  40. package/dist/render/W2dRenderer.d.ts +24 -0
  41. package/dist/render/W2dRenderer.js +198 -0
  42. package/dist/render/WebGlW2dBackend.d.ts +38 -0
  43. package/dist/render/WebGlW2dBackend.js +400 -0
  44. package/dist/render/XpsRenderer.d.ts +20 -0
  45. package/dist/render/XpsRenderer.js +310 -0
  46. package/dist/render/style.d.ts +16 -0
  47. package/dist/render/style.js +115 -0
  48. package/dist/render/viewport.d.ts +16 -0
  49. package/dist/render/viewport.js +27 -0
  50. package/dist/render/xpsPath.d.ts +41 -0
  51. package/dist/render/xpsPath.js +335 -0
  52. package/dist/viewer/DwfViewer.d.ts +69 -0
  53. package/dist/viewer/DwfViewer.js +386 -0
  54. package/dist/wasm/WasmRasterBackend.d.ts +21 -0
  55. package/dist/wasm/WasmRasterBackend.js +84 -0
  56. package/package.json +91 -0
  57. package/public/dwfv-render.wasm +0 -0
  58. package/styles/dwf-viewer.css +51 -0
@@ -0,0 +1,629 @@
1
+ import { diag } from './types.js';
2
+ const FONT_NAME_BIT = 0x0001;
3
+ const FONT_CHARSET_BIT = 0x0002;
4
+ const FONT_PITCH_BIT = 0x0004;
5
+ const FONT_FAMILY_BIT = 0x0008;
6
+ const FONT_STYLE_BIT = 0x0010;
7
+ const FONT_HEIGHT_BIT = 0x0020;
8
+ const FONT_ROTATION_BIT = 0x0040;
9
+ const FONT_WIDTH_SCALE_BIT = 0x0080;
10
+ const FONT_SPACING_BIT = 0x0100;
11
+ const FONT_OBLIQUE_BIT = 0x0200;
12
+ const FONT_FLAGS_BIT = 0x0400;
13
+ export function parseW2dBinary(bytes, sourcePath) {
14
+ const parser = new BinaryW2dParser(bytes, sourcePath);
15
+ return parser.parse();
16
+ }
17
+ class BinaryW2dParser {
18
+ constructor(bytes, sourcePath) {
19
+ this.bytes = bytes;
20
+ this.sourcePath = sourcePath;
21
+ this.pos = 0;
22
+ this.current = { x: 0, y: 0 };
23
+ this.primitives = [];
24
+ this.diagnostics = [];
25
+ this.unsupportedOpcodes = [];
26
+ this.opcodes = 0;
27
+ this.decimalRevision = 600;
28
+ this.state = {
29
+ stroke: '#000000',
30
+ fill: undefined,
31
+ fillOn: false,
32
+ visible: true,
33
+ lineWidth: 1,
34
+ fontHeight: 120,
35
+ fontRotation: 0,
36
+ fontName: 'sans-serif'
37
+ };
38
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
39
+ }
40
+ parse() {
41
+ while (this.pos < this.bytes.length) {
42
+ this.eatWhitespace();
43
+ if (this.pos >= this.bytes.length)
44
+ break;
45
+ const offset = this.pos;
46
+ const opcode = this.u8();
47
+ this.opcodes++;
48
+ try {
49
+ if (opcode === 0x28) { // '(', extended ASCII opcode/expression.
50
+ this.pos = offset;
51
+ this.parseExtendedAscii(offset);
52
+ continue;
53
+ }
54
+ if (opcode === 0x7b) { // '{', extended binary opcode.
55
+ if (!this.parseExtendedBinary(offset))
56
+ break;
57
+ continue;
58
+ }
59
+ if (this.parseSingleByteOpcode(opcode))
60
+ continue;
61
+ this.unsupportedOpcodes.push({ offset, opcode, message: `Unsupported/unknown W2D single-byte opcode 0x${opcode.toString(16).padStart(2, '0')}.` });
62
+ // Unknown single-byte opcodes usually imply an unsupported WHIP object and operand length is not knowable.
63
+ // Stop rather than desynchronizing and producing corrupt geometry.
64
+ break;
65
+ }
66
+ catch (err) {
67
+ this.unsupportedOpcodes.push({ offset, opcode, message: `Failed while decoding W2D opcode 0x${opcode.toString(16)}: ${String(err)}` });
68
+ break;
69
+ }
70
+ }
71
+ if (this.primitives.length > 0) {
72
+ this.diagnostics.push(diag('info', 'W2D_BINARY_PARSED', `Parsed ${this.primitives.length} vector primitives from binary WHIP!/W2D stream (${this.opcodes} opcodes).`, this.sourcePath));
73
+ }
74
+ else {
75
+ this.diagnostics.push(diag('warning', 'W2D_BINARY_NO_GEOMETRY', 'Binary WHIP!/W2D parser did not emit supported geometry.', this.sourcePath));
76
+ }
77
+ if (this.unsupportedOpcodes.length > 0) {
78
+ const first = this.unsupportedOpcodes[0];
79
+ this.diagnostics.push(diag('warning', 'W2D_BINARY_PARTIAL', `${this.unsupportedOpcodes.length} unsupported opcode(s); first at byte ${first.offset}: ${first.message}`, this.sourcePath));
80
+ }
81
+ return {
82
+ primitives: this.primitives,
83
+ diagnostics: this.diagnostics,
84
+ bounds: computeBounds(this.primitives),
85
+ opcodes: this.opcodes,
86
+ unsupportedOpcodes: this.unsupportedOpcodes
87
+ };
88
+ }
89
+ parseSingleByteOpcode(opcode) {
90
+ switch (opcode) {
91
+ case 0x00:
92
+ return true;
93
+ case 0x03: { // set color RGBA
94
+ const r = this.u8();
95
+ const g = this.u8();
96
+ const b = this.u8();
97
+ const a = this.u8();
98
+ this.state.stroke = rgba(r, g, b, a);
99
+ if (this.state.fillOn)
100
+ this.state.fill = this.state.stroke;
101
+ return true;
102
+ }
103
+ case 0x06: // set font
104
+ this.readBinaryFont();
105
+ return true;
106
+ case 0x0c: { // draw line, 16-bit relative
107
+ const pts = [this.point16Relative(), this.point16Relative()];
108
+ this.polyline(pts);
109
+ return true;
110
+ }
111
+ case 0x10: { // polyline/polygon, 16-bit relative pointset
112
+ const pts = this.pointSet16Relative();
113
+ this.pointSetPrimitive(pts);
114
+ return true;
115
+ }
116
+ case 0x12: { // full circle, 16-bit relative center + uint16 radius
117
+ const center = this.point16Relative();
118
+ const radius = this.u16();
119
+ this.ellipsePrimitive(center, radius, radius, 0, 0, 0);
120
+ return true;
121
+ }
122
+ case 0x14: { // polytriangle, 16-bit relative pointset
123
+ this.polytriangles(this.pointSet16Relative());
124
+ return true;
125
+ }
126
+ case 0x17: // line weight, int32
127
+ this.state.lineWidth = Math.max(0.2, Math.abs(this.i32()));
128
+ return true;
129
+ case 0x18: { // complex text
130
+ const p = this.point32Relative();
131
+ const text = cleanW2dText(this.readWhipString());
132
+ this.text(p, text);
133
+ this.skipComplexTextOptions();
134
+ return true;
135
+ }
136
+ case 0x92: { // partial circle, 32-bit relative center + radius + short start/end angles
137
+ const center = this.point32Relative();
138
+ const radius = this.u32();
139
+ const start = this.u16();
140
+ const end = this.u16();
141
+ this.ellipsePrimitive(center, radius, radius, start, end, 0, false);
142
+ return true;
143
+ }
144
+ case 0xac: // layer by index
145
+ this.readCount();
146
+ return true;
147
+ case 0xcc: // line pattern by index/enum; read count is tolerated for common Toolkit output.
148
+ this.readCount();
149
+ return true;
150
+ case 0xe4: // blockref/macro-ish rendition compact records in classic files: read an index count when present.
151
+ this.readCount();
152
+ return true;
153
+ }
154
+ const ch = String.fromCharCode(opcode);
155
+ switch (ch) {
156
+ case 'B': // Bezier 32R, approximate by drawing control polygon when encountered.
157
+ this.polyline(this.pointSet32Relative());
158
+ return true;
159
+ case 'C':
160
+ this.state.stroke = aciColor(this.readAsciiInteger());
161
+ if (this.state.fillOn)
162
+ this.state.fill = this.state.stroke;
163
+ return true;
164
+ case 'c':
165
+ this.state.stroke = aciColor(this.u8());
166
+ if (this.state.fillOn)
167
+ this.state.fill = this.state.stroke;
168
+ return true;
169
+ case 'E': {
170
+ const center = this.readAsciiPoint();
171
+ const axes = this.readAsciiPoint();
172
+ this.ellipsePrimitive(center, axes.x, axes.y, 0, 0, 0);
173
+ return true;
174
+ }
175
+ case 'e': {
176
+ const center = this.point32Relative();
177
+ const major = this.u32();
178
+ const minor = this.u32();
179
+ const start = this.u16();
180
+ const end = this.u16();
181
+ const tilt = this.u16();
182
+ this.ellipsePrimitive(center, major, minor, start, end, tilt);
183
+ return true;
184
+ }
185
+ case 'F':
186
+ this.state.fillOn = true;
187
+ this.state.fill = this.state.stroke;
188
+ return true;
189
+ case 'f':
190
+ this.state.fillOn = false;
191
+ this.state.fill = undefined;
192
+ return true;
193
+ case 'L': {
194
+ const pts = [this.readAsciiPoint(), this.readAsciiPoint()];
195
+ this.polyline(pts);
196
+ return true;
197
+ }
198
+ case 'l': {
199
+ const pts = [this.point32Relative(), this.point32Relative()];
200
+ this.polyline(pts);
201
+ return true;
202
+ }
203
+ case 'N': // object node, 32-bit form; object-node state is ignored for rendering.
204
+ this.i32();
205
+ return true;
206
+ case 'n': // object node, 16-bit form
207
+ this.u16();
208
+ return true;
209
+ case 'O': // origin, absolute point
210
+ this.current = { x: this.i32(), y: this.i32() };
211
+ return true;
212
+ case 'P':
213
+ this.pointSetPrimitive(this.pointSetAscii());
214
+ return true;
215
+ case 'p':
216
+ this.pointSetPrimitive(this.pointSet32Relative());
217
+ return true;
218
+ case 'R': {
219
+ const center = this.readAsciiPoint();
220
+ const radius = this.readAsciiInteger();
221
+ this.ellipsePrimitive(center, radius, radius, 0, 0, 0);
222
+ return true;
223
+ }
224
+ case 'r': {
225
+ const center = this.point32Relative();
226
+ const radius = this.u32();
227
+ this.ellipsePrimitive(center, radius, radius, 0, 0, 0);
228
+ return true;
229
+ }
230
+ case 'T':
231
+ this.polytriangles(this.pointSetAscii());
232
+ return true;
233
+ case 't':
234
+ this.polytriangles(this.pointSet32Relative());
235
+ return true;
236
+ case 'V':
237
+ this.state.visible = true;
238
+ return true;
239
+ case 'v':
240
+ this.state.visible = false;
241
+ return true;
242
+ case 'x': {
243
+ const p = this.point32Relative();
244
+ const text = cleanW2dText(this.readWhipString());
245
+ this.text(p, text);
246
+ return true;
247
+ }
248
+ }
249
+ return false;
250
+ }
251
+ parseExtendedAscii(start) {
252
+ const { token, expr } = this.readBalancedExpression(start);
253
+ switch (token) {
254
+ case '(W2D': {
255
+ const m = expr.match(/V(\d+)\.(\d+)/i);
256
+ if (m)
257
+ this.decimalRevision = Number(m[1]) * 100 + Number(m[2]);
258
+ break;
259
+ }
260
+ case '(Color': {
261
+ const nums = numbers(expr);
262
+ if (nums.length >= 3)
263
+ this.state.stroke = rgb(nums[0], nums[1], nums[2]);
264
+ break;
265
+ }
266
+ case '(LineWeight': {
267
+ const nums = numbers(expr);
268
+ if (nums.length >= 1)
269
+ this.state.lineWidth = Math.max(0.2, Math.abs(nums[0]));
270
+ break;
271
+ }
272
+ case '(Font': {
273
+ const height = expr.match(/\(Height\s+(-?\d+)/i)?.[1];
274
+ const rotation = expr.match(/\(Rotation\s+(-?\d+)/i)?.[1];
275
+ const name = expr.match(/\(Name\s+(['"])(.*?)\1/i)?.[2];
276
+ if (height)
277
+ this.state.fontHeight = Math.max(1, Math.abs(Number(height)));
278
+ if (rotation)
279
+ this.state.fontRotation = Number(rotation) || 0;
280
+ if (name)
281
+ this.state.fontName = name;
282
+ break;
283
+ }
284
+ case '(EndOfDWF':
285
+ this.pos = this.bytes.length;
286
+ break;
287
+ default:
288
+ // Metadata, viewport, line style, font extension, fill pattern, etc. are not needed to emit base geometry.
289
+ break;
290
+ }
291
+ }
292
+ parseExtendedBinary(start) {
293
+ if (this.remaining() < 6)
294
+ return false;
295
+ const size = this.u32();
296
+ const opcode = this.u16();
297
+ if (opcode === 0x0011 || opcode === 0x0010 || opcode === 0x0123) {
298
+ this.unsupportedOpcodes.push({ offset: start, opcode: `{${opcode.toString(16)}}`, message: 'Embedded compressed WHIP block detected. This build renders uncompressed binary W2D streams and manifest markup XML; compressed nested blocks are skipped.' });
299
+ // In many markup W2D resources the compressed block length is zero and the rest of the resource is the payload.
300
+ this.pos = size > 0 && this.pos + size <= this.bytes.length ? this.pos + size : this.bytes.length;
301
+ return true;
302
+ }
303
+ // Most extended binary objects carry an offset/length-like field. If it is non-zero and plausible, skip it.
304
+ if (size > 0 && this.pos + size <= this.bytes.length) {
305
+ this.pos += size;
306
+ return true;
307
+ }
308
+ this.unsupportedOpcodes.push({ offset: start, opcode: `{${opcode.toString(16)}}`, message: 'Unsupported extended binary WHIP object with unknown payload size.' });
309
+ return false;
310
+ }
311
+ readBinaryFont() {
312
+ const fields = this.u16();
313
+ if (fields & FONT_NAME_BIT)
314
+ this.state.fontName = this.readWhipString() || this.state.fontName;
315
+ if (fields & FONT_CHARSET_BIT)
316
+ this.u8();
317
+ if (fields & FONT_PITCH_BIT)
318
+ this.u8();
319
+ if (fields & FONT_FAMILY_BIT)
320
+ this.u8();
321
+ if (fields & FONT_STYLE_BIT)
322
+ this.u8();
323
+ if (fields & FONT_HEIGHT_BIT)
324
+ this.state.fontHeight = Math.max(1, Math.abs(this.i32()));
325
+ if (fields & FONT_ROTATION_BIT)
326
+ this.state.fontRotation = this.u16();
327
+ if (fields & FONT_WIDTH_SCALE_BIT)
328
+ this.u16();
329
+ if (fields & FONT_SPACING_BIT)
330
+ this.u16();
331
+ if (fields & FONT_OBLIQUE_BIT)
332
+ this.u16();
333
+ if (fields & FONT_FLAGS_BIT)
334
+ this.i32();
335
+ }
336
+ skipComplexTextOptions() {
337
+ this.skipScoringOption();
338
+ this.skipScoringOption();
339
+ for (let i = 0; i < 4; i++) {
340
+ this.i32();
341
+ this.i32();
342
+ }
343
+ // Reserved/CharPos option appears in package-format DWF 6+. Tolerate EOF for older streams.
344
+ if (this.decimalRevision >= 600 && this.pos < this.bytes.length) {
345
+ const count = Math.max(0, this.readCount() - 1);
346
+ for (let i = 0; i < count; i++)
347
+ this.readCount();
348
+ }
349
+ }
350
+ skipScoringOption() {
351
+ const count = Math.max(0, this.readCount() - 1);
352
+ for (let i = 0; i < count; i++)
353
+ this.readCount();
354
+ }
355
+ pointSetPrimitive(pts) {
356
+ if (this.state.fillOn)
357
+ this.polygon(pts, this.state.fill ?? this.state.stroke, this.state.stroke);
358
+ else
359
+ this.polyline(pts);
360
+ }
361
+ polytriangles(pts) {
362
+ if (!this.state.visible || pts.length < 3)
363
+ return;
364
+ for (let i = 0; i + 2 < pts.length; i++) {
365
+ this.polygon([pts[i], pts[i + 1], pts[i + 2]], this.state.stroke, undefined);
366
+ }
367
+ }
368
+ polyline(pts) {
369
+ if (!this.state.visible || pts.length < 2)
370
+ return;
371
+ this.primitives.push({ type: 'polyline', points: flattenPoints(pts), stroke: this.state.stroke, lineWidth: this.state.lineWidth });
372
+ }
373
+ polygon(pts, fill, stroke) {
374
+ if (!this.state.visible || pts.length < 3)
375
+ return;
376
+ this.primitives.push({ type: 'polygon', points: flattenPoints(pts), fill, stroke, lineWidth: this.state.lineWidth });
377
+ }
378
+ ellipsePrimitive(center, major, minor, start, end, tilt, filled = this.state.fillOn) {
379
+ const pts = ellipsePoints(center, major, minor, start, end, tilt);
380
+ if (filled)
381
+ this.polygon(pts, this.state.fill ?? this.state.stroke, this.state.stroke);
382
+ else
383
+ this.polyline(pts);
384
+ }
385
+ text(p, text) {
386
+ if (!this.state.visible || !text.trim())
387
+ return;
388
+ this.primitives.push({
389
+ type: 'text',
390
+ x: p.x,
391
+ y: p.y,
392
+ text,
393
+ size: Math.max(10, Math.abs(this.state.fontHeight)),
394
+ fill: this.state.stroke
395
+ });
396
+ }
397
+ pointSet32Relative() {
398
+ const count = this.readCount();
399
+ const pts = [];
400
+ for (let i = 0; i < count; i++)
401
+ pts.push(this.point32Relative());
402
+ return pts;
403
+ }
404
+ pointSet16Relative() {
405
+ const count = this.readCount();
406
+ const pts = [];
407
+ for (let i = 0; i < count; i++)
408
+ pts.push(this.point16Relative());
409
+ return pts;
410
+ }
411
+ pointSetAscii() {
412
+ const count = this.readAsciiInteger();
413
+ const pts = [];
414
+ for (let i = 0; i < count; i++)
415
+ pts.push(this.readAsciiPoint());
416
+ return pts;
417
+ }
418
+ point32Relative() {
419
+ const dx = this.i32();
420
+ const dy = this.i32();
421
+ this.current = { x: this.current.x + dx, y: this.current.y + dy };
422
+ return { ...this.current };
423
+ }
424
+ point16Relative() {
425
+ const dx = this.i16();
426
+ const dy = this.i16();
427
+ this.current = { x: this.current.x + dx, y: this.current.y + dy };
428
+ return { ...this.current };
429
+ }
430
+ readAsciiPoint() {
431
+ const x = this.readAsciiInteger();
432
+ if (this.peek() === 0x2c)
433
+ this.pos++;
434
+ const y = this.readAsciiInteger();
435
+ return { x, y };
436
+ }
437
+ readAsciiInteger() {
438
+ this.eatWhitespace();
439
+ const start = this.pos;
440
+ if (this.peek() === 0x2b || this.peek() === 0x2d)
441
+ this.pos++;
442
+ while (this.pos < this.bytes.length && isDigit(this.bytes[this.pos]))
443
+ this.pos++;
444
+ const raw = ascii(this.bytes.subarray(start, this.pos));
445
+ const n = Number.parseInt(raw, 10);
446
+ return Number.isFinite(n) ? n : 0;
447
+ }
448
+ readWhipString() {
449
+ this.eatWhitespace();
450
+ const lead = this.peek();
451
+ if (lead === 0x7b) { // binary UTF-16 string: { int32 length, uint16 data..., }
452
+ this.pos++;
453
+ const length = this.i32();
454
+ const byteLength = Math.max(0, length * 2);
455
+ if (this.pos + byteLength > this.bytes.length)
456
+ throw new Error('Truncated binary WHIP string.');
457
+ const s = new TextDecoder('utf-16le').decode(this.bytes.subarray(this.pos, this.pos + byteLength));
458
+ this.pos += byteLength;
459
+ if (this.peek() === 0x7d)
460
+ this.pos++;
461
+ return s;
462
+ }
463
+ if (lead === 0x22 || lead === 0x27) {
464
+ const quote = this.u8();
465
+ const out = [];
466
+ let escaped = false;
467
+ while (this.pos < this.bytes.length) {
468
+ const c = this.u8();
469
+ if (escaped) {
470
+ out.push(c);
471
+ escaped = false;
472
+ }
473
+ else if (c === 0x5c) {
474
+ out.push(c);
475
+ escaped = true;
476
+ }
477
+ else if (c === quote) {
478
+ break;
479
+ }
480
+ else {
481
+ out.push(c);
482
+ }
483
+ }
484
+ return ascii(new Uint8Array(out));
485
+ }
486
+ const start = this.pos;
487
+ while (this.pos < this.bytes.length) {
488
+ const c = this.bytes[this.pos];
489
+ if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d || c === 0x29)
490
+ break;
491
+ this.pos++;
492
+ }
493
+ return ascii(this.bytes.subarray(start, this.pos));
494
+ }
495
+ readBalancedExpression(start) {
496
+ this.pos = start;
497
+ let depth = 0;
498
+ let quote = 0;
499
+ let escaped = false;
500
+ while (this.pos < this.bytes.length) {
501
+ const c = this.u8();
502
+ if (quote) {
503
+ if (escaped)
504
+ escaped = false;
505
+ else if (c === 0x5c)
506
+ escaped = true;
507
+ else if (c === quote)
508
+ quote = 0;
509
+ }
510
+ else if (c === 0x22 || c === 0x27) {
511
+ quote = c;
512
+ }
513
+ else if (c === 0x28) {
514
+ depth++;
515
+ }
516
+ else if (c === 0x29) {
517
+ depth--;
518
+ if (depth <= 0)
519
+ break;
520
+ }
521
+ }
522
+ const raw = ascii(this.bytes.subarray(start, this.pos));
523
+ const token = raw.match(/^\([^\s()]+/)?.[0] ?? '(';
524
+ return { token, expr: raw };
525
+ }
526
+ readCount() {
527
+ const c = this.u8();
528
+ return c !== 0 ? c : 256 + this.u16();
529
+ }
530
+ eatWhitespace() {
531
+ while (this.pos < this.bytes.length) {
532
+ const b = this.bytes[this.pos];
533
+ if (b === 0x20 || b === 0x09 || b === 0x0a || b === 0x0d)
534
+ this.pos++;
535
+ else
536
+ break;
537
+ }
538
+ }
539
+ remaining() { return this.bytes.length - this.pos; }
540
+ peek() { return this.pos < this.bytes.length ? this.bytes[this.pos] : undefined; }
541
+ u8() { if (this.remaining() < 1)
542
+ throw new Error('Unexpected EOF'); return this.view.getUint8(this.pos++); }
543
+ i16() { if (this.remaining() < 2)
544
+ throw new Error('Unexpected EOF'); const n = this.view.getInt16(this.pos, true); this.pos += 2; return n; }
545
+ u16() { if (this.remaining() < 2)
546
+ throw new Error('Unexpected EOF'); const n = this.view.getUint16(this.pos, true); this.pos += 2; return n; }
547
+ i32() { if (this.remaining() < 4)
548
+ throw new Error('Unexpected EOF'); const n = this.view.getInt32(this.pos, true); this.pos += 4; return n; }
549
+ u32() { if (this.remaining() < 4)
550
+ throw new Error('Unexpected EOF'); const n = this.view.getUint32(this.pos, true); this.pos += 4; return n; }
551
+ }
552
+ function flattenPoints(pts) {
553
+ const out = [];
554
+ for (const p of pts)
555
+ out.push(p.x, p.y);
556
+ return out;
557
+ }
558
+ function ellipsePoints(center, major, minor, start, end, tilt) {
559
+ let s = start;
560
+ let e = end;
561
+ if (s === 0 && e === 0)
562
+ e = 0x10000;
563
+ else if (e <= s)
564
+ e += 0x10000;
565
+ const sweep = e - s;
566
+ const segs = Math.max(16, Math.min(144, Math.ceil(Math.abs(sweep) / 0x10000 * 96)));
567
+ const tiltRad = tilt * Math.PI * 2 / 0x10000;
568
+ const ct = Math.cos(tiltRad), st = Math.sin(tiltRad);
569
+ const pts = [];
570
+ for (let i = 0; i <= segs; i++) {
571
+ const a = (s + sweep * i / segs) * Math.PI * 2 / 0x10000;
572
+ const x = Math.cos(a) * major;
573
+ const y = Math.sin(a) * minor;
574
+ pts.push({ x: center.x + x * ct - y * st, y: center.y + x * st + y * ct });
575
+ }
576
+ return pts;
577
+ }
578
+ function cleanW2dText(text) {
579
+ return text
580
+ .replace(/\\P/gi, '\n')
581
+ .replace(/\\L|\\l/g, '')
582
+ .replace(/\\O|\\o/g, '')
583
+ .replace(/\\H[-+]?\d*\.?\d+x;/gi, '')
584
+ .replace(/\\S([^;]+);/gi, '$1')
585
+ .replace(/\\[A-Za-z][^;]*;/g, '')
586
+ .replace(/\\~/g, ' ')
587
+ .replace(/\\\\/g, '\\')
588
+ .trim();
589
+ }
590
+ function computeBounds(primitives) {
591
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
592
+ const add = (x, y) => { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); };
593
+ for (const p of primitives) {
594
+ if ('points' in p) {
595
+ for (let i = 0; i + 1 < p.points.length; i += 2)
596
+ add(p.points[i], p.points[i + 1]);
597
+ }
598
+ else if (p.type === 'rect') {
599
+ add(p.x, p.y);
600
+ add(p.x + p.width, p.y + p.height);
601
+ }
602
+ else if (p.type === 'text') {
603
+ const size = p.size ?? 12;
604
+ add(p.x, p.y);
605
+ add(p.x, p.y + size);
606
+ }
607
+ else if (p.type === 'path') {
608
+ for (const c of p.commands) {
609
+ if ('x' in c && 'y' in c)
610
+ add(c.x, c.y);
611
+ if ('x1' in c && 'y1' in c)
612
+ add(c.x1, c.y1);
613
+ if ('x2' in c && 'y2' in c)
614
+ add(c.x2, c.y2);
615
+ }
616
+ }
617
+ }
618
+ return Number.isFinite(minX) ? { minX, minY, maxX, maxY } : undefined;
619
+ }
620
+ function isDigit(b) { return b >= 0x30 && b <= 0x39; }
621
+ function ascii(bytes) { return Array.from(bytes, b => String.fromCharCode(b)).join(''); }
622
+ function rgb(r, g, b) { return `rgb(${clamp8(r)}, ${clamp8(g)}, ${clamp8(b)})`; }
623
+ function rgba(r, g, b, a) { return `rgba(${clamp8(r)}, ${clamp8(g)}, ${clamp8(b)}, ${Math.max(0, Math.min(1, a / 255))})`; }
624
+ function clamp8(n) { return Math.max(0, Math.min(255, Math.round(n))); }
625
+ function numbers(s) { return (s.match(/[-+]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][-+]?\d+)?/g) ?? []).map(Number).filter(Number.isFinite); }
626
+ function aciColor(index) {
627
+ const table = ['#000000', '#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#ff00ff', '#000000', '#808080', '#c0c0c0'];
628
+ return table[index] ?? '#000000';
629
+ }
@@ -0,0 +1,13 @@
1
+ import { type Diagnostic } from './types.js';
2
+ import type { W2dPrimitive } from './document.js';
3
+ export interface W2dTextParseResult {
4
+ primitives: W2dPrimitive[];
5
+ diagnostics: Diagnostic[];
6
+ bounds?: {
7
+ minX: number;
8
+ minY: number;
9
+ maxX: number;
10
+ maxY: number;
11
+ };
12
+ }
13
+ export declare function parseW2dText(bytes: Uint8Array, sourcePath: string): W2dTextParseResult;