@xom11/whiteboard 0.24.2 → 0.27.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 (103) hide show
  1. package/README.md +84 -11
  2. package/dist/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
  3. package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
  4. package/dist/ai.d.mts +3217 -434
  5. package/dist/ai.d.ts +3217 -434
  6. package/dist/ai.js +7679 -598
  7. package/dist/ai.js.map +1 -1
  8. package/dist/ai.mjs +5707 -679
  9. package/dist/ai.mjs.map +1 -1
  10. package/dist/catalog.json +5 -5
  11. package/dist/{chunk-7WQXXEVR.mjs → chunk-4ETJ4CDY.mjs} +5 -5
  12. package/dist/{chunk-7WQXXEVR.mjs.map → chunk-4ETJ4CDY.mjs.map} +1 -1
  13. package/dist/chunk-AJAHD35N.mjs +1708 -0
  14. package/dist/chunk-AJAHD35N.mjs.map +1 -0
  15. package/dist/chunk-AYJPOHCI.mjs +265 -0
  16. package/dist/chunk-AYJPOHCI.mjs.map +1 -0
  17. package/dist/chunk-B4NJJZFR.mjs +18 -0
  18. package/dist/chunk-B4NJJZFR.mjs.map +1 -0
  19. package/dist/{chunk-AZIARTGX.mjs → chunk-BNBOIDO5.mjs} +3 -3
  20. package/dist/{chunk-AZIARTGX.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
  21. package/dist/{chunk-LVNCYP4J.mjs → chunk-CXHNVYMD.mjs} +5 -5
  22. package/dist/{chunk-LVNCYP4J.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
  23. package/dist/{chunk-45CGKJ7S.mjs → chunk-D5JLJ3PT.mjs} +4 -4
  24. package/dist/{chunk-45CGKJ7S.mjs.map → chunk-D5JLJ3PT.mjs.map} +1 -1
  25. package/dist/{chunk-WM2VDYQA.mjs → chunk-D5LWSN2Y.mjs} +944 -196
  26. package/dist/chunk-D5LWSN2Y.mjs.map +1 -0
  27. package/dist/{chunk-KRC2XOIG.mjs → chunk-HLAOGXEK.mjs} +3 -3
  28. package/dist/{chunk-KRC2XOIG.mjs.map → chunk-HLAOGXEK.mjs.map} +1 -1
  29. package/dist/{chunk-2WF6KIGF.mjs → chunk-I3L56GVH.mjs} +212 -71
  30. package/dist/chunk-I3L56GVH.mjs.map +1 -0
  31. package/dist/{chunk-ZBJBQKJ2.mjs → chunk-IHUFOV7L.mjs} +4 -19
  32. package/dist/chunk-IHUFOV7L.mjs.map +1 -0
  33. package/dist/chunk-J5LGTIGS.mjs +10 -0
  34. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  35. package/dist/{chunk-BEZSQKPY.mjs → chunk-KYMBUTPO.mjs} +5 -4
  36. package/dist/chunk-KYMBUTPO.mjs.map +1 -0
  37. package/dist/{chunk-4DS3MKID.mjs → chunk-KZGPSTZI.mjs} +4 -4
  38. package/dist/{chunk-4DS3MKID.mjs.map → chunk-KZGPSTZI.mjs.map} +1 -1
  39. package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
  40. package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
  41. package/dist/{chunk-BKSXPNPQ.mjs → chunk-SZDAS7LK.mjs} +81 -3
  42. package/dist/chunk-SZDAS7LK.mjs.map +1 -0
  43. package/dist/chunk-T3SOHYB2.mjs +851 -0
  44. package/dist/chunk-T3SOHYB2.mjs.map +1 -0
  45. package/dist/geometry-2d.d.mts +2 -2
  46. package/dist/geometry-2d.d.ts +2 -2
  47. package/dist/geometry-2d.js +6288 -901
  48. package/dist/geometry-2d.js.map +1 -1
  49. package/dist/geometry-2d.mjs +7 -5
  50. package/dist/geometry-3d.d.mts +2 -2
  51. package/dist/geometry-3d.d.ts +2 -2
  52. package/dist/geometry-3d.js +1335 -253
  53. package/dist/geometry-3d.js.map +1 -1
  54. package/dist/geometry-3d.mjs +6 -4
  55. package/dist/graph-2d.d.mts +2 -2
  56. package/dist/graph-2d.d.ts +2 -2
  57. package/dist/graph-2d.js +1501 -342
  58. package/dist/graph-2d.js.map +1 -1
  59. package/dist/graph-2d.mjs +9 -7
  60. package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
  61. package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
  62. package/dist/{host-EPZCNFLH.mjs → host-HAYCJJ2T.mjs} +1390 -376
  63. package/dist/host-HAYCJJ2T.mjs.map +1 -0
  64. package/dist/{host-LKCMYEAV.mjs → host-LTJHAY5A.mjs} +12 -10
  65. package/dist/host-LTJHAY5A.mjs.map +1 -0
  66. package/dist/{host-ZIQ77W33.mjs → host-M26FS244.mjs} +8 -6
  67. package/dist/host-M26FS244.mjs.map +1 -0
  68. package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
  69. package/dist/host-ZQCDAT6O.mjs.map +1 -0
  70. package/dist/index.d.mts +4 -3
  71. package/dist/index.d.ts +4 -3
  72. package/dist/index.js +6493 -1102
  73. package/dist/index.js.map +1 -1
  74. package/dist/index.mjs +24 -21
  75. package/dist/index.mjs.map +1 -1
  76. package/dist/latex.d.mts +2 -2
  77. package/dist/latex.d.ts +2 -2
  78. package/dist/latex.mjs +2 -1
  79. package/dist/render-ZX2O2IK7.mjs +10 -0
  80. package/dist/{render-SA4JTOW3.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
  81. package/dist/serialize-C3LSUMSA.mjs +9 -0
  82. package/dist/{serialize-JAVOU22E.mjs.map → serialize-C3LSUMSA.mjs.map} +1 -1
  83. package/dist/types-zc_Pa0mp.d.mts +418 -0
  84. package/dist/types-zc_Pa0mp.d.ts +418 -0
  85. package/package.json +10 -1
  86. package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
  87. package/dist/chunk-2WF6KIGF.mjs.map +0 -1
  88. package/dist/chunk-BEZSQKPY.mjs.map +0 -1
  89. package/dist/chunk-BKSXPNPQ.mjs.map +0 -1
  90. package/dist/chunk-CGZZO4BX.mjs +0 -96
  91. package/dist/chunk-CGZZO4BX.mjs.map +0 -1
  92. package/dist/chunk-WM2VDYQA.mjs.map +0 -1
  93. package/dist/chunk-ZBJBQKJ2.mjs.map +0 -1
  94. package/dist/host-EPZCNFLH.mjs.map +0 -1
  95. package/dist/host-LKCMYEAV.mjs.map +0 -1
  96. package/dist/host-QS2EOTRJ.mjs.map +0 -1
  97. package/dist/host-ZIQ77W33.mjs.map +0 -1
  98. package/dist/render-SA4JTOW3.mjs +0 -8
  99. package/dist/serialize-JAVOU22E.mjs +0 -7
  100. package/dist/types-Crbefnfe.d.ts +0 -128
  101. package/dist/types-DxlMPh-6.d.mts +0 -49
  102. package/dist/types-DxlMPh-6.d.ts +0 -49
  103. package/dist/types-vtvyKGAA.d.mts +0 -128
@@ -1,6 +1,7 @@
1
1
  "use client";
2
- import { registerKind, compile, validate } from './chunk-ZBJBQKJ2.mjs';
2
+ import { compile, validate } from './chunk-IHUFOV7L.mjs';
3
3
  import { createEmptyState } from './chunk-73Q7ADVL.mjs';
4
+ import { registerKind } from './chunk-B4NJJZFR.mjs';
4
5
  import * as React from 'react';
5
6
 
6
7
  // src/core/scene/selectors.ts
@@ -546,12 +547,354 @@ function constraintRefs2D(c) {
546
547
  return [c.vertices[0], c.vertices[1], c.vertices[2]];
547
548
  case "orthocenter":
548
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]];
549
575
  default:
550
576
  return [];
551
577
  }
552
578
  }
553
579
 
554
- // 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
555
898
  function buildJxgTransforms(board, ctx, t) {
556
899
  switch (t.kind) {
557
900
  case "translate":
@@ -578,6 +921,283 @@ function buildJxgTransforms(board, ctx, t) {
578
921
  }
579
922
  }
580
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
581
1201
  var def3 = {
582
1202
  type: "point",
583
1203
  schemaVersion: 1,
@@ -587,43 +1207,7 @@ var def3 = {
587
1207
  throw new Error("point: constraint required");
588
1208
  }
589
1209
  const c = a.constraint;
590
- if (c.kind === "perpFoot") {
591
- if (!c.from || !c.onLine) {
592
- throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
593
- }
594
- }
595
- if (c.kind === "circumcenter") {
596
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
597
- throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
598
- }
599
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
600
- throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
601
- }
602
- }
603
- if (c.kind === "incenter") {
604
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
605
- throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
606
- }
607
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
608
- throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
609
- }
610
- }
611
- if (c.kind === "centroid") {
612
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
613
- throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
614
- }
615
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
616
- throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
617
- }
618
- }
619
- if (c.kind === "orthocenter") {
620
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
621
- throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
622
- }
623
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
624
- throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
625
- }
626
- }
1210
+ POINT_CONSTRAINTS.get(c.kind)?.validate?.(c);
627
1211
  },
628
1212
  dependsOn: (a) => constraintRefs2D(a.constraint),
629
1213
  measure: (obj) => {
@@ -638,132 +1222,16 @@ var def3 = {
638
1222
  },
639
1223
  describe: (obj, state) => {
640
1224
  const c = obj.attrs.constraint;
641
- if (c.kind === "free") return `\u0110i\u1EC3m ${obj.label}`;
642
- if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`;
643
- if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
644
- if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
645
- if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
646
- if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
647
- if (c.kind === "midpoint") {
648
- const l1 = state?.objects[c.p1]?.label ?? c.p1;
649
- const l2 = state?.objects[c.p2]?.label ?? c.p2;
650
- return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
651
- }
652
- if (c.kind === "transformed") {
653
- const t = c.transform;
654
- const labelRef = (id) => state?.objects[id]?.label ?? id;
655
- 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)}` : "";
656
- return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
657
- }
658
- if (c.kind === "perpFoot") {
659
- const fromLabel = state?.objects[c.from]?.label ?? c.from;
660
- const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
661
- return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
662
- }
663
- if (c.kind === "circumcenter") {
664
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
665
- return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
666
- }
667
- if (c.kind === "incenter") {
668
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
669
- return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
670
- }
671
- if (c.kind === "centroid") {
672
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
673
- return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
674
- }
675
- if (c.kind === "orthocenter") {
676
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
677
- return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
678
- }
1225
+ const mod = POINT_CONSTRAINTS.get(c.kind);
1226
+ if (mod) return mod.describe(obj, state, c);
679
1227
  return `\u0110i\u1EC3m ${obj.label}`;
680
1228
  },
681
1229
  render: (obj, ctx) => {
682
1230
  const board = ctx.jxg;
683
1231
  const c = obj.attrs.constraint;
684
- const opts = {
685
- name: obj.label,
686
- withLabel: obj.attrs.showLabel ?? true,
687
- visible: obj.visible,
688
- fixed: obj.locked,
689
- strokeColor: obj.attrs.color ?? "#1e40af",
690
- fillColor: obj.attrs.color ?? "#1e40af",
691
- face: obj.attrs.face ?? "o",
692
- size: obj.attrs.size ?? 4
693
- };
694
- if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
695
- if (c.kind === "onAxis") {
696
- const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
697
- return board.create("point", coords, opts);
698
- }
699
- if (c.kind === "onLine") {
700
- const line = ctx.resolveRef(c.lineId);
701
- return board.create("glider", [c.t, c.t, line], opts);
702
- }
703
- if (c.kind === "onSegment") {
704
- const seg = ctx.resolveRef(c.segmentId);
705
- return board.create("glider", [c.t, c.t, seg], opts);
706
- }
707
- if (c.kind === "onCircle") {
708
- const circle = ctx.resolveRef(c.circleId);
709
- return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
710
- }
711
- if (c.kind === "onPolygon") {
712
- const poly = ctx.resolveRef(c.polygonId);
713
- return board.create("glider", [c.u, c.v, poly], opts);
714
- }
715
- if (c.kind === "midpoint") {
716
- const p1 = ctx.resolveRef(c.p1);
717
- const p2 = ctx.resolveRef(c.p2);
718
- return board.create("midpoint", [p1, p2], opts);
719
- }
720
- if (c.kind === "transformed") {
721
- const src = ctx.resolveRef(c.source);
722
- const transforms = buildJxgTransforms(board, ctx, c.transform);
723
- const parent = transforms.length === 1 ? transforms[0] : transforms;
724
- const pt = board.create("point", [src, parent], opts);
725
- pt._helpers = transforms;
726
- return pt;
727
- }
728
- if (c.kind === "perpFoot") {
729
- const from = ctx.resolveRef(c.from);
730
- const onLine = ctx.resolveRef(c.onLine);
731
- return board.create("perpendicularpoint", [onLine, from], opts);
732
- }
733
- if (c.kind === "circumcenter") {
734
- const a = ctx.resolveRef(c.vertices[0]);
735
- const b = ctx.resolveRef(c.vertices[1]);
736
- const c3 = ctx.resolveRef(c.vertices[2]);
737
- return board.create("circumcenter", [a, b, c3], opts);
738
- }
739
- if (c.kind === "incenter") {
740
- const a = ctx.resolveRef(c.vertices[0]);
741
- const b = ctx.resolveRef(c.vertices[1]);
742
- const c3 = ctx.resolveRef(c.vertices[2]);
743
- return board.create("incenter", [a, b, c3], opts);
744
- }
745
- if (c.kind === "centroid") {
746
- const a = ctx.resolveRef(c.vertices[0]);
747
- const b = ctx.resolveRef(c.vertices[1]);
748
- const c3 = ctx.resolveRef(c.vertices[2]);
749
- return board.create("point", [
750
- () => (a.X() + b.X() + c3.X()) / 3,
751
- () => (a.Y() + b.Y() + c3.Y()) / 3
752
- ], opts);
753
- }
754
- if (c.kind === "orthocenter") {
755
- const a = ctx.resolveRef(c.vertices[0]);
756
- const b = ctx.resolveRef(c.vertices[1]);
757
- const c3 = ctx.resolveRef(c.vertices[2]);
758
- const hide = { visible: false, withLabel: false, fixed: true, name: "" };
759
- const lineBC = board.create("line", [b, c3], hide);
760
- const altA = board.create("perpendicular", [lineBC, a], hide);
761
- const lineAC = board.create("line", [a, c3], hide);
762
- const altB = board.create("perpendicular", [lineAC, b], hide);
763
- const ortho = board.create("intersection", [altA, altB, 0], opts);
764
- ortho._helpers = [lineBC, altA, lineAC, altB];
765
- return ortho;
766
- }
1232
+ const opts = buildPointOpts(obj);
1233
+ const mod = POINT_CONSTRAINTS.get(c.kind);
1234
+ if (mod) return mod.render(obj, ctx, c, opts);
767
1235
  return board.create("point", [0, 0], opts);
768
1236
  },
769
1237
  /**
@@ -863,6 +1331,10 @@ function constructionRefs(c) {
863
1331
  return [c.p1, c.vertex, c.p2];
864
1332
  case "angleBisectorLines":
865
1333
  return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
1334
+ case "lineThrough":
1335
+ return [...c.points];
1336
+ case "radicalAxis":
1337
+ return [c.circle1, c.circle2];
866
1338
  case "tangent":
867
1339
  return [c.throughPoint, c.toCircle];
868
1340
  }
@@ -891,6 +1363,10 @@ var def5 = {
891
1363
  return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
892
1364
  case "angleBisectorLines":
893
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)}`;
894
1370
  case "tangent":
895
1371
  return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
896
1372
  }
@@ -971,6 +1447,47 @@ var def5 = {
971
1447
  selected._helpers = [other];
972
1448
  return selected;
973
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
+ }
974
1491
  case "tangent": {
975
1492
  const through = ctx.resolveRef(c.throughPoint);
976
1493
  const toCircle = ctx.resolveRef(c.toCircle);
@@ -1073,10 +1590,29 @@ var def7 = {
1073
1590
  registerKind(def7);
1074
1591
 
1075
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
+ }
1076
1606
  function constructionRefs2(c) {
1077
1607
  switch (c.kind) {
1078
1608
  case "circumscribed":
1079
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];
1080
1616
  }
1081
1617
  }
1082
1618
  var def8 = {
@@ -1084,14 +1620,27 @@ var def8 = {
1084
1620
  schemaVersion: 1,
1085
1621
  migrate: {},
1086
1622
  validate: (a) => {
1087
- 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
+ }
1088
1629
  if (!a?.center || !a?.surfacePoint) {
1089
- 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)");
1090
1631
  }
1091
1632
  },
1092
- 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
+ },
1093
1639
  measure: (obj, state) => {
1094
- 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
+ }
1095
1644
  const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
1096
1645
  const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
1097
1646
  if (!center || !surface) return null;
@@ -1104,10 +1653,22 @@ var def8 = {
1104
1653
  },
1105
1654
  describe: (obj, state) => {
1106
1655
  const L = (id) => labelOf(id, state);
1107
- const c = obj.attrs.construction;
1656
+ const c = asConstruction(obj.attrs);
1108
1657
  if (c?.kind === "circumscribed") {
1109
1658
  return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1110
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
+ }
1111
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)}`;
1112
1673
  },
1113
1674
  render: (obj, ctx) => {
@@ -1122,13 +1683,66 @@ var def8 = {
1122
1683
  visible: obj.visible,
1123
1684
  fixed: obj.locked
1124
1685
  };
1125
- const c = obj.attrs.construction;
1686
+ const c = asConstruction(obj.attrs);
1126
1687
  if (c?.kind === "circumscribed") {
1127
1688
  const p1 = ctx.resolveRef(c.p1);
1128
1689
  const p2 = ctx.resolveRef(c.p2);
1129
1690
  const p3 = ctx.resolveRef(c.p3);
1130
1691
  return board.create("circumcircle", [p1, p2, p3], baseOpts);
1131
1692
  }
1693
+ if (c?.kind === "incircle") {
1694
+ const p1 = ctx.resolveRef(c.p1);
1695
+ const p2 = ctx.resolveRef(c.p2);
1696
+ const p3 = ctx.resolveRef(c.p3);
1697
+ const center2 = board.create("incenter", [p1, p2, p3], {
1698
+ visible: obj.visible,
1699
+ withLabel: true,
1700
+ fixed: true,
1701
+ name: obj.label
1702
+ });
1703
+ const circ = board.create("incircle", [p1, p2, p3], baseOpts);
1704
+ circ.center = circ.center ?? center2;
1705
+ circ._helpers = [center2];
1706
+ return circ;
1707
+ }
1708
+ if (c?.kind === "excircle") {
1709
+ const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
1710
+ const ids = [c.p1, c.p2, c.p3];
1711
+ const oppIdx = Math.max(0, ids.indexOf(c.opposite));
1712
+ const verts = () => [
1713
+ [P[0].X(), P[0].Y()],
1714
+ [P[1].X(), P[1].Y()],
1715
+ [P[2].X(), P[2].Y()]
1716
+ ];
1717
+ const ctr = () => excenter(verts(), oppIdx);
1718
+ const radius = () => {
1719
+ const I = ctr();
1720
+ const others = [0, 1, 2].filter((i) => i !== oppIdx);
1721
+ const v = verts();
1722
+ const a = v[others[0]];
1723
+ const b = v[others[1]];
1724
+ const dx = b[0] - a[0];
1725
+ const dy = b[1] - a[1];
1726
+ const len = Math.hypot(dx, dy) || 1;
1727
+ return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
1728
+ };
1729
+ const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
1730
+ const circ = board.create("circle", [center2, () => radius()], baseOpts);
1731
+ circ._helpers = [center2];
1732
+ return circ;
1733
+ }
1734
+ if (c?.kind === "diameter") {
1735
+ const p1 = ctx.resolveRef(c.p1);
1736
+ const p2 = ctx.resolveRef(c.p2);
1737
+ const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
1738
+ const circ = board.create("circle", [center2, p2], baseOpts);
1739
+ circ._helpers = [center2];
1740
+ return circ;
1741
+ }
1742
+ if (typeof obj.attrs.radius === "number") {
1743
+ const center2 = ctx.resolveRef(obj.attrs.center);
1744
+ return board.create("circle", [center2, obj.attrs.radius], baseOpts);
1745
+ }
1132
1746
  const center = ctx.resolveRef(obj.attrs.center);
1133
1747
  const surface = ctx.resolveRef(obj.attrs.surfacePoint);
1134
1748
  return board.create("circle", [center, surface], baseOpts);
@@ -1272,19 +1886,53 @@ function regularVertexLabels(p1Label, p2Label, n) {
1272
1886
  }
1273
1887
  return `${p1Label}${p2Label}\u2026`;
1274
1888
  }
1889
+ function specialShapeName(kind) {
1890
+ switch (kind) {
1891
+ case "square":
1892
+ return "H\xECnh vu\xF4ng";
1893
+ case "rectangle":
1894
+ return "H\xECnh ch\u1EEF nh\u1EADt";
1895
+ case "rhombus":
1896
+ return "H\xECnh thoi";
1897
+ case "parallelogram":
1898
+ return "H\xECnh b\xECnh h\xE0nh";
1899
+ case "isoTrapezoid":
1900
+ return "H\xECnh thang c\xE2n";
1901
+ case "isoTriangle":
1902
+ return "Tam gi\xE1c c\xE2n";
1903
+ case "rightTriangle":
1904
+ return "Tam gi\xE1c vu\xF4ng";
1905
+ case "regular":
1906
+ return "";
1907
+ }
1908
+ }
1275
1909
  var def11 = {
1276
1910
  type: "polygon",
1277
1911
  schemaVersion: 1,
1278
1912
  migrate: {},
1279
1913
  validate: (a) => {
1280
1914
  if (a?.construction) {
1281
- if (a.construction.kind === "regular") {
1282
- if (!a.construction.p1 || !a.construction.p2) {
1283
- throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1284
- }
1285
- if (!Number.isFinite(a.construction.n) || a.construction.n < 3) {
1286
- throw new Error("polygon (regular): n \u2265 3");
1287
- }
1915
+ const c = a.construction;
1916
+ if (c.kind === "regular") {
1917
+ if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1918
+ if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
1919
+ return;
1920
+ }
1921
+ if (c.kind === "square") {
1922
+ if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1923
+ return;
1924
+ }
1925
+ if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
1926
+ if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
1927
+ return;
1928
+ }
1929
+ if (c.kind === "isoTriangle") {
1930
+ if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
1931
+ return;
1932
+ }
1933
+ if (c.kind === "rightTriangle") {
1934
+ if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
1935
+ return;
1288
1936
  }
1289
1937
  return;
1290
1938
  }
@@ -1293,14 +1941,51 @@ var def11 = {
1293
1941
  }
1294
1942
  },
1295
1943
  dependsOn: (a) => {
1296
- if (a.construction?.kind === "regular") return [a.construction.p1, a.construction.p2];
1297
- return [...a.vertices ?? []];
1944
+ const c = a.construction;
1945
+ if (!c) return [...a.vertices ?? []];
1946
+ switch (c.kind) {
1947
+ case "regular":
1948
+ return [c.p1, c.p2];
1949
+ case "square":
1950
+ return [c.p1, c.p2];
1951
+ case "rectangle":
1952
+ case "rhombus":
1953
+ case "parallelogram":
1954
+ case "isoTrapezoid":
1955
+ return [c.p1, c.p2, c.p3];
1956
+ case "isoTriangle":
1957
+ return [c.base1, c.base2, c.apex];
1958
+ case "rightTriangle":
1959
+ return [c.rightAngle, c.leg1End, c.leg2End];
1960
+ }
1298
1961
  },
1299
1962
  describe: (obj, state) => {
1300
- if (obj.attrs.construction?.kind === "regular") {
1301
- const c = obj.attrs.construction;
1302
- const labels = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1303
- return `${regularPolygonName(c.n)} ${labels}`;
1963
+ const c = obj.attrs.construction;
1964
+ if (c) {
1965
+ if (c.kind === "regular") {
1966
+ const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1967
+ return `${regularPolygonName(c.n)} ${labels2}`;
1968
+ }
1969
+ const name = specialShapeName(c.kind);
1970
+ let labels = [];
1971
+ switch (c.kind) {
1972
+ case "square":
1973
+ labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
1974
+ break;
1975
+ case "rectangle":
1976
+ case "rhombus":
1977
+ case "parallelogram":
1978
+ case "isoTrapezoid":
1979
+ labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
1980
+ break;
1981
+ case "isoTriangle":
1982
+ labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
1983
+ break;
1984
+ case "rightTriangle":
1985
+ labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
1986
+ break;
1987
+ }
1988
+ return `${name} ${labels.join("")}`;
1304
1989
  }
1305
1990
  return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
1306
1991
  },
@@ -1308,22 +1993,85 @@ var def11 = {
1308
1993
  const board = ctx.jxg;
1309
1994
  const label = obj.label;
1310
1995
  const showValue = obj.attrs.showValue ?? false;
1311
- if (obj.attrs.construction?.kind === "regular") {
1312
- const c = obj.attrs.construction;
1313
- const p1 = ctx.resolveRef(c.p1);
1314
- const p2 = ctx.resolveRef(c.p2);
1315
- return board.create("regularpolygon", [p1, p2, c.n], {
1316
- name: label,
1317
- withLabel: obj.attrs.showLabel ?? false,
1318
- borders: {
1319
- strokeColor: obj.attrs.color ?? "#0f172a",
1320
- strokeWidth: obj.attrs.width ?? 2
1321
- },
1322
- fillColor: obj.attrs.color ?? "#60a5fa",
1323
- fillOpacity: obj.attrs.fillOpacity ?? 0.15,
1324
- visible: obj.visible,
1325
- fixed: obj.locked
1996
+ const cons = obj.attrs.construction;
1997
+ const commonAttrs = {
1998
+ name: label,
1999
+ withLabel: obj.attrs.showLabel ?? false,
2000
+ borders: {
2001
+ strokeColor: obj.attrs.color ?? "#0f172a",
2002
+ strokeWidth: obj.attrs.width ?? 2
2003
+ },
2004
+ fillColor: obj.attrs.color ?? "#60a5fa",
2005
+ fillOpacity: obj.attrs.fillOpacity ?? 0.15,
2006
+ visible: obj.visible,
2007
+ fixed: obj.locked
2008
+ };
2009
+ if (cons?.kind === "regular") {
2010
+ const p1 = ctx.resolveRef(cons.p1);
2011
+ const p2 = ctx.resolveRef(cons.p2);
2012
+ return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
2013
+ }
2014
+ if (cons?.kind === "square") {
2015
+ const p1 = ctx.resolveRef(cons.p1);
2016
+ const p2 = ctx.resolveRef(cons.p2);
2017
+ return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
2018
+ }
2019
+ if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
2020
+ const A = ctx.resolveRef(cons.p1);
2021
+ const B = ctx.resolveRef(cons.p2);
2022
+ const C = ctx.resolveRef(cons.p3);
2023
+ const D = board.create(
2024
+ "point",
2025
+ [() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
2026
+ { visible: false, withLabel: false, fixed: true, name: "" }
2027
+ );
2028
+ const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
2029
+ poly2._helpers = [D];
2030
+ return poly2;
2031
+ }
2032
+ if (cons?.kind === "isoTrapezoid") {
2033
+ const A = ctx.resolveRef(cons.p1);
2034
+ const B = ctx.resolveRef(cons.p2);
2035
+ const C = ctx.resolveRef(cons.p3);
2036
+ const Dx = () => {
2037
+ const Mx = (A.X() + B.X()) / 2;
2038
+ const My = (A.Y() + B.Y()) / 2;
2039
+ const ux = B.X() - A.X();
2040
+ const uy = B.Y() - A.Y();
2041
+ const len2 = ux * ux + uy * uy || 1;
2042
+ const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
2043
+ return C.X() - 2 * proj * ux;
2044
+ };
2045
+ const Dy = () => {
2046
+ const Mx = (A.X() + B.X()) / 2;
2047
+ const My = (A.Y() + B.Y()) / 2;
2048
+ const ux = B.X() - A.X();
2049
+ const uy = B.Y() - A.Y();
2050
+ const len2 = ux * ux + uy * uy || 1;
2051
+ const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
2052
+ return C.Y() - 2 * proj * uy;
2053
+ };
2054
+ const D = board.create("point", [Dx, Dy], {
2055
+ visible: false,
2056
+ withLabel: false,
2057
+ fixed: true,
2058
+ name: ""
1326
2059
  });
2060
+ const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
2061
+ poly2._helpers = [D];
2062
+ return poly2;
2063
+ }
2064
+ if (cons?.kind === "isoTriangle") {
2065
+ const Apex = ctx.resolveRef(cons.apex);
2066
+ const B1 = ctx.resolveRef(cons.base1);
2067
+ const B2 = ctx.resolveRef(cons.base2);
2068
+ return board.create("polygon", [Apex, B1, B2], commonAttrs);
2069
+ }
2070
+ if (cons?.kind === "rightTriangle") {
2071
+ const R = ctx.resolveRef(cons.rightAngle);
2072
+ const P = ctx.resolveRef(cons.leg1End);
2073
+ const Q = ctx.resolveRef(cons.leg2End);
2074
+ return board.create("polygon", [R, P, Q], commonAttrs);
1327
2075
  }
1328
2076
  const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
1329
2077
  const poly = board.create("polygon", verts, {
@@ -1767,5 +2515,5 @@ function deserializeScene(domain, raw) {
1767
2515
  }
1768
2516
 
1769
2517
  export { deserializeScene, listObjects, nextLabel, serializeScene, useEditorState };
1770
- //# sourceMappingURL=chunk-WM2VDYQA.mjs.map
1771
- //# sourceMappingURL=chunk-WM2VDYQA.mjs.map
2518
+ //# sourceMappingURL=chunk-D5LWSN2Y.mjs.map
2519
+ //# sourceMappingURL=chunk-D5LWSN2Y.mjs.map