@cadview/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,4048 @@
1
+ 'use strict';
2
+
3
+ var RBush = require('rbush');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var RBush__default = /*#__PURE__*/_interopDefault(RBush);
8
+
9
+ // src/parser/tokenizer.ts
10
+ function tokenize(content) {
11
+ const text = content.replace(/\r\n?/g, "\n");
12
+ const tokens = [];
13
+ const len = text.length;
14
+ let pos = 0;
15
+ while (pos < len) {
16
+ while (pos < len && text.charCodeAt(pos) === 10) pos++;
17
+ if (pos >= len) break;
18
+ let lineEnd = text.indexOf("\n", pos);
19
+ if (lineEnd === -1) lineEnd = len;
20
+ const codeLine = text.substring(pos, lineEnd).trim();
21
+ pos = lineEnd + 1;
22
+ const code = parseInt(codeLine, 10);
23
+ if (isNaN(code)) {
24
+ if (pos < len) {
25
+ lineEnd = text.indexOf("\n", pos);
26
+ if (lineEnd === -1) lineEnd = len;
27
+ pos = lineEnd + 1;
28
+ }
29
+ continue;
30
+ }
31
+ if (pos >= len) break;
32
+ lineEnd = text.indexOf("\n", pos);
33
+ if (lineEnd === -1) lineEnd = len;
34
+ const value = text.substring(pos, lineEnd).replace(/\s+$/, "");
35
+ pos = lineEnd + 1;
36
+ tokens.push({ code, value });
37
+ }
38
+ return tokens;
39
+ }
40
+
41
+ // src/parser/sections/header.ts
42
+ function parseHeader(tokens, i, header) {
43
+ while (i < tokens.length) {
44
+ const token = tokens[i];
45
+ if (token.code === 0 && token.value === "ENDSEC") return i + 1;
46
+ if (token.code === 9) {
47
+ const varName = token.value;
48
+ i++;
49
+ switch (varName) {
50
+ case "$ACADVER":
51
+ if (i < tokens.length) {
52
+ header.acadVersion = tokens[i].value;
53
+ i++;
54
+ }
55
+ break;
56
+ case "$EXTMIN":
57
+ if (i < tokens.length) {
58
+ const result = readHeaderPoint3D(tokens, i);
59
+ header.extMin = result.point;
60
+ i = result.nextIndex;
61
+ }
62
+ break;
63
+ case "$EXTMAX":
64
+ if (i < tokens.length) {
65
+ const result = readHeaderPoint3D(tokens, i);
66
+ header.extMax = result.point;
67
+ i = result.nextIndex;
68
+ }
69
+ break;
70
+ case "$INSUNITS":
71
+ if (i < tokens.length) {
72
+ header.insUnits = parseInt(tokens[i].value, 10);
73
+ i++;
74
+ }
75
+ break;
76
+ case "$MEASUREMENT":
77
+ if (i < tokens.length) {
78
+ header.measurement = parseInt(tokens[i].value, 10);
79
+ i++;
80
+ }
81
+ break;
82
+ case "$LTSCALE":
83
+ if (i < tokens.length) {
84
+ header.ltScale = parseFloat(tokens[i].value);
85
+ i++;
86
+ }
87
+ break;
88
+ case "$DWGCODEPAGE":
89
+ if (i < tokens.length) {
90
+ header.dwgCodePage = tokens[i].value;
91
+ i++;
92
+ }
93
+ break;
94
+ case "$HANDSEED":
95
+ if (i < tokens.length) {
96
+ header.handleSeed = tokens[i].value;
97
+ i++;
98
+ }
99
+ break;
100
+ default:
101
+ while (i < tokens.length && tokens[i].code !== 9 && tokens[i].code !== 0) {
102
+ header[varName] = tokens[i].value;
103
+ i++;
104
+ }
105
+ }
106
+ } else {
107
+ i++;
108
+ }
109
+ }
110
+ return i;
111
+ }
112
+ function readHeaderPoint3D(tokens, i) {
113
+ const point = { x: 0, y: 0, z: 0 };
114
+ while (i < tokens.length) {
115
+ const code = tokens[i].code;
116
+ if (code === 10) {
117
+ point.x = parseFloat(tokens[i].value);
118
+ i++;
119
+ } else if (code === 20) {
120
+ point.y = parseFloat(tokens[i].value);
121
+ i++;
122
+ } else if (code === 30) {
123
+ point.z = parseFloat(tokens[i].value);
124
+ i++;
125
+ } else break;
126
+ }
127
+ return { point, nextIndex: i };
128
+ }
129
+
130
+ // src/parser/sections/tables.ts
131
+ function parseTables(tokens, i, doc) {
132
+ while (i < tokens.length) {
133
+ const token = tokens[i];
134
+ if (token.code === 0 && token.value === "ENDSEC") return i + 1;
135
+ if (token.code === 0 && token.value === "TABLE") {
136
+ i++;
137
+ if (i >= tokens.length) break;
138
+ const tableType = tokens[i].value;
139
+ i++;
140
+ switch (tableType) {
141
+ case "LAYER":
142
+ i = parseLayerTable(tokens, i, doc.layers);
143
+ break;
144
+ case "LTYPE":
145
+ i = parseLTypeTable(tokens, i, doc.lineTypes);
146
+ break;
147
+ case "STYLE":
148
+ i = parseStyleTable(tokens, i, doc.styles);
149
+ break;
150
+ default:
151
+ i = skipTable(tokens, i);
152
+ break;
153
+ }
154
+ } else {
155
+ i++;
156
+ }
157
+ }
158
+ return i;
159
+ }
160
+ function skipTable(tokens, i) {
161
+ while (i < tokens.length) {
162
+ if (tokens[i].code === 0 && tokens[i].value === "ENDTAB") return i + 1;
163
+ i++;
164
+ }
165
+ return i;
166
+ }
167
+ function parseLayerTable(tokens, i, layers) {
168
+ while (i < tokens.length) {
169
+ const token = tokens[i];
170
+ if (token.code === 0 && token.value === "ENDTAB") return i + 1;
171
+ if (token.code === 0 && token.value === "LAYER") {
172
+ i++;
173
+ const layer = {
174
+ name: "0",
175
+ color: 7,
176
+ lineType: "Continuous",
177
+ flags: 0,
178
+ lineWeight: -3,
179
+ isOff: false,
180
+ isFrozen: false,
181
+ isLocked: false
182
+ };
183
+ while (i < tokens.length && tokens[i].code !== 0) {
184
+ const tag = tokens[i];
185
+ switch (tag.code) {
186
+ case 2:
187
+ layer.name = tag.value;
188
+ break;
189
+ case 6:
190
+ layer.lineType = tag.value;
191
+ break;
192
+ case 62:
193
+ layer.color = parseInt(tag.value, 10);
194
+ break;
195
+ case 70:
196
+ layer.flags = parseInt(tag.value, 10);
197
+ break;
198
+ case 370:
199
+ layer.lineWeight = parseInt(tag.value, 10);
200
+ break;
201
+ case 420:
202
+ layer.trueColor = parseInt(tag.value, 10);
203
+ break;
204
+ }
205
+ i++;
206
+ }
207
+ layers.set(layer.name, layer);
208
+ } else {
209
+ i++;
210
+ }
211
+ }
212
+ return i;
213
+ }
214
+ function parseLTypeTable(tokens, i, lineTypes) {
215
+ while (i < tokens.length) {
216
+ const token = tokens[i];
217
+ if (token.code === 0 && token.value === "ENDTAB") return i + 1;
218
+ if (token.code === 0 && token.value === "LTYPE") {
219
+ i++;
220
+ const lt = {
221
+ name: "",
222
+ description: "",
223
+ pattern: [],
224
+ totalLength: 0
225
+ };
226
+ while (i < tokens.length && tokens[i].code !== 0) {
227
+ const tag = tokens[i];
228
+ switch (tag.code) {
229
+ case 2:
230
+ lt.name = tag.value;
231
+ break;
232
+ case 3:
233
+ lt.description = tag.value;
234
+ break;
235
+ case 40:
236
+ lt.totalLength = parseFloat(tag.value);
237
+ break;
238
+ case 49:
239
+ lt.pattern.push(parseFloat(tag.value));
240
+ break;
241
+ }
242
+ i++;
243
+ }
244
+ if (lt.name) lineTypes.set(lt.name, lt);
245
+ } else {
246
+ i++;
247
+ }
248
+ }
249
+ return i;
250
+ }
251
+ function parseStyleTable(tokens, i, styles) {
252
+ while (i < tokens.length) {
253
+ const token = tokens[i];
254
+ if (token.code === 0 && token.value === "ENDTAB") return i + 1;
255
+ if (token.code === 0 && token.value === "STYLE") {
256
+ i++;
257
+ const style = {
258
+ name: "",
259
+ fontName: "",
260
+ bigFontName: "",
261
+ height: 0,
262
+ widthFactor: 1,
263
+ obliqueAngle: 0
264
+ };
265
+ while (i < tokens.length && tokens[i].code !== 0) {
266
+ const tag = tokens[i];
267
+ switch (tag.code) {
268
+ case 2:
269
+ style.name = tag.value;
270
+ break;
271
+ case 3:
272
+ style.fontName = tag.value;
273
+ break;
274
+ case 4:
275
+ style.bigFontName = tag.value;
276
+ break;
277
+ case 40:
278
+ style.height = parseFloat(tag.value);
279
+ break;
280
+ case 41:
281
+ style.widthFactor = parseFloat(tag.value);
282
+ break;
283
+ case 50:
284
+ style.obliqueAngle = parseFloat(tag.value);
285
+ break;
286
+ }
287
+ i++;
288
+ }
289
+ if (style.name) styles.set(style.name, style);
290
+ } else {
291
+ i++;
292
+ }
293
+ }
294
+ return i;
295
+ }
296
+
297
+ // src/parser/entities/base.ts
298
+ function parseBaseEntity(tags) {
299
+ const base = {
300
+ type: "",
301
+ layer: "0",
302
+ color: 256,
303
+ // BYLAYER
304
+ lineType: "BYLAYER",
305
+ lineTypeScale: 1,
306
+ lineWeight: -1,
307
+ // BYLAYER
308
+ visible: true,
309
+ extrusion: { x: 0, y: 0, z: 1 }
310
+ };
311
+ for (const tag of tags) {
312
+ switch (tag.code) {
313
+ case 5:
314
+ base.handle = tag.value;
315
+ break;
316
+ case 8:
317
+ base.layer = tag.value;
318
+ break;
319
+ case 6:
320
+ base.lineType = tag.value;
321
+ break;
322
+ case 48:
323
+ base.lineTypeScale = parseFloat(tag.value);
324
+ break;
325
+ case 60:
326
+ base.visible = tag.value === "0" || tag.value === "";
327
+ break;
328
+ case 62:
329
+ base.color = parseInt(tag.value, 10);
330
+ break;
331
+ case 370:
332
+ base.lineWeight = parseInt(tag.value, 10);
333
+ break;
334
+ case 420:
335
+ base.trueColor = parseInt(tag.value, 10);
336
+ break;
337
+ case 210:
338
+ base.extrusion.x = parseFloat(tag.value);
339
+ break;
340
+ case 220:
341
+ base.extrusion.y = parseFloat(tag.value);
342
+ break;
343
+ case 230:
344
+ base.extrusion.z = parseFloat(tag.value);
345
+ break;
346
+ }
347
+ }
348
+ return base;
349
+ }
350
+
351
+ // src/parser/entities/line.ts
352
+ function parseLine(tags) {
353
+ const base = parseBaseEntity(tags);
354
+ const entity = {
355
+ ...base,
356
+ type: "LINE",
357
+ start: { x: 0, y: 0, z: 0 },
358
+ end: { x: 0, y: 0, z: 0 }
359
+ };
360
+ for (const tag of tags) {
361
+ switch (tag.code) {
362
+ case 10:
363
+ entity.start.x = parseFloat(tag.value);
364
+ break;
365
+ case 20:
366
+ entity.start.y = parseFloat(tag.value);
367
+ break;
368
+ case 30:
369
+ entity.start.z = parseFloat(tag.value);
370
+ break;
371
+ case 11:
372
+ entity.end.x = parseFloat(tag.value);
373
+ break;
374
+ case 21:
375
+ entity.end.y = parseFloat(tag.value);
376
+ break;
377
+ case 31:
378
+ entity.end.z = parseFloat(tag.value);
379
+ break;
380
+ }
381
+ }
382
+ return entity;
383
+ }
384
+
385
+ // src/parser/entities/circle.ts
386
+ function parseCircle(tags) {
387
+ const base = parseBaseEntity(tags);
388
+ const entity = {
389
+ ...base,
390
+ type: "CIRCLE",
391
+ center: { x: 0, y: 0, z: 0 },
392
+ radius: 0
393
+ };
394
+ for (const tag of tags) {
395
+ switch (tag.code) {
396
+ case 10:
397
+ entity.center.x = parseFloat(tag.value);
398
+ break;
399
+ case 20:
400
+ entity.center.y = parseFloat(tag.value);
401
+ break;
402
+ case 30:
403
+ entity.center.z = parseFloat(tag.value);
404
+ break;
405
+ case 40:
406
+ entity.radius = parseFloat(tag.value);
407
+ break;
408
+ }
409
+ }
410
+ return entity;
411
+ }
412
+
413
+ // src/parser/entities/arc.ts
414
+ function parseArc(tags) {
415
+ const base = parseBaseEntity(tags);
416
+ const entity = {
417
+ ...base,
418
+ type: "ARC",
419
+ center: { x: 0, y: 0, z: 0 },
420
+ radius: 0,
421
+ startAngle: 0,
422
+ endAngle: 360
423
+ };
424
+ for (const tag of tags) {
425
+ switch (tag.code) {
426
+ case 10:
427
+ entity.center.x = parseFloat(tag.value);
428
+ break;
429
+ case 20:
430
+ entity.center.y = parseFloat(tag.value);
431
+ break;
432
+ case 30:
433
+ entity.center.z = parseFloat(tag.value);
434
+ break;
435
+ case 40:
436
+ entity.radius = parseFloat(tag.value);
437
+ break;
438
+ case 50:
439
+ entity.startAngle = parseFloat(tag.value);
440
+ break;
441
+ case 51:
442
+ entity.endAngle = parseFloat(tag.value);
443
+ break;
444
+ }
445
+ }
446
+ return entity;
447
+ }
448
+
449
+ // src/parser/entities/lwpolyline.ts
450
+ function parseLwPolyline(tags) {
451
+ const base = parseBaseEntity(tags);
452
+ const entity = {
453
+ ...base,
454
+ type: "LWPOLYLINE",
455
+ vertices: [],
456
+ closed: false,
457
+ constantWidth: 0,
458
+ elevation: 0
459
+ };
460
+ let currentVertex = null;
461
+ for (const tag of tags) {
462
+ switch (tag.code) {
463
+ case 70:
464
+ entity.closed = (parseInt(tag.value, 10) & 1) !== 0;
465
+ break;
466
+ case 38:
467
+ entity.elevation = parseFloat(tag.value);
468
+ break;
469
+ case 43:
470
+ entity.constantWidth = parseFloat(tag.value);
471
+ break;
472
+ case 10:
473
+ if (currentVertex) entity.vertices.push(currentVertex);
474
+ currentVertex = { x: parseFloat(tag.value), y: 0, bulge: 0, startWidth: 0, endWidth: 0 };
475
+ break;
476
+ case 20:
477
+ if (currentVertex) currentVertex.y = parseFloat(tag.value);
478
+ break;
479
+ case 40:
480
+ if (currentVertex) currentVertex.startWidth = parseFloat(tag.value);
481
+ break;
482
+ case 41:
483
+ if (currentVertex) currentVertex.endWidth = parseFloat(tag.value);
484
+ break;
485
+ case 42:
486
+ if (currentVertex) currentVertex.bulge = parseFloat(tag.value);
487
+ break;
488
+ }
489
+ }
490
+ if (currentVertex) entity.vertices.push(currentVertex);
491
+ return entity;
492
+ }
493
+
494
+ // src/parser/entities/polyline.ts
495
+ function parsePolyline(tags) {
496
+ const base = parseBaseEntity(tags);
497
+ const entity = {
498
+ ...base,
499
+ type: "POLYLINE",
500
+ vertices: [],
501
+ // filled in by entities dispatcher from VERTEX entities
502
+ closed: false,
503
+ is3d: false
504
+ };
505
+ for (const tag of tags) {
506
+ switch (tag.code) {
507
+ case 70: {
508
+ const flags = parseInt(tag.value, 10);
509
+ entity.closed = (flags & 1) !== 0;
510
+ entity.is3d = (flags & 8) !== 0;
511
+ break;
512
+ }
513
+ }
514
+ }
515
+ return entity;
516
+ }
517
+
518
+ // src/parser/entities/ellipse.ts
519
+ function parseEllipse(tags) {
520
+ const base = parseBaseEntity(tags);
521
+ const entity = {
522
+ ...base,
523
+ type: "ELLIPSE",
524
+ center: { x: 0, y: 0, z: 0 },
525
+ majorAxis: { x: 1, y: 0, z: 0 },
526
+ minorRatio: 1,
527
+ startParam: 0,
528
+ endParam: Math.PI * 2
529
+ };
530
+ for (const tag of tags) {
531
+ switch (tag.code) {
532
+ case 10:
533
+ entity.center.x = parseFloat(tag.value);
534
+ break;
535
+ case 20:
536
+ entity.center.y = parseFloat(tag.value);
537
+ break;
538
+ case 30:
539
+ entity.center.z = parseFloat(tag.value);
540
+ break;
541
+ case 11:
542
+ entity.majorAxis.x = parseFloat(tag.value);
543
+ break;
544
+ case 21:
545
+ entity.majorAxis.y = parseFloat(tag.value);
546
+ break;
547
+ case 31:
548
+ entity.majorAxis.z = parseFloat(tag.value);
549
+ break;
550
+ case 40:
551
+ entity.minorRatio = parseFloat(tag.value);
552
+ break;
553
+ case 41:
554
+ entity.startParam = parseFloat(tag.value);
555
+ break;
556
+ case 42:
557
+ entity.endParam = parseFloat(tag.value);
558
+ break;
559
+ }
560
+ }
561
+ return entity;
562
+ }
563
+
564
+ // src/parser/entities/spline.ts
565
+ function parseSpline(tags) {
566
+ const base = parseBaseEntity(tags);
567
+ const entity = {
568
+ ...base,
569
+ type: "SPLINE",
570
+ degree: 3,
571
+ flags: 0,
572
+ knots: [],
573
+ controlPoints: [],
574
+ fitPoints: [],
575
+ weights: []
576
+ };
577
+ let currentCP = null;
578
+ let currentFP = null;
579
+ for (const tag of tags) {
580
+ switch (tag.code) {
581
+ case 70:
582
+ entity.flags = parseInt(tag.value, 10);
583
+ break;
584
+ case 71:
585
+ entity.degree = parseInt(tag.value, 10);
586
+ break;
587
+ // 72 = knot count, 73 = control point count, 74 = fit point count — informational, skip
588
+ case 40:
589
+ entity.knots.push(parseFloat(tag.value));
590
+ break;
591
+ case 41:
592
+ entity.weights.push(parseFloat(tag.value));
593
+ break;
594
+ case 10:
595
+ if (currentCP) entity.controlPoints.push(currentCP);
596
+ currentCP = { x: parseFloat(tag.value), y: 0, z: 0 };
597
+ break;
598
+ case 20:
599
+ if (currentCP) currentCP.y = parseFloat(tag.value);
600
+ break;
601
+ case 30:
602
+ if (currentCP) currentCP.z = parseFloat(tag.value);
603
+ break;
604
+ case 11:
605
+ if (currentFP) entity.fitPoints.push(currentFP);
606
+ currentFP = { x: parseFloat(tag.value), y: 0, z: 0 };
607
+ break;
608
+ case 21:
609
+ if (currentFP) currentFP.y = parseFloat(tag.value);
610
+ break;
611
+ case 31:
612
+ if (currentFP) currentFP.z = parseFloat(tag.value);
613
+ break;
614
+ case 12:
615
+ if (!entity.startTangent) entity.startTangent = { x: 0, y: 0, z: 0 };
616
+ entity.startTangent.x = parseFloat(tag.value);
617
+ break;
618
+ case 22:
619
+ if (entity.startTangent) entity.startTangent.y = parseFloat(tag.value);
620
+ break;
621
+ case 32:
622
+ if (entity.startTangent) entity.startTangent.z = parseFloat(tag.value);
623
+ break;
624
+ case 13:
625
+ if (!entity.endTangent) entity.endTangent = { x: 0, y: 0, z: 0 };
626
+ entity.endTangent.x = parseFloat(tag.value);
627
+ break;
628
+ case 23:
629
+ if (entity.endTangent) entity.endTangent.y = parseFloat(tag.value);
630
+ break;
631
+ case 33:
632
+ if (entity.endTangent) entity.endTangent.z = parseFloat(tag.value);
633
+ break;
634
+ }
635
+ }
636
+ if (currentCP) entity.controlPoints.push(currentCP);
637
+ if (currentFP) entity.fitPoints.push(currentFP);
638
+ return entity;
639
+ }
640
+
641
+ // src/parser/entities/text.ts
642
+ function parseText(tags) {
643
+ const base = parseBaseEntity(tags);
644
+ const entity = {
645
+ ...base,
646
+ type: "TEXT",
647
+ text: "",
648
+ insertionPoint: { x: 0, y: 0, z: 0 },
649
+ height: 1,
650
+ rotation: 0,
651
+ widthFactor: 1,
652
+ obliqueAngle: 0,
653
+ style: "STANDARD",
654
+ hAlign: 0,
655
+ vAlign: 0,
656
+ generationFlags: 0
657
+ };
658
+ for (const tag of tags) {
659
+ switch (tag.code) {
660
+ case 1:
661
+ entity.text = tag.value;
662
+ break;
663
+ case 10:
664
+ entity.insertionPoint.x = parseFloat(tag.value);
665
+ break;
666
+ case 20:
667
+ entity.insertionPoint.y = parseFloat(tag.value);
668
+ break;
669
+ case 30:
670
+ entity.insertionPoint.z = parseFloat(tag.value);
671
+ break;
672
+ case 11:
673
+ if (!entity.alignmentPoint) entity.alignmentPoint = { x: 0, y: 0, z: 0 };
674
+ entity.alignmentPoint.x = parseFloat(tag.value);
675
+ break;
676
+ case 21:
677
+ if (!entity.alignmentPoint) entity.alignmentPoint = { x: 0, y: 0, z: 0 };
678
+ entity.alignmentPoint.y = parseFloat(tag.value);
679
+ break;
680
+ case 31:
681
+ if (!entity.alignmentPoint) entity.alignmentPoint = { x: 0, y: 0, z: 0 };
682
+ entity.alignmentPoint.z = parseFloat(tag.value);
683
+ break;
684
+ case 40:
685
+ entity.height = parseFloat(tag.value);
686
+ break;
687
+ case 41:
688
+ entity.widthFactor = parseFloat(tag.value);
689
+ break;
690
+ case 50:
691
+ entity.rotation = parseFloat(tag.value);
692
+ break;
693
+ case 51:
694
+ entity.obliqueAngle = parseFloat(tag.value);
695
+ break;
696
+ case 7:
697
+ entity.style = tag.value;
698
+ break;
699
+ case 71:
700
+ entity.generationFlags = parseInt(tag.value, 10);
701
+ break;
702
+ case 72:
703
+ entity.hAlign = parseInt(tag.value, 10);
704
+ break;
705
+ case 73:
706
+ entity.vAlign = parseInt(tag.value, 10);
707
+ break;
708
+ }
709
+ }
710
+ return entity;
711
+ }
712
+
713
+ // src/parser/entities/mtext.ts
714
+ function parseMText(tags) {
715
+ const base = parseBaseEntity(tags);
716
+ const entity = {
717
+ ...base,
718
+ type: "MTEXT",
719
+ text: "",
720
+ insertionPoint: { x: 0, y: 0, z: 0 },
721
+ height: 1,
722
+ width: 0,
723
+ attachmentPoint: 1,
724
+ drawingDirection: 1,
725
+ rotation: 0,
726
+ lineSpacingStyle: 1,
727
+ lineSpacingFactor: 1,
728
+ style: "STANDARD",
729
+ bgFill: 0,
730
+ bgFillScale: 1.5
731
+ };
732
+ const textChunks = [];
733
+ for (const tag of tags) {
734
+ switch (tag.code) {
735
+ case 1:
736
+ textChunks.push(tag.value);
737
+ break;
738
+ case 3:
739
+ textChunks.push(tag.value);
740
+ break;
741
+ case 7:
742
+ entity.style = tag.value;
743
+ break;
744
+ case 10:
745
+ entity.insertionPoint.x = parseFloat(tag.value);
746
+ break;
747
+ case 20:
748
+ entity.insertionPoint.y = parseFloat(tag.value);
749
+ break;
750
+ case 30:
751
+ entity.insertionPoint.z = parseFloat(tag.value);
752
+ break;
753
+ case 11:
754
+ if (!entity.textDirection) entity.textDirection = { x: 0, y: 0, z: 0 };
755
+ entity.textDirection.x = parseFloat(tag.value);
756
+ break;
757
+ case 21:
758
+ if (!entity.textDirection) entity.textDirection = { x: 0, y: 0, z: 0 };
759
+ entity.textDirection.y = parseFloat(tag.value);
760
+ break;
761
+ case 31:
762
+ if (!entity.textDirection) entity.textDirection = { x: 0, y: 0, z: 0 };
763
+ entity.textDirection.z = parseFloat(tag.value);
764
+ break;
765
+ case 40:
766
+ entity.height = parseFloat(tag.value);
767
+ break;
768
+ case 41:
769
+ entity.width = parseFloat(tag.value);
770
+ break;
771
+ case 44:
772
+ entity.lineSpacingFactor = parseFloat(tag.value);
773
+ break;
774
+ case 50:
775
+ entity.rotation = parseFloat(tag.value);
776
+ break;
777
+ case 71:
778
+ entity.attachmentPoint = parseInt(tag.value, 10);
779
+ break;
780
+ case 72:
781
+ entity.drawingDirection = parseInt(tag.value, 10);
782
+ break;
783
+ case 73:
784
+ entity.lineSpacingStyle = parseInt(tag.value, 10);
785
+ break;
786
+ case 90:
787
+ entity.bgFill = parseInt(tag.value, 10);
788
+ break;
789
+ }
790
+ }
791
+ entity.text = textChunks.join("");
792
+ return entity;
793
+ }
794
+
795
+ // src/parser/entities/insert.ts
796
+ function parseInsert(tags) {
797
+ const base = parseBaseEntity(tags);
798
+ const entity = {
799
+ ...base,
800
+ type: "INSERT",
801
+ blockName: "",
802
+ insertionPoint: { x: 0, y: 0, z: 0 },
803
+ scaleX: 1,
804
+ scaleY: 1,
805
+ scaleZ: 1,
806
+ rotation: 0,
807
+ columnCount: 1,
808
+ rowCount: 1,
809
+ columnSpacing: 0,
810
+ rowSpacing: 0,
811
+ attribs: []
812
+ };
813
+ for (const tag of tags) {
814
+ switch (tag.code) {
815
+ case 2:
816
+ entity.blockName = tag.value;
817
+ break;
818
+ case 10:
819
+ entity.insertionPoint.x = parseFloat(tag.value);
820
+ break;
821
+ case 20:
822
+ entity.insertionPoint.y = parseFloat(tag.value);
823
+ break;
824
+ case 30:
825
+ entity.insertionPoint.z = parseFloat(tag.value);
826
+ break;
827
+ case 41:
828
+ entity.scaleX = parseFloat(tag.value);
829
+ break;
830
+ case 42:
831
+ entity.scaleY = parseFloat(tag.value);
832
+ break;
833
+ case 43:
834
+ entity.scaleZ = parseFloat(tag.value);
835
+ break;
836
+ case 44:
837
+ entity.columnSpacing = parseFloat(tag.value);
838
+ break;
839
+ case 45:
840
+ entity.rowSpacing = parseFloat(tag.value);
841
+ break;
842
+ case 50:
843
+ entity.rotation = parseFloat(tag.value);
844
+ break;
845
+ case 70:
846
+ entity.columnCount = parseInt(tag.value, 10) || 1;
847
+ break;
848
+ case 71:
849
+ entity.rowCount = parseInt(tag.value, 10) || 1;
850
+ break;
851
+ }
852
+ }
853
+ return entity;
854
+ }
855
+
856
+ // src/parser/entities/dimension.ts
857
+ function parseDimension(tags) {
858
+ const base = parseBaseEntity(tags);
859
+ const entity = {
860
+ ...base,
861
+ type: "DIMENSION",
862
+ blockName: "",
863
+ dimStyle: "STANDARD",
864
+ dimType: 0,
865
+ defPoint: { x: 0, y: 0, z: 0 },
866
+ textMidpoint: { x: 0, y: 0, z: 0 },
867
+ textOverride: "",
868
+ rotation: 0,
869
+ textRotation: 0,
870
+ leaderLength: 0
871
+ };
872
+ for (const tag of tags) {
873
+ switch (tag.code) {
874
+ case 1:
875
+ entity.textOverride = tag.value;
876
+ break;
877
+ case 2:
878
+ entity.blockName = tag.value;
879
+ break;
880
+ case 3:
881
+ entity.dimStyle = tag.value;
882
+ break;
883
+ case 10:
884
+ entity.defPoint.x = parseFloat(tag.value);
885
+ break;
886
+ case 20:
887
+ entity.defPoint.y = parseFloat(tag.value);
888
+ break;
889
+ case 30:
890
+ entity.defPoint.z = parseFloat(tag.value);
891
+ break;
892
+ case 11:
893
+ entity.textMidpoint.x = parseFloat(tag.value);
894
+ break;
895
+ case 21:
896
+ entity.textMidpoint.y = parseFloat(tag.value);
897
+ break;
898
+ case 31:
899
+ entity.textMidpoint.z = parseFloat(tag.value);
900
+ break;
901
+ case 13:
902
+ if (!entity.defPoint2) entity.defPoint2 = { x: 0, y: 0, z: 0 };
903
+ entity.defPoint2.x = parseFloat(tag.value);
904
+ break;
905
+ case 23:
906
+ if (!entity.defPoint2) entity.defPoint2 = { x: 0, y: 0, z: 0 };
907
+ entity.defPoint2.y = parseFloat(tag.value);
908
+ break;
909
+ case 33:
910
+ if (!entity.defPoint2) entity.defPoint2 = { x: 0, y: 0, z: 0 };
911
+ entity.defPoint2.z = parseFloat(tag.value);
912
+ break;
913
+ case 14:
914
+ if (!entity.defPoint3) entity.defPoint3 = { x: 0, y: 0, z: 0 };
915
+ entity.defPoint3.x = parseFloat(tag.value);
916
+ break;
917
+ case 24:
918
+ if (!entity.defPoint3) entity.defPoint3 = { x: 0, y: 0, z: 0 };
919
+ entity.defPoint3.y = parseFloat(tag.value);
920
+ break;
921
+ case 34:
922
+ if (!entity.defPoint3) entity.defPoint3 = { x: 0, y: 0, z: 0 };
923
+ entity.defPoint3.z = parseFloat(tag.value);
924
+ break;
925
+ case 15:
926
+ if (!entity.defPoint4) entity.defPoint4 = { x: 0, y: 0, z: 0 };
927
+ entity.defPoint4.x = parseFloat(tag.value);
928
+ break;
929
+ case 25:
930
+ if (!entity.defPoint4) entity.defPoint4 = { x: 0, y: 0, z: 0 };
931
+ entity.defPoint4.y = parseFloat(tag.value);
932
+ break;
933
+ case 35:
934
+ if (!entity.defPoint4) entity.defPoint4 = { x: 0, y: 0, z: 0 };
935
+ entity.defPoint4.z = parseFloat(tag.value);
936
+ break;
937
+ case 16:
938
+ if (!entity.defPoint5) entity.defPoint5 = { x: 0, y: 0, z: 0 };
939
+ entity.defPoint5.x = parseFloat(tag.value);
940
+ break;
941
+ case 26:
942
+ if (!entity.defPoint5) entity.defPoint5 = { x: 0, y: 0, z: 0 };
943
+ entity.defPoint5.y = parseFloat(tag.value);
944
+ break;
945
+ case 36:
946
+ if (!entity.defPoint5) entity.defPoint5 = { x: 0, y: 0, z: 0 };
947
+ entity.defPoint5.z = parseFloat(tag.value);
948
+ break;
949
+ case 40:
950
+ entity.leaderLength = parseFloat(tag.value);
951
+ break;
952
+ case 50:
953
+ entity.rotation = parseFloat(tag.value);
954
+ break;
955
+ case 53:
956
+ entity.textRotation = parseFloat(tag.value);
957
+ break;
958
+ case 70:
959
+ entity.dimType = parseInt(tag.value, 10);
960
+ break;
961
+ }
962
+ }
963
+ return entity;
964
+ }
965
+
966
+ // src/parser/entities/hatch.ts
967
+ function parseHatch(tags) {
968
+ const base = parseBaseEntity(tags);
969
+ const entity = {
970
+ ...base,
971
+ type: "HATCH",
972
+ patternName: "",
973
+ solidFill: true,
974
+ associative: false,
975
+ hatchStyle: 0,
976
+ patternType: 1,
977
+ patternAngle: 0,
978
+ patternScale: 1,
979
+ boundaryPaths: []
980
+ };
981
+ let i = 0;
982
+ while (i < tags.length) {
983
+ const tag = tags[i];
984
+ switch (tag.code) {
985
+ case 2:
986
+ entity.patternName = tag.value;
987
+ break;
988
+ case 41:
989
+ entity.patternScale = parseFloat(tag.value);
990
+ break;
991
+ case 52:
992
+ entity.patternAngle = parseFloat(tag.value);
993
+ break;
994
+ case 70:
995
+ entity.solidFill = tag.value === "1";
996
+ break;
997
+ case 71:
998
+ entity.associative = tag.value === "1";
999
+ break;
1000
+ case 75:
1001
+ entity.hatchStyle = parseInt(tag.value, 10);
1002
+ break;
1003
+ case 76:
1004
+ entity.patternType = parseInt(tag.value, 10);
1005
+ break;
1006
+ case 91: {
1007
+ const pathCount = parseInt(tag.value, 10);
1008
+ i++;
1009
+ for (let p = 0; p < pathCount && i < tags.length; p++) {
1010
+ const result = parseBoundaryPath(tags, i);
1011
+ entity.boundaryPaths.push(result.path);
1012
+ i = result.nextIndex;
1013
+ }
1014
+ continue;
1015
+ }
1016
+ }
1017
+ i++;
1018
+ }
1019
+ return entity;
1020
+ }
1021
+ function parseBoundaryPath(tags, i) {
1022
+ let flags = 0;
1023
+ if (i < tags.length && tags[i].code === 92) {
1024
+ flags = parseInt(tags[i].value, 10);
1025
+ i++;
1026
+ }
1027
+ const isPolyline = (flags & 2) !== 0;
1028
+ if (isPolyline) {
1029
+ return parsePolylinePath(tags, i, flags);
1030
+ } else {
1031
+ return parseEdgePath(tags, i, flags);
1032
+ }
1033
+ }
1034
+ function parsePolylinePath(tags, i, flags) {
1035
+ const path = {
1036
+ type: "polyline",
1037
+ vertices: [],
1038
+ bulges: [],
1039
+ flags
1040
+ };
1041
+ let hasBulge = false;
1042
+ if (i < tags.length && tags[i].code === 72) {
1043
+ hasBulge = tags[i].value === "1";
1044
+ i++;
1045
+ }
1046
+ if (i < tags.length && tags[i].code === 73) {
1047
+ path.isClosed = tags[i].value === "1";
1048
+ i++;
1049
+ }
1050
+ let vertexCount = 0;
1051
+ if (i < tags.length && tags[i].code === 93) {
1052
+ vertexCount = parseInt(tags[i].value, 10);
1053
+ i++;
1054
+ }
1055
+ for (let v = 0; v < vertexCount && i < tags.length; v++) {
1056
+ const vertex = { x: 0, y: 0 };
1057
+ if (tags[i].code === 10) {
1058
+ vertex.x = parseFloat(tags[i].value);
1059
+ i++;
1060
+ }
1061
+ if (i < tags.length && tags[i].code === 20) {
1062
+ vertex.y = parseFloat(tags[i].value);
1063
+ i++;
1064
+ }
1065
+ path.vertices.push(vertex);
1066
+ if (hasBulge && i < tags.length && tags[i].code === 42) {
1067
+ path.bulges.push(parseFloat(tags[i].value));
1068
+ i++;
1069
+ } else if (hasBulge) {
1070
+ path.bulges.push(0);
1071
+ }
1072
+ }
1073
+ while (i < tags.length && (tags[i].code === 97 || tags[i].code === 330)) {
1074
+ if (tags[i].code === 97) {
1075
+ const count = parseInt(tags[i].value, 10);
1076
+ i++;
1077
+ for (let s = 0; s < count && i < tags.length && tags[i].code === 330; s++) {
1078
+ i++;
1079
+ }
1080
+ } else {
1081
+ i++;
1082
+ }
1083
+ }
1084
+ return { path, nextIndex: i };
1085
+ }
1086
+ function parseEdgePath(tags, i, flags) {
1087
+ const path = {
1088
+ type: "edges",
1089
+ edges: [],
1090
+ flags
1091
+ };
1092
+ let edgeCount = 0;
1093
+ if (i < tags.length && tags[i].code === 93) {
1094
+ edgeCount = parseInt(tags[i].value, 10);
1095
+ i++;
1096
+ }
1097
+ for (let e = 0; e < edgeCount && i < tags.length; e++) {
1098
+ if (tags[i].code !== 72) break;
1099
+ const edgeType = parseInt(tags[i].value, 10);
1100
+ i++;
1101
+ switch (edgeType) {
1102
+ case 1: {
1103
+ const edge = {
1104
+ type: "line",
1105
+ start: { x: 0, y: 0 },
1106
+ end: { x: 0, y: 0 }
1107
+ };
1108
+ if (i < tags.length && tags[i].code === 10) {
1109
+ edge.start.x = parseFloat(tags[i].value);
1110
+ i++;
1111
+ }
1112
+ if (i < tags.length && tags[i].code === 20) {
1113
+ edge.start.y = parseFloat(tags[i].value);
1114
+ i++;
1115
+ }
1116
+ if (i < tags.length && tags[i].code === 11) {
1117
+ edge.end.x = parseFloat(tags[i].value);
1118
+ i++;
1119
+ }
1120
+ if (i < tags.length && tags[i].code === 21) {
1121
+ edge.end.y = parseFloat(tags[i].value);
1122
+ i++;
1123
+ }
1124
+ path.edges.push(edge);
1125
+ break;
1126
+ }
1127
+ case 2: {
1128
+ const edge = {
1129
+ type: "arc",
1130
+ center: { x: 0, y: 0 },
1131
+ radius: 0,
1132
+ startAngle: 0,
1133
+ endAngle: 360,
1134
+ ccw: true
1135
+ };
1136
+ if (i < tags.length && tags[i].code === 10) {
1137
+ edge.center.x = parseFloat(tags[i].value);
1138
+ i++;
1139
+ }
1140
+ if (i < tags.length && tags[i].code === 20) {
1141
+ edge.center.y = parseFloat(tags[i].value);
1142
+ i++;
1143
+ }
1144
+ if (i < tags.length && tags[i].code === 40) {
1145
+ edge.radius = parseFloat(tags[i].value);
1146
+ i++;
1147
+ }
1148
+ if (i < tags.length && tags[i].code === 50) {
1149
+ edge.startAngle = parseFloat(tags[i].value);
1150
+ i++;
1151
+ }
1152
+ if (i < tags.length && tags[i].code === 51) {
1153
+ edge.endAngle = parseFloat(tags[i].value);
1154
+ i++;
1155
+ }
1156
+ if (i < tags.length && tags[i].code === 73) {
1157
+ edge.ccw = tags[i].value === "1";
1158
+ i++;
1159
+ }
1160
+ path.edges.push(edge);
1161
+ break;
1162
+ }
1163
+ case 3: {
1164
+ const edge = {
1165
+ type: "ellipse",
1166
+ center: { x: 0, y: 0 },
1167
+ majorAxis: { x: 1, y: 0 },
1168
+ minorRatio: 1,
1169
+ startAngle: 0,
1170
+ endAngle: Math.PI * 2,
1171
+ ccw: true
1172
+ };
1173
+ if (i < tags.length && tags[i].code === 10) {
1174
+ edge.center.x = parseFloat(tags[i].value);
1175
+ i++;
1176
+ }
1177
+ if (i < tags.length && tags[i].code === 20) {
1178
+ edge.center.y = parseFloat(tags[i].value);
1179
+ i++;
1180
+ }
1181
+ if (i < tags.length && tags[i].code === 11) {
1182
+ edge.majorAxis.x = parseFloat(tags[i].value);
1183
+ i++;
1184
+ }
1185
+ if (i < tags.length && tags[i].code === 21) {
1186
+ edge.majorAxis.y = parseFloat(tags[i].value);
1187
+ i++;
1188
+ }
1189
+ if (i < tags.length && tags[i].code === 40) {
1190
+ edge.minorRatio = parseFloat(tags[i].value);
1191
+ i++;
1192
+ }
1193
+ if (i < tags.length && tags[i].code === 50) {
1194
+ edge.startAngle = parseFloat(tags[i].value);
1195
+ i++;
1196
+ }
1197
+ if (i < tags.length && tags[i].code === 51) {
1198
+ edge.endAngle = parseFloat(tags[i].value);
1199
+ i++;
1200
+ }
1201
+ if (i < tags.length && tags[i].code === 73) {
1202
+ edge.ccw = tags[i].value === "1";
1203
+ i++;
1204
+ }
1205
+ path.edges.push(edge);
1206
+ break;
1207
+ }
1208
+ case 4: {
1209
+ let degree = 3;
1210
+ const knots = [];
1211
+ const controlPoints = [];
1212
+ if (i < tags.length && tags[i].code === 94) {
1213
+ degree = parseInt(tags[i].value, 10);
1214
+ i++;
1215
+ }
1216
+ if (i < tags.length && tags[i].code === 73) {
1217
+ i++;
1218
+ }
1219
+ if (i < tags.length && tags[i].code === 74) {
1220
+ i++;
1221
+ }
1222
+ let knotCount = 0;
1223
+ if (i < tags.length && tags[i].code === 95) {
1224
+ knotCount = parseInt(tags[i].value, 10);
1225
+ i++;
1226
+ }
1227
+ let cpCount = 0;
1228
+ if (i < tags.length && tags[i].code === 96) {
1229
+ cpCount = parseInt(tags[i].value, 10);
1230
+ i++;
1231
+ }
1232
+ for (let k = 0; k < knotCount && i < tags.length && tags[i].code === 40; k++) {
1233
+ knots.push(parseFloat(tags[i].value));
1234
+ i++;
1235
+ }
1236
+ for (let c = 0; c < cpCount && i < tags.length; c++) {
1237
+ const pt = { x: 0, y: 0 };
1238
+ if (tags[i].code === 10) {
1239
+ pt.x = parseFloat(tags[i].value);
1240
+ i++;
1241
+ }
1242
+ if (i < tags.length && tags[i].code === 20) {
1243
+ pt.y = parseFloat(tags[i].value);
1244
+ i++;
1245
+ }
1246
+ controlPoints.push(pt);
1247
+ }
1248
+ path.edges.push({ type: "spline", degree, knots, controlPoints });
1249
+ break;
1250
+ }
1251
+ }
1252
+ }
1253
+ while (i < tags.length && (tags[i].code === 97 || tags[i].code === 330)) {
1254
+ if (tags[i].code === 97) {
1255
+ const count = parseInt(tags[i].value, 10);
1256
+ i++;
1257
+ for (let s = 0; s < count && i < tags.length && tags[i].code === 330; s++) {
1258
+ i++;
1259
+ }
1260
+ } else {
1261
+ i++;
1262
+ }
1263
+ }
1264
+ return { path, nextIndex: i };
1265
+ }
1266
+
1267
+ // src/parser/entities/point.ts
1268
+ function parsePoint(tags) {
1269
+ const base = parseBaseEntity(tags);
1270
+ const entity = {
1271
+ ...base,
1272
+ type: "POINT",
1273
+ position: { x: 0, y: 0, z: 0 }
1274
+ };
1275
+ for (const tag of tags) {
1276
+ switch (tag.code) {
1277
+ case 10:
1278
+ entity.position.x = parseFloat(tag.value);
1279
+ break;
1280
+ case 20:
1281
+ entity.position.y = parseFloat(tag.value);
1282
+ break;
1283
+ case 30:
1284
+ entity.position.z = parseFloat(tag.value);
1285
+ break;
1286
+ }
1287
+ }
1288
+ return entity;
1289
+ }
1290
+
1291
+ // src/parser/sections/entities.ts
1292
+ function parseEntities(tokens, i, entities) {
1293
+ while (i < tokens.length) {
1294
+ const token = tokens[i];
1295
+ if (token.code !== 0) {
1296
+ i++;
1297
+ continue;
1298
+ }
1299
+ const type = token.value;
1300
+ if (type === "ENDSEC" || type === "ENDBLK") return i + 1;
1301
+ i++;
1302
+ const entityTags = [];
1303
+ while (i < tokens.length && tokens[i].code !== 0) {
1304
+ entityTags.push(tokens[i]);
1305
+ i++;
1306
+ }
1307
+ switch (type) {
1308
+ case "LINE":
1309
+ entities.push(parseLine(entityTags));
1310
+ break;
1311
+ case "CIRCLE":
1312
+ entities.push(parseCircle(entityTags));
1313
+ break;
1314
+ case "ARC":
1315
+ entities.push(parseArc(entityTags));
1316
+ break;
1317
+ case "LWPOLYLINE":
1318
+ entities.push(parseLwPolyline(entityTags));
1319
+ break;
1320
+ case "ELLIPSE":
1321
+ entities.push(parseEllipse(entityTags));
1322
+ break;
1323
+ case "SPLINE":
1324
+ entities.push(parseSpline(entityTags));
1325
+ break;
1326
+ case "TEXT":
1327
+ entities.push(parseText(entityTags));
1328
+ break;
1329
+ case "MTEXT":
1330
+ entities.push(parseMText(entityTags));
1331
+ break;
1332
+ case "INSERT": {
1333
+ const insert = parseInsert(entityTags);
1334
+ const hasAttribs = entityTags.some((t) => t.code === 66 && t.value === "1");
1335
+ if (hasAttribs) {
1336
+ i = parseAttribs(tokens, i, insert.attribs);
1337
+ }
1338
+ entities.push(insert);
1339
+ break;
1340
+ }
1341
+ case "DIMENSION":
1342
+ entities.push(parseDimension(entityTags));
1343
+ break;
1344
+ case "HATCH":
1345
+ entities.push(parseHatch(entityTags));
1346
+ break;
1347
+ case "POINT":
1348
+ entities.push(parsePoint(entityTags));
1349
+ break;
1350
+ case "POLYLINE": {
1351
+ const polyline = parsePolyline(entityTags);
1352
+ i = parseVertices(tokens, i, polyline.vertices);
1353
+ entities.push(polyline);
1354
+ break;
1355
+ }
1356
+ }
1357
+ }
1358
+ return i;
1359
+ }
1360
+ function parseVertices(tokens, i, vertices) {
1361
+ while (i < tokens.length) {
1362
+ if (tokens[i].code !== 0) {
1363
+ i++;
1364
+ continue;
1365
+ }
1366
+ const type = tokens[i].value;
1367
+ if (type === "SEQEND") return i + 1;
1368
+ if (type === "ENDSEC" || type === "ENDBLK") return i;
1369
+ if (type === "VERTEX") {
1370
+ i++;
1371
+ const vertex = { x: 0, y: 0, bulge: 0, startWidth: 0, endWidth: 0 };
1372
+ while (i < tokens.length && tokens[i].code !== 0) {
1373
+ const tag = tokens[i];
1374
+ switch (tag.code) {
1375
+ case 10:
1376
+ vertex.x = parseFloat(tag.value);
1377
+ break;
1378
+ case 20:
1379
+ vertex.y = parseFloat(tag.value);
1380
+ break;
1381
+ case 42:
1382
+ vertex.bulge = parseFloat(tag.value);
1383
+ break;
1384
+ case 40:
1385
+ vertex.startWidth = parseFloat(tag.value);
1386
+ break;
1387
+ case 41:
1388
+ vertex.endWidth = parseFloat(tag.value);
1389
+ break;
1390
+ }
1391
+ i++;
1392
+ }
1393
+ vertices.push(vertex);
1394
+ } else {
1395
+ i++;
1396
+ while (i < tokens.length && tokens[i].code !== 0) i++;
1397
+ }
1398
+ }
1399
+ return i;
1400
+ }
1401
+ function parseAttribs(tokens, i, attribs) {
1402
+ while (i < tokens.length) {
1403
+ if (tokens[i].code !== 0) {
1404
+ i++;
1405
+ continue;
1406
+ }
1407
+ const type = tokens[i].value;
1408
+ if (type === "SEQEND") return i + 1;
1409
+ if (type === "ENDSEC" || type === "ENDBLK") return i;
1410
+ if (type === "ATTRIB") {
1411
+ i++;
1412
+ const attrib = {
1413
+ tag: "",
1414
+ text: "",
1415
+ insertionPoint: { x: 0, y: 0, z: 0 },
1416
+ height: 1,
1417
+ rotation: 0,
1418
+ style: "STANDARD",
1419
+ layer: "0",
1420
+ color: 256
1421
+ };
1422
+ while (i < tokens.length && tokens[i].code !== 0) {
1423
+ const tag = tokens[i];
1424
+ switch (tag.code) {
1425
+ case 1:
1426
+ attrib.text = tag.value;
1427
+ break;
1428
+ case 2:
1429
+ attrib.tag = tag.value;
1430
+ break;
1431
+ case 7:
1432
+ attrib.style = tag.value;
1433
+ break;
1434
+ case 8:
1435
+ attrib.layer = tag.value;
1436
+ break;
1437
+ case 10:
1438
+ attrib.insertionPoint.x = parseFloat(tag.value);
1439
+ break;
1440
+ case 20:
1441
+ attrib.insertionPoint.y = parseFloat(tag.value);
1442
+ break;
1443
+ case 30:
1444
+ attrib.insertionPoint.z = parseFloat(tag.value);
1445
+ break;
1446
+ case 40:
1447
+ attrib.height = parseFloat(tag.value);
1448
+ break;
1449
+ case 50:
1450
+ attrib.rotation = parseFloat(tag.value);
1451
+ break;
1452
+ case 62:
1453
+ attrib.color = parseInt(tag.value, 10);
1454
+ break;
1455
+ }
1456
+ i++;
1457
+ }
1458
+ attribs.push(attrib);
1459
+ } else {
1460
+ i++;
1461
+ while (i < tokens.length && tokens[i].code !== 0) i++;
1462
+ }
1463
+ }
1464
+ return i;
1465
+ }
1466
+
1467
+ // src/parser/sections/blocks.ts
1468
+ function parseBlocks(tokens, i, blocks) {
1469
+ while (i < tokens.length) {
1470
+ const token = tokens[i];
1471
+ if (token.code === 0 && token.value === "ENDSEC") return i + 1;
1472
+ if (token.code === 0 && token.value === "BLOCK") {
1473
+ i++;
1474
+ const block = {
1475
+ name: "",
1476
+ basePoint: { x: 0, y: 0, z: 0 },
1477
+ entities: [],
1478
+ flags: 0
1479
+ };
1480
+ while (i < tokens.length && tokens[i].code !== 0) {
1481
+ const tag = tokens[i];
1482
+ switch (tag.code) {
1483
+ case 2:
1484
+ block.name = tag.value;
1485
+ break;
1486
+ case 8:
1487
+ break;
1488
+ case 10:
1489
+ block.basePoint.x = parseFloat(tag.value);
1490
+ break;
1491
+ case 20:
1492
+ block.basePoint.y = parseFloat(tag.value);
1493
+ break;
1494
+ case 30:
1495
+ block.basePoint.z = parseFloat(tag.value);
1496
+ break;
1497
+ case 70:
1498
+ block.flags = parseInt(tag.value, 10);
1499
+ break;
1500
+ }
1501
+ i++;
1502
+ }
1503
+ i = parseEntities(tokens, i, block.entities);
1504
+ if (block.name && !block.name.startsWith("*Model_Space") && !block.name.startsWith("*Paper_Space")) {
1505
+ blocks.set(block.name, block);
1506
+ }
1507
+ } else {
1508
+ i++;
1509
+ }
1510
+ }
1511
+ return i;
1512
+ }
1513
+
1514
+ // src/parser/index.ts
1515
+ var BINARY_DXF_SENTINEL = "AutoCAD Binary DXF";
1516
+ var CODEPAGE_MAP = {
1517
+ "ANSI_874": "windows-874",
1518
+ "ANSI_932": "shift_jis",
1519
+ "ANSI_936": "gbk",
1520
+ "ANSI_949": "euc-kr",
1521
+ "ANSI_950": "big5",
1522
+ "ANSI_1250": "windows-1250",
1523
+ "ANSI_1251": "windows-1251",
1524
+ "ANSI_1252": "windows-1252",
1525
+ "ANSI_1253": "windows-1253",
1526
+ "ANSI_1254": "windows-1254",
1527
+ "ANSI_1255": "windows-1255",
1528
+ "ANSI_1256": "windows-1256",
1529
+ "ANSI_1257": "windows-1257",
1530
+ "ANSI_1258": "windows-1258"
1531
+ };
1532
+ var UTF8_MIN_VERSION = "AC1021";
1533
+ function createDefaultHeader() {
1534
+ return {
1535
+ acadVersion: "",
1536
+ insUnits: 0,
1537
+ measurement: 0,
1538
+ ltScale: 1
1539
+ };
1540
+ }
1541
+ function decodeUnicodeEscapes(text) {
1542
+ return text.replace(
1543
+ /\\U\+([0-9A-Fa-f]{4})/g,
1544
+ (_, hex) => String.fromCodePoint(parseInt(hex, 16))
1545
+ );
1546
+ }
1547
+ function decodeInput(input) {
1548
+ if (typeof input === "string") {
1549
+ return decodeUnicodeEscapes(input);
1550
+ }
1551
+ const bytes = new Uint8Array(input);
1552
+ const sentinelBytes = new TextDecoder("ascii").decode(bytes.slice(0, BINARY_DXF_SENTINEL.length));
1553
+ if (sentinelBytes === BINARY_DXF_SENTINEL) {
1554
+ throw new Error("Binary DXF format is not supported. Please export as ASCII DXF.");
1555
+ }
1556
+ let text = new TextDecoder("utf-8").decode(input);
1557
+ const versionMatch = text.match(/\$ACADVER[\s\S]*?\n\s*1\s*\n\s*(\S+)/);
1558
+ const version = versionMatch?.[1] ?? "";
1559
+ if (version && version < UTF8_MIN_VERSION) {
1560
+ const cpMatch = text.match(/\$DWGCODEPAGE[\s\S]*?\n\s*3\s*\n\s*(\S+)/);
1561
+ const codepage = cpMatch?.[1] ?? "";
1562
+ const encoding = CODEPAGE_MAP[codepage] ?? "windows-1252";
1563
+ if (encoding !== "utf-8") {
1564
+ text = new TextDecoder(encoding).decode(input);
1565
+ }
1566
+ }
1567
+ return decodeUnicodeEscapes(text);
1568
+ }
1569
+ function skipSection(tokens, i) {
1570
+ while (i < tokens.length) {
1571
+ if (tokens[i].code === 0 && tokens[i].value === "ENDSEC") {
1572
+ return i + 1;
1573
+ }
1574
+ i++;
1575
+ }
1576
+ return i;
1577
+ }
1578
+ function ensureDefaultLayer(doc) {
1579
+ if (!doc.layers.has("0")) {
1580
+ doc.layers.set("0", {
1581
+ name: "0",
1582
+ color: 7,
1583
+ lineType: "Continuous",
1584
+ flags: 0,
1585
+ lineWeight: -3,
1586
+ isOff: false,
1587
+ isFrozen: false,
1588
+ isLocked: false
1589
+ });
1590
+ }
1591
+ }
1592
+ function computeDerivedLayerFlags(doc) {
1593
+ for (const [, layer] of doc.layers) {
1594
+ layer.isOff = layer.color < 0;
1595
+ if (layer.isOff) {
1596
+ layer.color = Math.abs(layer.color);
1597
+ }
1598
+ layer.isFrozen = (layer.flags & 1) !== 0;
1599
+ layer.isLocked = (layer.flags & 4) !== 0;
1600
+ }
1601
+ }
1602
+ var DxfParseError = class extends Error {
1603
+ constructor(message, cause) {
1604
+ super(message);
1605
+ this.cause = cause;
1606
+ this.name = "DxfParseError";
1607
+ }
1608
+ };
1609
+ function parseDxf(input) {
1610
+ if (typeof input === "string") {
1611
+ if (input.length === 0) {
1612
+ throw new DxfParseError("Input is empty. Expected a DXF string or ArrayBuffer.");
1613
+ }
1614
+ } else if (input instanceof ArrayBuffer) {
1615
+ if (input.byteLength === 0) {
1616
+ throw new DxfParseError("Input ArrayBuffer is empty.");
1617
+ }
1618
+ } else {
1619
+ throw new DxfParseError("Invalid input type. Expected a string or ArrayBuffer.");
1620
+ }
1621
+ let text;
1622
+ try {
1623
+ text = decodeInput(input);
1624
+ } catch (err) {
1625
+ if (err instanceof Error && err.message.includes("Binary DXF")) {
1626
+ throw err;
1627
+ }
1628
+ throw new DxfParseError("Failed to decode DXF input.", err);
1629
+ }
1630
+ let tokens;
1631
+ try {
1632
+ tokens = tokenize(text);
1633
+ } catch (err) {
1634
+ throw new DxfParseError("Failed to tokenize DXF content.", err);
1635
+ }
1636
+ if (tokens.length === 0) {
1637
+ throw new DxfParseError("DXF content produced no tokens. The input may not be a valid DXF file.");
1638
+ }
1639
+ const doc = {
1640
+ header: createDefaultHeader(),
1641
+ layers: /* @__PURE__ */ new Map(),
1642
+ lineTypes: /* @__PURE__ */ new Map(),
1643
+ styles: /* @__PURE__ */ new Map(),
1644
+ blocks: /* @__PURE__ */ new Map(),
1645
+ entities: []
1646
+ };
1647
+ try {
1648
+ let i = 0;
1649
+ while (i < tokens.length) {
1650
+ const token = tokens[i];
1651
+ if (token.code === 0 && token.value === "SECTION") {
1652
+ i++;
1653
+ if (i >= tokens.length) break;
1654
+ const sectionName = tokens[i].value;
1655
+ i++;
1656
+ switch (sectionName) {
1657
+ case "HEADER":
1658
+ i = parseHeader(tokens, i, doc.header);
1659
+ break;
1660
+ case "TABLES":
1661
+ i = parseTables(tokens, i, doc);
1662
+ break;
1663
+ case "BLOCKS":
1664
+ i = parseBlocks(tokens, i, doc.blocks);
1665
+ break;
1666
+ case "ENTITIES":
1667
+ i = parseEntities(tokens, i, doc.entities);
1668
+ break;
1669
+ default:
1670
+ i = skipSection(tokens, i);
1671
+ break;
1672
+ }
1673
+ } else if (token.code === 0 && token.value === "EOF") {
1674
+ break;
1675
+ } else {
1676
+ i++;
1677
+ }
1678
+ }
1679
+ } catch (err) {
1680
+ throw new DxfParseError("Failed to parse DXF sections.", err);
1681
+ }
1682
+ ensureDefaultLayer(doc);
1683
+ computeDerivedLayerFlags(doc);
1684
+ return doc;
1685
+ }
1686
+
1687
+ // src/parser/colors.ts
1688
+ var ACI_COLORS = [
1689
+ "#000000",
1690
+ // 0: BYBLOCK
1691
+ "#FF0000",
1692
+ // 1: Red
1693
+ "#FFFF00",
1694
+ // 2: Yellow
1695
+ "#00FF00",
1696
+ // 3: Green
1697
+ "#00FFFF",
1698
+ // 4: Cyan
1699
+ "#0000FF",
1700
+ // 5: Blue
1701
+ "#FF00FF",
1702
+ // 6: Magenta
1703
+ "#FFFFFF",
1704
+ // 7: White
1705
+ "#808080",
1706
+ // 8: Dark gray
1707
+ "#C0C0C0",
1708
+ // 9: Light gray
1709
+ "#FF0000",
1710
+ // 10
1711
+ "#FF7F7F",
1712
+ // 11
1713
+ "#A50000",
1714
+ // 12
1715
+ "#A55252",
1716
+ // 13
1717
+ "#7F0000",
1718
+ // 14
1719
+ "#7F3F3F",
1720
+ // 15
1721
+ "#4C0000",
1722
+ // 16
1723
+ "#4C2626",
1724
+ // 17
1725
+ "#260000",
1726
+ // 18
1727
+ "#261313",
1728
+ // 19
1729
+ "#FF3F00",
1730
+ // 20
1731
+ "#FF9F7F",
1732
+ // 21
1733
+ "#A52900",
1734
+ // 22
1735
+ "#A56752",
1736
+ // 23
1737
+ "#7F1F00",
1738
+ // 24
1739
+ "#7F4F3F",
1740
+ // 25
1741
+ "#4C1300",
1742
+ // 26
1743
+ "#4C2F26",
1744
+ // 27
1745
+ "#260900",
1746
+ // 28
1747
+ "#261713",
1748
+ // 29
1749
+ "#FF7F00",
1750
+ // 30
1751
+ "#FFBF7F",
1752
+ // 31
1753
+ "#A55200",
1754
+ // 32
1755
+ "#A57C52",
1756
+ // 33
1757
+ "#7F3F00",
1758
+ // 34
1759
+ "#7F5F3F",
1760
+ // 35
1761
+ "#4C2600",
1762
+ // 36
1763
+ "#4C3926",
1764
+ // 37
1765
+ "#261300",
1766
+ // 38
1767
+ "#261C13",
1768
+ // 39
1769
+ "#FFBF00",
1770
+ // 40
1771
+ "#FFDF7F",
1772
+ // 41
1773
+ "#A57C00",
1774
+ // 42
1775
+ "#A59152",
1776
+ // 43
1777
+ "#7F5F00",
1778
+ // 44
1779
+ "#7F6F3F",
1780
+ // 45
1781
+ "#4C3900",
1782
+ // 46
1783
+ "#4C4226",
1784
+ // 47
1785
+ "#261C00",
1786
+ // 48
1787
+ "#262113",
1788
+ // 49
1789
+ "#FFFF00",
1790
+ // 50
1791
+ "#FFFF7F",
1792
+ // 51
1793
+ "#A5A500",
1794
+ // 52
1795
+ "#A5A552",
1796
+ // 53
1797
+ "#7F7F00",
1798
+ // 54
1799
+ "#7F7F3F",
1800
+ // 55
1801
+ "#4C4C00",
1802
+ // 56
1803
+ "#4C4C26",
1804
+ // 57
1805
+ "#262600",
1806
+ // 58
1807
+ "#262613",
1808
+ // 59
1809
+ "#BFFF00",
1810
+ // 60
1811
+ "#DFFF7F",
1812
+ // 61
1813
+ "#7CA500",
1814
+ // 62
1815
+ "#91A552",
1816
+ // 63
1817
+ "#5F7F00",
1818
+ // 64
1819
+ "#6F7F3F",
1820
+ // 65
1821
+ "#394C00",
1822
+ // 66
1823
+ "#424C26",
1824
+ // 67
1825
+ "#1C2600",
1826
+ // 68
1827
+ "#212613",
1828
+ // 69
1829
+ "#7FFF00",
1830
+ // 70
1831
+ "#BFFF7F",
1832
+ // 71
1833
+ "#52A500",
1834
+ // 72
1835
+ "#7CA552",
1836
+ // 73
1837
+ "#3F7F00",
1838
+ // 74
1839
+ "#5F7F3F",
1840
+ // 75
1841
+ "#264C00",
1842
+ // 76
1843
+ "#394C26",
1844
+ // 77
1845
+ "#132600",
1846
+ // 78
1847
+ "#1C2613",
1848
+ // 79
1849
+ "#3FFF00",
1850
+ // 80
1851
+ "#9FFF7F",
1852
+ // 81
1853
+ "#29A500",
1854
+ // 82
1855
+ "#67A552",
1856
+ // 83
1857
+ "#1F7F00",
1858
+ // 84
1859
+ "#4F7F3F",
1860
+ // 85
1861
+ "#134C00",
1862
+ // 86
1863
+ "#2F4C26",
1864
+ // 87
1865
+ "#092600",
1866
+ // 88
1867
+ "#172613",
1868
+ // 89
1869
+ "#00FF00",
1870
+ // 90
1871
+ "#7FFF7F",
1872
+ // 91
1873
+ "#00A500",
1874
+ // 92
1875
+ "#52A552",
1876
+ // 93
1877
+ "#007F00",
1878
+ // 94
1879
+ "#3F7F3F",
1880
+ // 95
1881
+ "#004C00",
1882
+ // 96
1883
+ "#264C26",
1884
+ // 97
1885
+ "#002600",
1886
+ // 98
1887
+ "#132613",
1888
+ // 99
1889
+ "#00FF3F",
1890
+ // 100
1891
+ "#7FFF9F",
1892
+ // 101
1893
+ "#00A529",
1894
+ // 102
1895
+ "#52A567",
1896
+ // 103
1897
+ "#007F1F",
1898
+ // 104
1899
+ "#3F7F4F",
1900
+ // 105
1901
+ "#004C13",
1902
+ // 106
1903
+ "#264C2F",
1904
+ // 107
1905
+ "#002609",
1906
+ // 108
1907
+ "#132617",
1908
+ // 109
1909
+ "#00FF7F",
1910
+ // 110
1911
+ "#7FFFBF",
1912
+ // 111
1913
+ "#00A552",
1914
+ // 112
1915
+ "#52A57C",
1916
+ // 113
1917
+ "#007F3F",
1918
+ // 114
1919
+ "#3F7F5F",
1920
+ // 115
1921
+ "#004C26",
1922
+ // 116
1923
+ "#264C39",
1924
+ // 117
1925
+ "#002613",
1926
+ // 118
1927
+ "#13261C",
1928
+ // 119
1929
+ "#00FFBF",
1930
+ // 120
1931
+ "#7FFFDF",
1932
+ // 121
1933
+ "#00A57C",
1934
+ // 122
1935
+ "#52A591",
1936
+ // 123
1937
+ "#007F5F",
1938
+ // 124
1939
+ "#3F7F6F",
1940
+ // 125
1941
+ "#004C39",
1942
+ // 126
1943
+ "#264C42",
1944
+ // 127
1945
+ "#00261C",
1946
+ // 128
1947
+ "#132621",
1948
+ // 129
1949
+ "#00FFFF",
1950
+ // 130
1951
+ "#7FFFFF",
1952
+ // 131
1953
+ "#00A5A5",
1954
+ // 132
1955
+ "#52A5A5",
1956
+ // 133
1957
+ "#007F7F",
1958
+ // 134
1959
+ "#3F7F7F",
1960
+ // 135
1961
+ "#004C4C",
1962
+ // 136
1963
+ "#264C4C",
1964
+ // 137
1965
+ "#002626",
1966
+ // 138
1967
+ "#132626",
1968
+ // 139
1969
+ "#00BFFF",
1970
+ // 140
1971
+ "#7FDFFF",
1972
+ // 141
1973
+ "#007CA5",
1974
+ // 142
1975
+ "#5291A5",
1976
+ // 143
1977
+ "#005F7F",
1978
+ // 144
1979
+ "#3F6F7F",
1980
+ // 145
1981
+ "#00394C",
1982
+ // 146
1983
+ "#26424C",
1984
+ // 147
1985
+ "#001C26",
1986
+ // 148
1987
+ "#132126",
1988
+ // 149
1989
+ "#007FFF",
1990
+ // 150
1991
+ "#7FBFFF",
1992
+ // 151
1993
+ "#0052A5",
1994
+ // 152
1995
+ "#527CA5",
1996
+ // 153
1997
+ "#003F7F",
1998
+ // 154
1999
+ "#3F5F7F",
2000
+ // 155
2001
+ "#00264C",
2002
+ // 156
2003
+ "#26394C",
2004
+ // 157
2005
+ "#001326",
2006
+ // 158
2007
+ "#131C26",
2008
+ // 159
2009
+ "#003FFF",
2010
+ // 160
2011
+ "#7F9FFF",
2012
+ // 161
2013
+ "#0029A5",
2014
+ // 162
2015
+ "#5267A5",
2016
+ // 163
2017
+ "#001F7F",
2018
+ // 164
2019
+ "#3F4F7F",
2020
+ // 165
2021
+ "#00134C",
2022
+ // 166
2023
+ "#262F4C",
2024
+ // 167
2025
+ "#000926",
2026
+ // 168
2027
+ "#131726",
2028
+ // 169
2029
+ "#0000FF",
2030
+ // 170
2031
+ "#7F7FFF",
2032
+ // 171
2033
+ "#0000A5",
2034
+ // 172
2035
+ "#5252A5",
2036
+ // 173
2037
+ "#00007F",
2038
+ // 174
2039
+ "#3F3F7F",
2040
+ // 175
2041
+ "#00004C",
2042
+ // 176
2043
+ "#26264C",
2044
+ // 177
2045
+ "#000026",
2046
+ // 178
2047
+ "#131326",
2048
+ // 179
2049
+ "#3F00FF",
2050
+ // 180
2051
+ "#9F7FFF",
2052
+ // 181
2053
+ "#2900A5",
2054
+ // 182
2055
+ "#6752A5",
2056
+ // 183
2057
+ "#1F007F",
2058
+ // 184
2059
+ "#4F3F7F",
2060
+ // 185
2061
+ "#13004C",
2062
+ // 186
2063
+ "#2F264C",
2064
+ // 187
2065
+ "#090026",
2066
+ // 188
2067
+ "#171326",
2068
+ // 189
2069
+ "#7F00FF",
2070
+ // 190
2071
+ "#BF7FFF",
2072
+ // 191
2073
+ "#5200A5",
2074
+ // 192
2075
+ "#7C52A5",
2076
+ // 193
2077
+ "#3F007F",
2078
+ // 194
2079
+ "#5F3F7F",
2080
+ // 195
2081
+ "#26004C",
2082
+ // 196
2083
+ "#39264C",
2084
+ // 197
2085
+ "#130026",
2086
+ // 198
2087
+ "#1C1326",
2088
+ // 199
2089
+ "#BF00FF",
2090
+ // 200
2091
+ "#DF7FFF",
2092
+ // 201
2093
+ "#7C00A5",
2094
+ // 202
2095
+ "#9152A5",
2096
+ // 203
2097
+ "#5F007F",
2098
+ // 204
2099
+ "#6F3F7F",
2100
+ // 205
2101
+ "#39004C",
2102
+ // 206
2103
+ "#42264C",
2104
+ // 207
2105
+ "#1C0026",
2106
+ // 208
2107
+ "#211326",
2108
+ // 209
2109
+ "#FF00FF",
2110
+ // 210
2111
+ "#FF7FFF",
2112
+ // 211
2113
+ "#A500A5",
2114
+ // 212
2115
+ "#A552A5",
2116
+ // 213
2117
+ "#7F007F",
2118
+ // 214
2119
+ "#7F3F7F",
2120
+ // 215
2121
+ "#4C004C",
2122
+ // 216
2123
+ "#4C264C",
2124
+ // 217
2125
+ "#260026",
2126
+ // 218
2127
+ "#261326",
2128
+ // 219
2129
+ "#FF00BF",
2130
+ // 220
2131
+ "#FF7FDF",
2132
+ // 221
2133
+ "#A5007C",
2134
+ // 222
2135
+ "#A55291",
2136
+ // 223
2137
+ "#7F005F",
2138
+ // 224
2139
+ "#7F3F6F",
2140
+ // 225
2141
+ "#4C0039",
2142
+ // 226
2143
+ "#4C2642",
2144
+ // 227
2145
+ "#26001C",
2146
+ // 228
2147
+ "#261321",
2148
+ // 229
2149
+ "#FF007F",
2150
+ // 230
2151
+ "#FF7FBF",
2152
+ // 231
2153
+ "#A50052",
2154
+ // 232
2155
+ "#A5527C",
2156
+ // 233
2157
+ "#7F003F",
2158
+ // 234
2159
+ "#7F3F5F",
2160
+ // 235
2161
+ "#4C0026",
2162
+ // 236
2163
+ "#4C2639",
2164
+ // 237
2165
+ "#260013",
2166
+ // 238
2167
+ "#26131C",
2168
+ // 239
2169
+ "#FF003F",
2170
+ // 240
2171
+ "#FF7F9F",
2172
+ // 241
2173
+ "#A50029",
2174
+ // 242
2175
+ "#A55267",
2176
+ // 243
2177
+ "#7F001F",
2178
+ // 244
2179
+ "#7F3F4F",
2180
+ // 245
2181
+ "#4C0013",
2182
+ // 246
2183
+ "#4C262F",
2184
+ // 247
2185
+ "#260009",
2186
+ // 248
2187
+ "#261317",
2188
+ // 249
2189
+ "#333333",
2190
+ // 250: Gray (darkest)
2191
+ "#505050",
2192
+ // 251: Gray
2193
+ "#696969",
2194
+ // 252: Gray
2195
+ "#828282",
2196
+ // 253: Gray
2197
+ "#BEBEBE",
2198
+ // 254: Gray
2199
+ "#FFFFFF"
2200
+ // 255: Gray (lightest)
2201
+ ];
2202
+ function aciToHex(aci) {
2203
+ if (aci === 0 || aci === 256) return void 0;
2204
+ if (aci < 0 || aci > 255) return void 0;
2205
+ return ACI_COLORS[aci];
2206
+ }
2207
+ function aciToDisplayColor(aci, isDarkTheme) {
2208
+ if (aci === 7) return isDarkTheme ? "#FFFFFF" : "#000000";
2209
+ return ACI_COLORS[Math.abs(aci)] ?? (isDarkTheme ? "#FFFFFF" : "#000000");
2210
+ }
2211
+ function trueColorToHex(trueColor) {
2212
+ const r = trueColor >> 16 & 255;
2213
+ const g = trueColor >> 8 & 255;
2214
+ const b = trueColor & 255;
2215
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
2216
+ }
2217
+
2218
+ // src/renderer/theme.ts
2219
+ var THEMES = {
2220
+ dark: {
2221
+ backgroundColor: "#1a1a2e",
2222
+ defaultEntityColor: "#ffffff",
2223
+ selectionColor: "#00ff88",
2224
+ measureColor: "#ffaa00",
2225
+ gridColor: "#333333",
2226
+ crosshairColor: "#555555"
2227
+ },
2228
+ light: {
2229
+ backgroundColor: "#ffffff",
2230
+ defaultEntityColor: "#000000",
2231
+ selectionColor: "#0066ff",
2232
+ measureColor: "#ff6600",
2233
+ gridColor: "#dddddd",
2234
+ crosshairColor: "#cccccc"
2235
+ }
2236
+ };
2237
+
2238
+ // src/renderer/camera.ts
2239
+ function worldToScreen(vt, wx, wy) {
2240
+ return [
2241
+ wx * vt.scale + vt.offsetX,
2242
+ -wy * vt.scale + vt.offsetY
2243
+ ];
2244
+ }
2245
+ function screenToWorld(vt, sx, sy) {
2246
+ return [
2247
+ (sx - vt.offsetX) / vt.scale,
2248
+ -(sy - vt.offsetY) / vt.scale
2249
+ ];
2250
+ }
2251
+ function applyTransform(ctx, vt) {
2252
+ const dpr = window.devicePixelRatio || 1;
2253
+ ctx.setTransform(vt.scale * dpr, 0, 0, -vt.scale * dpr, vt.offsetX * dpr, vt.offsetY * dpr);
2254
+ }
2255
+ function fitToView(canvasWidth, canvasHeight, minX, minY, maxX, maxY, padding = 0.05) {
2256
+ const worldWidth = maxX - minX;
2257
+ const worldHeight = maxY - minY;
2258
+ if (worldWidth <= 0 || worldHeight <= 0) {
2259
+ return { scale: 1, offsetX: canvasWidth / 2, offsetY: canvasHeight / 2 };
2260
+ }
2261
+ const scaleX = canvasWidth / worldWidth;
2262
+ const scaleY = canvasHeight / worldHeight;
2263
+ const scale = Math.min(scaleX, scaleY) * (1 - padding * 2);
2264
+ const centerX = (minX + maxX) / 2;
2265
+ const centerY = (minY + maxY) / 2;
2266
+ const offsetX = canvasWidth / 2 - centerX * scale;
2267
+ const offsetY = canvasHeight / 2 + centerY * scale;
2268
+ return { scale, offsetX, offsetY };
2269
+ }
2270
+ function zoomAtPoint(vt, screenX, screenY, zoomFactor, minScale = 1e-4, maxScale = 1e5) {
2271
+ const newScale = Math.max(minScale, Math.min(maxScale, vt.scale * zoomFactor));
2272
+ const actualFactor = newScale / vt.scale;
2273
+ return {
2274
+ scale: newScale,
2275
+ offsetX: screenX - (screenX - vt.offsetX) * actualFactor,
2276
+ offsetY: screenY - (screenY - vt.offsetY) * actualFactor
2277
+ };
2278
+ }
2279
+ var Camera = class {
2280
+ transform = { scale: 1, offsetX: 0, offsetY: 0 };
2281
+ minScale;
2282
+ maxScale;
2283
+ constructor(options) {
2284
+ this.minScale = options.minZoom;
2285
+ this.maxScale = options.maxZoom;
2286
+ }
2287
+ getTransform() {
2288
+ return this.transform;
2289
+ }
2290
+ setTransform(vt) {
2291
+ this.transform = vt;
2292
+ }
2293
+ pan(dx, dy) {
2294
+ this.transform = {
2295
+ ...this.transform,
2296
+ offsetX: this.transform.offsetX + dx,
2297
+ offsetY: this.transform.offsetY + dy
2298
+ };
2299
+ }
2300
+ zoom(screenX, screenY, factor) {
2301
+ this.transform = zoomAtPoint(
2302
+ this.transform,
2303
+ screenX,
2304
+ screenY,
2305
+ factor,
2306
+ this.minScale,
2307
+ this.maxScale
2308
+ );
2309
+ }
2310
+ };
2311
+
2312
+ // src/renderer/resolve-color.ts
2313
+ function getDisplayColor(aciColor, theme) {
2314
+ if (aciColor === 7) {
2315
+ return theme === "dark" ? "#ffffff" : "#000000";
2316
+ }
2317
+ return aciToHex(aciColor) ?? "#ffffff";
2318
+ }
2319
+ function resolveEntityColor(entity, layers, theme, parentColor) {
2320
+ if (entity.trueColor !== void 0) {
2321
+ const r = entity.trueColor >> 16 & 255;
2322
+ const g = entity.trueColor >> 8 & 255;
2323
+ const b = entity.trueColor & 255;
2324
+ return `rgb(${r},${g},${b})`;
2325
+ }
2326
+ if (entity.color === 0) {
2327
+ return parentColor ?? THEMES[theme].defaultEntityColor;
2328
+ }
2329
+ if (entity.color !== 256) {
2330
+ return getDisplayColor(entity.color, theme);
2331
+ }
2332
+ const layer = layers.get(entity.layer);
2333
+ if (layer) {
2334
+ if (layer.trueColor !== void 0) {
2335
+ const r = layer.trueColor >> 16 & 255;
2336
+ const g = layer.trueColor >> 8 & 255;
2337
+ const b = layer.trueColor & 255;
2338
+ return `rgb(${r},${g},${b})`;
2339
+ }
2340
+ const layerAci = Math.abs(layer.color);
2341
+ return getDisplayColor(layerAci, theme);
2342
+ }
2343
+ return THEMES[theme].defaultEntityColor;
2344
+ }
2345
+
2346
+ // src/renderer/entities/draw-line.ts
2347
+ function drawLine(ctx, entity) {
2348
+ ctx.beginPath();
2349
+ ctx.moveTo(entity.start.x, entity.start.y);
2350
+ ctx.lineTo(entity.end.x, entity.end.y);
2351
+ ctx.stroke();
2352
+ }
2353
+
2354
+ // src/renderer/entities/draw-circle.ts
2355
+ function drawCircle(ctx, entity) {
2356
+ if (entity.radius <= 0) return;
2357
+ ctx.beginPath();
2358
+ ctx.arc(entity.center.x, entity.center.y, entity.radius, 0, Math.PI * 2);
2359
+ ctx.stroke();
2360
+ }
2361
+
2362
+ // src/renderer/entities/draw-arc.ts
2363
+ function drawArc(ctx, entity) {
2364
+ if (entity.radius <= 0) return;
2365
+ const startRad = entity.startAngle * Math.PI / 180;
2366
+ const endRad = entity.endAngle * Math.PI / 180;
2367
+ ctx.beginPath();
2368
+ ctx.arc(
2369
+ entity.center.x,
2370
+ entity.center.y,
2371
+ entity.radius,
2372
+ startRad,
2373
+ endRad,
2374
+ false
2375
+ );
2376
+ ctx.stroke();
2377
+ }
2378
+
2379
+ // src/utils/math.ts
2380
+ function bulgeToArc(x1, y1, x2, y2, bulge) {
2381
+ const dx = x2 - x1;
2382
+ const dy = y2 - y1;
2383
+ const chord = Math.sqrt(dx * dx + dy * dy);
2384
+ if (chord < 1e-10) {
2385
+ return { cx: x1, cy: y1, radius: 0, startAngle: 0, endAngle: 0, anticlockwise: false };
2386
+ }
2387
+ const sagitta = Math.abs(bulge) * chord / 2;
2388
+ const radius = ((chord / 2) ** 2 + sagitta ** 2) / (2 * sagitta);
2389
+ const mx = (x1 + x2) / 2;
2390
+ const my = (y1 + y2) / 2;
2391
+ const perpX = -dy / chord;
2392
+ const perpY = dx / chord;
2393
+ const d = radius - sagitta;
2394
+ const sign = bulge > 0 ? 1 : -1;
2395
+ const cx = mx + sign * d * perpX;
2396
+ const cy = my + sign * d * perpY;
2397
+ const startAngle = Math.atan2(y1 - cy, x1 - cx);
2398
+ const endAngle = Math.atan2(y2 - cy, x2 - cx);
2399
+ const anticlockwise = bulge < 0;
2400
+ return { cx, cy, radius, startAngle, endAngle, anticlockwise };
2401
+ }
2402
+ function drawBulgeArc(ctx, x1, y1, x2, y2, bulge) {
2403
+ const arc = bulgeToArc(x1, y1, x2, y2, bulge);
2404
+ if (arc.radius < 1e-10) {
2405
+ ctx.lineTo(x2, y2);
2406
+ return;
2407
+ }
2408
+ ctx.arc(arc.cx, arc.cy, arc.radius, arc.startAngle, arc.endAngle, arc.anticlockwise);
2409
+ }
2410
+
2411
+ // src/renderer/entities/draw-polyline.ts
2412
+ function drawLwPolyline(ctx, entity) {
2413
+ const verts = entity.vertices;
2414
+ if (verts.length < 2) return;
2415
+ ctx.beginPath();
2416
+ ctx.moveTo(verts[0].x, verts[0].y);
2417
+ const count = entity.closed ? verts.length : verts.length - 1;
2418
+ for (let i = 0; i < count; i++) {
2419
+ const current = verts[i];
2420
+ const next = verts[(i + 1) % verts.length];
2421
+ if (Math.abs(current.bulge) < 1e-10) {
2422
+ ctx.lineTo(next.x, next.y);
2423
+ } else {
2424
+ drawBulgeArc(ctx, current.x, current.y, next.x, next.y, current.bulge);
2425
+ }
2426
+ }
2427
+ ctx.stroke();
2428
+ }
2429
+ function drawPolyline(ctx, entity) {
2430
+ const verts = entity.vertices;
2431
+ if (verts.length < 2) return;
2432
+ ctx.beginPath();
2433
+ ctx.moveTo(verts[0].x, verts[0].y);
2434
+ const count = entity.closed ? verts.length : verts.length - 1;
2435
+ for (let i = 0; i < count; i++) {
2436
+ const current = verts[i];
2437
+ const next = verts[(i + 1) % verts.length];
2438
+ const bulge = current.bulge ?? 0;
2439
+ if (Math.abs(bulge) < 1e-10) {
2440
+ ctx.lineTo(next.x, next.y);
2441
+ } else {
2442
+ drawBulgeArc(ctx, current.x, current.y, next.x, next.y, bulge);
2443
+ }
2444
+ }
2445
+ ctx.stroke();
2446
+ }
2447
+
2448
+ // src/renderer/entities/draw-ellipse.ts
2449
+ function drawEllipse(ctx, entity) {
2450
+ const majorLength = Math.sqrt(
2451
+ entity.majorAxis.x ** 2 + entity.majorAxis.y ** 2 + entity.majorAxis.z ** 2
2452
+ );
2453
+ if (majorLength < 1e-10 || entity.minorRatio <= 0) return;
2454
+ const minorLength = majorLength * entity.minorRatio;
2455
+ const rotation = Math.atan2(entity.majorAxis.y, entity.majorAxis.x);
2456
+ ctx.beginPath();
2457
+ ctx.ellipse(
2458
+ entity.center.x,
2459
+ entity.center.y,
2460
+ majorLength,
2461
+ minorLength,
2462
+ rotation,
2463
+ entity.startParam,
2464
+ entity.endParam,
2465
+ false
2466
+ );
2467
+ ctx.stroke();
2468
+ }
2469
+
2470
+ // src/utils/spline.ts
2471
+ function deBoor(degree, controlPoints, knots, t, weights) {
2472
+ const n = controlPoints.length - 1;
2473
+ let k = degree;
2474
+ while (k < n && knots[k + 1] <= t) k++;
2475
+ const d = [];
2476
+ const w = [];
2477
+ for (let j = 0; j <= degree; j++) {
2478
+ const idx = k - degree + j;
2479
+ d.push({ ...controlPoints[idx] });
2480
+ w.push(weights ? weights[idx] : 1);
2481
+ }
2482
+ for (let r = 1; r <= degree; r++) {
2483
+ for (let j = degree; j >= r; j--) {
2484
+ const i = k - degree + j;
2485
+ const denom = knots[i + degree - r + 1] - knots[i];
2486
+ if (Math.abs(denom) < 1e-10) continue;
2487
+ const alpha = (t - knots[i]) / denom;
2488
+ if (weights) {
2489
+ const w0 = w[j - 1] * (1 - alpha);
2490
+ const w1 = w[j] * alpha;
2491
+ const wSum = w0 + w1;
2492
+ if (Math.abs(wSum) < 1e-10) continue;
2493
+ d[j].x = (d[j - 1].x * w0 + d[j].x * w1) / wSum;
2494
+ d[j].y = (d[j - 1].y * w0 + d[j].y * w1) / wSum;
2495
+ d[j].z = (d[j - 1].z * w0 + d[j].z * w1) / wSum;
2496
+ w[j] = wSum;
2497
+ } else {
2498
+ d[j].x = (1 - alpha) * d[j - 1].x + alpha * d[j].x;
2499
+ d[j].y = (1 - alpha) * d[j - 1].y + alpha * d[j].y;
2500
+ d[j].z = (1 - alpha) * d[j - 1].z + alpha * d[j].z;
2501
+ }
2502
+ }
2503
+ }
2504
+ return d[degree];
2505
+ }
2506
+ function fitPointsToPolyline(fitPoints) {
2507
+ if (fitPoints.length < 2) return fitPoints.map((p) => ({ x: p.x, y: p.y }));
2508
+ const result = [];
2509
+ const n = fitPoints.length;
2510
+ const segments = 10;
2511
+ for (let i = 0; i < n - 1; i++) {
2512
+ const p0 = fitPoints[Math.max(0, i - 1)];
2513
+ const p1 = fitPoints[i];
2514
+ const p2 = fitPoints[Math.min(n - 1, i + 1)];
2515
+ const p3 = fitPoints[Math.min(n - 1, i + 2)];
2516
+ for (let j = 0; j <= (i === n - 2 ? segments : segments - 1); j++) {
2517
+ const t = j / segments;
2518
+ const t2 = t * t;
2519
+ const t3 = t2 * t;
2520
+ const x = 0.5 * (2 * p1.x + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
2521
+ const y = 0.5 * (2 * p1.y + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
2522
+ result.push({ x, y });
2523
+ }
2524
+ }
2525
+ return result;
2526
+ }
2527
+ function evaluateSpline(entity, pixelSize) {
2528
+ if (entity.degree === 1) {
2529
+ return entity.controlPoints.map((p) => ({ x: p.x, y: p.y }));
2530
+ }
2531
+ if (entity.controlPoints.length === 0 && entity.fitPoints.length >= 2) {
2532
+ return fitPointsToPolyline(entity.fitPoints);
2533
+ }
2534
+ if (entity.controlPoints.length < entity.degree + 1) return [];
2535
+ if (entity.knots.length < entity.controlPoints.length + entity.degree + 1) return [];
2536
+ const points = [];
2537
+ const tMin = entity.knots[entity.degree];
2538
+ const tMax = entity.knots[entity.knots.length - entity.degree - 1];
2539
+ if (tMax <= tMin) return [];
2540
+ const numPoints = Math.max(
2541
+ entity.controlPoints.length * 10,
2542
+ Math.ceil((tMax - tMin) / pixelSize)
2543
+ );
2544
+ const cappedPoints = Math.min(numPoints, 5e3);
2545
+ const weights = entity.weights.length > 0 ? entity.weights : void 0;
2546
+ for (let i = 0; i <= cappedPoints; i++) {
2547
+ const t = tMin + (tMax - tMin) * (i / cappedPoints);
2548
+ const p = deBoor(entity.degree, entity.controlPoints, entity.knots, t, weights);
2549
+ points.push({ x: p.x, y: p.y });
2550
+ }
2551
+ return points;
2552
+ }
2553
+
2554
+ // src/renderer/entities/draw-spline.ts
2555
+ function drawSpline(ctx, entity, pixelSize) {
2556
+ const points = evaluateSpline(entity, pixelSize);
2557
+ if (points.length < 2) return;
2558
+ ctx.beginPath();
2559
+ ctx.moveTo(points[0].x, points[0].y);
2560
+ for (let i = 1; i < points.length; i++) {
2561
+ ctx.lineTo(points[i].x, points[i].y);
2562
+ }
2563
+ if (entity.flags & 1) {
2564
+ ctx.closePath();
2565
+ }
2566
+ ctx.stroke();
2567
+ }
2568
+
2569
+ // src/renderer/entities/draw-text.ts
2570
+ var CAD_FONT_MAP = {
2571
+ "standard": "Arial, sans-serif",
2572
+ "arial": "Arial, sans-serif",
2573
+ "arial.ttf": "Arial, sans-serif",
2574
+ "romans.shx": '"Courier New", monospace',
2575
+ "simplex.shx": '"Courier New", monospace',
2576
+ "txt.shx": "monospace",
2577
+ "monotxt.shx": "monospace",
2578
+ "isocp.shx": '"Courier New", monospace',
2579
+ "isocpeur.shx": '"Courier New", monospace',
2580
+ "times.ttf": '"Times New Roman", serif',
2581
+ "timesnr.ttf": '"Times New Roman", serif',
2582
+ "gothic.ttf": '"Century Gothic", sans-serif'
2583
+ };
2584
+ function mapCADFont(styleName) {
2585
+ const key = styleName.toLowerCase().trim();
2586
+ return CAD_FONT_MAP[key] || "sans-serif";
2587
+ }
2588
+ function drawText(ctx, entity, pixelSize) {
2589
+ if (!entity.text || entity.height < pixelSize * 3) return;
2590
+ const useAlignPoint = entity.hAlign !== 0 || entity.vAlign !== 0;
2591
+ const px = useAlignPoint && entity.alignmentPoint ? entity.alignmentPoint.x : entity.insertionPoint.x;
2592
+ const py = useAlignPoint && entity.alignmentPoint ? entity.alignmentPoint.y : entity.insertionPoint.y;
2593
+ ctx.save();
2594
+ ctx.translate(px, py);
2595
+ ctx.scale(1, -1);
2596
+ if (entity.rotation) {
2597
+ ctx.rotate(-entity.rotation * Math.PI / 180);
2598
+ }
2599
+ if (entity.widthFactor !== 1) {
2600
+ ctx.scale(entity.widthFactor, 1);
2601
+ }
2602
+ if (entity.obliqueAngle) {
2603
+ const skew = Math.tan(entity.obliqueAngle * Math.PI / 180);
2604
+ ctx.transform(1, 0, skew, 1, 0, 0);
2605
+ }
2606
+ const backward = (entity.generationFlags & 2) !== 0;
2607
+ const upsideDown = (entity.generationFlags & 4) !== 0;
2608
+ if (backward) ctx.scale(-1, 1);
2609
+ if (upsideDown) ctx.scale(1, -1);
2610
+ const fontName = mapCADFont(entity.style || "Standard");
2611
+ ctx.font = `${entity.height}px ${fontName}`;
2612
+ switch (entity.hAlign) {
2613
+ case 0:
2614
+ ctx.textAlign = "left";
2615
+ break;
2616
+ case 1:
2617
+ ctx.textAlign = "center";
2618
+ break;
2619
+ case 2:
2620
+ ctx.textAlign = "right";
2621
+ break;
2622
+ case 3:
2623
+ ctx.textAlign = "left";
2624
+ break;
2625
+ // ALIGNED
2626
+ case 4:
2627
+ ctx.textAlign = "center";
2628
+ break;
2629
+ // MIDDLE
2630
+ case 5:
2631
+ ctx.textAlign = "left";
2632
+ break;
2633
+ // FIT
2634
+ default:
2635
+ ctx.textAlign = "left";
2636
+ }
2637
+ switch (entity.vAlign) {
2638
+ case 0:
2639
+ ctx.textBaseline = "alphabetic";
2640
+ break;
2641
+ case 1:
2642
+ ctx.textBaseline = "bottom";
2643
+ break;
2644
+ case 2:
2645
+ ctx.textBaseline = "middle";
2646
+ break;
2647
+ case 3:
2648
+ ctx.textBaseline = "top";
2649
+ break;
2650
+ default:
2651
+ ctx.textBaseline = "alphabetic";
2652
+ }
2653
+ ctx.fillText(entity.text, 0, 0);
2654
+ ctx.restore();
2655
+ }
2656
+ function stripMTextFormatting(text) {
2657
+ return text.replace(/\\P/g, "\n").replace(/\\[fFHWQTCAaLlOoKk][^;]*;/g, "").replace(/\\\\/g, "\\").replace(/\\~/g, "\xA0").replace(/\{/g, "").replace(/\}/g, "").replace(/\\S[^;]*;/g, "").trim();
2658
+ }
2659
+ function drawMText(ctx, entity, pixelSize) {
2660
+ if (!entity.text || entity.height < pixelSize * 3) return;
2661
+ ctx.save();
2662
+ ctx.translate(entity.insertionPoint.x, entity.insertionPoint.y);
2663
+ ctx.scale(1, -1);
2664
+ const rotation = entity.textDirection ? Math.atan2(entity.textDirection.y, entity.textDirection.x) : (entity.rotation || 0) * Math.PI / 180;
2665
+ if (rotation) ctx.rotate(-rotation);
2666
+ const plainText = stripMTextFormatting(entity.text);
2667
+ const lines = plainText.split("\n");
2668
+ const lineHeight = entity.height * (entity.lineSpacingFactor || 1.4);
2669
+ const fontName = mapCADFont(entity.style || "Standard");
2670
+ ctx.font = `${entity.height}px ${fontName}`;
2671
+ const col = (entity.attachmentPoint - 1) % 3;
2672
+ const row = Math.floor((entity.attachmentPoint - 1) / 3);
2673
+ const alignments = ["left", "center", "right"];
2674
+ ctx.textAlign = alignments[col] ?? "left";
2675
+ let startY = 0;
2676
+ const totalHeight = lines.length * lineHeight;
2677
+ switch (row) {
2678
+ case 0:
2679
+ startY = entity.height;
2680
+ break;
2681
+ // top: first line baseline
2682
+ case 1:
2683
+ startY = entity.height - totalHeight / 2;
2684
+ break;
2685
+ // middle
2686
+ case 2:
2687
+ startY = entity.height - totalHeight;
2688
+ break;
2689
+ }
2690
+ for (let i = 0; i < lines.length; i++) {
2691
+ ctx.fillText(lines[i], 0, startY + i * lineHeight);
2692
+ }
2693
+ ctx.restore();
2694
+ }
2695
+
2696
+ // src/renderer/entities/draw-insert.ts
2697
+ var MAX_INSERT_DEPTH = 100;
2698
+ function drawInsert(ctx, entity, doc, vt, theme, pixelSize, depth = 0) {
2699
+ if (depth > MAX_INSERT_DEPTH) return;
2700
+ const block = doc.blocks.get(entity.blockName);
2701
+ if (!block) return;
2702
+ const cols = Math.max(1, entity.columnCount);
2703
+ const rows = Math.max(1, entity.rowCount);
2704
+ for (let row = 0; row < rows; row++) {
2705
+ for (let col = 0; col < cols; col++) {
2706
+ ctx.save();
2707
+ const ox = entity.insertionPoint.x + col * entity.columnSpacing;
2708
+ const oy = entity.insertionPoint.y + row * entity.rowSpacing;
2709
+ ctx.translate(ox, oy);
2710
+ if (entity.rotation) {
2711
+ ctx.rotate(entity.rotation * Math.PI / 180);
2712
+ }
2713
+ ctx.scale(entity.scaleX, entity.scaleY);
2714
+ ctx.translate(-block.basePoint.x, -block.basePoint.y);
2715
+ const scaleCompensation = Math.max(Math.abs(entity.scaleX), Math.abs(entity.scaleY));
2716
+ const adjustedPixelSize = pixelSize / (scaleCompensation || 1);
2717
+ for (const blockEntity of block.entities) {
2718
+ const color = resolveEntityColor(blockEntity, doc.layers, theme);
2719
+ ctx.strokeStyle = color;
2720
+ ctx.fillStyle = color;
2721
+ ctx.lineWidth = adjustedPixelSize;
2722
+ if (blockEntity.type === "INSERT") {
2723
+ drawInsert(ctx, blockEntity, doc, vt, theme, adjustedPixelSize, depth + 1);
2724
+ } else {
2725
+ drawEntity(ctx, blockEntity, doc, vt, theme, adjustedPixelSize);
2726
+ }
2727
+ }
2728
+ ctx.restore();
2729
+ }
2730
+ }
2731
+ }
2732
+
2733
+ // src/renderer/entities/draw-dimension.ts
2734
+ function drawDimension(ctx, entity, doc, vt, theme, pixelSize) {
2735
+ if (entity.blockName) {
2736
+ const block = doc.blocks.get(entity.blockName);
2737
+ if (block) {
2738
+ for (const blockEntity of block.entities) {
2739
+ const color = resolveEntityColor(blockEntity, doc.layers, theme);
2740
+ ctx.strokeStyle = color;
2741
+ ctx.fillStyle = color;
2742
+ ctx.lineWidth = pixelSize;
2743
+ drawEntity(ctx, blockEntity, doc, vt, theme, pixelSize);
2744
+ }
2745
+ return;
2746
+ }
2747
+ }
2748
+ if (entity.defPoint2 && entity.defPoint3) {
2749
+ ctx.beginPath();
2750
+ ctx.moveTo(entity.defPoint2.x, entity.defPoint2.y);
2751
+ ctx.lineTo(entity.defPoint.x, entity.defPoint.y);
2752
+ ctx.stroke();
2753
+ ctx.beginPath();
2754
+ ctx.moveTo(entity.defPoint3.x, entity.defPoint3.y);
2755
+ ctx.lineTo(entity.defPoint.x, entity.defPoint.y);
2756
+ ctx.stroke();
2757
+ }
2758
+ }
2759
+
2760
+ // src/renderer/entities/draw-hatch.ts
2761
+ function drawHatch(ctx, entity) {
2762
+ if (entity.boundaryPaths.length === 0) return;
2763
+ ctx.beginPath();
2764
+ for (const path of entity.boundaryPaths) {
2765
+ if (path.type === "polyline" && path.vertices && path.vertices.length > 0) {
2766
+ const firstVert = path.vertices[0];
2767
+ ctx.moveTo(firstVert.x, firstVert.y);
2768
+ for (let i = 1; i < path.vertices.length; i++) {
2769
+ const prev = path.vertices[i - 1];
2770
+ const curr = path.vertices[i];
2771
+ const bulge = path.bulges?.[i - 1] ?? 0;
2772
+ if (Math.abs(bulge) < 1e-10) {
2773
+ ctx.lineTo(curr.x, curr.y);
2774
+ } else {
2775
+ drawBulgeArc(ctx, prev.x, prev.y, curr.x, curr.y, bulge);
2776
+ }
2777
+ }
2778
+ if (path.isClosed) ctx.closePath();
2779
+ } else if (path.type === "edges" && path.edges) {
2780
+ for (let i = 0; i < path.edges.length; i++) {
2781
+ const edge = path.edges[i];
2782
+ if (edge.type === "line") {
2783
+ if (i === 0) ctx.moveTo(edge.start.x, edge.start.y);
2784
+ ctx.lineTo(edge.end.x, edge.end.y);
2785
+ } else if (edge.type === "arc") {
2786
+ if (i === 0) {
2787
+ const sx = edge.center.x + edge.radius * Math.cos(edge.startAngle * Math.PI / 180);
2788
+ const sy = edge.center.y + edge.radius * Math.sin(edge.startAngle * Math.PI / 180);
2789
+ ctx.moveTo(sx, sy);
2790
+ }
2791
+ ctx.arc(
2792
+ edge.center.x,
2793
+ edge.center.y,
2794
+ edge.radius,
2795
+ edge.startAngle * Math.PI / 180,
2796
+ edge.endAngle * Math.PI / 180,
2797
+ !edge.ccw
2798
+ );
2799
+ }
2800
+ }
2801
+ }
2802
+ }
2803
+ if (entity.solidFill) {
2804
+ ctx.globalAlpha = 0.3;
2805
+ ctx.fill("evenodd");
2806
+ ctx.globalAlpha = 1;
2807
+ }
2808
+ ctx.stroke();
2809
+ }
2810
+
2811
+ // src/renderer/entities/draw-point.ts
2812
+ function drawPoint(ctx, entity, pixelSize) {
2813
+ const size = pixelSize * 3;
2814
+ ctx.beginPath();
2815
+ ctx.arc(entity.position.x, entity.position.y, size, 0, Math.PI * 2);
2816
+ ctx.fill();
2817
+ }
2818
+
2819
+ // src/renderer/entities/draw-entity.ts
2820
+ function drawEntity(ctx, entity, doc, vt, theme, pixelSize) {
2821
+ switch (entity.type) {
2822
+ case "LINE":
2823
+ drawLine(ctx, entity);
2824
+ break;
2825
+ case "CIRCLE":
2826
+ drawCircle(ctx, entity);
2827
+ break;
2828
+ case "ARC":
2829
+ drawArc(ctx, entity);
2830
+ break;
2831
+ case "LWPOLYLINE":
2832
+ drawLwPolyline(ctx, entity);
2833
+ break;
2834
+ case "POLYLINE":
2835
+ drawPolyline(ctx, entity);
2836
+ break;
2837
+ case "ELLIPSE":
2838
+ drawEllipse(ctx, entity);
2839
+ break;
2840
+ case "SPLINE":
2841
+ drawSpline(ctx, entity, pixelSize);
2842
+ break;
2843
+ case "TEXT":
2844
+ drawText(ctx, entity, pixelSize);
2845
+ break;
2846
+ case "MTEXT":
2847
+ drawMText(ctx, entity, pixelSize);
2848
+ break;
2849
+ case "INSERT":
2850
+ drawInsert(ctx, entity, doc, vt, theme, pixelSize);
2851
+ break;
2852
+ case "DIMENSION":
2853
+ drawDimension(ctx, entity, doc, vt, theme, pixelSize);
2854
+ break;
2855
+ case "HATCH":
2856
+ drawHatch(ctx, entity);
2857
+ break;
2858
+ case "POINT":
2859
+ drawPoint(ctx, entity, pixelSize);
2860
+ break;
2861
+ }
2862
+ }
2863
+
2864
+ // src/renderer/canvas-renderer.ts
2865
+ var CanvasRenderer = class {
2866
+ constructor(canvas) {
2867
+ this.canvas = canvas;
2868
+ const ctx = canvas.getContext("2d", { alpha: false });
2869
+ if (!ctx) {
2870
+ throw new Error(
2871
+ "Failed to get 2D rendering context. The canvas may already have a different context type, or canvas rendering is not available in this environment."
2872
+ );
2873
+ }
2874
+ this.ctx = ctx;
2875
+ this.updateSize();
2876
+ }
2877
+ ctx;
2878
+ width = 0;
2879
+ height = 0;
2880
+ getContext() {
2881
+ return this.ctx;
2882
+ }
2883
+ getWidth() {
2884
+ return this.width;
2885
+ }
2886
+ getHeight() {
2887
+ return this.height;
2888
+ }
2889
+ updateSize() {
2890
+ const dpr = window.devicePixelRatio || 1;
2891
+ const rect = this.canvas.getBoundingClientRect();
2892
+ this.width = rect.width;
2893
+ this.height = rect.height;
2894
+ this.canvas.width = rect.width * dpr;
2895
+ this.canvas.height = rect.height * dpr;
2896
+ }
2897
+ render(doc, vt, theme, visibleLayers, selectedEntityIndex) {
2898
+ const ctx = this.ctx;
2899
+ const dpr = window.devicePixelRatio || 1;
2900
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
2901
+ ctx.fillStyle = THEMES[theme].backgroundColor;
2902
+ ctx.fillRect(0, 0, this.width, this.height);
2903
+ applyTransform(ctx, vt);
2904
+ const pixelSize = 1 / vt.scale;
2905
+ ctx.lineCap = "round";
2906
+ ctx.lineJoin = "round";
2907
+ for (let i = 0; i < doc.entities.length; i++) {
2908
+ const entity = doc.entities[i];
2909
+ if (!entity.visible) continue;
2910
+ if (!visibleLayers.has(entity.layer)) continue;
2911
+ const color = resolveEntityColor(entity, doc.layers, theme);
2912
+ ctx.strokeStyle = color;
2913
+ ctx.fillStyle = color;
2914
+ ctx.lineWidth = pixelSize;
2915
+ drawEntity(ctx, entity, doc, vt, theme, pixelSize);
2916
+ }
2917
+ if (selectedEntityIndex >= 0 && selectedEntityIndex < doc.entities.length) {
2918
+ const selEntity = doc.entities[selectedEntityIndex];
2919
+ applyTransform(ctx, vt);
2920
+ ctx.strokeStyle = THEMES[theme].selectionColor;
2921
+ ctx.fillStyle = THEMES[theme].selectionColor;
2922
+ ctx.lineWidth = pixelSize * 3;
2923
+ drawEntity(ctx, selEntity, doc, vt, theme, pixelSize);
2924
+ }
2925
+ }
2926
+ renderEmpty(theme) {
2927
+ const ctx = this.ctx;
2928
+ const dpr = window.devicePixelRatio || 1;
2929
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
2930
+ ctx.fillStyle = THEMES[theme].backgroundColor;
2931
+ ctx.fillRect(0, 0, this.width, this.height);
2932
+ }
2933
+ destroy() {
2934
+ }
2935
+ };
2936
+
2937
+ // src/viewer/layers.ts
2938
+ var LayerManager = class {
2939
+ layers = /* @__PURE__ */ new Map();
2940
+ visibility = /* @__PURE__ */ new Map();
2941
+ colorOverrides = /* @__PURE__ */ new Map();
2942
+ visibleCache = null;
2943
+ setLayers(layers) {
2944
+ this.layers = new Map(layers);
2945
+ this.visibility.clear();
2946
+ this.colorOverrides.clear();
2947
+ this.visibleCache = null;
2948
+ for (const [name, layer] of this.layers) {
2949
+ this.visibility.set(name, !layer.isOff && !layer.isFrozen);
2950
+ }
2951
+ }
2952
+ getAllLayers() {
2953
+ return Array.from(this.layers.values());
2954
+ }
2955
+ setVisible(name, visible) {
2956
+ this.visibility.set(name, visible);
2957
+ this.visibleCache = null;
2958
+ }
2959
+ isVisible(name) {
2960
+ return this.visibility.get(name) ?? true;
2961
+ }
2962
+ setColorOverride(name, color) {
2963
+ this.colorOverrides.set(name, color);
2964
+ }
2965
+ getColorOverride(name) {
2966
+ return this.colorOverrides.get(name);
2967
+ }
2968
+ getVisibleLayerNames() {
2969
+ if (this.visibleCache) return this.visibleCache;
2970
+ const visible = /* @__PURE__ */ new Set();
2971
+ for (const [name, isVisible] of this.visibility) {
2972
+ if (isVisible) visible.add(name);
2973
+ }
2974
+ this.visibleCache = visible;
2975
+ return visible;
2976
+ }
2977
+ clear() {
2978
+ this.layers.clear();
2979
+ this.visibility.clear();
2980
+ this.colorOverrides.clear();
2981
+ this.visibleCache = null;
2982
+ }
2983
+ };
2984
+
2985
+ // src/viewer/events.ts
2986
+ var EventEmitter = class {
2987
+ listeners = /* @__PURE__ */ new Map();
2988
+ on(event, callback) {
2989
+ if (!this.listeners.has(event)) {
2990
+ this.listeners.set(event, /* @__PURE__ */ new Set());
2991
+ }
2992
+ this.listeners.get(event).add(callback);
2993
+ }
2994
+ off(event, callback) {
2995
+ this.listeners.get(event)?.delete(callback);
2996
+ }
2997
+ emit(event, data) {
2998
+ const callbacks = this.listeners.get(event);
2999
+ if (callbacks) {
3000
+ for (const cb of callbacks) {
3001
+ cb(data);
3002
+ }
3003
+ }
3004
+ }
3005
+ removeAllListeners() {
3006
+ this.listeners.clear();
3007
+ }
3008
+ };
3009
+
3010
+ // src/viewer/input-handler.ts
3011
+ var InputHandler = class {
3012
+ constructor(canvas, viewer) {
3013
+ this.canvas = canvas;
3014
+ this.viewer = viewer;
3015
+ this.onPointerDown = this.handlePointerDown.bind(this);
3016
+ this.onPointerMove = this.handlePointerMove.bind(this);
3017
+ this.onPointerUp = this.handlePointerUp.bind(this);
3018
+ this.onWheel = this.handleWheel.bind(this);
3019
+ this.onTouchStart = this.handleTouchStart.bind(this);
3020
+ this.onTouchMove = this.handleTouchMove.bind(this);
3021
+ this.onTouchEnd = this.handleTouchEnd.bind(this);
3022
+ this.onContextMenu = (e) => e.preventDefault();
3023
+ canvas.addEventListener("pointerdown", this.onPointerDown);
3024
+ canvas.addEventListener("pointermove", this.onPointerMove);
3025
+ canvas.addEventListener("pointerup", this.onPointerUp);
3026
+ canvas.addEventListener("wheel", this.onWheel, { passive: false });
3027
+ canvas.addEventListener("touchstart", this.onTouchStart, { passive: false });
3028
+ canvas.addEventListener("touchmove", this.onTouchMove, { passive: false });
3029
+ canvas.addEventListener("touchend", this.onTouchEnd);
3030
+ canvas.addEventListener("contextmenu", this.onContextMenu);
3031
+ }
3032
+ isPanning = false;
3033
+ lastX = 0;
3034
+ lastY = 0;
3035
+ touches = /* @__PURE__ */ new Map();
3036
+ // Bound event handlers (for cleanup)
3037
+ onPointerDown;
3038
+ onPointerMove;
3039
+ onPointerUp;
3040
+ onWheel;
3041
+ onTouchStart;
3042
+ onTouchMove;
3043
+ onTouchEnd;
3044
+ onContextMenu;
3045
+ destroy() {
3046
+ this.canvas.removeEventListener("pointerdown", this.onPointerDown);
3047
+ this.canvas.removeEventListener("pointermove", this.onPointerMove);
3048
+ this.canvas.removeEventListener("pointerup", this.onPointerUp);
3049
+ this.canvas.removeEventListener("wheel", this.onWheel);
3050
+ this.canvas.removeEventListener("touchstart", this.onTouchStart);
3051
+ this.canvas.removeEventListener("touchmove", this.onTouchMove);
3052
+ this.canvas.removeEventListener("touchend", this.onTouchEnd);
3053
+ this.canvas.removeEventListener("contextmenu", this.onContextMenu);
3054
+ }
3055
+ // --- Pointer Events (Mouse) ---
3056
+ handlePointerDown(e) {
3057
+ const tool = this.viewer.getTool();
3058
+ const rect = this.canvas.getBoundingClientRect();
3059
+ const x = e.clientX - rect.left;
3060
+ const y = e.clientY - rect.top;
3061
+ if (e.button === 0 && tool === "pan") {
3062
+ this.startPan(e, x, y);
3063
+ } else if (e.button === 1) {
3064
+ this.startPan(e, x, y);
3065
+ } else if (e.button === 0 && (tool === "select" || tool === "measure")) {
3066
+ this.viewer.handleClick(x, y);
3067
+ }
3068
+ }
3069
+ startPan(e, x, y) {
3070
+ this.isPanning = true;
3071
+ this.lastX = x;
3072
+ this.lastY = y;
3073
+ this.canvas.setPointerCapture(e.pointerId);
3074
+ this.canvas.style.cursor = "grabbing";
3075
+ }
3076
+ handlePointerMove(e) {
3077
+ const rect = this.canvas.getBoundingClientRect();
3078
+ const x = e.clientX - rect.left;
3079
+ const y = e.clientY - rect.top;
3080
+ if (this.isPanning) {
3081
+ const dx = x - this.lastX;
3082
+ const dy = y - this.lastY;
3083
+ this.lastX = x;
3084
+ this.lastY = y;
3085
+ this.viewer.handlePan(dx, dy);
3086
+ } else {
3087
+ this.viewer.handleMouseMove(x, y);
3088
+ }
3089
+ }
3090
+ handlePointerUp(e) {
3091
+ if (this.isPanning) {
3092
+ this.isPanning = false;
3093
+ this.canvas.releasePointerCapture(e.pointerId);
3094
+ this.canvas.style.cursor = this.viewer.getTool() === "pan" ? "grab" : "crosshair";
3095
+ }
3096
+ }
3097
+ // --- Wheel (Zoom) ---
3098
+ handleWheel(e) {
3099
+ e.preventDefault();
3100
+ const rect = this.canvas.getBoundingClientRect();
3101
+ const x = e.clientX - rect.left;
3102
+ const y = e.clientY - rect.top;
3103
+ const delta = -Math.sign(e.deltaY);
3104
+ const speed = this.viewer.getZoomSpeed();
3105
+ const factor = delta > 0 ? speed : 1 / speed;
3106
+ this.viewer.handleZoom(x, y, factor);
3107
+ }
3108
+ // --- Touch Events (Pinch-to-Zoom) ---
3109
+ handleTouchStart(e) {
3110
+ e.preventDefault();
3111
+ for (const t of Array.from(e.changedTouches)) {
3112
+ this.touches.set(t.identifier, { x: t.clientX, y: t.clientY });
3113
+ }
3114
+ }
3115
+ handleTouchMove(e) {
3116
+ e.preventDefault();
3117
+ if (this.touches.size === 2 && e.touches.length === 2) {
3118
+ const t0 = e.touches[0];
3119
+ const t1 = e.touches[1];
3120
+ const prev = Array.from(this.touches.values());
3121
+ const prevDist = Math.hypot(prev[0].x - prev[1].x, prev[0].y - prev[1].y);
3122
+ const currDist = Math.hypot(t0.clientX - t1.clientX, t0.clientY - t1.clientY);
3123
+ if (prevDist > 0) {
3124
+ const zoomFactor = currDist / prevDist;
3125
+ const rect = this.canvas.getBoundingClientRect();
3126
+ const prevCenterX = (prev[0].x + prev[1].x) / 2 - rect.left;
3127
+ const prevCenterY = (prev[0].y + prev[1].y) / 2 - rect.top;
3128
+ const currCenterX = (t0.clientX + t1.clientX) / 2 - rect.left;
3129
+ const currCenterY = (t0.clientY + t1.clientY) / 2 - rect.top;
3130
+ this.viewer.handleZoom(prevCenterX, prevCenterY, zoomFactor);
3131
+ this.viewer.handlePan(currCenterX - prevCenterX, currCenterY - prevCenterY);
3132
+ }
3133
+ } else if (this.touches.size === 1 && e.touches.length === 1) {
3134
+ const t = e.touches[0];
3135
+ const prev = this.touches.get(t.identifier);
3136
+ if (prev) {
3137
+ this.viewer.handlePan(
3138
+ t.clientX - prev.x,
3139
+ t.clientY - prev.y
3140
+ );
3141
+ }
3142
+ }
3143
+ for (const t of Array.from(e.changedTouches)) {
3144
+ this.touches.set(t.identifier, { x: t.clientX, y: t.clientY });
3145
+ }
3146
+ }
3147
+ handleTouchEnd(e) {
3148
+ for (const t of Array.from(e.changedTouches)) {
3149
+ this.touches.delete(t.identifier);
3150
+ }
3151
+ }
3152
+ };
3153
+
3154
+ // src/utils/bbox.ts
3155
+ function computeEntitiesBounds(entities) {
3156
+ let minX = Infinity, minY = Infinity;
3157
+ let maxX = -Infinity, maxY = -Infinity;
3158
+ let hasAny = false;
3159
+ for (const entity of entities) {
3160
+ const bbox = computeEntityBBox(entity);
3161
+ if (!bbox) continue;
3162
+ hasAny = true;
3163
+ minX = Math.min(minX, bbox.minX);
3164
+ minY = Math.min(minY, bbox.minY);
3165
+ maxX = Math.max(maxX, bbox.maxX);
3166
+ maxY = Math.max(maxY, bbox.maxY);
3167
+ }
3168
+ return hasAny ? { minX, minY, maxX, maxY } : null;
3169
+ }
3170
+ function computeEntityBBox(entity) {
3171
+ switch (entity.type) {
3172
+ case "LINE":
3173
+ return {
3174
+ minX: Math.min(entity.start.x, entity.end.x),
3175
+ minY: Math.min(entity.start.y, entity.end.y),
3176
+ maxX: Math.max(entity.start.x, entity.end.x),
3177
+ maxY: Math.max(entity.start.y, entity.end.y)
3178
+ };
3179
+ case "CIRCLE":
3180
+ return {
3181
+ minX: entity.center.x - entity.radius,
3182
+ minY: entity.center.y - entity.radius,
3183
+ maxX: entity.center.x + entity.radius,
3184
+ maxY: entity.center.y + entity.radius
3185
+ };
3186
+ case "ARC": {
3187
+ return {
3188
+ minX: entity.center.x - entity.radius,
3189
+ minY: entity.center.y - entity.radius,
3190
+ maxX: entity.center.x + entity.radius,
3191
+ maxY: entity.center.y + entity.radius
3192
+ };
3193
+ }
3194
+ case "LWPOLYLINE":
3195
+ case "POLYLINE": {
3196
+ if (entity.vertices.length === 0) return null;
3197
+ let pMinX = Infinity, pMinY = Infinity, pMaxX = -Infinity, pMaxY = -Infinity;
3198
+ for (const v of entity.vertices) {
3199
+ pMinX = Math.min(pMinX, v.x);
3200
+ pMinY = Math.min(pMinY, v.y);
3201
+ pMaxX = Math.max(pMaxX, v.x);
3202
+ pMaxY = Math.max(pMaxY, v.y);
3203
+ }
3204
+ return { minX: pMinX, minY: pMinY, maxX: pMaxX, maxY: pMaxY };
3205
+ }
3206
+ case "ELLIPSE": {
3207
+ const majorLen = Math.sqrt(entity.majorAxis.x ** 2 + entity.majorAxis.y ** 2);
3208
+ return {
3209
+ minX: entity.center.x - majorLen,
3210
+ minY: entity.center.y - majorLen,
3211
+ maxX: entity.center.x + majorLen,
3212
+ maxY: entity.center.y + majorLen
3213
+ };
3214
+ }
3215
+ case "TEXT":
3216
+ return {
3217
+ minX: entity.insertionPoint.x,
3218
+ minY: entity.insertionPoint.y,
3219
+ maxX: entity.insertionPoint.x + entity.height * entity.text.length * 0.6,
3220
+ maxY: entity.insertionPoint.y + entity.height
3221
+ };
3222
+ case "MTEXT":
3223
+ return {
3224
+ minX: entity.insertionPoint.x,
3225
+ minY: entity.insertionPoint.y - entity.height,
3226
+ maxX: entity.insertionPoint.x + (entity.width || entity.height * 10),
3227
+ maxY: entity.insertionPoint.y + entity.height
3228
+ };
3229
+ case "SPLINE": {
3230
+ if (entity.controlPoints.length === 0) return null;
3231
+ let sMinX = Infinity, sMinY = Infinity, sMaxX = -Infinity, sMaxY = -Infinity;
3232
+ for (const p of entity.controlPoints) {
3233
+ sMinX = Math.min(sMinX, p.x);
3234
+ sMinY = Math.min(sMinY, p.y);
3235
+ sMaxX = Math.max(sMaxX, p.x);
3236
+ sMaxY = Math.max(sMaxY, p.y);
3237
+ }
3238
+ return { minX: sMinX, minY: sMinY, maxX: sMaxX, maxY: sMaxY };
3239
+ }
3240
+ case "POINT":
3241
+ return {
3242
+ minX: entity.position.x,
3243
+ minY: entity.position.y,
3244
+ maxX: entity.position.x,
3245
+ maxY: entity.position.y
3246
+ };
3247
+ case "INSERT":
3248
+ return {
3249
+ minX: entity.insertionPoint.x,
3250
+ minY: entity.insertionPoint.y,
3251
+ maxX: entity.insertionPoint.x,
3252
+ maxY: entity.insertionPoint.y
3253
+ };
3254
+ case "DIMENSION":
3255
+ if (entity.defPoint) {
3256
+ return {
3257
+ minX: Math.min(entity.defPoint.x, entity.defPoint2?.x ?? entity.defPoint.x, entity.defPoint3?.x ?? entity.defPoint.x),
3258
+ minY: Math.min(entity.defPoint.y, entity.defPoint2?.y ?? entity.defPoint.y, entity.defPoint3?.y ?? entity.defPoint.y),
3259
+ maxX: Math.max(entity.defPoint.x, entity.defPoint2?.x ?? entity.defPoint.x, entity.defPoint3?.x ?? entity.defPoint.x),
3260
+ maxY: Math.max(entity.defPoint.y, entity.defPoint2?.y ?? entity.defPoint.y, entity.defPoint3?.y ?? entity.defPoint.y)
3261
+ };
3262
+ }
3263
+ return null;
3264
+ case "HATCH": {
3265
+ let hMinX = Infinity, hMinY = Infinity, hMaxX = -Infinity, hMaxY = -Infinity;
3266
+ let hasPoints = false;
3267
+ for (const path of entity.boundaryPaths) {
3268
+ if (path.vertices) {
3269
+ for (const v of path.vertices) {
3270
+ hasPoints = true;
3271
+ hMinX = Math.min(hMinX, v.x);
3272
+ hMinY = Math.min(hMinY, v.y);
3273
+ hMaxX = Math.max(hMaxX, v.x);
3274
+ hMaxY = Math.max(hMaxY, v.y);
3275
+ }
3276
+ }
3277
+ }
3278
+ return hasPoints ? { minX: hMinX, minY: hMinY, maxX: hMaxX, maxY: hMaxY } : null;
3279
+ }
3280
+ default:
3281
+ return null;
3282
+ }
3283
+ }
3284
+
3285
+ // src/viewer/selection.ts
3286
+ var HIT_TOLERANCE_PX = 5;
3287
+ var SpatialIndex = class {
3288
+ tree = new RBush__default.default();
3289
+ items = [];
3290
+ build(entities) {
3291
+ this.items = [];
3292
+ for (let i = 0; i < entities.length; i++) {
3293
+ const entity = entities[i];
3294
+ const bbox = computeEntityBBox(entity);
3295
+ if (!bbox) continue;
3296
+ this.items.push({
3297
+ ...bbox,
3298
+ entityIndex: i
3299
+ });
3300
+ }
3301
+ this.tree.clear();
3302
+ this.tree.load(this.items);
3303
+ }
3304
+ search(minX, minY, maxX, maxY) {
3305
+ return this.tree.search({ minX, minY, maxX, maxY });
3306
+ }
3307
+ clear() {
3308
+ this.tree.clear();
3309
+ this.items = [];
3310
+ }
3311
+ };
3312
+ function hitTest(wx, wy, entities, spatialIndex, visibleLayers, scale) {
3313
+ const tolerance = HIT_TOLERANCE_PX / scale;
3314
+ const candidates = spatialIndex.search(
3315
+ wx - tolerance,
3316
+ wy - tolerance,
3317
+ wx + tolerance,
3318
+ wy + tolerance
3319
+ );
3320
+ let bestDist = tolerance;
3321
+ let bestIndex = -1;
3322
+ for (let i = candidates.length - 1; i >= 0; i--) {
3323
+ const item = candidates[i];
3324
+ const entity = entities[item.entityIndex];
3325
+ if (!entity) continue;
3326
+ if (!visibleLayers.has(entity.layer)) continue;
3327
+ if (!entity.visible) continue;
3328
+ const dist = distanceToEntity(wx, wy, entity);
3329
+ if (dist < bestDist) {
3330
+ bestDist = dist;
3331
+ bestIndex = item.entityIndex;
3332
+ }
3333
+ }
3334
+ return bestIndex;
3335
+ }
3336
+ function distanceToEntity(wx, wy, entity) {
3337
+ switch (entity.type) {
3338
+ case "LINE":
3339
+ return distPointToSegment(wx, wy, entity.start.x, entity.start.y, entity.end.x, entity.end.y);
3340
+ case "CIRCLE":
3341
+ return distPointToCircle(wx, wy, entity.center.x, entity.center.y, entity.radius);
3342
+ case "ARC":
3343
+ return distPointToArc(
3344
+ wx,
3345
+ wy,
3346
+ entity.center.x,
3347
+ entity.center.y,
3348
+ entity.radius,
3349
+ entity.startAngle * Math.PI / 180,
3350
+ entity.endAngle * Math.PI / 180
3351
+ );
3352
+ case "LWPOLYLINE":
3353
+ case "POLYLINE": {
3354
+ let minDist = Infinity;
3355
+ const verts = entity.vertices;
3356
+ const count = entity.closed ? verts.length : verts.length - 1;
3357
+ for (let i = 0; i < count; i++) {
3358
+ const a = verts[i];
3359
+ const b = verts[(i + 1) % verts.length];
3360
+ const d = distPointToSegment(wx, wy, a.x, a.y, b.x, b.y);
3361
+ minDist = Math.min(minDist, d);
3362
+ }
3363
+ return minDist;
3364
+ }
3365
+ case "ELLIPSE":
3366
+ return distPointToEllipse(
3367
+ wx,
3368
+ wy,
3369
+ entity.center.x,
3370
+ entity.center.y,
3371
+ entity.majorAxis,
3372
+ entity.minorRatio
3373
+ );
3374
+ case "TEXT":
3375
+ case "MTEXT": {
3376
+ const bbox = computeEntityBBox(entity);
3377
+ if (!bbox) return Infinity;
3378
+ return distPointToRect(wx, wy, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
3379
+ }
3380
+ case "POINT":
3381
+ return Math.hypot(wx - entity.position.x, wy - entity.position.y);
3382
+ case "SPLINE": {
3383
+ let minDist = Infinity;
3384
+ const pts = entity.controlPoints;
3385
+ for (let i = 0; i < pts.length - 1; i++) {
3386
+ const d = distPointToSegment(wx, wy, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y);
3387
+ minDist = Math.min(minDist, d);
3388
+ }
3389
+ return minDist;
3390
+ }
3391
+ default:
3392
+ return Infinity;
3393
+ }
3394
+ }
3395
+ function distPointToSegment(px, py, x1, y1, x2, y2) {
3396
+ const dx = x2 - x1;
3397
+ const dy = y2 - y1;
3398
+ const lenSq = dx * dx + dy * dy;
3399
+ if (lenSq === 0) return Math.hypot(px - x1, py - y1);
3400
+ let t = ((px - x1) * dx + (py - y1) * dy) / lenSq;
3401
+ t = Math.max(0, Math.min(1, t));
3402
+ const projX = x1 + t * dx;
3403
+ const projY = y1 + t * dy;
3404
+ return Math.hypot(px - projX, py - projY);
3405
+ }
3406
+ function distPointToCircle(px, py, cx, cy, radius) {
3407
+ return Math.abs(Math.hypot(px - cx, py - cy) - radius);
3408
+ }
3409
+ function distPointToArc(px, py, cx, cy, radius, startAngle, endAngle) {
3410
+ const angle = Math.atan2(py - cy, px - cx);
3411
+ const normAngle = (angle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
3412
+ const normStart = (startAngle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
3413
+ const normEnd = (endAngle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
3414
+ const inArc = normStart <= normEnd ? normAngle >= normStart && normAngle <= normEnd : normAngle >= normStart || normAngle <= normEnd;
3415
+ if (inArc) {
3416
+ return Math.abs(Math.hypot(px - cx, py - cy) - radius);
3417
+ }
3418
+ const d1 = Math.hypot(
3419
+ px - (cx + radius * Math.cos(startAngle)),
3420
+ py - (cy + radius * Math.sin(startAngle))
3421
+ );
3422
+ const d2 = Math.hypot(
3423
+ px - (cx + radius * Math.cos(endAngle)),
3424
+ py - (cy + radius * Math.sin(endAngle))
3425
+ );
3426
+ return Math.min(d1, d2);
3427
+ }
3428
+ function distPointToRect(px, py, minX, minY, maxX, maxY) {
3429
+ if (px >= minX && px <= maxX && py >= minY && py <= maxY) return 0;
3430
+ const dx = Math.max(minX - px, 0, px - maxX);
3431
+ const dy = Math.max(minY - py, 0, py - maxY);
3432
+ return Math.hypot(dx, dy);
3433
+ }
3434
+ function distPointToEllipse(px, py, cx, cy, majorAxis, minorRatio) {
3435
+ const rotation = Math.atan2(majorAxis.y, majorAxis.x);
3436
+ const cos = Math.cos(-rotation);
3437
+ const sin = Math.sin(-rotation);
3438
+ const localX = (px - cx) * cos - (py - cy) * sin;
3439
+ const localY = (px - cx) * sin + (py - cy) * cos;
3440
+ const a = Math.sqrt(majorAxis.x ** 2 + majorAxis.y ** 2);
3441
+ const b = a * minorRatio;
3442
+ if (a < 1e-10) return Math.hypot(px - cx, py - cy);
3443
+ const scaledY = localY * (a / b);
3444
+ const dist = Math.hypot(localX, scaledY);
3445
+ if (dist < 1e-10) return a;
3446
+ const onEllipseX = localX / dist * a;
3447
+ const onEllipseY = localY / dist * b;
3448
+ return Math.hypot(localX - onEllipseX, localY - onEllipseY);
3449
+ }
3450
+
3451
+ // src/viewer/measure.ts
3452
+ var MeasureTool = class {
3453
+ state = { phase: "idle" };
3454
+ currentSnap = null;
3455
+ activate() {
3456
+ this.state = { phase: "first-point" };
3457
+ this.currentSnap = null;
3458
+ }
3459
+ deactivate() {
3460
+ this.state = { phase: "idle" };
3461
+ this.currentSnap = null;
3462
+ }
3463
+ handleClick(wx, wy, snap) {
3464
+ const point = snap ? snap.point : { x: wx, y: wy };
3465
+ switch (this.state.phase) {
3466
+ case "first-point":
3467
+ this.state = {
3468
+ phase: "second-point",
3469
+ firstPoint: point,
3470
+ firstSnap: snap ?? void 0
3471
+ };
3472
+ return null;
3473
+ case "second-point": {
3474
+ const p1 = this.state.firstPoint;
3475
+ const p2 = point;
3476
+ const dx = p2.x - p1.x;
3477
+ const dy = p2.y - p1.y;
3478
+ const distance = Math.sqrt(dx * dx + dy * dy);
3479
+ const angle = Math.atan2(dy, dx) * 180 / Math.PI;
3480
+ this.state = { phase: "done", firstPoint: p1, secondPoint: p2, distance, angle };
3481
+ return {
3482
+ distance,
3483
+ angle,
3484
+ deltaX: dx,
3485
+ deltaY: dy,
3486
+ points: [p1, p2]
3487
+ };
3488
+ }
3489
+ case "done":
3490
+ this.state = { phase: "second-point", firstPoint: point };
3491
+ return null;
3492
+ default:
3493
+ return null;
3494
+ }
3495
+ }
3496
+ handleMove(snap) {
3497
+ this.currentSnap = snap;
3498
+ }
3499
+ };
3500
+ function findSnaps(wx, wy, entities, spatialIndex, scale, snapTypes = /* @__PURE__ */ new Set(["endpoint", "midpoint", "center"])) {
3501
+ const tolerance = 10 / scale;
3502
+ const results = [];
3503
+ const candidates = spatialIndex.search(
3504
+ wx - tolerance,
3505
+ wy - tolerance,
3506
+ wx + tolerance,
3507
+ wy + tolerance
3508
+ );
3509
+ for (const item of candidates) {
3510
+ const entity = entities[item.entityIndex];
3511
+ if (!entity) continue;
3512
+ const idx = item.entityIndex;
3513
+ switch (entity.type) {
3514
+ case "LINE": {
3515
+ if (snapTypes.has("endpoint")) {
3516
+ addIfClose(results, entity.start.x, entity.start.y, "endpoint", idx, wx, wy, tolerance);
3517
+ addIfClose(results, entity.end.x, entity.end.y, "endpoint", idx, wx, wy, tolerance);
3518
+ }
3519
+ if (snapTypes.has("midpoint")) {
3520
+ const mx = (entity.start.x + entity.end.x) / 2;
3521
+ const my = (entity.start.y + entity.end.y) / 2;
3522
+ addIfClose(results, mx, my, "midpoint", idx, wx, wy, tolerance);
3523
+ }
3524
+ break;
3525
+ }
3526
+ case "CIRCLE": {
3527
+ if (snapTypes.has("center")) {
3528
+ addIfClose(results, entity.center.x, entity.center.y, "center", idx, wx, wy, tolerance);
3529
+ }
3530
+ if (snapTypes.has("endpoint")) {
3531
+ for (const angle of [0, Math.PI / 2, Math.PI, 3 * Math.PI / 2]) {
3532
+ const qx = entity.center.x + entity.radius * Math.cos(angle);
3533
+ const qy = entity.center.y + entity.radius * Math.sin(angle);
3534
+ addIfClose(results, qx, qy, "endpoint", idx, wx, wy, tolerance);
3535
+ }
3536
+ }
3537
+ break;
3538
+ }
3539
+ case "ARC": {
3540
+ if (snapTypes.has("center")) {
3541
+ addIfClose(results, entity.center.x, entity.center.y, "center", idx, wx, wy, tolerance);
3542
+ }
3543
+ if (snapTypes.has("endpoint")) {
3544
+ const startRad = entity.startAngle * Math.PI / 180;
3545
+ const endRad = entity.endAngle * Math.PI / 180;
3546
+ addIfClose(results, entity.center.x + entity.radius * Math.cos(startRad), entity.center.y + entity.radius * Math.sin(startRad), "endpoint", idx, wx, wy, tolerance);
3547
+ addIfClose(results, entity.center.x + entity.radius * Math.cos(endRad), entity.center.y + entity.radius * Math.sin(endRad), "endpoint", idx, wx, wy, tolerance);
3548
+ }
3549
+ break;
3550
+ }
3551
+ case "LWPOLYLINE":
3552
+ case "POLYLINE": {
3553
+ if (snapTypes.has("endpoint")) {
3554
+ for (const v of entity.vertices) {
3555
+ addIfClose(results, v.x, v.y, "endpoint", idx, wx, wy, tolerance);
3556
+ }
3557
+ }
3558
+ if (snapTypes.has("midpoint")) {
3559
+ for (let i = 0; i < entity.vertices.length - 1; i++) {
3560
+ const a = entity.vertices[i];
3561
+ const b = entity.vertices[i + 1];
3562
+ addIfClose(results, (a.x + b.x) / 2, (a.y + b.y) / 2, "midpoint", idx, wx, wy, tolerance);
3563
+ }
3564
+ }
3565
+ break;
3566
+ }
3567
+ case "ELLIPSE": {
3568
+ if (snapTypes.has("center")) {
3569
+ addIfClose(results, entity.center.x, entity.center.y, "center", idx, wx, wy, tolerance);
3570
+ }
3571
+ break;
3572
+ }
3573
+ case "TEXT": {
3574
+ if (snapTypes.has("endpoint")) {
3575
+ addIfClose(results, entity.insertionPoint.x, entity.insertionPoint.y, "endpoint", idx, wx, wy, tolerance);
3576
+ }
3577
+ break;
3578
+ }
3579
+ case "MTEXT": {
3580
+ if (snapTypes.has("endpoint")) {
3581
+ addIfClose(results, entity.insertionPoint.x, entity.insertionPoint.y, "endpoint", idx, wx, wy, tolerance);
3582
+ }
3583
+ break;
3584
+ }
3585
+ }
3586
+ }
3587
+ results.sort(
3588
+ (a, b) => Math.hypot(a.point.x - wx, a.point.y - wy) - Math.hypot(b.point.x - wx, b.point.y - wy)
3589
+ );
3590
+ return results;
3591
+ }
3592
+ function addIfClose(results, x, y, type, entityIndex, wx, wy, tolerance) {
3593
+ if (Math.hypot(x - wx, y - wy) <= tolerance) {
3594
+ results.push({ point: { x, y }, type, entityIndex });
3595
+ }
3596
+ }
3597
+ function renderMeasureOverlay(ctx, vt, measureTool, mouseScreenX, mouseScreenY, theme) {
3598
+ const config = THEMES[theme];
3599
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
3600
+ const state = measureTool.state;
3601
+ if (measureTool.currentSnap) {
3602
+ const [sx, sy] = worldToScreen(vt, measureTool.currentSnap.point.x, measureTool.currentSnap.point.y);
3603
+ drawSnapIndicator(ctx, sx, sy, measureTool.currentSnap.type, config.measureColor);
3604
+ }
3605
+ if (state.phase === "second-point") {
3606
+ const [sx1, sy1] = worldToScreen(vt, state.firstPoint.x, state.firstPoint.y);
3607
+ let targetX;
3608
+ let targetY;
3609
+ if (measureTool.currentSnap) {
3610
+ const [snapSx, snapSy] = worldToScreen(vt, measureTool.currentSnap.point.x, measureTool.currentSnap.point.y);
3611
+ targetX = snapSx;
3612
+ targetY = snapSy;
3613
+ } else {
3614
+ targetX = mouseScreenX;
3615
+ targetY = mouseScreenY;
3616
+ }
3617
+ ctx.strokeStyle = config.measureColor;
3618
+ ctx.lineWidth = 1.5;
3619
+ ctx.setLineDash([6, 3]);
3620
+ ctx.beginPath();
3621
+ ctx.moveTo(sx1, sy1);
3622
+ ctx.lineTo(targetX, targetY);
3623
+ ctx.stroke();
3624
+ ctx.setLineDash([]);
3625
+ drawEndpointMarker(ctx, sx1, sy1, config.measureColor);
3626
+ drawEndpointMarker(ctx, targetX, targetY, config.measureColor);
3627
+ let twx;
3628
+ let twy;
3629
+ if (measureTool.currentSnap) {
3630
+ twx = measureTool.currentSnap.point.x;
3631
+ twy = measureTool.currentSnap.point.y;
3632
+ } else {
3633
+ [twx, twy] = screenToWorld(vt, mouseScreenX, mouseScreenY);
3634
+ }
3635
+ const dist = Math.hypot(twx - state.firstPoint.x, twy - state.firstPoint.y);
3636
+ drawMeasureLabel(ctx, (sx1 + targetX) / 2, (sy1 + targetY) / 2, dist, theme);
3637
+ }
3638
+ if (state.phase === "done") {
3639
+ const [sx1, sy1] = worldToScreen(vt, state.firstPoint.x, state.firstPoint.y);
3640
+ const [sx2, sy2] = worldToScreen(vt, state.secondPoint.x, state.secondPoint.y);
3641
+ ctx.strokeStyle = config.measureColor;
3642
+ ctx.lineWidth = 2;
3643
+ ctx.beginPath();
3644
+ ctx.moveTo(sx1, sy1);
3645
+ ctx.lineTo(sx2, sy2);
3646
+ ctx.stroke();
3647
+ drawEndpointMarker(ctx, sx1, sy1, config.measureColor);
3648
+ drawEndpointMarker(ctx, sx2, sy2, config.measureColor);
3649
+ drawMeasureLabel(ctx, (sx1 + sx2) / 2, (sy1 + sy2) / 2, state.distance, theme);
3650
+ }
3651
+ }
3652
+ function drawSnapIndicator(ctx, x, y, type, color) {
3653
+ ctx.strokeStyle = color;
3654
+ ctx.lineWidth = 2;
3655
+ const size = 8;
3656
+ switch (type) {
3657
+ case "endpoint":
3658
+ ctx.strokeRect(x - size / 2, y - size / 2, size, size);
3659
+ break;
3660
+ case "midpoint":
3661
+ ctx.beginPath();
3662
+ ctx.moveTo(x, y - size / 2);
3663
+ ctx.lineTo(x + size / 2, y + size / 2);
3664
+ ctx.lineTo(x - size / 2, y + size / 2);
3665
+ ctx.closePath();
3666
+ ctx.stroke();
3667
+ break;
3668
+ case "center":
3669
+ ctx.beginPath();
3670
+ ctx.arc(x, y, size / 2, 0, Math.PI * 2);
3671
+ ctx.stroke();
3672
+ break;
3673
+ case "nearest":
3674
+ ctx.beginPath();
3675
+ ctx.moveTo(x - size / 2, y - size / 2);
3676
+ ctx.lineTo(x + size / 2, y + size / 2);
3677
+ ctx.moveTo(x + size / 2, y - size / 2);
3678
+ ctx.lineTo(x - size / 2, y + size / 2);
3679
+ ctx.stroke();
3680
+ break;
3681
+ }
3682
+ }
3683
+ function drawEndpointMarker(ctx, x, y, color) {
3684
+ ctx.fillStyle = color;
3685
+ ctx.beginPath();
3686
+ ctx.arc(x, y, 4, 0, Math.PI * 2);
3687
+ ctx.fill();
3688
+ }
3689
+ function drawMeasureLabel(ctx, x, y, distance, theme) {
3690
+ const label = distance.toFixed(4);
3691
+ ctx.font = "13px monospace";
3692
+ ctx.textAlign = "center";
3693
+ ctx.textBaseline = "bottom";
3694
+ const metrics = ctx.measureText(label);
3695
+ const pad = 4;
3696
+ const bgX = x - metrics.width / 2 - pad;
3697
+ const bgY = y - 20;
3698
+ const bgW = metrics.width + pad * 2;
3699
+ const bgH = 18;
3700
+ ctx.fillStyle = theme === "dark" ? "rgba(0,0,0,0.8)" : "rgba(255,255,255,0.9)";
3701
+ ctx.fillRect(bgX, bgY, bgW, bgH);
3702
+ ctx.fillStyle = THEMES[theme].measureColor;
3703
+ ctx.fillText(label, x, y - 6);
3704
+ }
3705
+
3706
+ // src/viewer/viewer.ts
3707
+ var CadViewer = class {
3708
+ canvas;
3709
+ renderer;
3710
+ camera;
3711
+ layerManager;
3712
+ emitter;
3713
+ spatialIndex;
3714
+ measureTool;
3715
+ doc = null;
3716
+ options;
3717
+ currentTool;
3718
+ inputHandler;
3719
+ resizeObserver;
3720
+ selectedEntityIndex = -1;
3721
+ renderPending = false;
3722
+ destroyed = false;
3723
+ mouseScreenX = 0;
3724
+ mouseScreenY = 0;
3725
+ constructor(canvas, options) {
3726
+ this.canvas = canvas;
3727
+ this.options = {
3728
+ theme: options?.theme ?? "dark",
3729
+ backgroundColor: options?.backgroundColor,
3730
+ antialias: options?.antialias ?? true,
3731
+ minZoom: options?.minZoom ?? 1e-4,
3732
+ maxZoom: options?.maxZoom ?? 1e5,
3733
+ zoomSpeed: options?.zoomSpeed ?? 1.1,
3734
+ initialTool: options?.initialTool ?? "pan"
3735
+ };
3736
+ this.renderer = new CanvasRenderer(canvas);
3737
+ this.camera = new Camera(this.options);
3738
+ this.layerManager = new LayerManager();
3739
+ this.emitter = new EventEmitter();
3740
+ this.spatialIndex = new SpatialIndex();
3741
+ this.measureTool = new MeasureTool();
3742
+ this.currentTool = this.options.initialTool;
3743
+ this.inputHandler = new InputHandler(canvas, this);
3744
+ this.resizeObserver = new ResizeObserver(() => this.resize());
3745
+ this.resizeObserver.observe(canvas);
3746
+ canvas.style.cursor = this.getCursorForTool(this.currentTool);
3747
+ this.requestRender();
3748
+ }
3749
+ // === Loading ===
3750
+ async loadFile(file) {
3751
+ const buffer = await file.arrayBuffer();
3752
+ this.loadArrayBuffer(buffer);
3753
+ }
3754
+ loadString(dxf) {
3755
+ this.doc = parseDxf(dxf);
3756
+ this.onDocumentLoaded();
3757
+ }
3758
+ loadArrayBuffer(buffer) {
3759
+ this.doc = parseDxf(buffer);
3760
+ this.onDocumentLoaded();
3761
+ }
3762
+ /**
3763
+ * Clear the current document and reset all state without destroying the viewer.
3764
+ */
3765
+ clearDocument() {
3766
+ this.doc = null;
3767
+ this.selectedEntityIndex = -1;
3768
+ this.spatialIndex.clear();
3769
+ this.layerManager.clear();
3770
+ this.measureTool.deactivate();
3771
+ this.requestRender();
3772
+ }
3773
+ onDocumentLoaded() {
3774
+ if (!this.doc) return;
3775
+ this.layerManager.setLayers(this.doc.layers);
3776
+ this.spatialIndex.build(this.doc.entities);
3777
+ this.selectedEntityIndex = -1;
3778
+ this.measureTool.deactivate();
3779
+ if (this.currentTool === "measure") {
3780
+ this.measureTool.activate();
3781
+ }
3782
+ this.fitToView();
3783
+ }
3784
+ // === Camera Controls ===
3785
+ fitToView() {
3786
+ if (!this.doc) return;
3787
+ const bounds = this.computeDocumentBounds();
3788
+ if (!bounds) return;
3789
+ const rect = this.canvas.getBoundingClientRect();
3790
+ this.camera.setTransform(
3791
+ fitToView(rect.width, rect.height, bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)
3792
+ );
3793
+ this.requestRender();
3794
+ this.emitter.emit("viewchange", this.camera.getTransform());
3795
+ }
3796
+ zoomTo(scale) {
3797
+ const rect = this.canvas.getBoundingClientRect();
3798
+ const centerX = rect.width / 2;
3799
+ const centerY = rect.height / 2;
3800
+ const factor = scale / this.camera.getTransform().scale;
3801
+ this.camera.zoom(centerX, centerY, factor);
3802
+ this.requestRender();
3803
+ this.emitter.emit("viewchange", this.camera.getTransform());
3804
+ }
3805
+ panTo(worldX, worldY) {
3806
+ const rect = this.canvas.getBoundingClientRect();
3807
+ const vt = this.camera.getTransform();
3808
+ const currentSX = worldX * vt.scale + vt.offsetX;
3809
+ const currentSY = -worldY * vt.scale + vt.offsetY;
3810
+ const dx = rect.width / 2 - currentSX;
3811
+ const dy = rect.height / 2 - currentSY;
3812
+ this.camera.pan(dx, dy);
3813
+ this.requestRender();
3814
+ this.emitter.emit("viewchange", this.camera.getTransform());
3815
+ }
3816
+ getViewTransform() {
3817
+ return { ...this.camera.getTransform() };
3818
+ }
3819
+ /** @internal */
3820
+ getZoomSpeed() {
3821
+ return this.options.zoomSpeed;
3822
+ }
3823
+ // === Layers ===
3824
+ getLayers() {
3825
+ return this.layerManager.getAllLayers();
3826
+ }
3827
+ setLayerVisible(name, visible) {
3828
+ this.layerManager.setVisible(name, visible);
3829
+ this.requestRender();
3830
+ }
3831
+ setLayerColor(name, color) {
3832
+ this.layerManager.setColorOverride(name, color);
3833
+ this.requestRender();
3834
+ }
3835
+ // === Theme ===
3836
+ setTheme(theme) {
3837
+ this.options.theme = theme;
3838
+ this.requestRender();
3839
+ }
3840
+ getTheme() {
3841
+ return this.options.theme;
3842
+ }
3843
+ setBackgroundColor(color) {
3844
+ this.options.backgroundColor = color;
3845
+ this.requestRender();
3846
+ }
3847
+ // === Tools ===
3848
+ setTool(tool) {
3849
+ if (this.currentTool === "measure" && tool !== "measure") {
3850
+ this.measureTool.deactivate();
3851
+ }
3852
+ this.currentTool = tool;
3853
+ this.canvas.style.cursor = this.getCursorForTool(tool);
3854
+ if (tool === "measure") {
3855
+ this.measureTool.activate();
3856
+ }
3857
+ if (tool !== "select") {
3858
+ this.selectedEntityIndex = -1;
3859
+ }
3860
+ this.requestRender();
3861
+ }
3862
+ getTool() {
3863
+ return this.currentTool;
3864
+ }
3865
+ getCursorForTool(tool) {
3866
+ switch (tool) {
3867
+ case "pan":
3868
+ return "grab";
3869
+ case "select":
3870
+ return "crosshair";
3871
+ case "measure":
3872
+ return "crosshair";
3873
+ }
3874
+ }
3875
+ // === Events ===
3876
+ on(event, callback) {
3877
+ this.emitter.on(event, callback);
3878
+ }
3879
+ off(event, callback) {
3880
+ this.emitter.off(event, callback);
3881
+ }
3882
+ // === Document Access ===
3883
+ getDocument() {
3884
+ return this.doc;
3885
+ }
3886
+ getEntities() {
3887
+ return this.doc?.entities ?? [];
3888
+ }
3889
+ // === Lifecycle ===
3890
+ resize() {
3891
+ this.renderer.updateSize();
3892
+ this.requestRender();
3893
+ }
3894
+ destroy() {
3895
+ this.destroyed = true;
3896
+ this.inputHandler.destroy();
3897
+ this.resizeObserver.disconnect();
3898
+ this.renderer.destroy();
3899
+ this.emitter.removeAllListeners();
3900
+ this.spatialIndex.clear();
3901
+ this.layerManager.clear();
3902
+ this.doc = null;
3903
+ }
3904
+ // === Internal (called by InputHandler) ===
3905
+ /** @internal */
3906
+ requestRender() {
3907
+ if (this.renderPending || this.destroyed) return;
3908
+ this.renderPending = true;
3909
+ requestAnimationFrame(() => {
3910
+ this.renderPending = false;
3911
+ if (this.destroyed) return;
3912
+ this.doRender();
3913
+ });
3914
+ }
3915
+ doRender() {
3916
+ if (!this.doc) {
3917
+ this.renderer.renderEmpty(this.options.theme);
3918
+ return;
3919
+ }
3920
+ this.renderer.render(
3921
+ this.doc,
3922
+ this.camera.getTransform(),
3923
+ this.options.theme,
3924
+ this.layerManager.getVisibleLayerNames(),
3925
+ this.selectedEntityIndex
3926
+ );
3927
+ if (this.currentTool === "measure" && this.measureTool.state.phase !== "idle") {
3928
+ const ctx = this.renderer.getContext();
3929
+ renderMeasureOverlay(
3930
+ ctx,
3931
+ this.camera.getTransform(),
3932
+ this.measureTool,
3933
+ this.mouseScreenX,
3934
+ this.mouseScreenY,
3935
+ this.options.theme
3936
+ );
3937
+ }
3938
+ }
3939
+ /** @internal */
3940
+ handlePan(dx, dy) {
3941
+ this.camera.pan(dx, dy);
3942
+ this.requestRender();
3943
+ this.emitter.emit("viewchange", this.camera.getTransform());
3944
+ }
3945
+ /** @internal */
3946
+ handleZoom(screenX, screenY, factor) {
3947
+ this.camera.zoom(screenX, screenY, factor);
3948
+ this.requestRender();
3949
+ this.emitter.emit("viewchange", this.camera.getTransform());
3950
+ }
3951
+ /** @internal */
3952
+ handleClick(screenX, screenY) {
3953
+ if (!this.doc) return;
3954
+ const vt = this.camera.getTransform();
3955
+ const [wx, wy] = screenToWorld(vt, screenX, screenY);
3956
+ switch (this.currentTool) {
3957
+ case "select":
3958
+ this.handleSelect(wx, wy, screenX, screenY);
3959
+ break;
3960
+ case "measure": {
3961
+ const snaps = findSnaps(wx, wy, this.doc.entities, this.spatialIndex, vt.scale);
3962
+ const snap = snaps.length > 0 ? snaps[0] : null;
3963
+ const result = this.measureTool.handleClick(wx, wy, snap);
3964
+ this.requestRender();
3965
+ if (result) {
3966
+ this.emitter.emit("measure", result);
3967
+ }
3968
+ break;
3969
+ }
3970
+ }
3971
+ }
3972
+ /** @internal — called by InputHandler for mouse move */
3973
+ handleMouseMove(screenX, screenY) {
3974
+ this.mouseScreenX = screenX;
3975
+ this.mouseScreenY = screenY;
3976
+ if (this.currentTool === "measure" && this.doc) {
3977
+ const vt = this.camera.getTransform();
3978
+ const [wx, wy] = screenToWorld(vt, screenX, screenY);
3979
+ const snaps = findSnaps(wx, wy, this.doc.entities, this.spatialIndex, vt.scale);
3980
+ this.measureTool.handleMove(snaps[0] ?? null);
3981
+ this.requestRender();
3982
+ }
3983
+ }
3984
+ handleSelect(wx, wy, sx, sy) {
3985
+ if (!this.doc) return;
3986
+ const index = hitTest(
3987
+ wx,
3988
+ wy,
3989
+ this.doc.entities,
3990
+ this.spatialIndex,
3991
+ this.layerManager.getVisibleLayerNames(),
3992
+ this.camera.getTransform().scale
3993
+ );
3994
+ this.selectedEntityIndex = index;
3995
+ this.requestRender();
3996
+ if (index >= 0 && this.doc) {
3997
+ const entity = this.doc.entities[index];
3998
+ if (entity) {
3999
+ this.emitter.emit("select", {
4000
+ entity,
4001
+ entityIndex: index,
4002
+ worldPoint: { x: wx, y: wy },
4003
+ screenPoint: { x: sx, y: sy }
4004
+ });
4005
+ }
4006
+ }
4007
+ }
4008
+ computeDocumentBounds() {
4009
+ if (!this.doc) return null;
4010
+ if (this.doc.header.extMin && this.doc.header.extMax) {
4011
+ return {
4012
+ minX: this.doc.header.extMin.x,
4013
+ minY: this.doc.header.extMin.y,
4014
+ maxX: this.doc.header.extMax.x,
4015
+ maxY: this.doc.header.extMax.y
4016
+ };
4017
+ }
4018
+ return computeEntitiesBounds(this.doc.entities);
4019
+ }
4020
+ };
4021
+
4022
+ exports.CadViewer = CadViewer;
4023
+ exports.Camera = Camera;
4024
+ exports.CanvasRenderer = CanvasRenderer;
4025
+ exports.DxfParseError = DxfParseError;
4026
+ exports.EventEmitter = EventEmitter;
4027
+ exports.LayerManager = LayerManager;
4028
+ exports.MeasureTool = MeasureTool;
4029
+ exports.SpatialIndex = SpatialIndex;
4030
+ exports.THEMES = THEMES;
4031
+ exports.aciToDisplayColor = aciToDisplayColor;
4032
+ exports.aciToHex = aciToHex;
4033
+ exports.applyTransform = applyTransform;
4034
+ exports.computeEntitiesBounds = computeEntitiesBounds;
4035
+ exports.computeEntityBBox = computeEntityBBox;
4036
+ exports.drawEntity = drawEntity;
4037
+ exports.findSnaps = findSnaps;
4038
+ exports.fitToView = fitToView;
4039
+ exports.hitTest = hitTest;
4040
+ exports.parseDxf = parseDxf;
4041
+ exports.renderMeasureOverlay = renderMeasureOverlay;
4042
+ exports.resolveEntityColor = resolveEntityColor;
4043
+ exports.screenToWorld = screenToWorld;
4044
+ exports.trueColorToHex = trueColorToHex;
4045
+ exports.worldToScreen = worldToScreen;
4046
+ exports.zoomAtPoint = zoomAtPoint;
4047
+ //# sourceMappingURL=index.cjs.map
4048
+ //# sourceMappingURL=index.cjs.map