@xom11/whiteboard 0.25.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
  2. package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
  3. package/dist/ai.d.mts +3035 -108
  4. package/dist/ai.d.ts +3035 -108
  5. package/dist/ai.js +6780 -788
  6. package/dist/ai.js.map +1 -1
  7. package/dist/ai.mjs +5140 -577
  8. package/dist/ai.mjs.map +1 -1
  9. package/dist/catalog.json +5 -5
  10. package/dist/{chunk-NDEZJKNY.mjs → chunk-5JM35CXV.mjs} +4 -4
  11. package/dist/{chunk-NDEZJKNY.mjs.map → chunk-5JM35CXV.mjs.map} +1 -1
  12. package/dist/{chunk-VNCCIV6O.mjs → chunk-AJAHD35N.mjs} +779 -9
  13. package/dist/chunk-AJAHD35N.mjs.map +1 -0
  14. package/dist/{chunk-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
  15. package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
  16. package/dist/{chunk-ONBCUWVI.mjs → chunk-BU5KLO3P.mjs} +3 -3
  17. package/dist/{chunk-ONBCUWVI.mjs.map → chunk-BU5KLO3P.mjs.map} +1 -1
  18. package/dist/{chunk-CJBLJUWG.mjs → chunk-CXHNVYMD.mjs} +4 -4
  19. package/dist/{chunk-CJBLJUWG.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
  20. package/dist/chunk-H22OZYTW.mjs +265 -0
  21. package/dist/chunk-H22OZYTW.mjs.map +1 -0
  22. package/dist/chunk-J5LGTIGS.mjs +10 -0
  23. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  24. package/dist/{chunk-TB4CL25L.mjs → chunk-OQIQNKPQ.mjs} +206 -66
  25. package/dist/chunk-OQIQNKPQ.mjs.map +1 -0
  26. package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
  27. package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
  28. package/dist/{chunk-YSJOVBCD.mjs → chunk-QCZVFEN4.mjs} +4 -4
  29. package/dist/{chunk-YSJOVBCD.mjs.map → chunk-QCZVFEN4.mjs.map} +1 -1
  30. package/dist/{chunk-ESVPQWHX.mjs → chunk-QRUAEXLR.mjs} +5 -5
  31. package/dist/{chunk-ESVPQWHX.mjs.map → chunk-QRUAEXLR.mjs.map} +1 -1
  32. package/dist/{chunk-AYSFWUPK.mjs → chunk-SZDAS7LK.mjs} +79 -2
  33. package/dist/chunk-SZDAS7LK.mjs.map +1 -0
  34. package/dist/chunk-T3SOHYB2.mjs +851 -0
  35. package/dist/chunk-T3SOHYB2.mjs.map +1 -0
  36. package/dist/{chunk-I24QOHPU.mjs → chunk-V3YJ6JFL.mjs} +3 -3
  37. package/dist/{chunk-I24QOHPU.mjs.map → chunk-V3YJ6JFL.mjs.map} +1 -1
  38. package/dist/{chunk-REIJZDVZ.mjs → chunk-ZTQBUKLJ.mjs} +960 -196
  39. package/dist/chunk-ZTQBUKLJ.mjs.map +1 -0
  40. package/dist/geometry-2d.d.mts +1 -1
  41. package/dist/geometry-2d.d.ts +1 -1
  42. package/dist/geometry-2d.js +5521 -1384
  43. package/dist/geometry-2d.js.map +1 -1
  44. package/dist/geometry-2d.mjs +5 -4
  45. package/dist/geometry-3d.d.mts +1 -1
  46. package/dist/geometry-3d.d.ts +1 -1
  47. package/dist/geometry-3d.js +1351 -252
  48. package/dist/geometry-3d.js.map +1 -1
  49. package/dist/geometry-3d.mjs +4 -3
  50. package/dist/graph-2d.d.mts +1 -1
  51. package/dist/graph-2d.d.ts +1 -1
  52. package/dist/graph-2d.js +1517 -341
  53. package/dist/graph-2d.js.map +1 -1
  54. package/dist/graph-2d.mjs +7 -6
  55. package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
  56. package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
  57. package/dist/{host-A64ITWVX.mjs → host-2ISGVO7O.mjs} +6 -5
  58. package/dist/host-2ISGVO7O.mjs.map +1 -0
  59. package/dist/{host-L7FMFZUW.mjs → host-4P766V4J.mjs} +1363 -463
  60. package/dist/host-4P766V4J.mjs.map +1 -0
  61. package/dist/{host-QK53UYMD.mjs → host-HOSJHQ5H.mjs} +10 -9
  62. package/dist/host-HOSJHQ5H.mjs.map +1 -0
  63. package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
  64. package/dist/host-ZQCDAT6O.mjs.map +1 -0
  65. package/dist/index.d.mts +10 -4
  66. package/dist/index.d.ts +10 -4
  67. package/dist/index.js +5746 -1603
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +26 -22
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/latex.d.mts +1 -1
  72. package/dist/latex.d.ts +1 -1
  73. package/dist/latex.mjs +2 -1
  74. package/dist/render-ZX2O2IK7.mjs +10 -0
  75. package/dist/{render-3WTY7NZB.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
  76. package/dist/serialize-N4G6RFBB.mjs +9 -0
  77. package/dist/{serialize-SRJVKYUG.mjs.map → serialize-N4G6RFBB.mjs.map} +1 -1
  78. package/dist/{types-DWRyCa2m.d.ts → types-BHYC2Fiw.d.mts} +130 -1
  79. package/dist/{types-DWRyCa2m.d.mts → types-BHYC2Fiw.d.ts} +130 -1
  80. package/package.json +10 -1
  81. package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
  82. package/dist/chunk-AYSFWUPK.mjs.map +0 -1
  83. package/dist/chunk-REIJZDVZ.mjs.map +0 -1
  84. package/dist/chunk-TB4CL25L.mjs.map +0 -1
  85. package/dist/chunk-VNCCIV6O.mjs.map +0 -1
  86. package/dist/chunk-VRHWDZ66.mjs +0 -96
  87. package/dist/chunk-VRHWDZ66.mjs.map +0 -1
  88. package/dist/host-A64ITWVX.mjs.map +0 -1
  89. package/dist/host-L7FMFZUW.mjs.map +0 -1
  90. package/dist/host-QK53UYMD.mjs.map +0 -1
  91. package/dist/host-QS2EOTRJ.mjs.map +0 -1
  92. package/dist/render-3WTY7NZB.mjs +0 -9
  93. package/dist/serialize-SRJVKYUG.mjs +0 -8
@@ -547,12 +547,354 @@ function constraintRefs2D(c) {
547
547
  return [c.vertices[0], c.vertices[1], c.vertices[2]];
548
548
  case "orthocenter":
549
549
  return [c.vertices[0], c.vertices[1], c.vertices[2]];
550
+ case "onPerpendicular":
551
+ return [c.through, c.perpToA, c.perpToB];
552
+ case "onPerpBisector":
553
+ return [c.p1, c.p2];
554
+ case "onCircleAroundPoint":
555
+ return [c.center, c.radiusPoint];
556
+ case "tangentPointExt":
557
+ return [c.from, c.circle];
558
+ case "circleIntersection":
559
+ return [c.c1, c.c2];
560
+ case "circleSecondIntersection":
561
+ return [c.c1, c.c2, c.exclude];
562
+ case "secondIntersection":
563
+ return [c.line, c.circle, c.other];
564
+ case "tangencyPoint":
565
+ return [c.circle, c.onLine];
566
+ case "arcMidpoint":
567
+ return [c.circle, c.a, c.b, c.notContaining];
568
+ case "pointAtDistance": {
569
+ const d = c.distance;
570
+ const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
571
+ return [c.from, c.through, ...extra];
572
+ }
573
+ case "excenter":
574
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
550
575
  default:
551
576
  return [];
552
577
  }
553
578
  }
554
579
 
555
- // src/core/scene/kinds/point.ts
580
+ // src/core/scene/kinds/point-constraints/_types.ts
581
+ function definePointConstraint(m) {
582
+ return m;
583
+ }
584
+
585
+ // src/core/scene/kinds/point-constraints/free.ts
586
+ var freeConstraint = definePointConstraint({
587
+ kind: "free",
588
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
589
+ render: (obj, ctx, c, opts) => {
590
+ const board = ctx.jxg;
591
+ return board.create("point", [c.x, c.y], opts);
592
+ }
593
+ });
594
+
595
+ // src/core/scene/kinds/point-constraints/onAxis.ts
596
+ var onAxisConstraint = definePointConstraint({
597
+ kind: "onAxis",
598
+ describe: (obj, _state, c) => `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`,
599
+ render: (obj, ctx, c, opts) => {
600
+ const board = ctx.jxg;
601
+ const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
602
+ return board.create("point", coords, opts);
603
+ }
604
+ });
605
+
606
+ // src/core/scene/kinds/point-constraints/midpoint.ts
607
+ var midpointConstraint = definePointConstraint({
608
+ kind: "midpoint",
609
+ describe: (obj, state, c) => {
610
+ const l1 = state?.objects[c.p1]?.label ?? c.p1;
611
+ const l2 = state?.objects[c.p2]?.label ?? c.p2;
612
+ return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
613
+ },
614
+ render: (obj, ctx, c, opts) => {
615
+ const board = ctx.jxg;
616
+ const p1 = ctx.resolveRef(c.p1);
617
+ const p2 = ctx.resolveRef(c.p2);
618
+ return board.create("midpoint", [p1, p2], opts);
619
+ }
620
+ });
621
+
622
+ // src/core/scene/kinds/point-constraints/perpFoot.ts
623
+ var perpFootConstraint = definePointConstraint({
624
+ kind: "perpFoot",
625
+ validate: (c) => {
626
+ if (!c.from || !c.onLine) {
627
+ throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
628
+ }
629
+ },
630
+ describe: (obj, state, c) => {
631
+ const fromLabel = state?.objects[c.from]?.label ?? c.from;
632
+ const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
633
+ return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
634
+ },
635
+ render: (obj, ctx, c, opts) => {
636
+ const board = ctx.jxg;
637
+ const from = ctx.resolveRef(c.from);
638
+ const onLine = ctx.resolveRef(c.onLine);
639
+ return board.create("perpendicularpoint", [onLine, from], opts);
640
+ }
641
+ });
642
+
643
+ // src/core/scene/kinds/point-constraints/circumcenter.ts
644
+ var circumcenterConstraint = definePointConstraint({
645
+ kind: "circumcenter",
646
+ validate: (c) => {
647
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
648
+ throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
649
+ }
650
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
651
+ throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
652
+ }
653
+ },
654
+ describe: (obj, state, c) => {
655
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
656
+ return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
657
+ },
658
+ render: (obj, ctx, c, opts) => {
659
+ const board = ctx.jxg;
660
+ const a = ctx.resolveRef(c.vertices[0]);
661
+ const b = ctx.resolveRef(c.vertices[1]);
662
+ const c3 = ctx.resolveRef(c.vertices[2]);
663
+ return board.create("circumcenter", [a, b, c3], opts);
664
+ }
665
+ });
666
+
667
+ // src/core/scene/kinds/point-constraints/incenter.ts
668
+ var incenterConstraint = definePointConstraint({
669
+ kind: "incenter",
670
+ validate: (c) => {
671
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
672
+ throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
673
+ }
674
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
675
+ throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
676
+ }
677
+ },
678
+ describe: (obj, state, c) => {
679
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
680
+ return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
681
+ },
682
+ render: (obj, ctx, c, opts) => {
683
+ const board = ctx.jxg;
684
+ const a = ctx.resolveRef(c.vertices[0]);
685
+ const b = ctx.resolveRef(c.vertices[1]);
686
+ const c3 = ctx.resolveRef(c.vertices[2]);
687
+ return board.create("incenter", [a, b, c3], opts);
688
+ }
689
+ });
690
+
691
+ // src/core/scene/kinds/point-constraints/onLine.ts
692
+ var onLineConstraint = definePointConstraint({
693
+ kind: "onLine",
694
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`,
695
+ render: (obj, ctx, c, opts) => {
696
+ const board = ctx.jxg;
697
+ const line = ctx.resolveRef(c.lineId);
698
+ const p1 = line.point1;
699
+ const p2 = line.point2;
700
+ const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
701
+ const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
702
+ return board.create("glider", [sx, sy, line], opts);
703
+ }
704
+ });
705
+
706
+ // src/core/scene/kinds/point-constraints/onSegment.ts
707
+ var onSegmentConstraint = definePointConstraint({
708
+ kind: "onSegment",
709
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`,
710
+ render: (obj, ctx, c, opts) => {
711
+ const board = ctx.jxg;
712
+ const seg = ctx.resolveRef(c.segmentId);
713
+ const p1 = seg.point1;
714
+ const p2 = seg.point2;
715
+ const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
716
+ const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
717
+ return board.create("glider", [sx, sy, seg], opts);
718
+ }
719
+ });
720
+
721
+ // src/core/scene/kinds/point-constraints/onCircle.ts
722
+ var onCircleConstraint = definePointConstraint({
723
+ kind: "onCircle",
724
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`,
725
+ render: (obj, ctx, c, opts) => {
726
+ const board = ctx.jxg;
727
+ const circle = ctx.resolveRef(c.circleId);
728
+ const O = circle.center ?? circle.midpoint;
729
+ const ox = O ? O.X() : 0;
730
+ const oy = O ? O.Y() : 0;
731
+ return board.create("glider", [ox + Math.cos(c.theta), oy + Math.sin(c.theta), circle], opts);
732
+ }
733
+ });
734
+
735
+ // src/core/scene/kinds/point-constraints/onPolygon.ts
736
+ var onPolygonConstraint = definePointConstraint({
737
+ kind: "onPolygon",
738
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`,
739
+ render: (obj, ctx, c, opts) => {
740
+ const board = ctx.jxg;
741
+ const poly = ctx.resolveRef(c.polygonId);
742
+ return board.create("glider", [c.u, c.v, poly], opts);
743
+ }
744
+ });
745
+
746
+ // src/core/scene/kinds/point-constraints/centroid.ts
747
+ var centroidConstraint = definePointConstraint({
748
+ kind: "centroid",
749
+ validate: (c) => {
750
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
751
+ throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
752
+ }
753
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
754
+ throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
755
+ }
756
+ },
757
+ describe: (obj, state, c) => {
758
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
759
+ return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
760
+ },
761
+ render: (obj, ctx, c, opts) => {
762
+ const board = ctx.jxg;
763
+ const a = ctx.resolveRef(c.vertices[0]);
764
+ const b = ctx.resolveRef(c.vertices[1]);
765
+ const c3 = ctx.resolveRef(c.vertices[2]);
766
+ return board.create("point", [
767
+ () => (a.X() + b.X() + c3.X()) / 3,
768
+ () => (a.Y() + b.Y() + c3.Y()) / 3
769
+ ], opts);
770
+ }
771
+ });
772
+
773
+ // src/core/scene/kinds/pointConstructions.ts
774
+ function dist(p, q) {
775
+ return Math.hypot(p[0] - q[0], p[1] - q[1]);
776
+ }
777
+ function sideOf(a, b, p) {
778
+ return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
779
+ }
780
+ function arcMidpoint(center, radius, a, b, notContaining) {
781
+ const mcx = (a[0] + b[0]) / 2;
782
+ const mcy = (a[1] + b[1]) / 2;
783
+ let ux = mcx - center[0];
784
+ let uy = mcy - center[1];
785
+ let len = Math.hypot(ux, uy);
786
+ if (len < 1e-9) {
787
+ ux = -(b[1] - a[1]);
788
+ uy = b[0] - a[0];
789
+ len = Math.hypot(ux, uy) || 1;
790
+ }
791
+ ux /= len;
792
+ uy /= len;
793
+ const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
794
+ const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
795
+ const sN = sideOf(a, b, notContaining);
796
+ if (Math.abs(sN) < 1e-9) {
797
+ return dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
798
+ }
799
+ const s1 = sideOf(a, b, cand1);
800
+ return s1 * sN < 0 ? cand1 : cand2;
801
+ }
802
+ function excenter(vertices, oppositeIndex) {
803
+ const [A, B, C] = vertices;
804
+ const a = dist(B, C);
805
+ const b = dist(C, A);
806
+ const c = dist(A, B);
807
+ const w = [a, b, c];
808
+ w[oppositeIndex] = -w[oppositeIndex];
809
+ const sum = w[0] + w[1] + w[2];
810
+ if (Math.abs(sum) < 1e-9) return A;
811
+ return [
812
+ (w[0] * A[0] + w[1] * B[0] + w[2] * C[0]) / sum,
813
+ (w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
814
+ ];
815
+ }
816
+ function pointAtDistanceCoord(from, through, d) {
817
+ const dx = through[0] - from[0];
818
+ const dy = through[1] - from[1];
819
+ const len = Math.hypot(dx, dy) || 1;
820
+ return [through[0] + d * dx / len, through[1] + d * dy / len];
821
+ }
822
+ function radicalAxisFoot(o1, r1, o2, r2) {
823
+ const dx = o2[0] - o1[0], dy = o2[1] - o1[1];
824
+ const d2 = dx * dx + dy * dy;
825
+ if (d2 < 1e-12) return o1;
826
+ const t = (d2 + r1 * r1 - r2 * r2) / (2 * d2);
827
+ return [o1[0] + t * dx, o1[1] + t * dy];
828
+ }
829
+
830
+ // src/core/scene/kinds/point-constraints/arcMidpoint.ts
831
+ var arcMidpointConstraint = definePointConstraint({
832
+ kind: "arcMidpoint",
833
+ validate: (c) => {
834
+ if (!c.circle || !c.a || !c.b || !c.notContaining) {
835
+ throw new Error("point.arcMidpoint: circle, a, b, notContaining b\u1EAFt bu\u1ED9c");
836
+ }
837
+ },
838
+ describe: (obj, state, c) => {
839
+ const al = state?.objects[c.a]?.label ?? c.a;
840
+ const bl = state?.objects[c.b]?.label ?? c.b;
841
+ const nl = state?.objects[c.notContaining]?.label ?? c.notContaining;
842
+ return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (kh\xF4ng ch\u1EE9a ${nl})`;
843
+ },
844
+ render: (obj, ctx, c, opts) => {
845
+ const board = ctx.jxg;
846
+ const circle = ctx.resolveRef(c.circle);
847
+ const A = ctx.resolveRef(c.a);
848
+ const B = ctx.resolveRef(c.b);
849
+ const N = ctx.resolveRef(c.notContaining);
850
+ const O = circle?.center ?? circle?.midpoint ?? circle;
851
+ const am = () => arcMidpoint(
852
+ [O.X(), O.Y()],
853
+ circle.Radius(),
854
+ [A.X(), A.Y()],
855
+ [B.X(), B.Y()],
856
+ [N.X(), N.Y()]
857
+ );
858
+ return board.create("point", [() => am()[0], () => am()[1]], opts);
859
+ }
860
+ });
861
+
862
+ // src/core/scene/kinds/point-constraints/excenter.ts
863
+ var excenterConstraint = definePointConstraint({
864
+ kind: "excenter",
865
+ validate: (c) => {
866
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
867
+ throw new Error("point.excenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
868
+ }
869
+ if (!c.opposite) throw new Error("point.excenter: opposite b\u1EAFt bu\u1ED9c");
870
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
871
+ throw new Error("point.excenter: 3 vertex id ph\u1EA3i non-empty");
872
+ }
873
+ if (!c.vertices.includes(c.opposite)) {
874
+ throw new Error("point.excenter: opposite ph\u1EA3i l\xE0 m\u1ED9t trong vertices");
875
+ }
876
+ },
877
+ describe: (obj, state, c) => {
878
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
879
+ const opp = state?.objects[c.opposite]?.label ?? c.opposite;
880
+ return `${obj.label} = t\xE2m b\xE0ng ti\u1EBFp \u0394${labels} \u0111\u1ED1i di\u1EC7n ${opp}`;
881
+ },
882
+ render: (obj, ctx, c, opts) => {
883
+ const board = ctx.jxg;
884
+ const a = ctx.resolveRef(c.vertices[0]);
885
+ const b = ctx.resolveRef(c.vertices[1]);
886
+ const c3 = ctx.resolveRef(c.vertices[2]);
887
+ const oppIdx = c.vertices.indexOf(c.opposite);
888
+ const idx = oppIdx < 0 ? 0 : oppIdx;
889
+ const ex = () => excenter(
890
+ [[a.X(), a.Y()], [b.X(), b.Y()], [c3.X(), c3.Y()]],
891
+ idx
892
+ );
893
+ return board.create("point", [() => ex()[0], () => ex()[1]], opts);
894
+ }
895
+ });
896
+
897
+ // src/core/scene/kinds/point-constraints/shared.ts
556
898
  function buildJxgTransforms(board, ctx, t) {
557
899
  switch (t.kind) {
558
900
  case "translate":
@@ -579,6 +921,283 @@ function buildJxgTransforms(board, ctx, t) {
579
921
  }
580
922
  }
581
923
  }
924
+ function makeDistanceFn(ctx, d) {
925
+ const scale = d.scale ?? 1;
926
+ const offset = d.offset ?? 0;
927
+ if (d.kind === "literal") {
928
+ const v = d.value;
929
+ return () => scale * v + offset;
930
+ }
931
+ if (d.kind === "segmentLength") {
932
+ const p = ctx.resolveRef(d.p1);
933
+ const q = ctx.resolveRef(d.p2);
934
+ return () => scale * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
935
+ }
936
+ const circle = ctx.resolveRef(d.circle);
937
+ return () => scale * circle.Radius() + offset;
938
+ }
939
+ function buildPointOpts(obj) {
940
+ return {
941
+ name: obj.label,
942
+ withLabel: obj.attrs.showLabel ?? true,
943
+ visible: obj.visible,
944
+ fixed: obj.locked,
945
+ strokeColor: obj.attrs.color ?? "#1e40af",
946
+ fillColor: obj.attrs.color ?? "#1e40af",
947
+ face: obj.attrs.face ?? "o",
948
+ size: obj.attrs.size ?? 4
949
+ };
950
+ }
951
+
952
+ // src/core/scene/kinds/point-constraints/pointAtDistance.ts
953
+ var pointAtDistanceConstraint = definePointConstraint({
954
+ kind: "pointAtDistance",
955
+ describe: (obj, state, c) => {
956
+ const fromL = state?.objects[c.from]?.label ?? c.from;
957
+ const thrL = state?.objects[c.through]?.label ?? c.through;
958
+ const d = c.distance;
959
+ const dLabel = d.kind === "literal" ? `${d.value}` : d.kind === "segmentLength" ? `${state?.objects[d.p1]?.label ?? d.p1}${state?.objects[d.p2]?.label ?? d.p2}` : `b\xE1n k\xEDnh (${state?.objects[d.circle]?.label ?? d.circle})`;
960
+ return `${obj.label} = tr\xEAn tia ${fromL}${thrL} k\xE9o d\xE0i, c\xE1ch ${thrL} kho\u1EA3ng ${dLabel}`;
961
+ },
962
+ render: (obj, ctx, c, opts) => {
963
+ const board = ctx.jxg;
964
+ const A = ctx.resolveRef(c.from);
965
+ const B = ctx.resolveRef(c.through);
966
+ const dFn = makeDistanceFn(ctx, c.distance);
967
+ const pc = () => pointAtDistanceCoord([A.X(), A.Y()], [B.X(), B.Y()], dFn());
968
+ return board.create("point", [() => pc()[0], () => pc()[1]], opts);
969
+ }
970
+ });
971
+
972
+ // src/core/scene/kinds/point-constraints/circleIntersection.ts
973
+ var circleIntersectionConstraint = definePointConstraint({
974
+ kind: "circleIntersection",
975
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
976
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
977
+ render: (obj, ctx, c, opts) => {
978
+ const board = ctx.jxg;
979
+ const k1 = ctx.resolveRef(c.c1);
980
+ const k2 = ctx.resolveRef(c.c2);
981
+ return board.create("intersection", [k1, k2, c.which], opts);
982
+ }
983
+ });
984
+
985
+ // src/core/scene/kinds/point-constraints/circleSecondIntersection.ts
986
+ var circleSecondIntersectionConstraint = definePointConstraint({
987
+ kind: "circleSecondIntersection",
988
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
989
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
990
+ render: (obj, ctx, c, opts) => {
991
+ const board = ctx.jxg;
992
+ const k1 = ctx.resolveRef(c.c1);
993
+ const k2 = ctx.resolveRef(c.c2);
994
+ const ex = ctx.resolveRef(c.exclude);
995
+ return board.create("otherintersection", [k1, k2, ex], opts);
996
+ }
997
+ });
998
+
999
+ // src/core/scene/kinds/point-constraints/secondIntersection.ts
1000
+ var secondIntersectionConstraint = definePointConstraint({
1001
+ kind: "secondIntersection",
1002
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
1003
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1004
+ render: (obj, ctx, c, opts) => {
1005
+ const board = ctx.jxg;
1006
+ const line = ctx.resolveRef(c.line);
1007
+ const circle = ctx.resolveRef(c.circle);
1008
+ const other = ctx.resolveRef(c.other);
1009
+ return board.create("otherintersection", [circle, line, other], opts);
1010
+ }
1011
+ });
1012
+
1013
+ // src/core/scene/kinds/point-constraints/tangencyPoint.ts
1014
+ var tangencyPointConstraint = definePointConstraint({
1015
+ kind: "tangencyPoint",
1016
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
1017
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1018
+ render: (obj, ctx, c, opts) => {
1019
+ const board = ctx.jxg;
1020
+ const circle = ctx.resolveRef(c.circle);
1021
+ const line = ctx.resolveRef(c.onLine);
1022
+ const O = circle?.center ?? circle?.midpoint ?? circle;
1023
+ return board.create("perpendicularpoint", [line, O], opts);
1024
+ }
1025
+ });
1026
+
1027
+ // src/core/scene/kinds/point-constraints/transformed.ts
1028
+ var transformedConstraint = definePointConstraint({
1029
+ kind: "transformed",
1030
+ describe: (obj, state, c) => {
1031
+ const t = c.transform;
1032
+ const labelRef = (id) => state?.objects[id]?.label ?? id;
1033
+ const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
1034
+ return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
1035
+ },
1036
+ render: (obj, ctx, c, opts) => {
1037
+ const board = ctx.jxg;
1038
+ const src = ctx.resolveRef(c.source);
1039
+ const transforms = buildJxgTransforms(board, ctx, c.transform);
1040
+ const parent = transforms.length === 1 ? transforms[0] : transforms;
1041
+ const pt = board.create("point", [src, parent], opts);
1042
+ pt._helpers = transforms;
1043
+ return pt;
1044
+ }
1045
+ });
1046
+
1047
+ // src/core/scene/kinds/point-constraints/orthocenter.ts
1048
+ var orthocenterConstraint = definePointConstraint({
1049
+ kind: "orthocenter",
1050
+ validate: (c) => {
1051
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
1052
+ throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
1053
+ }
1054
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
1055
+ throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
1056
+ }
1057
+ },
1058
+ describe: (obj, state, c) => {
1059
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1060
+ return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
1061
+ },
1062
+ render: (obj, ctx, c, opts) => {
1063
+ const board = ctx.jxg;
1064
+ const a = ctx.resolveRef(c.vertices[0]);
1065
+ const b = ctx.resolveRef(c.vertices[1]);
1066
+ const c3 = ctx.resolveRef(c.vertices[2]);
1067
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1068
+ const lineBC = board.create("line", [b, c3], hide);
1069
+ const altA = board.create("perpendicular", [lineBC, a], hide);
1070
+ const lineAC = board.create("line", [a, c3], hide);
1071
+ const altB = board.create("perpendicular", [lineAC, b], hide);
1072
+ const ortho = board.create("intersection", [altA, altB, 0], opts);
1073
+ ortho._helpers = [lineBC, altA, lineAC, altB];
1074
+ return ortho;
1075
+ }
1076
+ });
1077
+
1078
+ // src/core/scene/kinds/point-constraints/onPerpendicular.ts
1079
+ var onPerpendicularConstraint = definePointConstraint({
1080
+ kind: "onPerpendicular",
1081
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1082
+ render: (obj, ctx, c, opts) => {
1083
+ const board = ctx.jxg;
1084
+ const T = ctx.resolveRef(c.through);
1085
+ const A = ctx.resolveRef(c.perpToA);
1086
+ const B = ctx.resolveRef(c.perpToB);
1087
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1088
+ const refLine = board.create("line", [A, B], hide);
1089
+ const perpLine = board.create("perpendicular", [refLine, T], hide);
1090
+ const dx = B.X() - A.X();
1091
+ const dy = B.Y() - A.Y();
1092
+ const len = Math.hypot(dx, dy) || 1;
1093
+ const ux = -dy / len;
1094
+ const uy = dx / len;
1095
+ const x0 = T.X() + c.t * ux;
1096
+ const y0 = T.Y() + c.t * uy;
1097
+ const gl = board.create("glider", [x0, y0, perpLine], opts);
1098
+ gl._helpers = [refLine, perpLine];
1099
+ return gl;
1100
+ }
1101
+ });
1102
+
1103
+ // src/core/scene/kinds/point-constraints/onPerpBisector.ts
1104
+ var onPerpBisectorConstraint = definePointConstraint({
1105
+ kind: "onPerpBisector",
1106
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1107
+ render: (obj, ctx, c, opts) => {
1108
+ const board = ctx.jxg;
1109
+ const A = ctx.resolveRef(c.p1);
1110
+ const B = ctx.resolveRef(c.p2);
1111
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1112
+ const refLine = board.create("line", [A, B], hide);
1113
+ const mid = board.create("midpoint", [A, B], hide);
1114
+ const bisLine = board.create("perpendicular", [refLine, mid], hide);
1115
+ const Mx = (A.X() + B.X()) / 2;
1116
+ const My = (A.Y() + B.Y()) / 2;
1117
+ const dx = B.X() - A.X();
1118
+ const dy = B.Y() - A.Y();
1119
+ const len = Math.hypot(dx, dy) || 1;
1120
+ const ux = -dy / len;
1121
+ const uy = dx / len;
1122
+ const x0 = Mx + c.t * ux;
1123
+ const y0 = My + c.t * uy;
1124
+ const gl = board.create("glider", [x0, y0, bisLine], opts);
1125
+ gl._helpers = [refLine, mid, bisLine];
1126
+ return gl;
1127
+ }
1128
+ });
1129
+
1130
+ // src/core/scene/kinds/point-constraints/onCircleAroundPoint.ts
1131
+ var onCircleAroundPointConstraint = definePointConstraint({
1132
+ kind: "onCircleAroundPoint",
1133
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1134
+ render: (obj, ctx, c, opts) => {
1135
+ const board = ctx.jxg;
1136
+ const C = ctx.resolveRef(c.center);
1137
+ const R = ctx.resolveRef(c.radiusPoint);
1138
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1139
+ const auxCircle = board.create("circle", [C, R], hide);
1140
+ const r = Math.hypot(R.X() - C.X(), R.Y() - C.Y());
1141
+ const x0 = C.X() + r * Math.cos(c.theta);
1142
+ const y0 = C.Y() + r * Math.sin(c.theta);
1143
+ const gl = board.create("glider", [x0, y0, auxCircle], opts);
1144
+ gl._helpers = [auxCircle];
1145
+ return gl;
1146
+ }
1147
+ });
1148
+
1149
+ // src/core/scene/kinds/point-constraints/tangentPointExt.ts
1150
+ var tangentPointExtConstraint = definePointConstraint({
1151
+ kind: "tangentPointExt",
1152
+ describe: (obj, state, c) => {
1153
+ const fromLabel = state?.objects[c.from]?.label ?? c.from;
1154
+ const circleLabel = state?.objects[c.circle]?.label ?? c.circle;
1155
+ return `${obj.label} = ti\u1EBFp \u0111i\u1EC3m c\u1EE7a (${circleLabel}) v\u1EDBi ti\u1EBFp tuy\u1EBFn t\u1EEB ${fromLabel}`;
1156
+ },
1157
+ render: (obj, ctx, c, opts) => {
1158
+ const board = ctx.jxg;
1159
+ const P = ctx.resolveRef(c.from);
1160
+ const K = ctx.resolveRef(c.circle);
1161
+ const O = K.center ?? K.midpoint;
1162
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1163
+ const mid = board.create("midpoint", [P, O], hide);
1164
+ const thales = board.create("circle", [mid, P], hide);
1165
+ const inter = board.create("intersection", [thales, K, c.which], opts);
1166
+ inter._helpers = [mid, thales];
1167
+ return inter;
1168
+ }
1169
+ });
1170
+
1171
+ // src/core/scene/kinds/point-constraints/registry.ts
1172
+ var ALL = [
1173
+ freeConstraint,
1174
+ onAxisConstraint,
1175
+ midpointConstraint,
1176
+ perpFootConstraint,
1177
+ circumcenterConstraint,
1178
+ incenterConstraint,
1179
+ onLineConstraint,
1180
+ onSegmentConstraint,
1181
+ onCircleConstraint,
1182
+ onPolygonConstraint,
1183
+ centroidConstraint,
1184
+ arcMidpointConstraint,
1185
+ excenterConstraint,
1186
+ pointAtDistanceConstraint,
1187
+ circleIntersectionConstraint,
1188
+ circleSecondIntersectionConstraint,
1189
+ secondIntersectionConstraint,
1190
+ tangencyPointConstraint,
1191
+ transformedConstraint,
1192
+ orthocenterConstraint,
1193
+ onPerpendicularConstraint,
1194
+ onPerpBisectorConstraint,
1195
+ onCircleAroundPointConstraint,
1196
+ tangentPointExtConstraint
1197
+ ];
1198
+ var POINT_CONSTRAINTS = new Map(ALL.map((m) => [m.kind, m]));
1199
+
1200
+ // src/core/scene/kinds/point.ts
582
1201
  var def3 = {
583
1202
  type: "point",
584
1203
  schemaVersion: 1,
@@ -588,43 +1207,7 @@ var def3 = {
588
1207
  throw new Error("point: constraint required");
589
1208
  }
590
1209
  const c = a.constraint;
591
- if (c.kind === "perpFoot") {
592
- if (!c.from || !c.onLine) {
593
- throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
594
- }
595
- }
596
- if (c.kind === "circumcenter") {
597
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
598
- throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
599
- }
600
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
601
- throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
602
- }
603
- }
604
- if (c.kind === "incenter") {
605
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
606
- throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
607
- }
608
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
609
- throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
610
- }
611
- }
612
- if (c.kind === "centroid") {
613
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
614
- throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
615
- }
616
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
617
- throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
618
- }
619
- }
620
- if (c.kind === "orthocenter") {
621
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
622
- throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
623
- }
624
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
625
- throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
626
- }
627
- }
1210
+ POINT_CONSTRAINTS.get(c.kind)?.validate?.(c);
628
1211
  },
629
1212
  dependsOn: (a) => constraintRefs2D(a.constraint),
630
1213
  measure: (obj) => {
@@ -639,132 +1222,16 @@ var def3 = {
639
1222
  },
640
1223
  describe: (obj, state) => {
641
1224
  const c = obj.attrs.constraint;
642
- if (c.kind === "free") return `\u0110i\u1EC3m ${obj.label}`;
643
- if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`;
644
- if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
645
- if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
646
- if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
647
- if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
648
- if (c.kind === "midpoint") {
649
- const l1 = state?.objects[c.p1]?.label ?? c.p1;
650
- const l2 = state?.objects[c.p2]?.label ?? c.p2;
651
- return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
652
- }
653
- if (c.kind === "transformed") {
654
- const t = c.transform;
655
- const labelRef = (id) => state?.objects[id]?.label ?? id;
656
- const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
657
- return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
658
- }
659
- if (c.kind === "perpFoot") {
660
- const fromLabel = state?.objects[c.from]?.label ?? c.from;
661
- const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
662
- return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
663
- }
664
- if (c.kind === "circumcenter") {
665
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
666
- return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
667
- }
668
- if (c.kind === "incenter") {
669
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
670
- return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
671
- }
672
- if (c.kind === "centroid") {
673
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
674
- return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
675
- }
676
- if (c.kind === "orthocenter") {
677
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
678
- return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
679
- }
1225
+ const mod = POINT_CONSTRAINTS.get(c.kind);
1226
+ if (mod) return mod.describe(obj, state, c);
680
1227
  return `\u0110i\u1EC3m ${obj.label}`;
681
1228
  },
682
1229
  render: (obj, ctx) => {
683
1230
  const board = ctx.jxg;
684
1231
  const c = obj.attrs.constraint;
685
- const opts = {
686
- name: obj.label,
687
- withLabel: obj.attrs.showLabel ?? true,
688
- visible: obj.visible,
689
- fixed: obj.locked,
690
- strokeColor: obj.attrs.color ?? "#1e40af",
691
- fillColor: obj.attrs.color ?? "#1e40af",
692
- face: obj.attrs.face ?? "o",
693
- size: obj.attrs.size ?? 4
694
- };
695
- if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
696
- if (c.kind === "onAxis") {
697
- const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
698
- return board.create("point", coords, opts);
699
- }
700
- if (c.kind === "onLine") {
701
- const line = ctx.resolveRef(c.lineId);
702
- return board.create("glider", [c.t, c.t, line], opts);
703
- }
704
- if (c.kind === "onSegment") {
705
- const seg = ctx.resolveRef(c.segmentId);
706
- return board.create("glider", [c.t, c.t, seg], opts);
707
- }
708
- if (c.kind === "onCircle") {
709
- const circle = ctx.resolveRef(c.circleId);
710
- return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
711
- }
712
- if (c.kind === "onPolygon") {
713
- const poly = ctx.resolveRef(c.polygonId);
714
- return board.create("glider", [c.u, c.v, poly], opts);
715
- }
716
- if (c.kind === "midpoint") {
717
- const p1 = ctx.resolveRef(c.p1);
718
- const p2 = ctx.resolveRef(c.p2);
719
- return board.create("midpoint", [p1, p2], opts);
720
- }
721
- if (c.kind === "transformed") {
722
- const src = ctx.resolveRef(c.source);
723
- const transforms = buildJxgTransforms(board, ctx, c.transform);
724
- const parent = transforms.length === 1 ? transforms[0] : transforms;
725
- const pt = board.create("point", [src, parent], opts);
726
- pt._helpers = transforms;
727
- return pt;
728
- }
729
- if (c.kind === "perpFoot") {
730
- const from = ctx.resolveRef(c.from);
731
- const onLine = ctx.resolveRef(c.onLine);
732
- return board.create("perpendicularpoint", [onLine, from], opts);
733
- }
734
- if (c.kind === "circumcenter") {
735
- const a = ctx.resolveRef(c.vertices[0]);
736
- const b = ctx.resolveRef(c.vertices[1]);
737
- const c3 = ctx.resolveRef(c.vertices[2]);
738
- return board.create("circumcenter", [a, b, c3], opts);
739
- }
740
- if (c.kind === "incenter") {
741
- const a = ctx.resolveRef(c.vertices[0]);
742
- const b = ctx.resolveRef(c.vertices[1]);
743
- const c3 = ctx.resolveRef(c.vertices[2]);
744
- return board.create("incenter", [a, b, c3], opts);
745
- }
746
- if (c.kind === "centroid") {
747
- const a = ctx.resolveRef(c.vertices[0]);
748
- const b = ctx.resolveRef(c.vertices[1]);
749
- const c3 = ctx.resolveRef(c.vertices[2]);
750
- return board.create("point", [
751
- () => (a.X() + b.X() + c3.X()) / 3,
752
- () => (a.Y() + b.Y() + c3.Y()) / 3
753
- ], opts);
754
- }
755
- if (c.kind === "orthocenter") {
756
- const a = ctx.resolveRef(c.vertices[0]);
757
- const b = ctx.resolveRef(c.vertices[1]);
758
- const c3 = ctx.resolveRef(c.vertices[2]);
759
- const hide = { visible: false, withLabel: false, fixed: true, name: "" };
760
- const lineBC = board.create("line", [b, c3], hide);
761
- const altA = board.create("perpendicular", [lineBC, a], hide);
762
- const lineAC = board.create("line", [a, c3], hide);
763
- const altB = board.create("perpendicular", [lineAC, b], hide);
764
- const ortho = board.create("intersection", [altA, altB, 0], opts);
765
- ortho._helpers = [lineBC, altA, lineAC, altB];
766
- return ortho;
767
- }
1232
+ const opts = buildPointOpts(obj);
1233
+ const mod = POINT_CONSTRAINTS.get(c.kind);
1234
+ if (mod) return mod.render(obj, ctx, c, opts);
768
1235
  return board.create("point", [0, 0], opts);
769
1236
  },
770
1237
  /**
@@ -864,6 +1331,10 @@ function constructionRefs(c) {
864
1331
  return [c.p1, c.vertex, c.p2];
865
1332
  case "angleBisectorLines":
866
1333
  return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
1334
+ case "lineThrough":
1335
+ return [...c.points];
1336
+ case "radicalAxis":
1337
+ return [c.circle1, c.circle2];
867
1338
  case "tangent":
868
1339
  return [c.throughPoint, c.toCircle];
869
1340
  }
@@ -892,6 +1363,10 @@ var def5 = {
892
1363
  return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
893
1364
  case "angleBisectorLines":
894
1365
  return `${obj.label}: ph\xE2n gi\xE1c ${L(c.line1)} & ${L(c.line2)} (${c.branch === 0 ? "1" : "2"})`;
1366
+ case "lineThrough":
1367
+ return `${obj.label}: \u0111\u01B0\u1EDDng qua ${c.points.map(L).join("")}`;
1368
+ case "radicalAxis":
1369
+ return `${obj.label}: tr\u1EE5c \u0111\u1EB3ng ph\u01B0\u01A1ng ${L(c.circle1)} & ${L(c.circle2)}`;
895
1370
  case "tangent":
896
1371
  return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
897
1372
  }
@@ -972,6 +1447,47 @@ var def5 = {
972
1447
  selected._helpers = [other];
973
1448
  return selected;
974
1449
  }
1450
+ case "lineThrough": {
1451
+ const pts = c.points.map((id) => ctx.resolveRef(id));
1452
+ let bi = 0, bj = 1, best = -1;
1453
+ for (let i = 0; i < pts.length; i++) {
1454
+ for (let j = i + 1; j < pts.length; j++) {
1455
+ const dx = pts[i].X() - pts[j].X();
1456
+ const dy = pts[i].Y() - pts[j].Y();
1457
+ const d = dx * dx + dy * dy;
1458
+ if (d > best) {
1459
+ best = d;
1460
+ bi = i;
1461
+ bj = j;
1462
+ }
1463
+ }
1464
+ }
1465
+ return board.create("line", [pts[bi], pts[bj]], {
1466
+ ...baseOpts,
1467
+ straightFirst: true,
1468
+ straightLast: true
1469
+ });
1470
+ }
1471
+ case "radicalAxis": {
1472
+ const k1 = ctx.resolveRef(c.circle1);
1473
+ const k2 = ctx.resolveRef(c.circle2);
1474
+ const o1 = () => [k1.center.X(), k1.center.Y()];
1475
+ const o2 = () => [k2.center.X(), k2.center.Y()];
1476
+ const foot = () => radicalAxisFoot(o1(), k1.Radius(), o2(), k2.Radius());
1477
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1478
+ const f1 = board.create("point", [() => foot()[0], () => foot()[1]], hide);
1479
+ const f2 = board.create("point", [
1480
+ () => foot()[0] - (o2()[1] - o1()[1]),
1481
+ () => foot()[1] + (o2()[0] - o1()[0])
1482
+ ], hide);
1483
+ const line = board.create("line", [f1, f2], {
1484
+ ...baseOpts,
1485
+ straightFirst: true,
1486
+ straightLast: true
1487
+ });
1488
+ line._helpers = [f1, f2];
1489
+ return line;
1490
+ }
975
1491
  case "tangent": {
976
1492
  const through = ctx.resolveRef(c.throughPoint);
977
1493
  const toCircle = ctx.resolveRef(c.toCircle);
@@ -1074,10 +1590,29 @@ var def7 = {
1074
1590
  registerKind(def7);
1075
1591
 
1076
1592
  // src/core/scene/kinds/circle.ts
1593
+ function asConstruction(a) {
1594
+ if (a.construction) return a.construction;
1595
+ const raw = a;
1596
+ if (raw.kind === "incircle" && raw.vertices) {
1597
+ return {
1598
+ kind: "incircle",
1599
+ p1: raw.vertices[0],
1600
+ p2: raw.vertices[1],
1601
+ p3: raw.vertices[2]
1602
+ };
1603
+ }
1604
+ return void 0;
1605
+ }
1077
1606
  function constructionRefs2(c) {
1078
1607
  switch (c.kind) {
1079
1608
  case "circumscribed":
1080
1609
  return [c.p1, c.p2, c.p3];
1610
+ case "incircle":
1611
+ return [c.p1, c.p2, c.p3];
1612
+ case "excircle":
1613
+ return [c.p1, c.p2, c.p3];
1614
+ case "diameter":
1615
+ return [c.p1, c.p2];
1081
1616
  }
1082
1617
  }
1083
1618
  var def8 = {
@@ -1085,14 +1620,27 @@ var def8 = {
1085
1620
  schemaVersion: 1,
1086
1621
  migrate: {},
1087
1622
  validate: (a) => {
1088
- if (a?.construction) return;
1623
+ if (asConstruction(a)) return;
1624
+ if (typeof a?.radius === "number") {
1625
+ if (!a.center) throw new Error("circle: center b\u1EAFt bu\u1ED9c khi d\xF9ng radius");
1626
+ if (!(a.radius > 0)) throw new Error("circle: radius ph\u1EA3i > 0");
1627
+ return;
1628
+ }
1089
1629
  if (!a?.center || !a?.surfacePoint) {
1090
- throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
1630
+ throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction / radius)");
1091
1631
  }
1092
1632
  },
1093
- dependsOn: (a) => a.construction ? constructionRefs2(a.construction) : [a.center, a.surfacePoint],
1633
+ dependsOn: (a) => {
1634
+ const c = asConstruction(a);
1635
+ if (c) return constructionRefs2(c);
1636
+ if (typeof a.radius === "number") return [a.center];
1637
+ return [a.center, a.surfacePoint];
1638
+ },
1094
1639
  measure: (obj, state) => {
1095
- if (obj.attrs.construction) return null;
1640
+ if (asConstruction(obj.attrs)) return null;
1641
+ if (typeof obj.attrs.radius === "number") {
1642
+ return [{ label: "r", value: obj.attrs.radius }];
1643
+ }
1096
1644
  const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
1097
1645
  const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
1098
1646
  if (!center || !surface) return null;
@@ -1105,17 +1653,31 @@ var def8 = {
1105
1653
  },
1106
1654
  describe: (obj, state) => {
1107
1655
  const L = (id) => labelOf(id, state);
1108
- const c = obj.attrs.construction;
1656
+ const c = asConstruction(obj.attrs);
1109
1657
  if (c?.kind === "circumscribed") {
1110
1658
  return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1111
1659
  }
1660
+ if (c?.kind === "incircle") {
1661
+ return `\u0110\u01B0\u1EDDng tr\xF2n n\u1ED9i ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1662
+ }
1663
+ if (c?.kind === "excircle") {
1664
+ return `\u0110\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)} \u0111\u1ED1i di\u1EC7n ${L(c.opposite)}`;
1665
+ }
1666
+ if (c?.kind === "diameter") {
1667
+ return `\u0110\u01B0\u1EDDng tr\xF2n \u0111\u01B0\u1EDDng k\xEDnh ${L(c.p1)}${L(c.p2)}`;
1668
+ }
1669
+ if (typeof obj.attrs.radius === "number") {
1670
+ return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${obj.attrs.radius}`;
1671
+ }
1112
1672
  return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${L(obj.attrs.center)}${L(obj.attrs.surfacePoint)}`;
1113
1673
  },
1114
1674
  render: (obj, ctx) => {
1115
1675
  const board = ctx.jxg;
1676
+ const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
1677
+ const isCenter = isCenterLabel(obj.label);
1116
1678
  const baseOpts = {
1117
1679
  name: obj.label,
1118
- withLabel: obj.attrs.showLabel ?? false,
1680
+ withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
1119
1681
  strokeColor: obj.attrs.color ?? "#0f172a",
1120
1682
  strokeWidth: obj.attrs.width ?? 2,
1121
1683
  dash: obj.attrs.dash ?? 0,
@@ -1123,13 +1685,81 @@ var def8 = {
1123
1685
  visible: obj.visible,
1124
1686
  fixed: obj.locked
1125
1687
  };
1126
- const c = obj.attrs.construction;
1688
+ const c = asConstruction(obj.attrs);
1127
1689
  if (c?.kind === "circumscribed") {
1128
1690
  const p1 = ctx.resolveRef(c.p1);
1129
1691
  const p2 = ctx.resolveRef(c.p2);
1130
1692
  const p3 = ctx.resolveRef(c.p3);
1693
+ if (isCenter) {
1694
+ const center2 = board.create("circumcenter", [p1, p2, p3], {
1695
+ visible: obj.visible,
1696
+ withLabel: true,
1697
+ fixed: true,
1698
+ name: obj.label
1699
+ });
1700
+ const circ = board.create("circumcircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
1701
+ circ.center = circ.center ?? center2;
1702
+ circ._helpers = [center2];
1703
+ return circ;
1704
+ }
1131
1705
  return board.create("circumcircle", [p1, p2, p3], baseOpts);
1132
1706
  }
1707
+ if (c?.kind === "incircle") {
1708
+ const p1 = ctx.resolveRef(c.p1);
1709
+ const p2 = ctx.resolveRef(c.p2);
1710
+ const p3 = ctx.resolveRef(c.p3);
1711
+ if (isCenter) {
1712
+ const center2 = board.create("incenter", [p1, p2, p3], {
1713
+ visible: obj.visible,
1714
+ withLabel: true,
1715
+ fixed: true,
1716
+ name: obj.label
1717
+ });
1718
+ const circ = board.create("incircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
1719
+ circ.center = circ.center ?? center2;
1720
+ circ._helpers = [center2];
1721
+ return circ;
1722
+ }
1723
+ return board.create("incircle", [p1, p2, p3], baseOpts);
1724
+ }
1725
+ if (c?.kind === "excircle") {
1726
+ const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
1727
+ const ids = [c.p1, c.p2, c.p3];
1728
+ const oppIdx = Math.max(0, ids.indexOf(c.opposite));
1729
+ const verts = () => [
1730
+ [P[0].X(), P[0].Y()],
1731
+ [P[1].X(), P[1].Y()],
1732
+ [P[2].X(), P[2].Y()]
1733
+ ];
1734
+ const ctr = () => excenter(verts(), oppIdx);
1735
+ const radius = () => {
1736
+ const I = ctr();
1737
+ const others = [0, 1, 2].filter((i) => i !== oppIdx);
1738
+ const v = verts();
1739
+ const a = v[others[0]];
1740
+ const b = v[others[1]];
1741
+ const dx = b[0] - a[0];
1742
+ const dy = b[1] - a[1];
1743
+ const len = Math.hypot(dx, dy) || 1;
1744
+ return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
1745
+ };
1746
+ const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
1747
+ const circ = board.create("circle", [center2, () => radius()], baseOpts);
1748
+ circ._helpers = [center2];
1749
+ return circ;
1750
+ }
1751
+ if (c?.kind === "diameter") {
1752
+ const p1 = ctx.resolveRef(c.p1);
1753
+ const p2 = ctx.resolveRef(c.p2);
1754
+ const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
1755
+ const circ = board.create("circle", [center2, p2], baseOpts);
1756
+ circ._helpers = [center2];
1757
+ return circ;
1758
+ }
1759
+ if (typeof obj.attrs.radius === "number") {
1760
+ const center2 = ctx.resolveRef(obj.attrs.center);
1761
+ return board.create("circle", [center2, obj.attrs.radius], baseOpts);
1762
+ }
1133
1763
  const center = ctx.resolveRef(obj.attrs.center);
1134
1764
  const surface = ctx.resolveRef(obj.attrs.surfacePoint);
1135
1765
  return board.create("circle", [center, surface], baseOpts);
@@ -1273,19 +1903,53 @@ function regularVertexLabels(p1Label, p2Label, n) {
1273
1903
  }
1274
1904
  return `${p1Label}${p2Label}\u2026`;
1275
1905
  }
1906
+ function specialShapeName(kind) {
1907
+ switch (kind) {
1908
+ case "square":
1909
+ return "H\xECnh vu\xF4ng";
1910
+ case "rectangle":
1911
+ return "H\xECnh ch\u1EEF nh\u1EADt";
1912
+ case "rhombus":
1913
+ return "H\xECnh thoi";
1914
+ case "parallelogram":
1915
+ return "H\xECnh b\xECnh h\xE0nh";
1916
+ case "isoTrapezoid":
1917
+ return "H\xECnh thang c\xE2n";
1918
+ case "isoTriangle":
1919
+ return "Tam gi\xE1c c\xE2n";
1920
+ case "rightTriangle":
1921
+ return "Tam gi\xE1c vu\xF4ng";
1922
+ case "regular":
1923
+ return "";
1924
+ }
1925
+ }
1276
1926
  var def11 = {
1277
1927
  type: "polygon",
1278
1928
  schemaVersion: 1,
1279
1929
  migrate: {},
1280
1930
  validate: (a) => {
1281
1931
  if (a?.construction) {
1282
- if (a.construction.kind === "regular") {
1283
- if (!a.construction.p1 || !a.construction.p2) {
1284
- throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1285
- }
1286
- if (!Number.isFinite(a.construction.n) || a.construction.n < 3) {
1287
- throw new Error("polygon (regular): n \u2265 3");
1288
- }
1932
+ const c = a.construction;
1933
+ if (c.kind === "regular") {
1934
+ if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1935
+ if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
1936
+ return;
1937
+ }
1938
+ if (c.kind === "square") {
1939
+ if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1940
+ return;
1941
+ }
1942
+ if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
1943
+ if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
1944
+ return;
1945
+ }
1946
+ if (c.kind === "isoTriangle") {
1947
+ if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
1948
+ return;
1949
+ }
1950
+ if (c.kind === "rightTriangle") {
1951
+ if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
1952
+ return;
1289
1953
  }
1290
1954
  return;
1291
1955
  }
@@ -1294,14 +1958,51 @@ var def11 = {
1294
1958
  }
1295
1959
  },
1296
1960
  dependsOn: (a) => {
1297
- if (a.construction?.kind === "regular") return [a.construction.p1, a.construction.p2];
1298
- return [...a.vertices ?? []];
1961
+ const c = a.construction;
1962
+ if (!c) return [...a.vertices ?? []];
1963
+ switch (c.kind) {
1964
+ case "regular":
1965
+ return [c.p1, c.p2];
1966
+ case "square":
1967
+ return [c.p1, c.p2];
1968
+ case "rectangle":
1969
+ case "rhombus":
1970
+ case "parallelogram":
1971
+ case "isoTrapezoid":
1972
+ return [c.p1, c.p2, c.p3];
1973
+ case "isoTriangle":
1974
+ return [c.base1, c.base2, c.apex];
1975
+ case "rightTriangle":
1976
+ return [c.rightAngle, c.leg1End, c.leg2End];
1977
+ }
1299
1978
  },
1300
1979
  describe: (obj, state) => {
1301
- if (obj.attrs.construction?.kind === "regular") {
1302
- const c = obj.attrs.construction;
1303
- const labels = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1304
- return `${regularPolygonName(c.n)} ${labels}`;
1980
+ const c = obj.attrs.construction;
1981
+ if (c) {
1982
+ if (c.kind === "regular") {
1983
+ const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1984
+ return `${regularPolygonName(c.n)} ${labels2}`;
1985
+ }
1986
+ const name = specialShapeName(c.kind);
1987
+ let labels = [];
1988
+ switch (c.kind) {
1989
+ case "square":
1990
+ labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
1991
+ break;
1992
+ case "rectangle":
1993
+ case "rhombus":
1994
+ case "parallelogram":
1995
+ case "isoTrapezoid":
1996
+ labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
1997
+ break;
1998
+ case "isoTriangle":
1999
+ labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
2000
+ break;
2001
+ case "rightTriangle":
2002
+ labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
2003
+ break;
2004
+ }
2005
+ return `${name} ${labels.join("")}`;
1305
2006
  }
1306
2007
  return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
1307
2008
  },
@@ -1309,22 +2010,85 @@ var def11 = {
1309
2010
  const board = ctx.jxg;
1310
2011
  const label = obj.label;
1311
2012
  const showValue = obj.attrs.showValue ?? false;
1312
- if (obj.attrs.construction?.kind === "regular") {
1313
- const c = obj.attrs.construction;
1314
- const p1 = ctx.resolveRef(c.p1);
1315
- const p2 = ctx.resolveRef(c.p2);
1316
- return board.create("regularpolygon", [p1, p2, c.n], {
1317
- name: label,
1318
- withLabel: obj.attrs.showLabel ?? false,
1319
- borders: {
1320
- strokeColor: obj.attrs.color ?? "#0f172a",
1321
- strokeWidth: obj.attrs.width ?? 2
1322
- },
1323
- fillColor: obj.attrs.color ?? "#60a5fa",
1324
- fillOpacity: obj.attrs.fillOpacity ?? 0.15,
1325
- visible: obj.visible,
1326
- fixed: obj.locked
2013
+ const cons = obj.attrs.construction;
2014
+ const commonAttrs = {
2015
+ name: label,
2016
+ withLabel: obj.attrs.showLabel ?? false,
2017
+ borders: {
2018
+ strokeColor: obj.attrs.color ?? "#0f172a",
2019
+ strokeWidth: obj.attrs.width ?? 2
2020
+ },
2021
+ fillColor: obj.attrs.color ?? "#60a5fa",
2022
+ fillOpacity: obj.attrs.fillOpacity ?? 0.15,
2023
+ visible: obj.visible,
2024
+ fixed: obj.locked
2025
+ };
2026
+ if (cons?.kind === "regular") {
2027
+ const p1 = ctx.resolveRef(cons.p1);
2028
+ const p2 = ctx.resolveRef(cons.p2);
2029
+ return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
2030
+ }
2031
+ if (cons?.kind === "square") {
2032
+ const p1 = ctx.resolveRef(cons.p1);
2033
+ const p2 = ctx.resolveRef(cons.p2);
2034
+ return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
2035
+ }
2036
+ if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
2037
+ const A = ctx.resolveRef(cons.p1);
2038
+ const B = ctx.resolveRef(cons.p2);
2039
+ const C = ctx.resolveRef(cons.p3);
2040
+ const D = board.create(
2041
+ "point",
2042
+ [() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
2043
+ { visible: false, withLabel: false, fixed: true, name: "" }
2044
+ );
2045
+ const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
2046
+ poly2._helpers = [D];
2047
+ return poly2;
2048
+ }
2049
+ if (cons?.kind === "isoTrapezoid") {
2050
+ const A = ctx.resolveRef(cons.p1);
2051
+ const B = ctx.resolveRef(cons.p2);
2052
+ const C = ctx.resolveRef(cons.p3);
2053
+ const Dx = () => {
2054
+ const Mx = (A.X() + B.X()) / 2;
2055
+ const My = (A.Y() + B.Y()) / 2;
2056
+ const ux = B.X() - A.X();
2057
+ const uy = B.Y() - A.Y();
2058
+ const len2 = ux * ux + uy * uy || 1;
2059
+ const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
2060
+ return C.X() - 2 * proj * ux;
2061
+ };
2062
+ const Dy = () => {
2063
+ const Mx = (A.X() + B.X()) / 2;
2064
+ const My = (A.Y() + B.Y()) / 2;
2065
+ const ux = B.X() - A.X();
2066
+ const uy = B.Y() - A.Y();
2067
+ const len2 = ux * ux + uy * uy || 1;
2068
+ const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
2069
+ return C.Y() - 2 * proj * uy;
2070
+ };
2071
+ const D = board.create("point", [Dx, Dy], {
2072
+ visible: false,
2073
+ withLabel: false,
2074
+ fixed: true,
2075
+ name: ""
1327
2076
  });
2077
+ const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
2078
+ poly2._helpers = [D];
2079
+ return poly2;
2080
+ }
2081
+ if (cons?.kind === "isoTriangle") {
2082
+ const Apex = ctx.resolveRef(cons.apex);
2083
+ const B1 = ctx.resolveRef(cons.base1);
2084
+ const B2 = ctx.resolveRef(cons.base2);
2085
+ return board.create("polygon", [Apex, B1, B2], commonAttrs);
2086
+ }
2087
+ if (cons?.kind === "rightTriangle") {
2088
+ const R = ctx.resolveRef(cons.rightAngle);
2089
+ const P = ctx.resolveRef(cons.leg1End);
2090
+ const Q = ctx.resolveRef(cons.leg2End);
2091
+ return board.create("polygon", [R, P, Q], commonAttrs);
1328
2092
  }
1329
2093
  const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
1330
2094
  const poly = board.create("polygon", verts, {
@@ -1768,5 +2532,5 @@ function deserializeScene(domain, raw) {
1768
2532
  }
1769
2533
 
1770
2534
  export { deserializeScene, listObjects, nextLabel, serializeScene, useEditorState };
1771
- //# sourceMappingURL=chunk-REIJZDVZ.mjs.map
1772
- //# sourceMappingURL=chunk-REIJZDVZ.mjs.map
2535
+ //# sourceMappingURL=chunk-ZTQBUKLJ.mjs.map
2536
+ //# sourceMappingURL=chunk-ZTQBUKLJ.mjs.map