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