@xom11/whiteboard 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.d.mts +149 -28
- package/dist/ai.d.ts +149 -28
- package/dist/ai.js +2652 -430
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +2482 -426
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +4 -4
- package/dist/{chunk-JJ4FPCBE.mjs → chunk-4EXRNZRX.mjs} +641 -75
- package/dist/chunk-4EXRNZRX.mjs.map +1 -0
- package/dist/{chunk-TMRFSOM7.mjs → chunk-4NJUTCFF.mjs} +4 -4
- package/dist/{chunk-TMRFSOM7.mjs.map → chunk-4NJUTCFF.mjs.map} +1 -1
- package/dist/{chunk-LTLLQUMN.mjs → chunk-BZZ5BROM.mjs} +6 -7
- package/dist/chunk-BZZ5BROM.mjs.map +1 -0
- package/dist/{host-KMWP7KBT.mjs → chunk-C3RDFBQ5.mjs} +20 -35
- package/dist/chunk-C3RDFBQ5.mjs.map +1 -0
- package/dist/chunk-C7C66BAV.mjs +11 -0
- package/dist/chunk-C7C66BAV.mjs.map +1 -0
- package/dist/{chunk-T3N4BSJV.mjs → chunk-DCFZTKFE.mjs} +13 -5
- package/dist/chunk-DCFZTKFE.mjs.map +1 -0
- package/dist/{chunk-44JY2AKC.mjs → chunk-F42MZ745.mjs} +3 -3
- package/dist/{chunk-44JY2AKC.mjs.map → chunk-F42MZ745.mjs.map} +1 -1
- package/dist/{chunk-BMYC2ILT.mjs → chunk-FOPZOVI3.mjs} +5 -4
- package/dist/chunk-FOPZOVI3.mjs.map +1 -0
- package/dist/{chunk-K5BS2H56.mjs → chunk-GKDQ5IV7.mjs} +4 -4
- package/dist/{chunk-K5BS2H56.mjs.map → chunk-GKDQ5IV7.mjs.map} +1 -1
- package/dist/{chunk-73Q7ADVL.mjs → chunk-MNHOFOM5.mjs} +2 -2
- package/dist/{chunk-73Q7ADVL.mjs.map → chunk-MNHOFOM5.mjs.map} +1 -1
- package/dist/{chunk-KOXOC2FI.mjs → chunk-NBMYET5H.mjs} +11 -15
- package/dist/chunk-NBMYET5H.mjs.map +1 -0
- package/dist/{chunk-DWIEVCGK.mjs → chunk-NTFG7ZRN.mjs} +53 -3
- package/dist/chunk-NTFG7ZRN.mjs.map +1 -0
- package/dist/{chunk-C76SOFXF.mjs → chunk-QCK3Z5WN.mjs} +3 -3
- package/dist/{chunk-C76SOFXF.mjs.map → chunk-QCK3Z5WN.mjs.map} +1 -1
- package/dist/chunk-SUXIMLYN.mjs +238 -0
- package/dist/chunk-SUXIMLYN.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +1026 -231
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +6 -5
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +4001 -475
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +5 -5
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +712 -115
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +5 -5
- package/dist/{handleExtractProblem-BrDY9ifM.d.ts → handleExtractProblem-mtJyhEw3.d.mts} +2 -0
- package/dist/{handleExtractProblem-BrDY9ifM.d.mts → handleExtractProblem-mtJyhEw3.d.ts} +2 -0
- package/dist/host-6WTLCXUE.mjs +62 -0
- package/dist/host-6WTLCXUE.mjs.map +1 -0
- package/dist/{host-H2IGOKJU.mjs → host-BWIXT5TR.mjs} +3 -3
- package/dist/{host-H2IGOKJU.mjs.map → host-BWIXT5TR.mjs.map} +1 -1
- package/dist/{host-4FIUNIDQ.mjs → host-QDVRULJV.mjs} +14 -11
- package/dist/host-QDVRULJV.mjs.map +1 -0
- package/dist/host-USOIVTQB.mjs +4976 -0
- package/dist/host-USOIVTQB.mjs.map +1 -0
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +4273 -521
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -27
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.mjs +1 -1
- package/dist/serialize-7UZWQQ4I.mjs +9 -0
- package/dist/{serialize-PGHQZEPV.mjs.map → serialize-7UZWQQ4I.mjs.map} +1 -1
- package/dist/studio.d.mts +35 -0
- package/dist/studio.d.ts +35 -0
- package/dist/studio.js +10883 -0
- package/dist/studio.js.map +1 -0
- package/dist/studio.mjs +28 -0
- package/dist/studio.mjs.map +1 -0
- package/dist/{types-tePd94vW.d.ts → types-eEEEVsLF.d.mts} +1 -1
- package/dist/{types-tePd94vW.d.mts → types-eEEEVsLF.d.ts} +1 -1
- package/package.json +12 -2
- package/dist/chunk-BMYC2ILT.mjs.map +0 -1
- package/dist/chunk-DWIEVCGK.mjs.map +0 -1
- package/dist/chunk-JJ4FPCBE.mjs.map +0 -1
- package/dist/chunk-KOXOC2FI.mjs.map +0 -1
- package/dist/chunk-LTLLQUMN.mjs.map +0 -1
- package/dist/chunk-QK6OVDLC.mjs +0 -103
- package/dist/chunk-QK6OVDLC.mjs.map +0 -1
- package/dist/chunk-T3N4BSJV.mjs.map +0 -1
- package/dist/host-4FIUNIDQ.mjs.map +0 -1
- package/dist/host-4ZB4XD4S.mjs +0 -2378
- package/dist/host-4ZB4XD4S.mjs.map +0 -1
- package/dist/host-KMWP7KBT.mjs.map +0 -1
- package/dist/serialize-PGHQZEPV.mjs +0 -9
package/dist/ai.js
CHANGED
|
@@ -15,6 +15,11 @@ var GEOMETRY_KEYWORDS = [
|
|
|
15
15
|
"h\xECnh thoi",
|
|
16
16
|
"h\xECnh thang",
|
|
17
17
|
"t\u1EE9 gi\xE1c",
|
|
18
|
+
// Đa giác ≥5 đỉnh: "Cho lục giác ABCDEF …" — thiếu các từ này thì clause chỉ
|
|
19
|
+
// có tên đa giác (không "ngoại/nội tiếp") bị coi văn xuôi → hexagonRule miss.
|
|
20
|
+
"ng\u0169 gi\xE1c",
|
|
21
|
+
"l\u1EE5c gi\xE1c",
|
|
22
|
+
"\u0111a gi\xE1c",
|
|
18
23
|
// Triangle variants
|
|
19
24
|
"vu\xF4ng t\u1EA1i",
|
|
20
25
|
"c\xE2n t\u1EA1i",
|
|
@@ -96,6 +101,13 @@ var GEOMETRY_KEYWORDS = [
|
|
|
96
101
|
"c\u1EAFt",
|
|
97
102
|
"\u0111\u01B0\u1EDDng th\u1EB3ng",
|
|
98
103
|
"\u0111\u01B0\u01A1ng th\u1EB3ng",
|
|
104
|
+
// "thẳng hàng" — "Cho 3 điểm A, B, C thẳng hàng theo thứ tự" (C41): clause GIỚI
|
|
105
|
+
// THIỆU bộ điểm thẳng hàng (collinearPointsRule dựng A,B,C) NHƯNG không có keyword
|
|
106
|
+
// nào khác → hasGeometry=false → clause bị lọc khỏi rule engine → A,B,C KHÔNG dựng
|
|
107
|
+
// → cascade làm sụp tiếp tuyến/trung điểm phụ thuộc chúng. "thẳng hàng" (≥8 ký tự,
|
|
108
|
+
// phân biệt) đánh dấu geo-clause. Clause CHỨNG MINH ("Chứng minh A,B,C thẳng hàng")
|
|
109
|
+
// vẫn non-geo nhờ guard proofOnly (startsProofSection) — không bị ảnh hưởng.
|
|
110
|
+
"th\u1EB3ng h\xE0ng",
|
|
99
111
|
// Ký hiệu (symbol-only phrasing tương đương từ chữ): "⊥" ≡ "vuông góc".
|
|
100
112
|
// perpFoot rule đã nhận "⊥" ở tầng construct (PERP_DRAW), nhưng gate
|
|
101
113
|
// hasGeometry trước đây CHỈ nhận chữ "vuông góc" → clause mà "⊥" là tín hiệu
|
|
@@ -211,17 +223,18 @@ function countGeometryKeywords(text) {
|
|
|
211
223
|
var MASK_SEMI = "";
|
|
212
224
|
var MASK_DOT = "";
|
|
213
225
|
var MASK_COMMA = "";
|
|
226
|
+
var MASK_SEMI_LIST = "";
|
|
214
227
|
function maskNonBoundaryPunct(s) {
|
|
215
228
|
return s.replace(
|
|
216
229
|
/\(([^()\n]{1,40})\)/g,
|
|
217
230
|
(m) => m.replace(/;/g, MASK_SEMI).replace(/\./g, MASK_DOT).replace(/,/g, MASK_COMMA)
|
|
218
231
|
).replace(
|
|
219
|
-
/;(?=\s*[A-Z][A-Z]?['′]?\d?\s*(?:[;,]|cắt nhau|đồng quy))/gu,
|
|
220
|
-
|
|
221
|
-
);
|
|
232
|
+
/;(?=\s*[A-Z][A-Z]?['′]?\d?\s*(?:[;,]|lần lượt|cắt nhau|đồng quy|đôi một))/gu,
|
|
233
|
+
MASK_SEMI_LIST
|
|
234
|
+
).replace(/;(?=\s*[A-Z][A-Z]?['′]?\d?\s*(?:[;,.]|$))/gu, MASK_SEMI_LIST);
|
|
222
235
|
}
|
|
223
236
|
function unmask(s) {
|
|
224
|
-
return s.replace(/\u0001/g, ";").replace(/\u0002/g, ".").replace(/\u0003/g, ",");
|
|
237
|
+
return s.replace(/\u0001/g, ";").replace(/\u0002/g, ".").replace(/\u0003/g, ",").replace(/\u0004/g, ",");
|
|
225
238
|
}
|
|
226
239
|
function segmentClauses(problem) {
|
|
227
240
|
let proofMode = false;
|
|
@@ -230,7 +243,7 @@ function segmentClauses(problem) {
|
|
|
230
243
|
// giờ khớp trước space → split chết im lặng (bug class \b+tiếng Việt).
|
|
231
244
|
/[.;\n]+|,\s*(?=(?:Gọi|Vẽ|Kẻ|Cho|Lấy|Dựng|trên|với|Let|Draw|Mark|Take|Construct|Join)(?!\p{L}))/u
|
|
232
245
|
).map((s) => unmask(s).trim()).filter((s) => s.length > 0).map((text, id) => {
|
|
233
|
-
const hasGeometryKeyword = countGeometryKeywords(text) > 0;
|
|
246
|
+
const hasGeometryKeyword = countGeometryKeywords(text) > 0 || NAMED_LINE_PICK.test(text) || BARE_SEG_PICK.test(text) && !/tia\s+đối/u.test(text);
|
|
234
247
|
const proofOnly = isProofOnlyClause(text, proofMode);
|
|
235
248
|
if (startsProofSection(text)) proofMode = true;
|
|
236
249
|
const locusOnly = LOCUS_CLAUSE.test(text) && !CONSTRUCTION_LEAD.test(text);
|
|
@@ -239,6 +252,8 @@ function segmentClauses(problem) {
|
|
|
239
252
|
}
|
|
240
253
|
var ENUM_PREFIX = "(?:[0-9]+\\s*[.)]?\\s*|[a-zA-Z]\\s*[.)]\\s*)*";
|
|
241
254
|
var LOCUS_CLAUSE = /(?:di\s*chuyển|di\s*động)\s+trên\s+(?:\(|đường\s*tròn|đương\s*tròn|cung|nửa)/u;
|
|
255
|
+
var NAMED_LINE_PICK = /(?:[Tt]rên|[Tt]huộc)\s+(?:đường\s*thẳng\s+)?[a-z]{1,2}[0-9]?(?![\p{L}])\s+(?:[Ll]ấy|có)|[Ll]ấy\s+điểm\s+[A-Z]['′]?[0-9]?\s+(?:trên|thuộc)\s+(?:đường\s*thẳng\s+)?[a-z]{1,2}[0-9]?(?![\p{L}])/u;
|
|
256
|
+
var BARE_SEG_PICK = /[Ll]ấy\s+(?:điểm\s+)?[A-Z]['′]?(?:\s*,\s*[A-Z]['′]?)?\s+(?:bất\s*k[iìyỳ]\s+)?(?:trên|thuộc)\s+(?:cạnh\s+|đoạn\s+|tia\s+)?[A-Z]{2}(?![A-Z])|[Tt]rên\s+(?:cạnh\s+|đoạn\s+|tia\s+)?[A-Z]{2}(?:\s*,\s*[A-Z]{2})*\s+(?:(?:theo\s+)?thứ\s+tự\s+|lần\s*lượt\s+)?lấy\s+(?:các\s+)?(?:điểm\s+)?[A-Z]/u;
|
|
242
257
|
var PROOF_SECTION_START = new RegExp(
|
|
243
258
|
`^${ENUM_PREFIX}(?:[Cc]h\u1EE9ng\\s*minh|C/m|CMR|[Tt]\xEDnh|[Tt]\xECm|[Xx]\xE1c\\s*\u0111\u1ECBnh|[Hh]\xE3y\\s+x\xE1c\\s*\u0111\u1ECBnh)(?!\\p{L})`,
|
|
244
259
|
"u"
|
|
@@ -307,6 +322,9 @@ function pairFromToken(token) {
|
|
|
307
322
|
return m ? [m[1], m[2]] : [];
|
|
308
323
|
}
|
|
309
324
|
var SIDE_PREFIX = "(?:c\u1EA1nh(?:\\s+huy\u1EC1n)?\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|d\xE2y(?:\\s+cung)?\\s+)?";
|
|
325
|
+
function escapeRe(s) {
|
|
326
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
327
|
+
}
|
|
310
328
|
var DUONG_KW = "[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng";
|
|
311
329
|
var CIRCLE_KW = DUONG_KW + "\\s*tr\xF2n";
|
|
312
330
|
|
|
@@ -475,44 +493,44 @@ var QUAD = "([A-Z])([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
|
475
493
|
var KINDS = [
|
|
476
494
|
// hình thang cân / vuông (modifier TRƯỚC đỉnh) — phải match trước thang chung
|
|
477
495
|
{
|
|
478
|
-
name: new RegExp("
|
|
496
|
+
name: new RegExp("[Hh]\xECnh\\s+thang\\s+c\xE2n\\s+" + QUAD, "gu"),
|
|
479
497
|
shape: "trapezoid",
|
|
480
498
|
variant: "isoceles"
|
|
481
499
|
},
|
|
482
500
|
{
|
|
483
|
-
name: new RegExp("
|
|
501
|
+
name: new RegExp("[Hh]\xECnh\\s+thang\\s+vu\xF4ng\\s+" + QUAD, "gu"),
|
|
484
502
|
shape: "trapezoid",
|
|
485
503
|
variant: "right"
|
|
486
504
|
},
|
|
487
505
|
// hình thang chung — sau đó dò modifier 'vuông'/'cân' đứng SAU đỉnh
|
|
488
506
|
// ("hình thang ABCD vuông tại A" → right; "...cân" → isoceles).
|
|
489
507
|
{
|
|
490
|
-
name: new RegExp("
|
|
508
|
+
name: new RegExp("[Hh]\xECnh\\s+thang\\s+" + QUAD, "gu"),
|
|
491
509
|
shape: "trapezoid",
|
|
492
510
|
variant: "general"
|
|
493
511
|
},
|
|
494
512
|
{
|
|
495
|
-
name: new RegExp("
|
|
513
|
+
name: new RegExp("[Hh]\xECnh\\s+vu\xF4ng\\s+" + QUAD, "gu"),
|
|
496
514
|
shape: "square",
|
|
497
515
|
variant: "standard"
|
|
498
516
|
},
|
|
499
517
|
{
|
|
500
|
-
name: new RegExp("
|
|
518
|
+
name: new RegExp("[Hh]\xECnh\\s+ch\u1EEF\\s+nh\u1EADt\\s+" + QUAD, "gu"),
|
|
501
519
|
shape: "rectangle",
|
|
502
520
|
variant: "wide"
|
|
503
521
|
},
|
|
504
522
|
{
|
|
505
|
-
name: new RegExp("
|
|
523
|
+
name: new RegExp("[Hh]\xECnh\\s+b\xECnh\\s+h\xE0nh\\s+" + QUAD, "gu"),
|
|
506
524
|
shape: "parallelogram",
|
|
507
525
|
variant: "standard"
|
|
508
526
|
},
|
|
509
527
|
{
|
|
510
|
-
name: new RegExp("
|
|
528
|
+
name: new RegExp("[Hh]\xECnh\\s+thoi\\s+" + QUAD, "gu"),
|
|
511
529
|
shape: "rhombus",
|
|
512
530
|
variant: "standard"
|
|
513
531
|
},
|
|
514
532
|
{
|
|
515
|
-
name: new RegExp("
|
|
533
|
+
name: new RegExp("[Tt]\u1EE9\\s+gi\xE1c(?:\\s+l\u1ED3i)?\\s+" + QUAD, "gu"),
|
|
516
534
|
shape: "quadrilateral",
|
|
517
535
|
variant: "any"
|
|
518
536
|
},
|
|
@@ -562,10 +580,10 @@ var KINDS = [
|
|
|
562
580
|
variant: "any"
|
|
563
581
|
}
|
|
564
582
|
];
|
|
565
|
-
var PREFILTER = /
|
|
583
|
+
var PREFILTER = /[Hh]ình\s+(?:vuông|chữ\s+nhật|bình\s+hành|thoi|thang)|[Tt]ứ\s+giác|[Ss]quare|[Rr]ectangle|[Pp]arallelogram|[Rr]hombus|[Tt]rapezoid|[Tt]rapezium|[Qq]uadrilateral/u;
|
|
566
584
|
var RIGHT_AFTER = /vuông(?!\p{L})/u;
|
|
567
585
|
var ISO_AFTER = /(?<!\p{L})cân(?!\p{L})/u;
|
|
568
|
-
var NEXT_SHAPE = /
|
|
586
|
+
var NEXT_SHAPE = /[Hh]ình\s|[Tt]ứ\s+giác/u;
|
|
569
587
|
function scanClause(text) {
|
|
570
588
|
const claimed = [];
|
|
571
589
|
const taken = [];
|
|
@@ -609,13 +627,17 @@ var CYCLIC_QUAD_THETAS = CYCLIC_QUAD_COORDS.map(
|
|
|
609
627
|
);
|
|
610
628
|
var CENTER = "(?:\\(\\s*([A-Z])\\s*\\)|t\xE2m\\s+([A-Z]))?";
|
|
611
629
|
var QUAD_INSCRIBED_IN_CIRCLE = new RegExp(
|
|
612
|
-
"^[\\s,]*(?:n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?(?:m\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n|(?:is\\s+|are\\s+)?inscribed\\s+in\\s+(?:a\\s+|the\\s+)?circle
|
|
630
|
+
"^[\\s,]*(?:n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?(?:m\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*" + CENTER + "|n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?\\(\\s*([A-Z])\\s*\\)|(?:is\\s+|are\\s+)?inscribed\\s+in\\s+(?:a\\s+|the\\s+)?circle\\s*" + CENTER + ")",
|
|
613
631
|
"iu"
|
|
614
632
|
);
|
|
615
633
|
var CIRCLE_CIRCUMSCRIBES_QUAD = new RegExp(
|
|
616
634
|
"(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n|circle)\\s*" + CENTER + "\\s*(?:ngo\u1EA1i\\s*ti\u1EBFp|(?:is\\s+|are\\s+)?circumscrib(?:es|ed)(?:\\s+(?:about|around))?)\\s+$",
|
|
617
635
|
"iu"
|
|
618
636
|
);
|
|
637
|
+
var TUGIAC_NOITIEP = new RegExp(
|
|
638
|
+
"[Tt]\u1EE9\\s+gi\xE1c(?:\\s+l\u1ED3i)?\\s+n\u1ED9i\\s*ti\u1EBFp\\s+(?:(?:trong\\s+)?(?:m\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+)?(?:\\(\\s*([A-Z])\\s*\\)\\s+)?" + QUAD,
|
|
639
|
+
"gu"
|
|
640
|
+
);
|
|
619
641
|
var TRIANGLE_DECL = /(?:tam\s*giác|[Tt]riangle)\s+([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
620
642
|
function ownedByOthers(ctx, selfLabels) {
|
|
621
643
|
const owned = /* @__PURE__ */ new Set();
|
|
@@ -634,9 +656,26 @@ function ownedByOthers(ctx, selfLabels) {
|
|
|
634
656
|
}
|
|
635
657
|
return owned;
|
|
636
658
|
}
|
|
659
|
+
function conflictingOwners(ctx, quadLabels) {
|
|
660
|
+
const quadSet = new Set(quadLabels);
|
|
661
|
+
const owned = /* @__PURE__ */ new Set();
|
|
662
|
+
const addIfOutside = (verts) => {
|
|
663
|
+
if (verts.some((v) => !quadSet.has(v))) for (const v of verts) owned.add(v);
|
|
664
|
+
};
|
|
665
|
+
TRIANGLE_DECL.lastIndex = 0;
|
|
666
|
+
for (const m of ctx.problem.matchAll(TRIANGLE_DECL)) addIfOutside([m[1], m[2], m[3]]);
|
|
667
|
+
const selfKey = quadLabels.join("");
|
|
668
|
+
for (const c of ctx.clauses) {
|
|
669
|
+
for (const h of scanClause(c.text)) {
|
|
670
|
+
if (h.labels.join("") === selfKey) continue;
|
|
671
|
+
addIfOutside(h.labels);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return owned;
|
|
675
|
+
}
|
|
637
676
|
function detectCyclic(text, hit) {
|
|
638
677
|
const a = QUAD_INSCRIBED_IN_CIRCLE.exec(text.slice(hit.afterEnd));
|
|
639
|
-
if (a) return a[1] ?? a[2] ?? "";
|
|
678
|
+
if (a) return a[1] ?? a[2] ?? a[3] ?? a[4] ?? a[5] ?? "";
|
|
640
679
|
const b = CIRCLE_CIRCUMSCRIBES_QUAD.exec(text.slice(0, hit.index));
|
|
641
680
|
if (b) return b[1] ?? b[2] ?? "";
|
|
642
681
|
return void 0;
|
|
@@ -649,12 +688,30 @@ var quadRule = {
|
|
|
649
688
|
match(ctx) {
|
|
650
689
|
const out = [];
|
|
651
690
|
for (const c of ctx.clauses) {
|
|
691
|
+
TUGIAC_NOITIEP.lastIndex = 0;
|
|
692
|
+
for (const m of c.text.matchAll(TUGIAC_NOITIEP)) {
|
|
693
|
+
const labels = [m[2], m[3], m[4], m[5]];
|
|
694
|
+
const owned = ownedByOthers(ctx, labels);
|
|
695
|
+
if (labels.some((lbl) => owned.has(lbl))) continue;
|
|
696
|
+
const centerName = m[1] || "O";
|
|
697
|
+
out.push({
|
|
698
|
+
ruleId: "quad",
|
|
699
|
+
clauseIds: [c.id],
|
|
700
|
+
intents: [
|
|
701
|
+
drawCircle(centerName, "centerRadius", { center: centerName, radius: CYCLIC_RADIUS }),
|
|
702
|
+
...labels.map(
|
|
703
|
+
(lbl, i) => addPoint(lbl, { kind: "onCircle", circle: centerName, theta: CYCLIC_QUAD_THETAS[i] })
|
|
704
|
+
),
|
|
705
|
+
markShape("quadrilateral", labels)
|
|
706
|
+
]
|
|
707
|
+
});
|
|
708
|
+
}
|
|
652
709
|
for (const hit of scanClause(c.text)) {
|
|
653
710
|
let intents = [drawShape(hit.shape, hit.labels, hit.variant)];
|
|
654
711
|
if (hit.shape === "quadrilateral") {
|
|
655
712
|
const center = detectCyclic(c.text, hit);
|
|
656
713
|
if (center !== void 0) {
|
|
657
|
-
const owned =
|
|
714
|
+
const owned = conflictingOwners(ctx, hit.labels);
|
|
658
715
|
const shared = hit.labels.some((lbl) => owned.has(lbl));
|
|
659
716
|
if (!shared) {
|
|
660
717
|
const centerName = center || "O";
|
|
@@ -764,11 +821,11 @@ var NAME_AFTER_G = new RegExp(
|
|
|
764
821
|
"gu"
|
|
765
822
|
);
|
|
766
823
|
var DISTRIB = new RegExp(
|
|
767
|
-
`((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*,\\s*)*(?:[A-Z][A-Z]\\s*,\\s*)?[A-Z][A-Z](?:\\s*v\xE0\\s*[A-Z][A-Z])?)`,
|
|
824
|
+
`((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|(?:theo\\s+)?th\u1EE9\\s+t\u1EF1\\s+)?(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*,\\s*)*(?:[A-Z][A-Z]\\s*,\\s*)?[A-Z][A-Z](?:\\s*v\xE0\\s*[A-Z][A-Z])?)`,
|
|
768
825
|
"u"
|
|
769
826
|
);
|
|
770
827
|
var DISTRIB_AFTER = new RegExp(
|
|
771
|
-
`trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*(?:,|v\xE0)\\s*)+[A-Z][A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?l\xE0\\s+((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))(?!\\p{L})`,
|
|
828
|
+
`trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*(?:,|v\xE0)\\s*)+[A-Z][A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|(?:theo\\s+)?th\u1EE9\\s+t\u1EF1\\s+)?l\xE0\\s+((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))(?!\\p{L})`,
|
|
772
829
|
"u"
|
|
773
830
|
);
|
|
774
831
|
function nameToken(raw) {
|
|
@@ -1134,8 +1191,8 @@ var PREFILTER4 = [
|
|
|
1134
1191
|
/[Bb]isector\s+of\s+(?:the\s+)?(?:[Aa]ngle|∠)/u
|
|
1135
1192
|
// Form A EN
|
|
1136
1193
|
];
|
|
1137
|
-
var ANGLE3 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?góc\s
|
|
1138
|
-
var ANGLE1 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?góc\s
|
|
1194
|
+
var ANGLE3 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?(?:góc\s+|∠\s*)([A-Z])([A-Z])([A-Z])(?![A-Z])/giu;
|
|
1195
|
+
var ANGLE1 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?(?:góc\s+|∠\s*)([A-Z])(?![A-Z])/giu;
|
|
1139
1196
|
var CHAN_BEFORE = /chân\s+(?:của\s+)?(?:đường\s*|tia\s+)?$/u;
|
|
1140
1197
|
var ANGLE3_EN_A = /[Bb]isector\s+of\s+(?:the\s+)?(?:[Aa]ngle|∠)\s*([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
1141
1198
|
var ANGLE3_EN_B = /[Aa]ngle\s+bisector\s+of\s+([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
@@ -1313,6 +1370,13 @@ function resolveTriangle(clauseText, kwIndex, uniqueProblemTri) {
|
|
|
1313
1370
|
}
|
|
1314
1371
|
return uniqueProblemTri;
|
|
1315
1372
|
}
|
|
1373
|
+
function dropDegenerate(intents) {
|
|
1374
|
+
return intents.filter((i) => {
|
|
1375
|
+
if (i.op !== "add-point") return true;
|
|
1376
|
+
const of = i.constraint.of;
|
|
1377
|
+
return !(Array.isArray(of) && of.includes(i.name));
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1316
1380
|
var centersRule = {
|
|
1317
1381
|
id: "centers",
|
|
1318
1382
|
priority: 70,
|
|
@@ -1345,7 +1409,8 @@ var centersRule = {
|
|
|
1345
1409
|
if (of) {
|
|
1346
1410
|
intents.push(addPoint(dc[1], { kind: centerTypeToKind(dc[3]), of }));
|
|
1347
1411
|
intents.push(addPoint(dc[2], { kind: centerTypeToKind(dc[4]), of }));
|
|
1348
|
-
|
|
1412
|
+
const kept2 = dropDegenerate(intents);
|
|
1413
|
+
if (kept2.length) out.push({ ruleId: "centers", clauseIds: [c.id], intents: kept2 });
|
|
1349
1414
|
continue;
|
|
1350
1415
|
}
|
|
1351
1416
|
}
|
|
@@ -1394,8 +1459,9 @@ var centersRule = {
|
|
|
1394
1459
|
const of = ofFor(c.text, INCENTER_KW_EN);
|
|
1395
1460
|
if (name && of) intents.push(addPoint(name, { kind: "incenter", of }));
|
|
1396
1461
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1462
|
+
const kept = dropDegenerate(intents);
|
|
1463
|
+
if (kept.length > 0) {
|
|
1464
|
+
out.push({ ruleId: "centers", clauseIds: [c.id], intents: kept });
|
|
1399
1465
|
}
|
|
1400
1466
|
}
|
|
1401
1467
|
return out;
|
|
@@ -1408,9 +1474,11 @@ var PREFILTER5 = new RegExp(
|
|
|
1408
1474
|
"u"
|
|
1409
1475
|
);
|
|
1410
1476
|
var PREFILTER_EN = /projection|perpendicular|foot\s+of/i;
|
|
1411
|
-
var
|
|
1477
|
+
var LINE_PREFIX = "(?:" + DUONG_KW + "\\s*th\u1EB3ng\\s+|ti\u1EBFp\\s*tuy\u1EBFn\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?";
|
|
1478
|
+
var LINE_TOK = "([A-Z]{1,2}(?![A-Za-z])|[A-Z]?[a-z][a-z]?[0-9]?(?![A-Za-z]))";
|
|
1479
|
+
var LINE = LINE_PREFIX + LINE_TOK;
|
|
1412
1480
|
var PREP = "(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn|t\u1EDBi)";
|
|
1413
|
-
var LANLUOT_OPT = "(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1|th\u1EE9\\s+t\u1EF1)\\s+)?";
|
|
1481
|
+
var LANLUOT_OPT = "(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1|th\u1EE9\\s+t\u1EF1|t\u01B0\u01A1ng\\s+\u1EE9ng)\\s+)?";
|
|
1414
1482
|
var PROJ_CORE = `h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s+${PREP}\\s+${LINE}`;
|
|
1415
1483
|
var FOOT_CORE = `ch\xE2n\\s+(?:c\u1EE7a\\s+)?${DUONG_KW}\\s+(?:vu\xF4ng\\s*g\xF3c|cao)\\s*(?:h\u1EA1\\s+|k\u1EBB\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s+${PREP}\\s+${LINE}`;
|
|
1416
1484
|
var LANLUOT = new RegExp(
|
|
@@ -1421,14 +1489,27 @@ var SINGLE = new RegExp(`(?:${PROJ_CORE})|(?:${FOOT_CORE})`, "gu");
|
|
|
1421
1489
|
var NAMES_BLOB = "((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))";
|
|
1422
1490
|
var LINES_BLOB = "((?:[A-Z]{1,2}\\s*(?:,|v\xE0)\\s*)+[A-Z]{1,2})(?!\\p{L})";
|
|
1423
1491
|
var LINES_PREFIX = "(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?";
|
|
1492
|
+
var NO_HOA_BEFORE = "(?<![A-Z])";
|
|
1424
1493
|
var DISTRIB_PROJ = new RegExp(
|
|
1425
|
-
NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1494
|
+
NO_HOA_BEFORE + NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1426
1495
|
"gu"
|
|
1427
1496
|
);
|
|
1428
1497
|
var DISTRIB_FOOT = new RegExp(
|
|
1429
|
-
NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?ch\xE2n\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?\u0111\u01B0\u1EDDng\\s+(?:vu\xF4ng\\s*g\xF3c|cao)\\s*(?:h\u1EA1\\s+|k\u1EBB\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:\u0111\u1EBFn|xu\u1ED1ng|tr\xEAn|t\u1EDBi)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1498
|
+
NO_HOA_BEFORE + NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?ch\xE2n\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?\u0111\u01B0\u1EDDng\\s+(?:vu\xF4ng\\s*g\xF3c|cao)\\s*(?:h\u1EA1\\s+|k\u1EBB\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:\u0111\u1EBFn|xu\u1ED1ng|tr\xEAn|t\u1EDBi)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1499
|
+
"gu"
|
|
1500
|
+
);
|
|
1501
|
+
var NAMES_BLOB_IDX = "((?:[A-Z][0-9]?(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z][0-9]?(?:['\u2032]?))";
|
|
1502
|
+
var FROMS_BLOB_IDX = "((?:[A-Z][0-9]?(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z][0-9]?(?:['\u2032]?))(?!\\p{L})";
|
|
1503
|
+
var ZIP_FROM_PROJ = new RegExp(
|
|
1504
|
+
NO_HOA_BEFORE + NAMES_BLOB_IDX + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?" + FROMS_BLOB_IDX + "\\s+(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1430
1505
|
"gu"
|
|
1431
1506
|
);
|
|
1507
|
+
function splitNamesIdx(blob) {
|
|
1508
|
+
return blob.split(/,|và/).map((s) => {
|
|
1509
|
+
const mm = /^([A-Z][0-9]?)(['′]?)/u.exec(s.trim());
|
|
1510
|
+
return mm ? mm[2] ? `${mm[1]}'` : mm[1] : "";
|
|
1511
|
+
}).filter(Boolean);
|
|
1512
|
+
}
|
|
1432
1513
|
var TWO_LINES_PREP = "(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn|t\u1EDBi)";
|
|
1433
1514
|
var TWO_LINES_LINE = "(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2})(?![A-Z])";
|
|
1434
1515
|
var SHARED_FROM_TWO = new RegExp(
|
|
@@ -1436,21 +1517,44 @@ var SHARED_FROM_TWO = new RegExp(
|
|
|
1436
1517
|
"gu"
|
|
1437
1518
|
);
|
|
1438
1519
|
var FEET_FROM_SHARED_LINE = new RegExp(
|
|
1439
|
-
"(?:[Hh]ai\\s+\u0111i\u1EC3m\\s+|[Cc]\xE1c\\s+\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032])?\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032])?\\s+
|
|
1520
|
+
"(?:[Hh]ai\\s+\u0111i\u1EC3m\\s+|[Cc]\xE1c\\s+\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032])?\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032])?\\s+" + // "lần lượt"/"theo thứ tự"/"thứ tự" = phân phối 1-1, OPTIONAL (LANLUOT_OPT).
|
|
1521
|
+
LANLUOT_OPT + "l\xE0\\s+ch\xE2n\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s+)?(?:vu\xF4ng\\s*g\xF3c|cao)\\s+(?:k\u1EBB\\s+|h\u1EA1\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?([A-Z](?:['\u2032])?)\\s*(?:,|v\xE0)\\s*([A-Z](?:['\u2032])?)\\s+(?:\u0111\u1EBFn|xu\u1ED1ng|tr\xEAn|t\u1EDBi)\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2}(?:['\u2032])?)(?![A-Z])",
|
|
1440
1522
|
"gu"
|
|
1441
1523
|
);
|
|
1524
|
+
var PERP_LINE_PREFIX = "(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|ti\u1EBFp\\s*tuy\u1EBFn\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?";
|
|
1525
|
+
var PERP_LINE_TOK = "([A-Z]{1,2}(?![A-Za-z])|[A-Z]?[a-z][a-z]?[0-9]?(?![A-Za-z]))";
|
|
1442
1526
|
var PERP_DRAW_DISTRIB = new RegExp(
|
|
1443
1527
|
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u22A5|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?)\\s+(?:v\u1EDBi\\s+)?(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])",
|
|
1444
1528
|
"gu"
|
|
1445
1529
|
);
|
|
1530
|
+
var PAIRS_BLOB = "((?:[A-Z]{2}\\s*,\\s*)+[A-Z]{2})(?![A-Z])";
|
|
1531
|
+
var PERP_DRAW_ARE = new RegExp(
|
|
1532
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+" + PAIRS_BLOB + "\\s+l\xE0\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111\u01B0\u1EDDng\\s+)?vu\xF4ng\\s*g\xF3c\\s+(?:t\u1EDBi|\u0111\u1EBFn|xu\u1ED1ng|v\u1EDBi)\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?((?:[A-Z]{2}\\s*(?:,|v\xE0)\\s*)+[A-Z]{2})(?![A-Z])",
|
|
1533
|
+
"gu"
|
|
1534
|
+
);
|
|
1446
1535
|
var PERP_SHARED_CUNG = new RegExp(
|
|
1447
|
-
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+([A-Z])([A-Z])(?![A-Z])\\s+c\xF9ng\\s+vu\xF4ng\\s*g\xF3c\\s+(?:v\u1EDBi\\s+)?
|
|
1536
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+([A-Z])([A-Z])(?![A-Z])\\s+(?:c\xF9ng\\s+)?vu\xF4ng\\s*g\xF3c\\s+(?:v\u1EDBi\\s+)?" + PERP_LINE_PREFIX + PERP_LINE_TOK,
|
|
1448
1537
|
"gu"
|
|
1449
1538
|
);
|
|
1450
1539
|
var FROM_DRAW_DISTRIB = new RegExp(
|
|
1451
1540
|
"t\u1EEB\\s+([A-Z])(?!\\p{L})\\s+k\u1EBB\\s+([A-Z]{2})\\s*,\\s*([A-Z]{2})\\s+vu\xF4ng\\s*g\xF3c\\s+v\u1EDBi\\s+c\xE1c\\s+c\u1EA1nh\\s+([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])",
|
|
1452
1541
|
"gu"
|
|
1453
1542
|
);
|
|
1543
|
+
var DECLARE_LINE = new RegExp(
|
|
1544
|
+
"(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng|ti\u1EBFp\\s*tuy\u1EBFn|c\xE1t\\s*tuy\u1EBFn|tia)\\s+([A-Z]?[a-z][a-z]?[0-9]?)(?![A-Za-z])",
|
|
1545
|
+
"gu"
|
|
1546
|
+
);
|
|
1547
|
+
function declaredNamedLines(problem) {
|
|
1548
|
+
const out = /* @__PURE__ */ new Set();
|
|
1549
|
+
DECLARE_LINE.lastIndex = 0;
|
|
1550
|
+
let m;
|
|
1551
|
+
while ((m = DECLARE_LINE.exec(problem)) !== null) out.add(m[1]);
|
|
1552
|
+
return out;
|
|
1553
|
+
}
|
|
1554
|
+
function isAllowedLine(line, declared) {
|
|
1555
|
+
if (/^[A-Z]{1,2}$/u.test(line)) return true;
|
|
1556
|
+
return declared.has(line);
|
|
1557
|
+
}
|
|
1454
1558
|
function splitNames(blob) {
|
|
1455
1559
|
return blob.split(/,|và/).map((s) => {
|
|
1456
1560
|
const mm = /^([A-Z])(['′]?)/u.exec(s.trim());
|
|
@@ -1458,11 +1562,11 @@ function splitNames(blob) {
|
|
|
1458
1562
|
}).filter(Boolean);
|
|
1459
1563
|
}
|
|
1460
1564
|
var PERP_DRAW = new RegExp(
|
|
1461
|
-
`(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)
|
|
1565
|
+
`(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?${PERP_LINE_PREFIX}${PERP_LINE_TOK}(?:\\s+t\u1EA1i\\s+([A-Z]))?`,
|
|
1462
1566
|
"gu"
|
|
1463
1567
|
);
|
|
1464
1568
|
var PERP_BARE = new RegExp(
|
|
1465
|
-
`([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)
|
|
1569
|
+
`([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?${PERP_LINE_PREFIX}${PERP_LINE_TOK}(?:\\s+t\u1EA1i\\s+([A-Z]))?`,
|
|
1466
1570
|
"gu"
|
|
1467
1571
|
);
|
|
1468
1572
|
var DRAW_VERB = /[Kk]ẻ|[Vv]ẽ|[Dd]ựng/u;
|
|
@@ -1531,7 +1635,7 @@ function parseAltitudeBundle(text, fallbackTri) {
|
|
|
1531
1635
|
}
|
|
1532
1636
|
return out;
|
|
1533
1637
|
}
|
|
1534
|
-
function parseFeet(text, fallbackTri) {
|
|
1638
|
+
function parseFeet(text, fallbackTri, declared = /* @__PURE__ */ new Set()) {
|
|
1535
1639
|
const out = [];
|
|
1536
1640
|
const consumed = [];
|
|
1537
1641
|
const bundled = parseAltitudeBundle(text, fallbackTri);
|
|
@@ -1573,6 +1677,24 @@ function parseFeet(text, fallbackTri) {
|
|
|
1573
1677
|
}
|
|
1574
1678
|
if (ok) return local;
|
|
1575
1679
|
}
|
|
1680
|
+
ZIP_FROM_PROJ.lastIndex = 0;
|
|
1681
|
+
for (const zm of text.matchAll(ZIP_FROM_PROJ)) {
|
|
1682
|
+
const names = splitNamesIdx(zm[1]);
|
|
1683
|
+
const froms = splitNamesIdx(zm[2]);
|
|
1684
|
+
const lines = zm[3].split(/,|và/).map((s) => s.trim()).filter(Boolean);
|
|
1685
|
+
if (names.length < 2) continue;
|
|
1686
|
+
if (names.length !== froms.length || names.length !== lines.length) continue;
|
|
1687
|
+
const local = [];
|
|
1688
|
+
let ok = true;
|
|
1689
|
+
for (let i = 0; i < names.length; i++) {
|
|
1690
|
+
if (lines[i].includes(names[i]) || names[i] === froms[i]) {
|
|
1691
|
+
ok = false;
|
|
1692
|
+
break;
|
|
1693
|
+
}
|
|
1694
|
+
local.push({ name: names[i], from: froms[i], onLine: lines[i], withSegment: true });
|
|
1695
|
+
}
|
|
1696
|
+
if (ok && local.length > 0) return local;
|
|
1697
|
+
}
|
|
1576
1698
|
for (const re of [DISTRIB_PROJ, DISTRIB_FOOT]) {
|
|
1577
1699
|
re.lastIndex = 0;
|
|
1578
1700
|
for (const dm2 of text.matchAll(re)) {
|
|
@@ -1613,8 +1735,8 @@ function parseFeet(text, fallbackTri) {
|
|
|
1613
1735
|
for (const fm of text.matchAll(FEET_FROM_SHARED_LINE)) {
|
|
1614
1736
|
const start = fm.index ?? 0;
|
|
1615
1737
|
if (consumed.some(([a, b]) => start >= a && start < b)) continue;
|
|
1616
|
-
const
|
|
1617
|
-
const [n1, n2, f1, f2, line] = [fm[1], fm[2],
|
|
1738
|
+
const norm5 = (s) => s.replace(/′/g, "'");
|
|
1739
|
+
const [n1, n2, f1, f2, line] = [fm[1], fm[2], norm5(fm[3]), norm5(fm[4]), norm5(fm[5])];
|
|
1618
1740
|
if (n1 === n2 || f1 === f2 || line.includes(n1) || line.includes(n2)) continue;
|
|
1619
1741
|
out.push({ name: n1, from: f1, onLine: line, withSegment: true });
|
|
1620
1742
|
out.push({ name: n2, from: f2, onLine: line, withSegment: true });
|
|
@@ -1639,7 +1761,9 @@ function parseFeet(text, fallbackTri) {
|
|
|
1639
1761
|
if (MID_BEFORE.test(before)) continue;
|
|
1640
1762
|
const nm = NAME_BEFORE.exec(before);
|
|
1641
1763
|
if (!nm) continue;
|
|
1642
|
-
|
|
1764
|
+
const onLine = m[2] ?? m[4];
|
|
1765
|
+
if (!isAllowedLine(onLine, declared)) continue;
|
|
1766
|
+
out.push({ name: nm[1], from: m[1] ?? m[3], onLine });
|
|
1643
1767
|
}
|
|
1644
1768
|
PERP_DRAW_DISTRIB.lastIndex = 0;
|
|
1645
1769
|
for (const ddm of text.matchAll(PERP_DRAW_DISTRIB)) {
|
|
@@ -1650,10 +1774,36 @@ function parseFeet(text, fallbackTri) {
|
|
|
1650
1774
|
out.push({ name: foot2, from: from2, onLine: line2, withSegment: true });
|
|
1651
1775
|
consumed.push([ddm.index ?? 0, (ddm.index ?? 0) + ddm[0].length]);
|
|
1652
1776
|
}
|
|
1777
|
+
PERP_DRAW_ARE.lastIndex = 0;
|
|
1778
|
+
for (const am of text.matchAll(PERP_DRAW_ARE)) {
|
|
1779
|
+
const start = am.index ?? 0;
|
|
1780
|
+
if (consumed.some(([a, b]) => start >= a && start < b)) continue;
|
|
1781
|
+
const pairs = am[1].split(",").map((s) => s.trim()).filter((s) => /^[A-Z]{2}$/.test(s));
|
|
1782
|
+
const lines = am[2].split(/,|và/).map((s) => s.trim()).filter((s) => /^[A-Z]{2}$/.test(s));
|
|
1783
|
+
if (pairs.length < 2 || pairs.length !== lines.length) continue;
|
|
1784
|
+
const from = pairs[0][0];
|
|
1785
|
+
if (!pairs.every((p) => p[0] === from)) continue;
|
|
1786
|
+
const local = [];
|
|
1787
|
+
let ok = true;
|
|
1788
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
1789
|
+
const foot = pairs[i][1];
|
|
1790
|
+
const onLine = lines[i];
|
|
1791
|
+
if (onLine.includes(foot)) {
|
|
1792
|
+
ok = false;
|
|
1793
|
+
break;
|
|
1794
|
+
}
|
|
1795
|
+
local.push({ name: foot, from, onLine, withSegment: true });
|
|
1796
|
+
}
|
|
1797
|
+
if (ok && local.length > 0) {
|
|
1798
|
+
out.push(...local);
|
|
1799
|
+
consumed.push([start, start + am[0].length]);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1653
1802
|
PERP_SHARED_CUNG.lastIndex = 0;
|
|
1654
1803
|
for (const sm of text.matchAll(PERP_SHARED_CUNG)) {
|
|
1655
1804
|
const [f1, t1, f2, t2, line] = [sm[1], sm[2], sm[3], sm[4], sm[5]];
|
|
1656
1805
|
if (t1 === t2 || line.includes(t1) || line.includes(t2)) continue;
|
|
1806
|
+
if (!isAllowedLine(line, declared)) continue;
|
|
1657
1807
|
out.push({ name: t1, from: f1, onLine: line, withSegment: true });
|
|
1658
1808
|
out.push({ name: t2, from: f2, onLine: line, withSegment: true });
|
|
1659
1809
|
consumed.push([sm.index ?? 0, (sm.index ?? 0) + sm[0].length]);
|
|
@@ -1667,6 +1817,7 @@ function parseFeet(text, fallbackTri) {
|
|
|
1667
1817
|
const at = pm[4];
|
|
1668
1818
|
if (at && at !== foot) continue;
|
|
1669
1819
|
if (onLine.includes(foot)) continue;
|
|
1820
|
+
if (!isAllowedLine(onLine, declared)) continue;
|
|
1670
1821
|
out.push({ name: foot, from, onLine });
|
|
1671
1822
|
}
|
|
1672
1823
|
if (DRAW_VERB.test(text)) {
|
|
@@ -1679,6 +1830,7 @@ function parseFeet(text, fallbackTri) {
|
|
|
1679
1830
|
const at = bm[4];
|
|
1680
1831
|
if (at && at !== foot) continue;
|
|
1681
1832
|
if (onLine.includes(foot)) continue;
|
|
1833
|
+
if (!isAllowedLine(onLine, declared)) continue;
|
|
1682
1834
|
if (out.some((o) => o.name === foot)) continue;
|
|
1683
1835
|
out.push({ name: foot, from, onLine });
|
|
1684
1836
|
}
|
|
@@ -1714,13 +1866,14 @@ var perpFootRule = {
|
|
|
1714
1866
|
match(ctx) {
|
|
1715
1867
|
const out = [];
|
|
1716
1868
|
const fallbackTri = uniqueTriangle(ctx.problem);
|
|
1869
|
+
const declared = declaredNamedLines(ctx.problem);
|
|
1717
1870
|
for (const c of ctx.clauses) {
|
|
1718
|
-
const feet = parseFeet(c.text, fallbackTri);
|
|
1871
|
+
const feet = parseFeet(c.text, fallbackTri, declared);
|
|
1719
1872
|
if (feet.length === 0) continue;
|
|
1720
1873
|
const intents = feet.flatMap((f) => {
|
|
1721
1874
|
if (f.from === "") return [addPoint(f.name, { kind: "orthocenter", of: f.onLine.split("") })];
|
|
1722
|
-
const
|
|
1723
|
-
return f.withSegment ? [
|
|
1875
|
+
const add2 = addPoint(f.name, { kind: "perpFoot", from: f.from, onLine: f.onLine });
|
|
1876
|
+
return f.withSegment ? [add2, connect(f.from, f.name, "segment")] : [add2];
|
|
1724
1877
|
});
|
|
1725
1878
|
out.push({ ruleId: "perpFoot", clauseIds: [c.id], intents });
|
|
1726
1879
|
}
|
|
@@ -1767,28 +1920,28 @@ function parseNum(raw) {
|
|
|
1767
1920
|
function isInscribedCircumscribed(problem, center) {
|
|
1768
1921
|
const REL = "(?:(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp|[Ii]nscribed|[Cc]ircumscrib(?:es|ed))";
|
|
1769
1922
|
const after = new RegExp(
|
|
1770
|
-
`${REL}[^.]{0,30}?(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*|[Cc]ircle\\s*)?\\(?\\s*${center}(?![A-Z])`,
|
|
1923
|
+
`${REL}[^.]{0,30}?(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*|[Cc]ircle\\s*)?\\(?\\s*${escapeRe(center)}(?![A-Z])`,
|
|
1771
1924
|
"u"
|
|
1772
1925
|
);
|
|
1773
1926
|
const before = new RegExp(
|
|
1774
|
-
`\\(\\s*${center}\\s*[;,][^()]*\\)[^.]{0,30}?${REL}`,
|
|
1927
|
+
`\\(\\s*${escapeRe(center)}\\s*[;,][^()]*\\)[^.]{0,30}?${REL}`,
|
|
1775
1928
|
"u"
|
|
1776
1929
|
);
|
|
1777
1930
|
return after.test(problem) || before.test(problem);
|
|
1778
1931
|
}
|
|
1779
1932
|
function isDiameterCircle(problem, center) {
|
|
1780
1933
|
const parenThenDiameter = new RegExp(
|
|
1781
|
-
`\\(\\s*${center}\\s*[;,][^()]*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1934
|
+
`\\(\\s*${escapeRe(center)}\\s*[;,][^()]*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1782
1935
|
"u"
|
|
1783
1936
|
);
|
|
1784
1937
|
const namedThenDiameter = new RegExp(
|
|
1785
|
-
`${CIRCLE_KW}\\s*\\(\\s*${center}\\s*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1938
|
+
`${CIRCLE_KW}\\s*\\(\\s*${escapeRe(center)}\\s*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1786
1939
|
"u"
|
|
1787
1940
|
);
|
|
1788
1941
|
return parenThenDiameter.test(problem) || namedThenDiameter.test(problem);
|
|
1789
1942
|
}
|
|
1790
1943
|
function findParenClauseId(clauses, center) {
|
|
1791
|
-
const frag = new RegExp(`\\(\\s*${center}(?![A-Z])`, "u");
|
|
1944
|
+
const frag = new RegExp(`\\(\\s*${escapeRe(center)}(?![A-Z])`, "u");
|
|
1792
1945
|
for (const c of clauses) if (frag.test(c.text)) return c.id;
|
|
1793
1946
|
return void 0;
|
|
1794
1947
|
}
|
|
@@ -2326,8 +2479,52 @@ var externalPointRule = {
|
|
|
2326
2479
|
}
|
|
2327
2480
|
};
|
|
2328
2481
|
|
|
2482
|
+
// src/stamps/geometry-2d/ai/rules/externalPointAtRadius.ts
|
|
2483
|
+
var METRIC = /([A-Z])(?:['′]?)([A-Z])(?:['′]?)\s*=\s*([2-9])\s*[Rr](?![A-Za-z])/gu;
|
|
2484
|
+
function isCenter(problem, c) {
|
|
2485
|
+
const e = escapeRe(c);
|
|
2486
|
+
return new RegExp(
|
|
2487
|
+
String.raw`\(\s*${e}[\s);,]|tâm\s*${e}(?![A-Za-z])|đường\s*tròn\s*\(?\s*${e}(?![A-Za-z])`,
|
|
2488
|
+
"u"
|
|
2489
|
+
).test(problem);
|
|
2490
|
+
}
|
|
2491
|
+
var externalPointAtRadiusRule = {
|
|
2492
|
+
id: "externalPointAtRadius",
|
|
2493
|
+
priority: 67,
|
|
2494
|
+
languages: ["vi"],
|
|
2495
|
+
patterns: [/[A-Z](?:['′]?)[A-Z](?:['′]?)\s*=\s*[2-9]\s*[Rr](?![A-Za-z])/u],
|
|
2496
|
+
match(ctx) {
|
|
2497
|
+
const out = [];
|
|
2498
|
+
for (const c of ctx.clauses) {
|
|
2499
|
+
METRIC.lastIndex = 0;
|
|
2500
|
+
for (const m of c.text.matchAll(METRIC)) {
|
|
2501
|
+
const a = m[1];
|
|
2502
|
+
const b = m[2];
|
|
2503
|
+
let center;
|
|
2504
|
+
let point;
|
|
2505
|
+
const aC = isCenter(ctx.problem, a);
|
|
2506
|
+
const bC = isCenter(ctx.problem, b);
|
|
2507
|
+
if (aC && !bC) {
|
|
2508
|
+
center = a;
|
|
2509
|
+
point = b;
|
|
2510
|
+
} else if (bC && !aC) {
|
|
2511
|
+
center = b;
|
|
2512
|
+
point = a;
|
|
2513
|
+
}
|
|
2514
|
+
if (!center || !point || center === point) continue;
|
|
2515
|
+
out.push({
|
|
2516
|
+
ruleId: "externalPointAtRadius",
|
|
2517
|
+
clauseIds: [c.id],
|
|
2518
|
+
intents: [addPoint(point, { kind: "externalToCircle", circle: center })]
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return out;
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
|
|
2329
2526
|
// src/stamps/geometry-2d/ai/rules/arcMidpoint.ts
|
|
2330
|
-
var ARC_MID = /(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+)
|
|
2527
|
+
var ARC_MID = /(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+|các\s+)*cung/u;
|
|
2331
2528
|
var ARC_PAIR = /(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+)?cung\s+(nhỏ\s+|lớn\s+)?([A-Z])([A-Z])/u;
|
|
2332
2529
|
var NAME_BEFORE2 = /([A-Z])(?:['′]?)\s+(?:là\s+)?(?:điểm\s+)?(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+)?cung/u;
|
|
2333
2530
|
var TRI3 = /tam giác\s+([A-Z])([A-Z])([A-Z])/u;
|
|
@@ -2363,6 +2560,10 @@ var DISTRIB_TWO_ARCS = new RegExp(
|
|
|
2363
2560
|
"([A-Z])(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032]?)\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+(?:c\u1EE7a\\s+)?cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+(?:\u0111i\u1EC3m\\s+)?(?:(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+)?(?:c\u1EE7a\\s+)?cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?([A-Z])([A-Z])(?![A-Z])",
|
|
2364
2561
|
"u"
|
|
2365
2562
|
);
|
|
2563
|
+
var DISTRIB_TWO_ARCS3 = new RegExp(
|
|
2564
|
+
"([A-Z])(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032]?)\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+(?:c\u1EE7a\\s+|c\xE1c\\s+)*cung\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+(?:\u0111i\u1EC3m\\s+)?(?:(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+)?(?:c\u1EE7a\\s+)?(?:cung\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])",
|
|
2565
|
+
"u"
|
|
2566
|
+
);
|
|
2366
2567
|
function resolveCircle(problem) {
|
|
2367
2568
|
const w = CIRCLE_WORDS.exec(problem);
|
|
2368
2569
|
if (w) return w[1];
|
|
@@ -2410,6 +2611,27 @@ var arcMidpointRule = {
|
|
|
2410
2611
|
};
|
|
2411
2612
|
for (const c of ctx.clauses) {
|
|
2412
2613
|
if (!ARC_MID.test(c.text)) continue;
|
|
2614
|
+
const twoArcs3 = DISTRIB_TWO_ARCS3.exec(c.text);
|
|
2615
|
+
if (twoArcs3) {
|
|
2616
|
+
const [, n1, n2, x1, y1, z1, x2, y2, z210] = twoArcs3;
|
|
2617
|
+
const triples = [
|
|
2618
|
+
[n1, x1, z1, y1],
|
|
2619
|
+
[n2, x2, z210, y2]
|
|
2620
|
+
];
|
|
2621
|
+
const ok = triples.every(
|
|
2622
|
+
([n, a2, b2, ct]) => arcOnCircum(a2, b2) && (/* @__PURE__ */ new Set([a2, b2, ct])).size === 3 && n !== a2 && n !== b2 && n !== ct
|
|
2623
|
+
);
|
|
2624
|
+
if (ok) {
|
|
2625
|
+
const mark = out.length;
|
|
2626
|
+
let good = true;
|
|
2627
|
+
for (const [n, a2, b2, ct] of triples) {
|
|
2628
|
+
good = pushArc(c, n, a2, b2, { rel: "in", point: ct });
|
|
2629
|
+
if (!good) break;
|
|
2630
|
+
}
|
|
2631
|
+
if (!good) out.length = mark;
|
|
2632
|
+
}
|
|
2633
|
+
continue;
|
|
2634
|
+
}
|
|
2413
2635
|
const twoArcs = DISTRIB_TWO_ARCS.exec(c.text);
|
|
2414
2636
|
if (twoArcs) {
|
|
2415
2637
|
const [, n1, n2, a1, b1, a2, b2] = twoArcs;
|
|
@@ -2433,7 +2655,7 @@ var arcMidpointRule = {
|
|
|
2433
2655
|
}
|
|
2434
2656
|
const pairM = ARC_PAIR.exec(c.text);
|
|
2435
2657
|
if (!pairM) continue;
|
|
2436
|
-
|
|
2658
|
+
const isMajor = !!pairM[1] && /lớn/u.test(pairM[1]);
|
|
2437
2659
|
const pair = pairFromToken(pairM[2] + pairM[3]);
|
|
2438
2660
|
if (pair.length !== 2) continue;
|
|
2439
2661
|
const [a, b] = pair;
|
|
@@ -2458,7 +2680,9 @@ var arcMidpointRule = {
|
|
|
2458
2680
|
pushArc(c, name, a, b, conts[0]);
|
|
2459
2681
|
} else if (tri) {
|
|
2460
2682
|
const third = [tri[1], tri[2], tri[3]].find((v) => v !== a && v !== b);
|
|
2461
|
-
if (third) pushArc(c, name, a, b, { rel: "not", point: third });
|
|
2683
|
+
if (third) pushArc(c, name, a, b, isMajor ? { rel: "in", point: third } : { rel: "not", point: third });
|
|
2684
|
+
} else if (isMajor && explicitCircle) {
|
|
2685
|
+
pushArc(c, name, a, b, { rel: "in", point: explicitCircle });
|
|
2462
2686
|
} else {
|
|
2463
2687
|
out.push({
|
|
2464
2688
|
ruleId: "arcMidpoint",
|
|
@@ -2554,6 +2778,14 @@ var RE_CUT = new RegExp(
|
|
|
2554
2778
|
THROUGH_LINE + "\\s+c\u1EAFt\\s+(?:\u0111\u01B0\u1EDDng\\s*(?:th\u1EB3ng\\s*)?)?([A-Z])([A-Z])(?!\\p{L})\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
2555
2779
|
"gu"
|
|
2556
2780
|
);
|
|
2781
|
+
var FOOT2 = "([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])";
|
|
2782
|
+
var FOOT3 = "([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])";
|
|
2783
|
+
var TARGET2 = "([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])";
|
|
2784
|
+
var TARGET3 = "([A-Z]{2})\\s*,\\s*([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])";
|
|
2785
|
+
var PERP_KW = "(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:\u22A5|vu\xF4ng\\s*g\xF3c)\\s+(?:v\u1EDBi\\s+)?(?:c\xE1c\\s+)?(?:c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?";
|
|
2786
|
+
var FROM = "(?:Qua|qua|T\u1EEB|t\u1EEB)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032]?)(?!\\p{L})[^.]{0,16}?(?:k\u1EBB|v\u1EBD|d\u1EF1ng)\\s+";
|
|
2787
|
+
var PERP_NAMED_DISTRIB3 = new RegExp(FROM + FOOT3 + "\\s+" + PERP_KW + TARGET3, "gu");
|
|
2788
|
+
var PERP_NAMED_DISTRIB2 = new RegExp(FROM + FOOT2 + "\\s+" + PERP_KW + TARGET2, "gu");
|
|
2557
2789
|
var PREFILTER6 = /(?:Qua|qua|Từ|từ)\s+(?:điểm\s+)?[A-Z][^.]{0,24}?(?:song\s*song|vuông\s*góc)|đường\s*thẳng\s+qua\s+[A-Z][^.]{0,24}?(?:song\s*song|vuông\s*góc)/u;
|
|
2558
2790
|
function lineNameOf(isParallel, through) {
|
|
2559
2791
|
return (isParallel ? "par" : "prp") + through;
|
|
@@ -2583,6 +2815,43 @@ var parallelPerpRule = {
|
|
|
2583
2815
|
});
|
|
2584
2816
|
cutSpans.push([start, end]);
|
|
2585
2817
|
};
|
|
2818
|
+
const emitNamedPerp = (from, foot, target, start, end) => {
|
|
2819
|
+
if (foot === from || target.includes(foot)) return;
|
|
2820
|
+
const lineName = lineNameOf(false, from) + foot;
|
|
2821
|
+
out.push({
|
|
2822
|
+
ruleId: "parallelPerp",
|
|
2823
|
+
clauseIds: [c.id],
|
|
2824
|
+
intents: [
|
|
2825
|
+
drawLine(lineName, "perpThrough", { through: from, to: target }),
|
|
2826
|
+
addPoint(foot, { kind: "intersection", of: [lineName, target] })
|
|
2827
|
+
]
|
|
2828
|
+
});
|
|
2829
|
+
cutSpans.push([start, end]);
|
|
2830
|
+
};
|
|
2831
|
+
PERP_NAMED_DISTRIB3.lastIndex = 0;
|
|
2832
|
+
for (const m of c.text.matchAll(PERP_NAMED_DISTRIB3)) {
|
|
2833
|
+
const from = m[1];
|
|
2834
|
+
const triples = [
|
|
2835
|
+
[m[3], m[8]],
|
|
2836
|
+
[m[5], m[9]],
|
|
2837
|
+
[m[7], m[10]]
|
|
2838
|
+
];
|
|
2839
|
+
if ([m[2], m[4], m[6]].every((f) => f === from)) {
|
|
2840
|
+
const s = m.index ?? 0;
|
|
2841
|
+
const e = s + m[0].length;
|
|
2842
|
+
for (const [foot, target] of triples) emitNamedPerp(from, foot, target, s, e);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
PERP_NAMED_DISTRIB2.lastIndex = 0;
|
|
2846
|
+
for (const m of c.text.matchAll(PERP_NAMED_DISTRIB2)) {
|
|
2847
|
+
const s = m.index ?? 0;
|
|
2848
|
+
if (cutSpans.some(([a, b]) => s >= a && s < b)) continue;
|
|
2849
|
+
const from = m[1];
|
|
2850
|
+
if (m[2] !== from || m[4] !== from) continue;
|
|
2851
|
+
const pairs = [[m[3], m[6]], [m[5], m[7]]];
|
|
2852
|
+
const e = s + m[0].length;
|
|
2853
|
+
for (const [foot, target] of pairs) emitNamedPerp(from, foot, target, s, e);
|
|
2854
|
+
}
|
|
2586
2855
|
RE_GIAO.lastIndex = 0;
|
|
2587
2856
|
for (const m of c.text.matchAll(RE_GIAO)) {
|
|
2588
2857
|
emitCut(m[2], m[3], m[4], m[5], m[6] + m[7], m[1], m.index ?? 0, (m.index ?? 0) + m[0].length);
|
|
@@ -2616,12 +2885,23 @@ var REFLECT = /đối\s*xứng/u;
|
|
|
2616
2885
|
var PT = `[A-Z](?:['\u2032])?`;
|
|
2617
2886
|
var NAME_BEFORE4 = new RegExp(
|
|
2618
2887
|
`(${PT})\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?(${PT})\\s+qua\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+|tr\u1EE5c\\s+|trung\\s*\u0111i\u1EC3m\\s+|t\xE2m\\s+|\u0111i\u1EC3m\\s+)?([A-Za-z][A-Za-z\u2032']?)`,
|
|
2619
|
-
"
|
|
2888
|
+
"gu"
|
|
2620
2889
|
);
|
|
2621
2890
|
var DISTRIB2 = new RegExp(
|
|
2622
2891
|
`(${PT})\\s*,\\s*(${PT})\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?(${PT})\\s+qua\\s+([A-Za-z]{1,2})(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*([A-Za-z]{1,2})(?:['\u2032]?)`,
|
|
2623
2892
|
"u"
|
|
2624
2893
|
);
|
|
2894
|
+
var LL = `(?:l\u1EA7n\\s*l\u01B0\u1EE3t|t\u01B0\u01A1ng\\s*\u1EE9ng|(?:theo\\s+)?th\u1EE9\\s+t\u1EF1)`;
|
|
2895
|
+
var NAMES_BLOB2 = `(${PT}(?:\\s*(?:,|v\xE0)\\s*${PT})+)`;
|
|
2896
|
+
var SRCS_BLOB = `(${PT}(?:\\s*(?:,|v\xE0)\\s*${PT})+)`;
|
|
2897
|
+
var MIRRORS_BLOB = `([A-Za-z]{1,2}(?:['\u2032]?)(?:\\s*(?:,|v\xE0)\\s*[A-Za-z]{1,2}(?:['\u2032]?))+)`;
|
|
2898
|
+
var DISTRIB_MULTI = new RegExp(
|
|
2899
|
+
`${NAMES_BLOB2}\\s+(?:${LL}\\s+)?(?:l\xE0\\s+)?(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?${SRCS_BLOB}\\s+(?:${LL}\\s+)?qua\\s+${MIRRORS_BLOB}`,
|
|
2900
|
+
"u"
|
|
2901
|
+
);
|
|
2902
|
+
function splitList(blob) {
|
|
2903
|
+
return blob.split(/\s*,\s*|\s+và\s+/u).map((s) => s.trim()).filter(Boolean);
|
|
2904
|
+
}
|
|
2625
2905
|
var NAME_AFTER = new RegExp(
|
|
2626
2906
|
`(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?(${PT})\\s+qua\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+|tr\u1EE5c\\s+|trung\\s*\u0111i\u1EC3m\\s+|t\xE2m\\s+|\u0111i\u1EC3m\\s+)?([A-Za-z][A-Za-z\u2032']?)`,
|
|
2627
2907
|
"u"
|
|
@@ -2632,10 +2912,10 @@ function normalizePointName(name) {
|
|
|
2632
2912
|
var REFLECT_EN = /(?<![A-Za-z])([A-Z])(?:['′]?)\s+(?:is|be)\s+the\s+reflection\s+of\s+([A-Z])(?:['′]?)\s+(?:over|across|in|about|through)\s+(?:the\s+)?(?:line\s+|point\s+|segment\s+)?([A-Za-z][A-Za-z′']?)/u;
|
|
2633
2913
|
var REFLECT_EN_PRE = /[Rr]eflection/u;
|
|
2634
2914
|
function classifyThrough(raw) {
|
|
2635
|
-
const
|
|
2636
|
-
if (/^[A-Z]$/u.test(
|
|
2637
|
-
if (/^[A-Z][A-Z]$/u.test(
|
|
2638
|
-
if (/^[a-z][A-Za-z0-9]*$/u.test(
|
|
2915
|
+
const z47 = raw.replace(/['′]/gu, "").trim();
|
|
2916
|
+
if (/^[A-Z]$/u.test(z47)) return { kind: "point", value: z47 };
|
|
2917
|
+
if (/^[A-Z][A-Z]$/u.test(z47)) return { kind: "line", value: z47 };
|
|
2918
|
+
if (/^[a-z][A-Za-z0-9]*$/u.test(z47)) return { kind: "line", value: z47 };
|
|
2639
2919
|
return void 0;
|
|
2640
2920
|
}
|
|
2641
2921
|
var reflectionRule = {
|
|
@@ -2647,6 +2927,28 @@ var reflectionRule = {
|
|
|
2647
2927
|
const out = [];
|
|
2648
2928
|
for (const c of ctx.clauses) {
|
|
2649
2929
|
if (!REFLECT.test(c.text) && !REFLECT_EN_PRE.test(c.text)) continue;
|
|
2930
|
+
const dmm = DISTRIB_MULTI.exec(c.text);
|
|
2931
|
+
if (dmm) {
|
|
2932
|
+
const names = splitList(dmm[1]).map(normalizePointName);
|
|
2933
|
+
const srcs = splitList(dmm[2]).map(normalizePointName);
|
|
2934
|
+
const mirrors = splitList(dmm[3]);
|
|
2935
|
+
if (names.length >= 2 && names.length === srcs.length && names.length === mirrors.length) {
|
|
2936
|
+
const through2 = mirrors.map(classifyThrough);
|
|
2937
|
+
if (through2.every((t) => !!t)) {
|
|
2938
|
+
out.push({
|
|
2939
|
+
ruleId: "reflection",
|
|
2940
|
+
clauseIds: [c.id],
|
|
2941
|
+
intents: names.map(
|
|
2942
|
+
(nm, i) => addPoint(
|
|
2943
|
+
nm,
|
|
2944
|
+
through2[i].kind === "point" ? { kind: "reflectPoint", of: srcs[i], through: through2[i].value } : { kind: "reflectLine", of: srcs[i], through: through2[i].value }
|
|
2945
|
+
)
|
|
2946
|
+
)
|
|
2947
|
+
});
|
|
2948
|
+
continue;
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2650
2952
|
const dm = DISTRIB2.exec(c.text);
|
|
2651
2953
|
if (dm) {
|
|
2652
2954
|
const [n1, n2, ofPt, raw1, raw2] = [
|
|
@@ -2668,15 +2970,31 @@ var reflectionRule = {
|
|
|
2668
2970
|
continue;
|
|
2669
2971
|
}
|
|
2670
2972
|
}
|
|
2973
|
+
NAME_BEFORE4.lastIndex = 0;
|
|
2974
|
+
const befores = [...c.text.matchAll(NAME_BEFORE4)];
|
|
2975
|
+
if (befores.length) {
|
|
2976
|
+
for (const b of befores) {
|
|
2977
|
+
const nm = normalizePointName(b[1]);
|
|
2978
|
+
const ofP = normalizePointName(b[2]);
|
|
2979
|
+
const thr = classifyThrough(b[3]);
|
|
2980
|
+
if (!thr) continue;
|
|
2981
|
+
out.push({
|
|
2982
|
+
ruleId: "reflection",
|
|
2983
|
+
clauseIds: [c.id],
|
|
2984
|
+
intents: [
|
|
2985
|
+
addPoint(
|
|
2986
|
+
nm,
|
|
2987
|
+
thr.kind === "point" ? { kind: "reflectPoint", of: ofP, through: thr.value } : { kind: "reflectLine", of: ofP, through: thr.value }
|
|
2988
|
+
)
|
|
2989
|
+
]
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
continue;
|
|
2993
|
+
}
|
|
2671
2994
|
let name;
|
|
2672
2995
|
let of;
|
|
2673
2996
|
let throughRaw;
|
|
2674
|
-
|
|
2675
|
-
if (before) {
|
|
2676
|
-
name = normalizePointName(before[1]);
|
|
2677
|
-
of = normalizePointName(before[2]);
|
|
2678
|
-
throughRaw = before[3];
|
|
2679
|
-
} else {
|
|
2997
|
+
{
|
|
2680
2998
|
const after = NAME_AFTER.exec(c.text);
|
|
2681
2999
|
if (after) {
|
|
2682
3000
|
name = extractPointName(c.text);
|
|
@@ -3179,7 +3497,7 @@ var DISTRIB_TWO = new RegExp(
|
|
|
3179
3497
|
"gu"
|
|
3180
3498
|
);
|
|
3181
3499
|
var CAT_ONE_TWO = new RegExp(
|
|
3182
|
-
`${REF}\\s+c\u1EAFt\\s+${REF}\\s*(?:,|v\xE0)\\s*${REF}\\s+(?:theo\\s+th\u1EE9\\s+t\u1EF1\\s+|l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?t\u1EA1i\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:t\u1EA1i\\s+)?([A-Z])(?![A-Z])`,
|
|
3500
|
+
`${REF}\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?c\u1EAFt\\s+${REF}\\s*(?:,|v\xE0)\\s*${REF}\\s+(?:theo\\s+th\u1EE9\\s+t\u1EF1\\s+|l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?t\u1EA1i\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:t\u1EA1i\\s+)?([A-Z])(?![A-Z])`,
|
|
3183
3501
|
"gu"
|
|
3184
3502
|
);
|
|
3185
3503
|
var CAT_TWO_ONE = new RegExp(
|
|
@@ -3194,12 +3512,22 @@ var CAT_ONE_RAY = new RegExp(
|
|
|
3194
3512
|
`${REF}\\s+c\u1EAFt\\s+(?:tia\\s+)?(?:ti\u1EBFp\\s*tuy\u1EBFn\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?([A-Z][a-z]|[a-z])(?![\\p{L}\\d])[^.;,]{0,40}?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])`,
|
|
3195
3513
|
"gu"
|
|
3196
3514
|
);
|
|
3515
|
+
var NAMED_CAT_TAI = new RegExp(
|
|
3516
|
+
`(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|tia\\s+)?\\b([a-z]{1,2}[0-9]?)(?![\\p{L}\\d])\\s+(?:k\xE9o\\s+d\xE0i\\s+)?c\u1EAFt\\s+${REF}\\s+(?:k\xE9o\\s+d\xE0i\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])`,
|
|
3517
|
+
"gu"
|
|
3518
|
+
);
|
|
3197
3519
|
var GIAO_TWO_ONE_LA = new RegExp(
|
|
3198
3520
|
`giao\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?${REF}\\s*(?:,|v\xE0)\\s*${REF}\\s+v\u1EDBi\\s+${REF}\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?l\xE0\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
3199
3521
|
"gu"
|
|
3200
3522
|
);
|
|
3523
|
+
var ZIP_MK = `(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1|t\u01B0\u01A1ng\\s+\u1EE9ng)`;
|
|
3524
|
+
var ZIP_SIDE = `(?:\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|c\u1EA1nh\\s+|tia\\s+|${DUONG_KW}\\s*th\u1EB3ng\\s+)?`;
|
|
3201
3525
|
var CAT_ZIP = new RegExp(
|
|
3202
|
-
`${REF}\\s*,\\s*${REF}\\s
|
|
3526
|
+
`${REF}\\s*,\\s*${REF}\\s+${ZIP_MK}\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?${ZIP_SIDE}${REF}\\s*,\\s*${REF}\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
3527
|
+
"gu"
|
|
3528
|
+
);
|
|
3529
|
+
var CAT_ZIP_POST = new RegExp(
|
|
3530
|
+
`${REF}\\s*,\\s*${REF}\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?${ZIP_SIDE}${REF}\\s*,\\s*${REF}\\s+${ZIP_MK}\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
3203
3531
|
"gu"
|
|
3204
3532
|
);
|
|
3205
3533
|
var NAME_BEFORE6 = /([A-Z])(?:['′]?)\s+là\s+$/u;
|
|
@@ -3214,6 +3542,10 @@ var CAP_SYMBOL = new RegExp(
|
|
|
3214
3542
|
`(${PT2})\\s*=\\s*(${PT2})\\s*(${PT2})\\s*\u2229\\s*(${PT2})\\s*(${PT2})(?!${PT2})`,
|
|
3215
3543
|
"gu"
|
|
3216
3544
|
);
|
|
3545
|
+
var CAP_SYMBOL_POST = new RegExp(
|
|
3546
|
+
`(${PT2})\\s*(${PT2})\\s*\u2229\\s*(${PT2})\\s*(${PT2})\\s*=\\s*\\{?\\s*(${PT2})\\s*\\}?`,
|
|
3547
|
+
"gu"
|
|
3548
|
+
);
|
|
3217
3549
|
var PTP = "[A-Z](?:['\u2032])?";
|
|
3218
3550
|
var REFP = `(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|tia\\s+|c\u1EA1nh\\s+)?(${PTP}\\s*${PTP})(?![A-Za-z])`;
|
|
3219
3551
|
var GIAO_PRIMED = new RegExp(
|
|
@@ -3238,6 +3570,12 @@ function makeIntent(name, ref1, ref2) {
|
|
|
3238
3570
|
if (ends.includes(name)) return null;
|
|
3239
3571
|
return addPoint(name, { kind: "intersection", of: [ref1, ref2] });
|
|
3240
3572
|
}
|
|
3573
|
+
function declaredNamedLines2(problem) {
|
|
3574
|
+
const out = /* @__PURE__ */ new Set();
|
|
3575
|
+
const RE31 = /(?:đường\s*thẳng|tiếp\s*tuyến|cát\s*tuyến)\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])/gu;
|
|
3576
|
+
for (const m of problem.matchAll(RE31)) out.add(m[1]);
|
|
3577
|
+
return out;
|
|
3578
|
+
}
|
|
3241
3579
|
function makeIntentEnds(name, e1, e2, e3, e4) {
|
|
3242
3580
|
const ends = [e1, e2, e3, e4];
|
|
3243
3581
|
if (new Set(ends).size !== 4) return null;
|
|
@@ -3255,6 +3593,7 @@ var intersectionRule = {
|
|
|
3255
3593
|
patterns: [PREFILTER8],
|
|
3256
3594
|
match(ctx) {
|
|
3257
3595
|
const out = [];
|
|
3596
|
+
const namedLines = declaredNamedLines2(ctx.problem);
|
|
3258
3597
|
for (const c of ctx.clauses) {
|
|
3259
3598
|
const seen = /* @__PURE__ */ new Set();
|
|
3260
3599
|
const emit = (name, ref1, ref2) => {
|
|
@@ -3278,6 +3617,15 @@ var intersectionRule = {
|
|
|
3278
3617
|
seen.add(name);
|
|
3279
3618
|
out.push({ ruleId: "intersection", clauseIds: [c.id], intents: [intent] });
|
|
3280
3619
|
}
|
|
3620
|
+
CAP_SYMBOL_POST.lastIndex = 0;
|
|
3621
|
+
for (const m of c.text.matchAll(CAP_SYMBOL_POST)) {
|
|
3622
|
+
const name = m[5];
|
|
3623
|
+
if (seen.has(name)) continue;
|
|
3624
|
+
const intent = makeIntentEnds(name, m[1], m[2], m[3], m[4]);
|
|
3625
|
+
if (!intent) continue;
|
|
3626
|
+
seen.add(name);
|
|
3627
|
+
out.push({ ruleId: "intersection", clauseIds: [c.id], intents: [intent] });
|
|
3628
|
+
}
|
|
3281
3629
|
CAT_ONE_TWO.lastIndex = 0;
|
|
3282
3630
|
for (const m of c.text.matchAll(CAT_ONE_TWO)) {
|
|
3283
3631
|
emit(m[4], m[1], m[2]);
|
|
@@ -3309,6 +3657,12 @@ var intersectionRule = {
|
|
|
3309
3657
|
for (const m of c.text.matchAll(CAT_ONE_RAY)) {
|
|
3310
3658
|
emitShape(m[3], m[1], m[2]);
|
|
3311
3659
|
}
|
|
3660
|
+
if (namedLines.size > 0) {
|
|
3661
|
+
NAMED_CAT_TAI.lastIndex = 0;
|
|
3662
|
+
for (const m of c.text.matchAll(NAMED_CAT_TAI)) {
|
|
3663
|
+
if (namedLines.has(m[1])) emitShape(m[3], m[2], m[1]);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3312
3666
|
GIAO_TWO_ONE_LA.lastIndex = 0;
|
|
3313
3667
|
for (const m of c.text.matchAll(GIAO_TWO_ONE_LA)) {
|
|
3314
3668
|
emit(m[4], m[1], m[3]);
|
|
@@ -3319,6 +3673,11 @@ var intersectionRule = {
|
|
|
3319
3673
|
emit(m[5], m[1], m[3]);
|
|
3320
3674
|
emit(m[6], m[2], m[4]);
|
|
3321
3675
|
}
|
|
3676
|
+
CAT_ZIP_POST.lastIndex = 0;
|
|
3677
|
+
for (const m of c.text.matchAll(CAT_ZIP_POST)) {
|
|
3678
|
+
emit(m[5], m[1], m[3]);
|
|
3679
|
+
emit(m[6], m[2], m[4]);
|
|
3680
|
+
}
|
|
3322
3681
|
GIAO_LA.lastIndex = 0;
|
|
3323
3682
|
for (const m of c.text.matchAll(GIAO_LA)) emit(m[3], m[1], m[2]);
|
|
3324
3683
|
GIAO_DIEM.lastIndex = 0;
|
|
@@ -3402,9 +3761,6 @@ var THETA_A = 2.3;
|
|
|
3402
3761
|
var THETA_B = 0.7;
|
|
3403
3762
|
var THETA_STEP = 0.9;
|
|
3404
3763
|
var PREFILTER9 = /[Dd]ây/u;
|
|
3405
|
-
function escapeRe(s) {
|
|
3406
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3407
|
-
}
|
|
3408
3764
|
function findCircleClauseId(clauses, circle) {
|
|
3409
3765
|
const frag = new RegExp(CIRCLE_KW + `|\\(\\s*${escapeRe(circle)}\\s*\\)`, "u");
|
|
3410
3766
|
for (const c of clauses) if (frag.test(c.text)) return c.id;
|
|
@@ -3417,6 +3773,29 @@ function resolveCircle2(problem) {
|
|
|
3417
3773
|
if (p) return p[1];
|
|
3418
3774
|
return void 0;
|
|
3419
3775
|
}
|
|
3776
|
+
function inscribedTriangleVertices(problem, circle) {
|
|
3777
|
+
const out = /* @__PURE__ */ new Set();
|
|
3778
|
+
const NAMED_O = "(?:\\(\\s*([^\\s;,).:]+)\\s*\\)|t\xE2m\\s+([A-Z]))?";
|
|
3779
|
+
const TRI7 = "tam\\s*gi\xE1c\\s+(?:(?:nh\u1ECDn|c\xE2n|\u0111\u1EC1u|vu\xF4ng|t\xF9)\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
3780
|
+
const RE_A = new RegExp(TRI7 + "[^.]{0,40}?n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?(?:" + CIRCLE_KW + ")?\\s*" + NAMED_O, "gu");
|
|
3781
|
+
for (const m of problem.matchAll(RE_A)) {
|
|
3782
|
+
const named = m[4] ?? m[5];
|
|
3783
|
+
if (named && named !== circle) continue;
|
|
3784
|
+
out.add(m[1]);
|
|
3785
|
+
out.add(m[2]);
|
|
3786
|
+
out.add(m[3]);
|
|
3787
|
+
}
|
|
3788
|
+
const NAMED_O_B = "(?:\\(\\s*([^\\s;,).:]+)\\s*\\)|t\xE2m\\s+([A-Z])|([A-Z]))?";
|
|
3789
|
+
const RE_B = new RegExp(CIRCLE_KW + "\\s*" + NAMED_O_B + "\\s*ngo\u1EA1i\\s*ti\u1EBFp\\s+" + TRI7, "gu");
|
|
3790
|
+
for (const m of problem.matchAll(RE_B)) {
|
|
3791
|
+
const named = m[1] ?? m[2] ?? m[3];
|
|
3792
|
+
if (named && named !== circle) continue;
|
|
3793
|
+
out.add(m[4]);
|
|
3794
|
+
out.add(m[5]);
|
|
3795
|
+
out.add(m[6]);
|
|
3796
|
+
}
|
|
3797
|
+
return out;
|
|
3798
|
+
}
|
|
3420
3799
|
var chordRule = {
|
|
3421
3800
|
id: "chord",
|
|
3422
3801
|
// Dưới circleRadius (75) — nếu circle O có bán kính cụ thể, định nghĩa đó thắng.
|
|
@@ -3430,6 +3809,7 @@ var chordRule = {
|
|
|
3430
3809
|
match(ctx) {
|
|
3431
3810
|
const circle = resolveCircle2(ctx.problem);
|
|
3432
3811
|
if (!circle) return [];
|
|
3812
|
+
const inscribedVerts = inscribedTriangleVertices(ctx.problem, circle);
|
|
3433
3813
|
const chords = [];
|
|
3434
3814
|
const seen = /* @__PURE__ */ new Set();
|
|
3435
3815
|
for (const c of ctx.clauses) {
|
|
@@ -3464,15 +3844,11 @@ var chordRule = {
|
|
|
3464
3844
|
});
|
|
3465
3845
|
chords.forEach((ch, k) => {
|
|
3466
3846
|
const offset = k * THETA_STEP;
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
addPoint(ch.b, { kind: "onCircle", circle, theta: THETA_B + offset }),
|
|
3473
|
-
connect(ch.a, ch.b, "segment")
|
|
3474
|
-
]
|
|
3475
|
-
});
|
|
3847
|
+
const intents = [];
|
|
3848
|
+
if (!inscribedVerts.has(ch.a)) intents.push(addPoint(ch.a, { kind: "onCircle", circle, theta: THETA_A + offset }));
|
|
3849
|
+
if (!inscribedVerts.has(ch.b)) intents.push(addPoint(ch.b, { kind: "onCircle", circle, theta: THETA_B + offset }));
|
|
3850
|
+
intents.push(connect(ch.a, ch.b, "segment"));
|
|
3851
|
+
out.push({ ruleId: "chord", clauseIds: [ch.clauseId], intents });
|
|
3476
3852
|
});
|
|
3477
3853
|
return out;
|
|
3478
3854
|
}
|
|
@@ -3482,9 +3858,13 @@ var chordRule = {
|
|
|
3482
3858
|
var PREFILTER10 = /tiếp\s*xúc|tiếp\s*điểm/u;
|
|
3483
3859
|
var TANGENT_NAMED_FWD = /tiếp\s*điểm\s+(?:của\s+)?(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+với\s+(?:các\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?là\s+(?:các\s+)?(?:điểm\s+)?([A-Z](?:\s*(?:,|và)\s*[A-Z])*)(?![A-Za-z])/iu;
|
|
3484
3860
|
var TANGENT_NAMED_REV = /tiếp\s*điểm\s+(?:của\s+)?(?:các\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+với\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?là\s+(?:các\s+)?(?:điểm\s+)?([A-Z](?:\s*(?:,|và)\s*[A-Z])*)(?![A-Za-z])/iu;
|
|
3861
|
+
var TANGENT_NAMED_BEFORE = /([A-Z](?:\s*(?:,|và)\s*[A-Z])*)\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?là\s+(?:các\s+)?(?:điểm\s+)?tiếp\s*điểm\s+(?:của\s+)?(?:các\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+với\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)/iu;
|
|
3485
3862
|
var INCIRCLE_IN_CLAUSE = /đường\s*tròn\s*(?:\(\s*([A-Z])\s*\)|tâm\s+([A-Z]))?\s*nội\s*tiếp\s+tam\s*giác(?:\s+([A-Z])([A-Z])([A-Z])(?![A-Z]))?/iu;
|
|
3486
3863
|
var SIDE_POINT_LIST = /tiếp\s*xúc\s+(?:với\s+)?(?:các\s+|ba\s+|hai\s+|bốn\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+(?:lần\s*lượt\s+|tương\s*ứng\s+)?tại\s+(?:các\s+|ba\s+|hai\s+|bốn\s+)?(?:điểm\s+)?([A-Z]'?(?:\s*(?:,|và)\s*[A-Z]'?)*)(?![A-Za-z])/iu;
|
|
3487
3864
|
var REVERSED_SIDE_POINT = /(?:các\s+)?(?:cạnh|đoạn)\s+([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+tiếp\s*xúc\s+(?:với\s+)?(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:lần\s*lượt\s+|tương\s*ứng\s+)?tại\s+(?:các\s+)?(?:điểm\s+)?([A-Z]'?(?:\s*(?:,|và)\s*[A-Z]'?)*)(?![A-Za-z])/iu;
|
|
3865
|
+
var INTERLEAVED_CIRCLE = /(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*[,;]?\s*[Rr]?\s*\)\s*tiếp\s*xúc\s+(?:với\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}\s+tại\s+[A-Z]'?(?:\s*,\s*[A-Z]{2}\s+tại\s+[A-Z]'?)+)(?![A-Za-z])/iu;
|
|
3866
|
+
var PAIR_RE = /([A-Z]{2})\s+tại\s+([A-Z]'?)/giu;
|
|
3867
|
+
var REVERSED_BARE = /(?<![A-Za-z])([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+tiếp\s*xúc\s+(?:với\s+)?(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:lần\s*lượt\s+|tương\s*ứng\s+)?tại\s+(?:các\s+)?(?:điểm\s+)?([A-Z]'?(?:\s*(?:,|và)\s*[A-Z]'?)*)(?![A-Za-z])/iu;
|
|
3488
3868
|
function splitCsv(blob) {
|
|
3489
3869
|
return blob.split(/,|và/iu).map((s) => s.trim()).filter(Boolean);
|
|
3490
3870
|
}
|
|
@@ -3522,6 +3902,22 @@ var incircleTangencyRule = {
|
|
|
3522
3902
|
match(ctx) {
|
|
3523
3903
|
const out = [];
|
|
3524
3904
|
for (const chunk of logicalChunks(ctx.problem, ctx.clauses)) {
|
|
3905
|
+
const tb = TANGENT_NAMED_BEFORE.exec(chunk.text);
|
|
3906
|
+
if (tb) {
|
|
3907
|
+
const names = splitCsv(tb[1]);
|
|
3908
|
+
const sides2 = splitCsv(tb[2]);
|
|
3909
|
+
const circle2 = tb[3];
|
|
3910
|
+
if (sides2.length >= 1 && sides2.length === names.length) {
|
|
3911
|
+
const verts2 = [...new Set(sides2.join("").split(""))];
|
|
3912
|
+
const intents2 = [];
|
|
3913
|
+
if (verts2.length === 3) intents2.push(drawCircle(circle2, "inscribedIn", { triangle: verts2 }));
|
|
3914
|
+
for (let i = 0; i < names.length; i++) {
|
|
3915
|
+
intents2.push(addPoint(names[i], { kind: "tangencyPoint", circle: circle2, onLine: sides2[i] }));
|
|
3916
|
+
}
|
|
3917
|
+
out.push({ ruleId: "incircleTangency", clauseIds: chunk.clauseIds, intents: intents2 });
|
|
3918
|
+
continue;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3525
3921
|
const tn = TANGENT_NAMED_FWD.exec(chunk.text) ?? TANGENT_NAMED_REV.exec(chunk.text);
|
|
3526
3922
|
if (tn) {
|
|
3527
3923
|
const fwd = TANGENT_NAMED_FWD.test(chunk.text);
|
|
@@ -3539,6 +3935,20 @@ var incircleTangencyRule = {
|
|
|
3539
3935
|
continue;
|
|
3540
3936
|
}
|
|
3541
3937
|
}
|
|
3938
|
+
const il = INTERLEAVED_CIRCLE.exec(chunk.text);
|
|
3939
|
+
if (il) {
|
|
3940
|
+
const circle2 = il[1];
|
|
3941
|
+
const pairs = [];
|
|
3942
|
+
PAIR_RE.lastIndex = 0;
|
|
3943
|
+
for (const pm of il[2].matchAll(PAIR_RE)) pairs.push([pm[1], pm[2]]);
|
|
3944
|
+
if (pairs.length >= 1) {
|
|
3945
|
+
const intents2 = pairs.map(
|
|
3946
|
+
([side, name]) => addPoint(name, { kind: "tangencyPoint", circle: circle2, onLine: side })
|
|
3947
|
+
);
|
|
3948
|
+
out.push({ ruleId: "incircleTangency", clauseIds: chunk.clauseIds, intents: intents2 });
|
|
3949
|
+
continue;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3542
3952
|
const rev = REVERSED_SIDE_POINT.exec(chunk.text);
|
|
3543
3953
|
if (rev) {
|
|
3544
3954
|
const sides2 = splitCsv(rev[1]);
|
|
@@ -3557,6 +3967,19 @@ var incircleTangencyRule = {
|
|
|
3557
3967
|
continue;
|
|
3558
3968
|
}
|
|
3559
3969
|
}
|
|
3970
|
+
const rb = REVERSED_BARE.exec(chunk.text);
|
|
3971
|
+
if (rb) {
|
|
3972
|
+
const sides2 = splitCsv(rb[1]);
|
|
3973
|
+
const circle2 = rb[2];
|
|
3974
|
+
const names = splitCsv(rb[3]);
|
|
3975
|
+
if (sides2.length >= 1 && sides2.length === names.length) {
|
|
3976
|
+
const intents2 = sides2.map(
|
|
3977
|
+
(side, i) => addPoint(names[i], { kind: "tangencyPoint", circle: circle2, onLine: side })
|
|
3978
|
+
);
|
|
3979
|
+
out.push({ ruleId: "incircleTangency", clauseIds: chunk.clauseIds, intents: intents2 });
|
|
3980
|
+
continue;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3560
3983
|
let circle = parseIncircleName(chunk.text);
|
|
3561
3984
|
if (!circle) {
|
|
3562
3985
|
const fm = FORWARD_CIRCLE.exec(chunk.text);
|
|
@@ -3671,16 +4094,17 @@ var altitudeDiameterCirclesRule = {
|
|
|
3671
4094
|
|
|
3672
4095
|
// src/stamps/geometry-2d/ai/rules/diameterCircleCutsSides.ts
|
|
3673
4096
|
var PATTERN = new RegExp(
|
|
3674
|
-
// Chỉ-báo đường tròn: "đường tròn (tâm X|
|
|
3675
|
-
// đường kính BC"
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
//
|
|
3679
|
-
"
|
|
4097
|
+
// Chỉ-báo đường tròn: "đường tròn ((X)|tâm X|X-trần)?" HOẶC "(X)" trần (httcd:2
|
|
4098
|
+
// "(O) đường kính BC"). "X-trần" = tâm 1-HOA không ngoặc ("đường tròn O đường
|
|
4099
|
+
// kính BC" — vxhung:14). Bắt buộc có 1 trong 2 nhánh.
|
|
4100
|
+
"(?:" + CIRCLE_KW + "\\s+(?:(?:\\(\\s*[A-Z]\\s*\\)|t\xE2m\\s+[A-Z]|[A-Z])\\s+)?|\\(\\s*[A-Z]\\s*\\)\\s+)" + DUONG_KW + // optional phẩy + đại từ "nó" giữa diameter và "cắt" (vao10:81 "AH, cắt…").
|
|
4101
|
+
// "theo thứ tự" = "lần lượt". Anchor điểm = "tại"; "(các)? (điểm)?" trước danh
|
|
4102
|
+
// sách tên ("tại các điểm D và E" — vxhung:6).
|
|
4103
|
+
"\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])\\s*,?\\s*(?:n\xF3\\s+)?\\s*c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?t\u1EA1i\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))*)",
|
|
3680
4104
|
"gu"
|
|
3681
4105
|
);
|
|
3682
4106
|
var PATTERN_O = new RegExp(
|
|
3683
|
-
"(?:" + CIRCLE_KW + "\\s+(?:(?:\\(\\s*[A-Z]\\s*\\)|t\xE2m\\s+[A-Z])\\s+)?|\\(\\s*[A-Z]\\s*\\)\\s+)" + DUONG_KW + "\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])\\s*,?\\s*(?:n\xF3\\s+)?\\s*c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?\u1EDF\\s+([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))+)",
|
|
4107
|
+
"(?:" + CIRCLE_KW + "\\s+(?:(?:\\(\\s*[A-Z]\\s*\\)|t\xE2m\\s+[A-Z]|[A-Z])\\s+)?|\\(\\s*[A-Z]\\s*\\)\\s+)" + DUONG_KW + "\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])\\s*,?\\s*(?:n\xF3\\s+)?\\s*c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?\u1EDF\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))+)",
|
|
3684
4108
|
"gu"
|
|
3685
4109
|
);
|
|
3686
4110
|
var PATTERN_INTERLEAVED = new RegExp(
|
|
@@ -3779,6 +4203,10 @@ var GIVEN_BARE = new RegExp(
|
|
|
3779
4203
|
String.raw`[Cc]ho\s+\(\s*(${CTR})\s*\)\s*,?\s*(?:có\s+)?` + DUONG_KW + String.raw`\s*kính\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
3780
4204
|
"gu"
|
|
3781
4205
|
);
|
|
4206
|
+
var CHO_BARE = new RegExp(
|
|
4207
|
+
String.raw`[Cc]ho\s+(${CTR})(?![A-Z\p{L}])\s+` + DUONG_KW + String.raw`\s*kính\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
4208
|
+
"gu"
|
|
4209
|
+
);
|
|
3782
4210
|
var BARE_PAREN = new RegExp(
|
|
3783
4211
|
String.raw`\(\s*(${CTR})\s*\)\s*(?:có\s+)?` + DUONG_KW + String.raw`\s*kính\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
3784
4212
|
"gu"
|
|
@@ -3869,6 +4297,22 @@ var circleDiameterRule = {
|
|
|
3869
4297
|
intents: intentsFor(p)
|
|
3870
4298
|
});
|
|
3871
4299
|
}
|
|
4300
|
+
CHO_BARE.lastIndex = 0;
|
|
4301
|
+
let cm;
|
|
4302
|
+
while ((cm = CHO_BARE.exec(ctx.problem)) !== null) {
|
|
4303
|
+
const p = { center: cm[1], a: cm[2], b: cm[3] };
|
|
4304
|
+
if (!p.center || p.center === p.a || p.center === p.b || p.a === p.b) continue;
|
|
4305
|
+
const key = `${p.center}|${p.a}${p.b}`;
|
|
4306
|
+
if (emitted.has(key)) continue;
|
|
4307
|
+
emitted.add(key);
|
|
4308
|
+
const matched = cm[0];
|
|
4309
|
+
const claim = ctx.clauses.filter((c) => c.text.includes(matched.replace(/^[Cc]ho\s+/u, ""))).map((c) => c.id);
|
|
4310
|
+
out.push({
|
|
4311
|
+
ruleId: "circle-diameter",
|
|
4312
|
+
clauseIds: claim.length > 0 ? claim : [ctx.clauses[0]?.id ?? 0],
|
|
4313
|
+
intents: intentsFor(p)
|
|
4314
|
+
});
|
|
4315
|
+
}
|
|
3872
4316
|
BARE_PAREN.lastIndex = 0;
|
|
3873
4317
|
let bm;
|
|
3874
4318
|
while ((bm = BARE_PAREN.exec(ctx.problem)) !== null) {
|
|
@@ -3879,7 +4323,7 @@ var circleDiameterRule = {
|
|
|
3879
4323
|
const key = `${center}|${a}${b}`;
|
|
3880
4324
|
if (emitted.has(key)) continue;
|
|
3881
4325
|
const before = ctx.problem.slice(0, bm.index);
|
|
3882
|
-
const known = (p) => new RegExp(`(?<![a-z])${p}(?![a-z])`, "u").test(before);
|
|
4326
|
+
const known = (p) => new RegExp(`(?<![a-z])${escapeRe(p)}(?![a-z])`, "u").test(before);
|
|
3883
4327
|
if (!known(a) || !known(b)) continue;
|
|
3884
4328
|
emitted.add(key);
|
|
3885
4329
|
const matched = bm[0];
|
|
@@ -3901,7 +4345,7 @@ var circleDiameterRule = {
|
|
|
3901
4345
|
var PREFILTER12 = /(?:nằm|thuộc|lấy\s+điểm|trên\s+(?:nửa\s+)?(?:đường\s*tròn|cung))/iu;
|
|
3902
4346
|
var NAMED_CIRCLE = /(?:nửa\s+)?đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)?/u;
|
|
3903
4347
|
var COMPACT_CIRCLE = /\(\s*([A-Z])\s*[;,]\s*[Rr]\s*\)/u;
|
|
3904
|
-
var ON_SUFFIX = "(?:cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?[A-Z]{2}(?:\\s*(?:nh\u1ECF|l\u1EDBn))?|(?:n\u1EEDa\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n
|
|
4348
|
+
var ON_SUFFIX = "(?:cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?[A-Z]{2}(?:\\s*(?:nh\u1ECF|l\u1EDBn))?|(?:n\u1EEDa\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n|(?:\u0111\u01B0\u1EDDng\\s+)?\\(\\s*[A-Z]\\s*\\))";
|
|
3905
4349
|
var POINT_ON = new RegExp(
|
|
3906
4350
|
// "nằm" optional → bắt "M (bất kì)? trên (nửa)? đường tròn" / "điểm A trên (O)"
|
|
3907
4351
|
// (chỉ "trên" trần, không "nằm trên"). ON_SUFFIX neo circle/cung nên "trên"
|
|
@@ -3922,18 +4366,21 @@ var TAKE_ON_CUNG = /[Tt]rên\s+cung\s+(?:nhỏ\s+|lớn\s+)?[A-Z]{2}(?:\s*(?:nh
|
|
|
3922
4366
|
var TAKE_ON_REV = /[Tt]rên\s+(?:nửa\s+)?(?:đường\s*tròn|cung)\s*\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)[^.]{0,16}?lấy\s+điểm\s+([A-Z])(?![A-Z])/u;
|
|
3923
4367
|
var TWO_ON = /(?:lấy\s+)?hai\s*điểm\s+([A-Z])(?![A-Za-z])\s+và\s+([A-Z])(?![A-Za-z])(?:\s+bất\s*k[ìiyỳ])?\s+thuộc\s+(?:nửa\s+)?(?:đường\s*tròn|cung)/u;
|
|
3924
4368
|
var TWO_ON_COMMA = /(?:[CcNn]ác\s+|[Nn]hững\s+)?điểm\s+([A-Z])(?![A-Za-z])\s*,\s*([A-Z])(?![A-Za-z])\s+thuộc\s+(?:nửa\s+)?(?:đường\s*tròn|cung)/u;
|
|
3925
|
-
var TWO_ON_NAMES = /([A-Z])(?![A-Za-z])\s
|
|
4369
|
+
var TWO_ON_NAMES = /([A-Z])(?![A-Za-z])\s*(?:,|và)\s*([A-Z])(?![A-Za-z])\s+là\s+(?:hai|các)\s+điểm\s+(?:thuộc|nằm\s+trên|trên)\s+(?:(?:nửa\s+)?(?:đường\s*tròn|cung)|\(\s*[A-Z]\s*\))/u;
|
|
3926
4370
|
var TWO_ON_CUNG_REV = /[Tt]rên\s+cung\s+(?:nhỏ\s+|lớn\s+)?[A-Z]{2}(?:\s*(?:nhỏ|lớn))?\s+lấy\s+hai\s+điểm\s+([A-Z])(?![A-Za-z])\s*(?:,|và)\s*([A-Z])(?![A-Za-z])/u;
|
|
3927
4371
|
var BARE_CIRCLE = /\(\s*([A-Z])\s*\)/u;
|
|
3928
4372
|
var UNNAMED_DIAMETER = /(?:nửa\s+)?đường\s*tròn\s+đường\s*kính\s+([A-Z])([A-Z])(?![A-Z])/u;
|
|
4373
|
+
var BARE_CENTER_DIAMETER = /(?<![\p{L}\d(])([A-Z])\s+đường\s*kính/u;
|
|
3929
4374
|
function resolveCircle3(problem) {
|
|
3930
4375
|
const m = NAMED_CIRCLE.exec(problem) ?? COMPACT_CIRCLE.exec(problem) ?? BARE_CIRCLE.exec(problem);
|
|
3931
4376
|
if (!m) {
|
|
3932
4377
|
const dm = UNNAMED_DIAMETER.exec(problem);
|
|
3933
|
-
|
|
4378
|
+
if (dm) return `k${dm[1]}${dm[2]}`;
|
|
4379
|
+
const bd = BARE_CENTER_DIAMETER.exec(problem);
|
|
4380
|
+
return bd ? `${bd[1]}_c` : void 0;
|
|
3934
4381
|
}
|
|
3935
4382
|
const center = m[1];
|
|
3936
|
-
const c = center
|
|
4383
|
+
const c = escapeRe(center);
|
|
3937
4384
|
const diameterOfThisCenter = new RegExp(`(?:\\(\\s*${c}\\s*[;,)]|t\xE2m\\s+${c}(?![A-Za-z]))[^.]{0,25}?\u0111\u01B0\u1EDDng\\s*k\xEDnh`, "u").test(problem) || new RegExp(`\u0111\u01B0\u1EDDng\\s*k\xEDnh[^.]{0,25}?\\(\\s*${c}\\s*\\)`, "u").test(problem);
|
|
3938
4385
|
return diameterOfThisCenter ? `${center}_c` : center;
|
|
3939
4386
|
}
|
|
@@ -3978,10 +4425,12 @@ var onCirclePointRule = {
|
|
|
3978
4425
|
if (!m) continue;
|
|
3979
4426
|
const name = m[1];
|
|
3980
4427
|
if (name.length !== 1) continue;
|
|
4428
|
+
const major = /cung\s+lớn|lớn\s+[A-Z]{2}/u.test(c.text);
|
|
4429
|
+
const ptTheta = major ? -1.6 : theta;
|
|
3981
4430
|
out.push({
|
|
3982
4431
|
ruleId: "on-circle-point",
|
|
3983
4432
|
clauseIds: [c.id],
|
|
3984
|
-
intents: [addPoint(name, { kind: "onCircle", circle, theta })]
|
|
4433
|
+
intents: [addPoint(name, { kind: "onCircle", circle, theta: ptTheta })]
|
|
3985
4434
|
});
|
|
3986
4435
|
theta += 0.8;
|
|
3987
4436
|
}
|
|
@@ -3990,10 +4439,10 @@ var onCirclePointRule = {
|
|
|
3990
4439
|
};
|
|
3991
4440
|
|
|
3992
4441
|
// src/stamps/geometry-2d/ai/rules/tangentAt.ts
|
|
3993
|
-
var PREFILTER13 = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\([^)]*\)\s*)?tại/u;
|
|
4442
|
+
var PREFILTER13 = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\([^)]*\)\s*)?(?:tại|qua)/u;
|
|
3994
4443
|
var CIRCLE_REF = /đường\s*tròn\s*\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)|\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)/u;
|
|
3995
4444
|
var DISTRIB3 = /[Tt]iếp\s*tuyến\s+tại\s+([A-Z])\s+cắt\s+tiếp\s*tuyến\s+tại\s+([A-Z])\s+và\s+([A-Z])[^.]{0,60}?lần\s*lượt\s+tại\s+([A-Z])\s+và\s+([A-Z])/u;
|
|
3996
|
-
var TWO_MEET = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\(\s*[A-Z]['′]?\s*\)\s*)?tại\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])[^.]{0,30}?cắt\s+nhau\s+(?:ở|tại)\s+([A-Z])(?![A-Z])/u;
|
|
4445
|
+
var TWO_MEET = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\(\s*[A-Z]['′]?\s*\)\s*)?(?:tại|qua)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])[^.]{0,30}?cắt\s+nhau\s+(?:ở|tại)\s+([A-Z])(?![A-Z])/u;
|
|
3997
4446
|
var SINGLE2 = /[Tt]iếp\s*tuyến\s+tại\s+(?:điểm\s+)?([A-Z])(?:\s+với|\s+của|\s+đường|\s*$)/u;
|
|
3998
4447
|
function circleName(problem) {
|
|
3999
4448
|
const m = CIRCLE_REF.exec(problem);
|
|
@@ -4064,17 +4513,37 @@ var tangentAtRule = {
|
|
|
4064
4513
|
};
|
|
4065
4514
|
|
|
4066
4515
|
// src/stamps/geometry-2d/ai/rules/onSegmentPoint.ts
|
|
4067
|
-
var PREFILTER14 = /(?:thuộc\s+(?:các\s+|hai\s+|ba\s+)?(?:cạnh|đoạn|đáy|tia|bán\s*kính|đường\s*kính|dây|[A-
|
|
4516
|
+
var PREFILTER14 = /(?:thuộc\s+(?:các\s+|hai\s+|ba\s+)?(?:cạnh|đoạn|đáy|tia|bán\s*kính|đường\s*kính|dây|[A-Za-z]{1,2}[0-9]?(?![\p{L}\d]))|[Tt]rên\s+(?:các\s+|hai\s+|ba\s+)?(?:cạnh|đoạn|đáy|tia|bán\s*kính|đường\s*kính|đường\s*thẳng|dây|[A-Za-z]{1,2}[0-9]?(?![\p{L}\d]))|(?:di\s*chuyển|di\s*động)\s+trên|nằm\s+giữa)/u;
|
|
4068
4517
|
var ON_NAMED_LINE = new RegExp(
|
|
4069
4518
|
String.raw`[Tt]rên\s+đường\s*thẳng\s+([a-z][0-9]?)(?!\p{L})[^.]{0,20}?(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])`,
|
|
4070
4519
|
"gu"
|
|
4071
4520
|
);
|
|
4521
|
+
var ON_NAMED_LINE_BARE = new RegExp(
|
|
4522
|
+
String.raw`[Tt]rên\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])\s+(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])`,
|
|
4523
|
+
"gu"
|
|
4524
|
+
);
|
|
4525
|
+
var POINT_THUOC_NAMED_BARE = new RegExp(
|
|
4526
|
+
String.raw`(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])\s+(?:là\s+(?:một\s+)?điểm\s+(?:bất\s*k[iìyỳ]\s+)?)?thuộc\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])`,
|
|
4527
|
+
"gu"
|
|
4528
|
+
);
|
|
4072
4529
|
var SEG = "([A-Z]{2})(?![A-Z])";
|
|
4073
4530
|
var POINT = "([A-Z](?:['\u2032])?)(?![A-Z])";
|
|
4531
|
+
var TAKE_POINT_ON_SEG = new RegExp(
|
|
4532
|
+
String.raw`[Ll]ấy\s+(?:điểm\s+)?${POINT}\s+(?:bất\s*k[iìyỳ]\s+)?(?:trên|thuộc)\s+(?:cạnh\s+|đáy\s+|đoạn(?:\s+thẳng)?\s+|tia\s+|bán\s*kính\s+|đường\s*kính\s+)?${SEG}`,
|
|
4533
|
+
"gu"
|
|
4534
|
+
);
|
|
4535
|
+
var TAKE_TWO_ON_TWO_SEG = new RegExp(
|
|
4536
|
+
String.raw`[Ll]ấy\s+(?:điểm\s+)?([A-Z](?:['′])?)\s*,\s*([A-Z](?:['′])?)(?![A-Z])\s+(?:bất\s*k[iìyỳ]\s+)?(?:trên|thuộc)\s+(?:các\s+)?(?:cạnh\s+|đoạn\s+|tia\s+)?([A-Z]{2})\s*(?:,|và)\s*([A-Z]{2})(?![A-Z])`,
|
|
4537
|
+
"gu"
|
|
4538
|
+
);
|
|
4074
4539
|
var ON_SEG_THEN_POINT = new RegExp(
|
|
4075
4540
|
String.raw`[Tt]rên\s+(?:cạnh|đáy|đoạn(?:\s+thẳng)?|bán\s*kính|đường\s*kính|dây\s*(?:cung)?)\s+${SEG}[^.]{0,30}?(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?${POINT}`,
|
|
4076
4541
|
"gu"
|
|
4077
4542
|
);
|
|
4543
|
+
var ON_BARE_SEG_THEN_POINT = new RegExp(
|
|
4544
|
+
String.raw`[Tt]rên\s+${SEG}[^.]{0,14}?\blấy\s+(?:một\s+)?(?:điểm\s+)?${POINT}`,
|
|
4545
|
+
"gu"
|
|
4546
|
+
);
|
|
4078
4547
|
var ON_SEG_MULTI_POINT = new RegExp(
|
|
4079
4548
|
String.raw`[Tt]rên\s+(?:cạnh\s+|đáy\s+|đoạn(?:\s+thẳng)?\s+)?${SEG}\s+(?:lấy\s+)?(?:các\s+)?(?:điểm\s+)?((?:[A-Z](?:['′])?\s*,\s*)+[A-Z](?:['′])?)(?![A-Z])`,
|
|
4080
4549
|
"gu"
|
|
@@ -4108,13 +4577,17 @@ var ZIP_SEG = new RegExp(
|
|
|
4108
4577
|
"gu"
|
|
4109
4578
|
);
|
|
4110
4579
|
var SEGS_THEN_POINTS = new RegExp(
|
|
4111
|
-
String.raw`[Tt]rên\s+(?:các\s+)?(?:cạnh\s+|đoạn(?:\s+thẳng)?\s+|đáy\s+|bán\s*kính\s+)?((?:[A-Z]{2}\s*,\s*)+[A-Z]{2})(?![A-Z])(?:\s+của\s+(?:tam\s*giác\s+|tứ\s*giác\s+)?[A-Z]{2,4})?\s+(?:(?:theo\s+)?thứ\s+tự\s+|lần\s*lượt\s+)?lấy\s+(?:các\s+)
|
|
4580
|
+
String.raw`[Tt]rên\s+(?:các\s+)?(?:cạnh\s+|đoạn(?:\s+thẳng)?\s+|đáy\s+|bán\s*kính\s+)?((?:[A-Z]{2}\s*,\s*)+[A-Z]{2})(?![A-Z])(?:\s+của\s+(?:tam\s*giác\s+|tứ\s*giác\s+)?[A-Z]{2,4})?\s+(?:(?:theo\s+)?thứ\s+tự\s+|lần\s*lượt\s+)?lấy\s+(?:các\s+)?(?:điểm\s+)?((?:[A-Z]\s*,\s*)+[A-Z])(?![A-Z])`,
|
|
4112
4581
|
"gu"
|
|
4113
4582
|
);
|
|
4114
4583
|
var TWO_SEG_LAY = new RegExp(
|
|
4115
4584
|
String.raw`[Tt]rên\s+(?:cạnh|đáy|đoạn(?:\s+thẳng)?)\s+${SEG}\s+(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?${POINT}\s+và\s+(?:trên\s+)?(?:cạnh|đáy|đoạn(?:\s+thẳng)?)\s+${SEG}\s+(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?${POINT}`,
|
|
4116
4585
|
"gu"
|
|
4117
4586
|
);
|
|
4587
|
+
var TWO_ON_NAMED_RAY = new RegExp(
|
|
4588
|
+
String.raw`[Tt]rên\s+(?:tia\s+)?([A-Z][xyzt])(?!\p{L})\s+(?:lấy\s+)?(?:hai\s+|2\s+)?(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])\s*(?:,|và)\s*([A-Z](?:['′])?)(?![A-Z])`,
|
|
4589
|
+
"gu"
|
|
4590
|
+
);
|
|
4118
4591
|
function normalizePoint(name) {
|
|
4119
4592
|
return name.replace("\u2032", "'");
|
|
4120
4593
|
}
|
|
@@ -4124,6 +4597,18 @@ function validOnSegment(name, segment) {
|
|
|
4124
4597
|
function hasMetricConstraint(text) {
|
|
4125
4598
|
return /sao\s+cho[^.]{0,40}(?:=|>|<)/u.test(text);
|
|
4126
4599
|
}
|
|
4600
|
+
function declaredNamedLines3(problem) {
|
|
4601
|
+
const out = /* @__PURE__ */ new Set();
|
|
4602
|
+
const RE31 = /(?:đường\s*thẳng|tiếp\s*tuyến|cát\s*tuyến)\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])/gu;
|
|
4603
|
+
for (const m of problem.matchAll(RE31)) out.add(m[1]);
|
|
4604
|
+
return out;
|
|
4605
|
+
}
|
|
4606
|
+
function declaredNamedRays(problem) {
|
|
4607
|
+
const out = /* @__PURE__ */ new Set();
|
|
4608
|
+
const RE31 = /tiếp\s*tuyến\s+([A-Z][xyzt])(?![A-Za-z])/gu;
|
|
4609
|
+
for (const m of problem.matchAll(RE31)) out.add(m[1]);
|
|
4610
|
+
return out;
|
|
4611
|
+
}
|
|
4127
4612
|
var onSegmentPointRule = {
|
|
4128
4613
|
id: "on-segment-point",
|
|
4129
4614
|
priority: 62,
|
|
@@ -4131,8 +4616,73 @@ var onSegmentPointRule = {
|
|
|
4131
4616
|
patterns: [PREFILTER14],
|
|
4132
4617
|
match(ctx) {
|
|
4133
4618
|
const out = [];
|
|
4619
|
+
const namedLines = declaredNamedLines3(ctx.problem);
|
|
4620
|
+
const namedRays = declaredNamedRays(ctx.problem);
|
|
4134
4621
|
for (const c of ctx.clauses) {
|
|
4135
4622
|
const intents = [];
|
|
4623
|
+
ON_NAMED_LINE.lastIndex = 0;
|
|
4624
|
+
for (const m of c.text.matchAll(ON_NAMED_LINE)) {
|
|
4625
|
+
const line = m[1];
|
|
4626
|
+
const name = normalizePoint(m[2]);
|
|
4627
|
+
if (/^[A-Z]['′]?$/u.test(name)) intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4628
|
+
}
|
|
4629
|
+
if (namedLines.size > 0) {
|
|
4630
|
+
ON_NAMED_LINE_BARE.lastIndex = 0;
|
|
4631
|
+
for (const m of c.text.matchAll(ON_NAMED_LINE_BARE)) {
|
|
4632
|
+
const line = m[1];
|
|
4633
|
+
const name = normalizePoint(m[2]);
|
|
4634
|
+
if (namedLines.has(line) && /^[A-Z]['′]?$/u.test(name)) {
|
|
4635
|
+
intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
POINT_THUOC_NAMED_BARE.lastIndex = 0;
|
|
4639
|
+
for (const m of c.text.matchAll(POINT_THUOC_NAMED_BARE)) {
|
|
4640
|
+
const name = normalizePoint(m[1]);
|
|
4641
|
+
const line = m[2];
|
|
4642
|
+
if (namedLines.has(line) && /^[A-Z]['′]?$/u.test(name)) {
|
|
4643
|
+
intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
if (namedRays.size > 0) {
|
|
4648
|
+
TWO_ON_NAMED_RAY.lastIndex = 0;
|
|
4649
|
+
for (const m of c.text.matchAll(TWO_ON_NAMED_RAY)) {
|
|
4650
|
+
const ray = m[1];
|
|
4651
|
+
if (!namedRays.has(ray)) continue;
|
|
4652
|
+
for (const raw of [m[2], m[3]]) {
|
|
4653
|
+
const name = normalizePoint(raw);
|
|
4654
|
+
if (/^[A-Z]['′]?$/u.test(name) && name[0] !== ray[0]) {
|
|
4655
|
+
intents.push(addPoint(name, { kind: "onSegment", of: ray }));
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
if (!/tia\s+đối/u.test(c.text)) {
|
|
4661
|
+
TAKE_TWO_ON_TWO_SEG.lastIndex = 0;
|
|
4662
|
+
for (const m of c.text.matchAll(TAKE_TWO_ON_TWO_SEG)) {
|
|
4663
|
+
const [n1, n2, s1, s2] = [normalizePoint(m[1]), normalizePoint(m[2]), m[3], m[4]];
|
|
4664
|
+
if (validOnSegment(n1, s1) && validOnSegment(n2, s2)) {
|
|
4665
|
+
intents.push(addPoint(n1, { kind: "onSegment", of: s1 }));
|
|
4666
|
+
intents.push(addPoint(n2, { kind: "onSegment", of: s2 }));
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
TAKE_POINT_ON_SEG.lastIndex = 0;
|
|
4670
|
+
for (const m of c.text.matchAll(TAKE_POINT_ON_SEG)) {
|
|
4671
|
+
const name = normalizePoint(m[1]);
|
|
4672
|
+
const segment = m[2];
|
|
4673
|
+
if (validOnSegment(name, segment)) {
|
|
4674
|
+
intents.push(addPoint(name, { kind: "onSegment", of: segment }));
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
ON_BARE_SEG_THEN_POINT.lastIndex = 0;
|
|
4678
|
+
for (const m of c.text.matchAll(ON_BARE_SEG_THEN_POINT)) {
|
|
4679
|
+
const segment = m[1];
|
|
4680
|
+
const name = normalizePoint(m[2]);
|
|
4681
|
+
if (validOnSegment(name, segment)) {
|
|
4682
|
+
intents.push(addPoint(name, { kind: "onSegment", of: segment }));
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4136
4686
|
SEGS_THEN_POINTS.lastIndex = 0;
|
|
4137
4687
|
for (const m of c.text.matchAll(SEGS_THEN_POINTS)) {
|
|
4138
4688
|
const segs = m[1].split(",").map((s) => s.trim());
|
|
@@ -4192,12 +4742,6 @@ var onSegmentPointRule = {
|
|
|
4192
4742
|
if (intents.length > 0) out.push({ ruleId: "on-segment-point", clauseIds: [c.id], intents });
|
|
4193
4743
|
continue;
|
|
4194
4744
|
}
|
|
4195
|
-
ON_NAMED_LINE.lastIndex = 0;
|
|
4196
|
-
for (const m of c.text.matchAll(ON_NAMED_LINE)) {
|
|
4197
|
-
const line = m[1];
|
|
4198
|
-
const name = normalizePoint(m[2]);
|
|
4199
|
-
if (/^[A-Z]['′]?$/u.test(name)) intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4200
|
-
}
|
|
4201
4745
|
ON_SEG_THEN_POINT.lastIndex = 0;
|
|
4202
4746
|
for (const m of c.text.matchAll(ON_SEG_THEN_POINT)) {
|
|
4203
4747
|
const segment = m[1];
|
|
@@ -4249,7 +4793,7 @@ var onSegmentPointRule = {
|
|
|
4249
4793
|
};
|
|
4250
4794
|
|
|
4251
4795
|
// src/stamps/geometry-2d/ai/rules/lineCircleIntersection.ts
|
|
4252
|
-
var PREFILTER15 = /cắt\s+(?:lại\s+)?(?:(?:nửa\s+)?đường\s*tròn\s*)?\(|cắt\s+(?:lại\s+)?(?:nửa\s+)?đường\s*tròn\s+(?:ở|tại)|cắt\s+[A-Z]{2}\s+và\s+\(|giao\s*điểm\s+(?:thứ\s+hai\s+)?(?:của\s+|khác\s+[A-Z]\s+của\s+)?(?:đường\s*thẳng\s+)?[A-Z]{2}
|
|
4796
|
+
var PREFILTER15 = /cắt\s+(?:lại\s+)?(?:(?:nửa\s+)?đường\s*tròn\s*)?\(|cắt\s+(?:lại\s+)?(?:nửa\s+)?đường\s*tròn\s+(?:tâm\s+[A-Z](?:['′])?\s+)?(?:ở|tại)|cắt\s+[A-Z]{2}\s+và\s+\(|\(\s*[A-Z](?:['′]?)\s*(?:[;,]\s*[Rr]\s*)?\)\s+cắt\s+(?:lại\s+)?[A-Z]{2}\s*(?:,|và)?\s*(?:[A-Z]{2})?\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+|tương\s+ứng\s+)?(?:ở|tại)|\(\s*[A-Z]{3}\s*\)\s*(?:,|và)\s*\(\s*[A-Z]{3}\s*\)\s+cắt|giao\s*điểm\s+(?:thứ\s+hai\s+)?(?:của\s+|khác\s+[A-Z]\s+của\s+)?(?:đường\s*thẳng\s+)?[A-Z]{2}(?:\s*,\s*[A-Z]{2})?\s+(?:và|với)\s+(?:(?:nửa\s+)?đường\s*tròn|\(|ngoại\s*tiếp\s+tam\s*giác)/u;
|
|
4253
4797
|
var CIRCLE = String.raw`(?:(?:nửa\s+)?đường\s*tròn\s*)?\(\s*([A-Z])(?:['′]?)\s*(?:[;,]\s*[Rr]\s*)?\)`;
|
|
4254
4798
|
var CIRCLE_P = String.raw`(?:(?:nửa\s+)?đường\s*tròn\s*)?\(\s*([A-Z](?:['′])?)\s*(?:[;,]\s*[Rr]\s*)?\)`;
|
|
4255
4799
|
var PERP_CUTS_CIRCLE = new RegExp(
|
|
@@ -4265,13 +4809,21 @@ var DOUBLE_DISTRIB = new RegExp(
|
|
|
4265
4809
|
"gu"
|
|
4266
4810
|
);
|
|
4267
4811
|
var SINGLE3 = new RegExp(
|
|
4268
|
-
String.raw`([A-Z]{2})(?![A-Z])\s+cắt\s+(?:lại\s+)?` + CIRCLE + String.raw`\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4812
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:kéo\s+dài\s+)?cắt\s+(?:lại\s+)?` + CIRCLE + String.raw`\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4813
|
+
"gu"
|
|
4814
|
+
);
|
|
4815
|
+
var SINGLE_NAMED_CENTER = new RegExp(
|
|
4816
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:kéo\s+dài\s+)?cắt\s+(?:lại\s+)?(?:nửa\s+)?đường\s*tròn\s+tâm\s+([A-Z](?:['′])?)(?![A-Za-z])` + String.raw`\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])`,
|
|
4269
4817
|
"gu"
|
|
4270
4818
|
);
|
|
4271
4819
|
var BOTH = new RegExp(
|
|
4272
4820
|
String.raw`([A-Z]{2})(?![A-Z])\s+cắt\s+` + CIRCLE + String.raw`\s+(?:ở|tại)\s+(?:hai\s+|các\s+)?điểm\s+(?:phân\s*biệt\s+)?([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4273
4821
|
"gu"
|
|
4274
4822
|
);
|
|
4823
|
+
var TWO_NAMED = new RegExp(
|
|
4824
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:kéo\s+dài\s+)?cắt\s+(?:lại\s+)?` + CIRCLE + String.raw`\s+(?:ở|tại)\s+([A-Z])\s+và\s+([A-Z])(?![A-Z])`,
|
|
4825
|
+
"gu"
|
|
4826
|
+
);
|
|
4275
4827
|
var LINE_AND_CIRCLE = new RegExp(
|
|
4276
4828
|
String.raw`([A-Z]{2})(?![A-Z])\s+cắt\s+([A-Z]{2})(?![A-Z])\s+và\s+` + CIRCLE + String.raw`\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?(?:tại|ở)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4277
4829
|
"gu"
|
|
@@ -4288,12 +4840,16 @@ var CIRCLE_SUBJECT = new RegExp(
|
|
|
4288
4840
|
CIRCLE + String.raw`\s+cắt\s+(?:lại\s+)?([A-Z]{2})(?![A-Z])\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])`,
|
|
4289
4841
|
"gu"
|
|
4290
4842
|
);
|
|
4843
|
+
var CIRCLE_SUBJECT_TWO = new RegExp(
|
|
4844
|
+
CIRCLE + String.raw`\s+cắt\s+(?:lại\s+)?([A-Z]{2})\s*(?:,|và)\s*([A-Z]{2})(?![A-Z])\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+|tương\s+ứng\s+)?(?:ở|tại)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4845
|
+
"gu"
|
|
4846
|
+
);
|
|
4291
4847
|
var GIAO_CIRCLE = new RegExp(
|
|
4292
4848
|
String.raw`giao\s*điểm\s+(?:thứ\s+hai\s+)?(?:của\s+)?([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE + String.raw`\s+là\s+([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4293
4849
|
"gu"
|
|
4294
4850
|
);
|
|
4295
4851
|
var NAME_2ND_CUA = new RegExp(
|
|
4296
|
-
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+thứ\s+hai\s+của\s+([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4852
|
+
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+thứ\s+hai\s+của\s+(?:đường\s*thẳng\s+)?([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4297
4853
|
"gu"
|
|
4298
4854
|
);
|
|
4299
4855
|
var NAME_2ND_BARE = new RegExp(
|
|
@@ -4309,13 +4865,28 @@ var NAME_KHAC_CUA = new RegExp(
|
|
|
4309
4865
|
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+(?:thứ\s+hai\s+)?khác\s+([A-Z])(?![A-Z])\s+của\s+([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4310
4866
|
"gu"
|
|
4311
4867
|
);
|
|
4868
|
+
var NAME_CUA = new RegExp(
|
|
4869
|
+
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+của\s+(?!thứ\s+hai)(?!khác)(?:đường\s*thẳng\s+)?([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4870
|
+
"gu"
|
|
4871
|
+
);
|
|
4312
4872
|
var NAME_2ND_CIRCUM = new RegExp(
|
|
4313
4873
|
String.raw`giao\s*điểm\s+thứ\s+hai\s+của\s+([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + String.raw`(?:nửa\s+)?` + CIRCLE_KW + String.raw`\s+ngoại\s*tiếp\s+tam\s*giác\s+[A-Z]{3}(?![A-Z])` + String.raw`\s+là\s+(?:điểm\s+)?([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4314
4874
|
"gu"
|
|
4315
4875
|
);
|
|
4876
|
+
var LINE_CUTS_PAREN_PAIR = new RegExp(
|
|
4877
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+|tương\s+ứng\s+)?cắt\s+(?:lại\s+)?` + String.raw`\(\s*([A-Z]{3})\s*\)\s*(?:,|và)\s*\(\s*([A-Z]{3})\s*\)` + String.raw`\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?(?:là\s+)?)?([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4878
|
+
"gu"
|
|
4879
|
+
);
|
|
4880
|
+
var PAREN_PAIR_CUTS = new RegExp(
|
|
4881
|
+
String.raw`\(\s*([A-Z]{3})\s*\)\s*(?:,|và)\s*\(\s*([A-Z]{3})\s*\)\s+cắt\s+(?:lại\s+)?` + String.raw`([A-Z]{2})\s*(?:,|và)\s*([A-Z]{2})(?![A-Z])\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?(?:ở|tại)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4882
|
+
"gu"
|
|
4883
|
+
);
|
|
4316
4884
|
function secondIntersection(name, line, circle, other) {
|
|
4317
4885
|
return addPoint(name, { kind: "secondIntersection", line, circle, other: other ?? line[0] });
|
|
4318
4886
|
}
|
|
4887
|
+
function vertexOnLine(verts, line) {
|
|
4888
|
+
return verts.split("").find((v) => line.includes(v));
|
|
4889
|
+
}
|
|
4319
4890
|
function valid(name, line) {
|
|
4320
4891
|
return /^[A-Z]$/u.test(name) && /^[A-Z]{2}$/u.test(line) && !line.includes(name);
|
|
4321
4892
|
}
|
|
@@ -4352,6 +4923,13 @@ var lineCircleIntersectionRule = {
|
|
|
4352
4923
|
const other = m[4];
|
|
4353
4924
|
if (valid(name, line)) intents.push(secondIntersection(name, line, circle, other));
|
|
4354
4925
|
}
|
|
4926
|
+
SINGLE_NAMED_CENTER.lastIndex = 0;
|
|
4927
|
+
for (const m of c.text.matchAll(SINGLE_NAMED_CENTER)) {
|
|
4928
|
+
const line = m[1];
|
|
4929
|
+
const circle = m[2].replace(/′/g, "'");
|
|
4930
|
+
const name = m[3];
|
|
4931
|
+
if (valid(name, line)) intents.push(secondIntersection(name, line, circle, line[0]));
|
|
4932
|
+
}
|
|
4355
4933
|
LINE_AND_CIRCLE.lastIndex = 0;
|
|
4356
4934
|
for (const m of c.text.matchAll(LINE_AND_CIRCLE)) {
|
|
4357
4935
|
const [line, line2, circle, n1, n2] = [m[1], m[2], m[3], m[4], m[5]];
|
|
@@ -4372,7 +4950,14 @@ var lineCircleIntersectionRule = {
|
|
|
4372
4950
|
addPoint(y, { kind: "intersection", of: [line, circle], branch: 1 })
|
|
4373
4951
|
);
|
|
4374
4952
|
}
|
|
4375
|
-
|
|
4953
|
+
TWO_NAMED.lastIndex = 0;
|
|
4954
|
+
for (const m of c.text.matchAll(TWO_NAMED)) {
|
|
4955
|
+
const [line, circle, p, q] = [m[1], m[2], m[3], m[4]];
|
|
4956
|
+
if (p === q) continue;
|
|
4957
|
+
if (line.includes(p) && !line.includes(q)) intents.push(secondIntersection(q, line, circle, p));
|
|
4958
|
+
else if (line.includes(q) && !line.includes(p)) intents.push(secondIntersection(p, line, circle, q));
|
|
4959
|
+
}
|
|
4960
|
+
NAME_2ND_BARE.lastIndex = 0;
|
|
4376
4961
|
for (const m of c.text.matchAll(NAME_2ND_BARE)) {
|
|
4377
4962
|
const rc = RESOLVE_CIRCLE_BARE.exec(ctx.problem);
|
|
4378
4963
|
const circle = rc?.[1] ?? rc?.[2];
|
|
@@ -4409,6 +4994,13 @@ var lineCircleIntersectionRule = {
|
|
|
4409
4994
|
const [line, name] = [m[1], m[2]];
|
|
4410
4995
|
if (circle && valid(name, line)) intents.push(secondIntersection(name, line, circle, line[0]));
|
|
4411
4996
|
}
|
|
4997
|
+
CIRCLE_SUBJECT_TWO.lastIndex = 0;
|
|
4998
|
+
for (const m of c.text.matchAll(CIRCLE_SUBJECT_TWO)) {
|
|
4999
|
+
const [circle, l1, l2, n1, n2] = [m[1], m[2], m[3], m[4], m[5]];
|
|
5000
|
+
if (n1 === n2) continue;
|
|
5001
|
+
if (valid(n1, l1)) intents.push(secondIntersection(n1, l1, circle));
|
|
5002
|
+
if (valid(n2, l2)) intents.push(secondIntersection(n2, l2, circle));
|
|
5003
|
+
}
|
|
4412
5004
|
CIRCLE_SUBJECT.lastIndex = 0;
|
|
4413
5005
|
for (const m of c.text.matchAll(CIRCLE_SUBJECT)) {
|
|
4414
5006
|
const [circle, line, name] = [m[1], m[2], m[3]];
|
|
@@ -4430,6 +5022,11 @@ var lineCircleIntersectionRule = {
|
|
|
4430
5022
|
const circle = m[4];
|
|
4431
5023
|
if (valid(name, line)) intents.push(secondIntersection(name, line, circle, other));
|
|
4432
5024
|
}
|
|
5025
|
+
NAME_CUA.lastIndex = 0;
|
|
5026
|
+
for (const m of c.text.matchAll(NAME_CUA)) {
|
|
5027
|
+
const [name, line, circle] = [m[1], m[2], m[3]];
|
|
5028
|
+
if (valid(name, line)) intents.push(secondIntersection(name, line, circle));
|
|
5029
|
+
}
|
|
4433
5030
|
NAME_2ND_CIRCUM.lastIndex = 0;
|
|
4434
5031
|
for (const m of c.text.matchAll(NAME_2ND_CIRCUM)) {
|
|
4435
5032
|
const line = m[1];
|
|
@@ -4445,6 +5042,34 @@ var lineCircleIntersectionRule = {
|
|
|
4445
5042
|
if (line.includes(name)) continue;
|
|
4446
5043
|
intents.push(addPoint(name, { kind: "intersection", of: [line, circle], branch: 0 }));
|
|
4447
5044
|
}
|
|
5045
|
+
LINE_CUTS_PAREN_PAIR.lastIndex = 0;
|
|
5046
|
+
for (const m of c.text.matchAll(LINE_CUTS_PAREN_PAIR)) {
|
|
5047
|
+
const [line, t1, t2, n1, n2] = [m[1], m[2], m[3], m[4], m[5]];
|
|
5048
|
+
const o1 = vertexOnLine(t1, line);
|
|
5049
|
+
const o2 = vertexOnLine(t2, line);
|
|
5050
|
+
if (!o1 || !o2 || n1 === n2) continue;
|
|
5051
|
+
if (line.includes(n1) || line.includes(n2)) continue;
|
|
5052
|
+
intents.push(
|
|
5053
|
+
drawCircle(`w${t1}`, "through3", { points: t1.split("") }),
|
|
5054
|
+
drawCircle(`w${t2}`, "through3", { points: t2.split("") }),
|
|
5055
|
+
secondIntersection(n1, line, `w${t1}`, o1),
|
|
5056
|
+
secondIntersection(n2, line, `w${t2}`, o2)
|
|
5057
|
+
);
|
|
5058
|
+
}
|
|
5059
|
+
PAREN_PAIR_CUTS.lastIndex = 0;
|
|
5060
|
+
for (const m of c.text.matchAll(PAREN_PAIR_CUTS)) {
|
|
5061
|
+
const [t1, t2, l1, l2, n1, n2] = [m[1], m[2], m[3], m[4], m[5], m[6]];
|
|
5062
|
+
const o1 = vertexOnLine(t1, l1);
|
|
5063
|
+
const o2 = vertexOnLine(t2, l2);
|
|
5064
|
+
if (!o1 || !o2 || n1 === n2) continue;
|
|
5065
|
+
if (l1.includes(n1) || l2.includes(n2)) continue;
|
|
5066
|
+
intents.push(
|
|
5067
|
+
drawCircle(`w${t1}`, "through3", { points: t1.split("") }),
|
|
5068
|
+
drawCircle(`w${t2}`, "through3", { points: t2.split("") }),
|
|
5069
|
+
secondIntersection(n1, l1, `w${t1}`, o1),
|
|
5070
|
+
secondIntersection(n2, l2, `w${t2}`, o2)
|
|
5071
|
+
);
|
|
5072
|
+
}
|
|
4448
5073
|
if (intents.length > 0) out.push({ ruleId: "line-circle-intersection", clauseIds: [c.id], intents });
|
|
4449
5074
|
}
|
|
4450
5075
|
return out;
|
|
@@ -4505,8 +5130,9 @@ var incenterNamedTriangleRule = {
|
|
|
4505
5130
|
};
|
|
4506
5131
|
|
|
4507
5132
|
// src/stamps/geometry-2d/ai/rules/perpThroughCutsLines.ts
|
|
5133
|
+
var LTOK = "(?:[A-Z]{2}|[A-Z][a-z][0-9]?)";
|
|
4508
5134
|
var RE2 = new RegExp(
|
|
4509
|
-
|
|
5135
|
+
`(?:Qua|qua|T\u1EEB|t\u1EEB)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032]?)(?!\\p{L})[^.]{0,24}?(song\\s*song|vu\xF4ng\\s*g\xF3c)\\s+(?:v\u1EDBi\\s+)?(?:c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?([A-Z])([A-Z])(?!\\p{L})[^.]{0,40}?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?(${LTOK})\\s*(?:,|v\xE0)\\s*(${LTOK})(?![A-Z])[^.]{0,30}?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
4510
5136
|
"gu"
|
|
4511
5137
|
);
|
|
4512
5138
|
var PREFILTER16 = /(?:Qua|qua|Từ|từ)\s+(?:điểm\s+)?[A-Z][^.]{0,24}?(?:song\s*song|vuông\s*góc)[^.]{0,80}?cắt/u;
|
|
@@ -4529,10 +5155,10 @@ var perpThroughCutsLinesRule = {
|
|
|
4529
5155
|
const through = m[1];
|
|
4530
5156
|
const isParallel = /song/.test(m[2]);
|
|
4531
5157
|
const to = m[3] + m[4];
|
|
4532
|
-
const line1 = m[5]
|
|
4533
|
-
const line2 = m[
|
|
4534
|
-
const h = m[
|
|
4535
|
-
const k = m[
|
|
5158
|
+
const line1 = m[5];
|
|
5159
|
+
const line2 = m[6];
|
|
5160
|
+
const h = m[7];
|
|
5161
|
+
const k = m[8];
|
|
4536
5162
|
if (isParallel && to.includes(through)) continue;
|
|
4537
5163
|
if (h === k) continue;
|
|
4538
5164
|
const kind = isParallel ? "parallelThrough" : "perpThrough";
|
|
@@ -4578,6 +5204,40 @@ var CIRCLE_CENTER3 = /\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)|tâm\s+([A-Z])(?![A-Z
|
|
|
4578
5204
|
var DIAMETER_ENDS = /đường\s*kính\s+([A-Z])([A-Z])(?![A-Z])/u;
|
|
4579
5205
|
var AFTER_TANGENT = /tiếp\s*tuyến\s+((?:[A-Z][xyzt](?![A-Za-z])\s*[,và\s]*)+)/gu;
|
|
4580
5206
|
var RAY_TOKEN = /([A-Z])([xyzt])(?![A-Za-z])/gu;
|
|
5207
|
+
var SHAPE_VERTICES = /[Cc]ho\s+(?:tam\s*giác|tứ\s*giác|hình\s+(?:vuông|chữ\s*nhật|thang|bình\s*hành|thoi))\s+([A-Z]{3,})/gu;
|
|
5208
|
+
function matchFreeTangentPoint(ctx, cm) {
|
|
5209
|
+
const center = cm ? cm[1] ?? cm[2] : void 0;
|
|
5210
|
+
if (!center) return [];
|
|
5211
|
+
const known = /* @__PURE__ */ new Set();
|
|
5212
|
+
SHAPE_VERTICES.lastIndex = 0;
|
|
5213
|
+
for (const m of ctx.problem.matchAll(SHAPE_VERTICES)) {
|
|
5214
|
+
for (const ch of m[1]) known.add(ch);
|
|
5215
|
+
}
|
|
5216
|
+
const out = [];
|
|
5217
|
+
for (const c of ctx.clauses) {
|
|
5218
|
+
const intents = [];
|
|
5219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5220
|
+
AFTER_TANGENT.lastIndex = 0;
|
|
5221
|
+
for (const grp of c.text.matchAll(AFTER_TANGENT)) {
|
|
5222
|
+
RAY_TOKEN.lastIndex = 0;
|
|
5223
|
+
for (const m of grp[1].matchAll(RAY_TOKEN)) {
|
|
5224
|
+
const endpoint = m[1];
|
|
5225
|
+
const token = `${m[1]}${m[2]}`;
|
|
5226
|
+
if (known.has(endpoint)) continue;
|
|
5227
|
+
if (seen.has(token)) continue;
|
|
5228
|
+
seen.add(token);
|
|
5229
|
+
intents.push(
|
|
5230
|
+
addPoint(endpoint, { kind: "onCircle", circle: center, theta: 0.7 }),
|
|
5231
|
+
drawLine(token, "tangentAt", { through: endpoint, circle: center })
|
|
5232
|
+
);
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
if (intents.length > 0) {
|
|
5236
|
+
out.push({ ruleId: "tangent-ray", clauseIds: [c.id], intents });
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
return out;
|
|
5240
|
+
}
|
|
4581
5241
|
var tangentRayRule = {
|
|
4582
5242
|
id: "tangent-ray",
|
|
4583
5243
|
// Cao hơn point-on-ray (55) + intersect-ray (48): token tia phải được dựng
|
|
@@ -4589,7 +5249,7 @@ var tangentRayRule = {
|
|
|
4589
5249
|
match(ctx) {
|
|
4590
5250
|
const cm = CIRCLE_CENTER3.exec(ctx.problem);
|
|
4591
5251
|
const dm = DIAMETER_ENDS.exec(ctx.problem);
|
|
4592
|
-
if (!dm) return
|
|
5252
|
+
if (!dm) return matchFreeTangentPoint(ctx, cm);
|
|
4593
5253
|
const center = cm ? cm[1] ?? cm[2] : `k${dm[1]}${dm[2]}`;
|
|
4594
5254
|
const ends = /* @__PURE__ */ new Set([dm[1], dm[2]]);
|
|
4595
5255
|
const out = [];
|
|
@@ -5238,7 +5898,7 @@ var PREFILTER30 = /[Tt]iếp\s*tuyến[^.]{0,30}?tại\s+[A-Z][^.]{0,40}?cắt|Q
|
|
|
5238
5898
|
var CIRCLE_REF4 = /đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)?/u;
|
|
5239
5899
|
var UNNAMED_DIAMETER2 = /(?:nửa\s+)?đường\s*tròn\s+đường\s*kính\s+([A-Z])([A-Z])(?![A-Z])/u;
|
|
5240
5900
|
var QUA_RE = new RegExp(
|
|
5241
|
-
"Qua\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,40}?ti\u1EBFp\\s*tuy\u1EBFn\\s+(?:th\u1EE9\\s+(?:ba|3|hai|2)\\s+)?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5901
|
+
"Qua\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,40}?ti\u1EBFp\\s*tuy\u1EBFn\\s+(?:th\u1EE9\\s+(?:ba|3|hai|2)\\s+)?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})(?:\\s+k\xE9o\\s+d\xE0i)?\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:(?:t\u1EA1i|\u1EDF)\\s+)?([A-Z])(?![A-Z])",
|
|
5242
5902
|
"gu"
|
|
5243
5903
|
);
|
|
5244
5904
|
var SINGLE_RE = new RegExp(
|
|
@@ -5246,7 +5906,7 @@ var SINGLE_RE = new RegExp(
|
|
|
5246
5906
|
"gu"
|
|
5247
5907
|
);
|
|
5248
5908
|
var RE6 = new RegExp(
|
|
5249
|
-
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+(?:c\u1EE7a\\s+[^.]{0,14}?\\s+)?t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})(?:\\s+c\u1EE7a\\s+[^.]{0,14}?)?\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:t\u1EA1i|\u1EDF)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5909
|
+
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+(?:(?:c\u1EE7a|v\u1EDBi)\\s+[^.]{0,14}?\\s+)?t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})(?:\\s+(?:c\u1EE7a|v\u1EDBi)\\s+[^.]{0,14}?)?\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})(?:\\s+k\xE9o\\s+d\xE0i)?\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:t\u1EA1i|\u1EDF)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:(?:t\u1EA1i|\u1EDF)\\s+)?([A-Z])(?![A-Z])",
|
|
5250
5910
|
"gu"
|
|
5251
5911
|
);
|
|
5252
5912
|
function circleName3(problem) {
|
|
@@ -5308,15 +5968,15 @@ var tangentAtCutsLinesRule = {
|
|
|
5308
5968
|
for (const m of c.text.matchAll(SINGLE_RE)) {
|
|
5309
5969
|
const at = m[1];
|
|
5310
5970
|
const line = m[2];
|
|
5311
|
-
const
|
|
5312
|
-
if (line.includes(
|
|
5971
|
+
const z47 = m[3];
|
|
5972
|
+
if (line.includes(z47) || line.includes(at)) continue;
|
|
5313
5973
|
const t = `t${at}`;
|
|
5314
5974
|
out.push({
|
|
5315
5975
|
ruleId: "tangentAtCutsLines",
|
|
5316
5976
|
clauseIds: [c.id],
|
|
5317
5977
|
intents: [
|
|
5318
5978
|
drawLine(t, "tangentAt", { through: at, circle }),
|
|
5319
|
-
addPoint(
|
|
5979
|
+
addPoint(z47, { kind: "intersection", of: [t, line] })
|
|
5320
5980
|
]
|
|
5321
5981
|
});
|
|
5322
5982
|
}
|
|
@@ -5330,12 +5990,16 @@ var RE7 = new RegExp(
|
|
|
5330
5990
|
"([A-Z])([A-Z])(?![A-Z])\\s+l\xE0\\s+\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+(?:c\u1EE7a\\s+)?(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*)?(?:t\xE2m\\s+)?\\(?\\s*([A-Z])\\s*\\)?",
|
|
5331
5991
|
"gu"
|
|
5332
5992
|
);
|
|
5993
|
+
var VERB_GAP = "(?:[^.;,]{0,24}?v\xE0\\s+|(?:(?!\u0111\u01B0\u1EDDng\\s*tr\xF2n|\\()[^.;,]){0,24}?)?";
|
|
5333
5994
|
var RE_VERB = new RegExp(
|
|
5334
|
-
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+
|
|
5995
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+" + VERB_GAP + "\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+([A-Z])([A-Z]['\u2032]?)(?![A-Za-z'\u2032])",
|
|
5335
5996
|
"gu"
|
|
5336
5997
|
);
|
|
5337
5998
|
var RESOLVE_CENTER = /\(\s*([A-Z])(?:\s*[;,]\s*[Rr0-9][^)]*)?\s*\)|tâm\s+([A-Z])(?![A-Za-z])/u;
|
|
5338
|
-
var PREFILTER31 =
|
|
5999
|
+
var PREFILTER31 = new RegExp(
|
|
6000
|
+
"[A-Z]{2}\\s+l\xE0\\s+\u0111\u01B0\u1EDDng\\s*k\xEDnh|(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+" + VERB_GAP + "\u0111\u01B0\u1EDDng\\s*k\xEDnh",
|
|
6001
|
+
"u"
|
|
6002
|
+
);
|
|
5339
6003
|
var diameterEndpointRule = {
|
|
5340
6004
|
id: "diameterEndpoint",
|
|
5341
6005
|
priority: 56,
|
|
@@ -5369,7 +6033,7 @@ var diameterEndpointRule = {
|
|
|
5369
6033
|
const d = m[2].replace(/′/g, "'");
|
|
5370
6034
|
if (a === d || center === a || center === d || emitted.has(d)) continue;
|
|
5371
6035
|
const before = ctx.problem.slice(0, ctx.problem.indexOf(m[0]));
|
|
5372
|
-
if (!new RegExp(`(?<![a-z])${a}(?![a-z])`, "u").test(before)) continue;
|
|
6036
|
+
if (!new RegExp(`(?<![a-z])${escapeRe(a)}(?![a-z])`, "u").test(before)) continue;
|
|
5373
6037
|
emitted.add(d);
|
|
5374
6038
|
out.push({
|
|
5375
6039
|
ruleId: "diameterEndpoint",
|
|
@@ -5433,8 +6097,69 @@ var circleThroughTwoCutsSidesRule = {
|
|
|
5433
6097
|
}
|
|
5434
6098
|
};
|
|
5435
6099
|
|
|
6100
|
+
// src/stamps/geometry-2d/ai/rules/circleCenterRadiusCutsSides.ts
|
|
6101
|
+
var PATTERN4 = new RegExp(
|
|
6102
|
+
CIRCLE_KW + "\\s+t\xE2m\\s+([A-Z])(?![A-Z])\\s*b\xE1n\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])[^.;\\n]*?c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?t\u1EA1i\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))*)",
|
|
6103
|
+
"gu"
|
|
6104
|
+
);
|
|
6105
|
+
var PREFILTER33 = new RegExp(
|
|
6106
|
+
CIRCLE_KW + "\\s+t\xE2m\\s+[A-Z]\\s*b\xE1n\\s*k\xEDnh\\s+[A-Z]{2}[^.;\\n]*?c\u1EAFt",
|
|
6107
|
+
"u"
|
|
6108
|
+
);
|
|
6109
|
+
var circleCenterRadiusCutsSidesRule = {
|
|
6110
|
+
id: "circle-center-radius-cuts-sides",
|
|
6111
|
+
// THẤP HƠN triangle (100) + centers (cần đỉnh + tâm H dựng trước). Cùng họ với
|
|
6112
|
+
// diameterCircleCutsSides (66) / circleThroughTwoCutsSides (58). Đặt 57.
|
|
6113
|
+
priority: 57,
|
|
6114
|
+
languages: ["vi"],
|
|
6115
|
+
patterns: [PREFILTER33],
|
|
6116
|
+
match(ctx) {
|
|
6117
|
+
const out = [];
|
|
6118
|
+
for (const c of ctx.clauses) {
|
|
6119
|
+
PATTERN4.lastIndex = 0;
|
|
6120
|
+
for (const m of c.text.matchAll(PATTERN4)) {
|
|
6121
|
+
const center = m[1];
|
|
6122
|
+
const r0 = m[2];
|
|
6123
|
+
const r1 = m[3];
|
|
6124
|
+
let through;
|
|
6125
|
+
if (r0 === center && r1 !== center) through = r1;
|
|
6126
|
+
else if (r1 === center && r0 !== center) through = r0;
|
|
6127
|
+
if (!through) continue;
|
|
6128
|
+
const lines = m[4].match(/[A-Z][A-Z](?![A-Z])/gu) ?? [];
|
|
6129
|
+
const points = m[5].match(/[A-Z](?![A-Z])/gu) ?? [];
|
|
6130
|
+
if (lines.length === 0 || lines.length !== points.length) continue;
|
|
6131
|
+
const circ = `${center}_c`;
|
|
6132
|
+
const intents = [
|
|
6133
|
+
drawCircle(circ, "centerThrough", { center, through })
|
|
6134
|
+
];
|
|
6135
|
+
let ok = true;
|
|
6136
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6137
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6138
|
+
const line = lines[i];
|
|
6139
|
+
const pt = points[i];
|
|
6140
|
+
if (!line.includes(through)) {
|
|
6141
|
+
ok = false;
|
|
6142
|
+
break;
|
|
6143
|
+
}
|
|
6144
|
+
if (line.includes(pt) || pt === through || pt === center || seen.has(pt)) {
|
|
6145
|
+
ok = false;
|
|
6146
|
+
break;
|
|
6147
|
+
}
|
|
6148
|
+
seen.add(pt);
|
|
6149
|
+
intents.push(
|
|
6150
|
+
addPoint(pt, { kind: "secondIntersection", line, circle: circ, other: through })
|
|
6151
|
+
);
|
|
6152
|
+
}
|
|
6153
|
+
if (!ok) continue;
|
|
6154
|
+
out.push({ ruleId: "circle-center-radius-cuts-sides", clauseIds: [c.id], intents });
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
return out;
|
|
6158
|
+
}
|
|
6159
|
+
};
|
|
6160
|
+
|
|
5436
6161
|
// src/stamps/geometry-2d/ai/rules/circumcircleNamedDistrib.ts
|
|
5437
|
-
var
|
|
6162
|
+
var PREFILTER34 = /lần\s*lượt\s+là\s+(?:đường\s*tròn\s+)?ngoại\s*tiếp/u;
|
|
5438
6163
|
var RE9 = new RegExp(
|
|
5439
6164
|
"([A-Z])\\s*,\\s*([A-Z])\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+l\xE0\\s+(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+)?ngo\u1EA1i\\s*ti\u1EBFp\\s+(?:c\xE1c\\s+)?tam\\s*gi\xE1c\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])\\s*,\\s*(?:tam\\s*gi\xE1c\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])",
|
|
5440
6165
|
"u"
|
|
@@ -5443,7 +6168,7 @@ var circumcircleNamedDistribRule = {
|
|
|
5443
6168
|
id: "circumcircleNamedDistrib",
|
|
5444
6169
|
priority: 67,
|
|
5445
6170
|
languages: ["vi"],
|
|
5446
|
-
patterns: [
|
|
6171
|
+
patterns: [PREFILTER34],
|
|
5447
6172
|
match(ctx) {
|
|
5448
6173
|
const out = [];
|
|
5449
6174
|
for (const c of ctx.clauses) {
|
|
@@ -5468,7 +6193,7 @@ var circumcircleNamedDistribRule = {
|
|
|
5468
6193
|
};
|
|
5469
6194
|
|
|
5470
6195
|
// src/stamps/geometry-2d/ai/rules/circumcirclePairMeet.ts
|
|
5471
|
-
var
|
|
6196
|
+
var PREFILTER35 = /[Đđ]ường\s*tròn\s+ngoại\s*tiếp\s+(?:các\s+)?tam\s*giác\s+[A-Z]{3}\s*(?:,|\s+(?:và|cắt)\s+(?:lại\s+)?đường\s*tròn\s+ngoại\s*tiếp\s+tam\s*giác)/u;
|
|
5472
6197
|
var RE_CAT = new RegExp(
|
|
5473
6198
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+ngo\u1EA1i\\s*ti\u1EBFp\\s+tam\\s*gi\xE1c\\s+([A-Z]{3})(?![A-Z])\\s+c\u1EAFt\\s+(?:l\u1EA1i\\s+)?[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+ngo\u1EA1i\\s*ti\u1EBFp\\s+tam\\s*gi\xE1c\\s+([A-Z]{3})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?)?(?:l\xE0\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
5474
6199
|
"u"
|
|
@@ -5490,22 +6215,27 @@ var RE_PAREN_BEFORE = new RegExp(
|
|
|
5490
6215
|
`([A-Z])(?![A-Z])\\s+l\xE0\\s+giao\\s*\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?(?:c\u1EE7a\\s+)?${PAR}\\s+v\xE0\\s+${PAR}`,
|
|
5491
6216
|
"u"
|
|
5492
6217
|
);
|
|
6218
|
+
var RE_PAREN_CAT = new RegExp(
|
|
6219
|
+
`${PAR}\\s+v\xE0\\s+${PAR}\\s+c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?`,
|
|
6220
|
+
"u"
|
|
6221
|
+
);
|
|
5493
6222
|
var circumcirclePairMeetRule = {
|
|
5494
6223
|
id: "circumcirclePairMeet",
|
|
5495
6224
|
priority: 59,
|
|
5496
6225
|
languages: ["vi"],
|
|
5497
|
-
patterns: [
|
|
6226
|
+
patterns: [PREFILTER35, new RegExp(`${PAR}\\s+v\xE0\\s+${PAR}`, "u")],
|
|
5498
6227
|
match(ctx) {
|
|
5499
6228
|
const out = [];
|
|
5500
6229
|
for (const c of ctx.clauses) {
|
|
5501
6230
|
const pl = RE_PAREN_LA.exec(c.text);
|
|
5502
|
-
const
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
const
|
|
5506
|
-
const
|
|
6231
|
+
const pc = pl ? null : RE_PAREN_CAT.exec(c.text);
|
|
6232
|
+
const pb = pl || pc ? null : RE_PAREN_BEFORE.exec(c.text);
|
|
6233
|
+
if (pl || pc || pb) {
|
|
6234
|
+
const t1 = pl ? pl[1] : pc ? pc[1] : pb[2];
|
|
6235
|
+
const t2 = pl ? pl[2] : pc ? pc[2] : pb[3];
|
|
6236
|
+
const q2 = pl ? pl[3] : pc ? pc[3] : pb[1];
|
|
5507
6237
|
const tri12 = t1.split(""), tri22 = t2.split("");
|
|
5508
|
-
const shared2 = pl && pl[4] || tri12.find((v) => tri22.includes(v));
|
|
6238
|
+
const shared2 = pl && pl[4] || pc && pc[4] || tri12.find((v) => tri22.includes(v));
|
|
5509
6239
|
if (shared2 && !tri12.includes(q2) && !tri22.includes(q2)) {
|
|
5510
6240
|
out.push({
|
|
5511
6241
|
ruleId: "circumcirclePairMeet",
|
|
@@ -5543,7 +6273,7 @@ var circumcirclePairMeetRule = {
|
|
|
5543
6273
|
};
|
|
5544
6274
|
|
|
5545
6275
|
// src/stamps/geometry-2d/ai/rules/tangentCircleAtPoint.ts
|
|
5546
|
-
var
|
|
6276
|
+
var PREFILTER36 = /[Đđ]ường\s*tròn\s*\(?[A-Z]\)?[^.]{0,40}?tiếp\s*xúc[^.]{0,20}?tại\s+[A-Z]/u;
|
|
5547
6277
|
var RE11 = new RegExp(
|
|
5548
6278
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s*\\(\\s*([A-Z])\\s*\\)\\s*(?:\u0111i\\s+)?qua\\s+([A-Z])\\s+(?:v\xE0\\s+)?ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?(?:c\u1EA1nh\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?([A-Z]{2})\\s+t\u1EA1i\\s+([A-Z])(?!\\p{L})[^.]{0,24}?c\u1EAFt\\s+(?:c\xE1c\\s+(?:c\u1EA1nh|\u0111o\u1EA1n)\\s+|c\u1EA1nh\\s+)?([A-Z]{2})\\s*(?:,|v\xE0)\\s*([A-Z]{2})(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5549
6279
|
"gu"
|
|
@@ -5556,7 +6286,7 @@ var tangentCircleAtPointRule = {
|
|
|
5556
6286
|
id: "tangentCircleAtPoint",
|
|
5557
6287
|
priority: 60,
|
|
5558
6288
|
languages: ["vi"],
|
|
5559
|
-
patterns: [
|
|
6289
|
+
patterns: [PREFILTER36],
|
|
5560
6290
|
match(ctx) {
|
|
5561
6291
|
const out = [];
|
|
5562
6292
|
for (const c of ctx.clauses) {
|
|
@@ -5612,7 +6342,7 @@ var tangentCircleAtPointRule = {
|
|
|
5612
6342
|
};
|
|
5613
6343
|
|
|
5614
6344
|
// src/stamps/geometry-2d/ai/rules/externalBisectorCutsLines.ts
|
|
5615
|
-
var
|
|
6345
|
+
var PREFILTER37 = /phân\s*giác\s+ngoài\s+(?:của\s+)?góc\s+[A-Z]{3}[^.]{0,30}?cắt/u;
|
|
5616
6346
|
var RE12 = new RegExp(
|
|
5617
6347
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+(?:ch\u1EE9a\\s+)?ph\xE2n\\s*gi\xE1c\\s+ngo\xE0i\\s+(?:c\u1EE7a\\s+)?g\xF3c\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+(?:c\xE1c\\s+(?:c\u1EA1nh|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng)\\s+)?([A-Z]{2})\\s*(?:,|v\xE0)\\s*([A-Z]{2})(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+(?:c\xE1c\\s+\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5618
6348
|
"gu"
|
|
@@ -5621,7 +6351,7 @@ var externalBisectorCutsLinesRule = {
|
|
|
5621
6351
|
id: "externalBisectorCutsLines",
|
|
5622
6352
|
priority: 57,
|
|
5623
6353
|
languages: ["vi"],
|
|
5624
|
-
patterns: [
|
|
6354
|
+
patterns: [PREFILTER37],
|
|
5625
6355
|
match(ctx) {
|
|
5626
6356
|
const out = [];
|
|
5627
6357
|
for (const c of ctx.clauses) {
|
|
@@ -5652,7 +6382,7 @@ var externalBisectorCutsLinesRule = {
|
|
|
5652
6382
|
};
|
|
5653
6383
|
|
|
5654
6384
|
// src/stamps/geometry-2d/ai/rules/tangentTwoSidesCircle.ts
|
|
5655
|
-
var
|
|
6385
|
+
var PREFILTER38 = /[Đđ]ường\s*tròn[^.]{0,30}?tiếp\s*xúc[^.]{0,40}?tiếp\s*xúc\s+trong/u;
|
|
5656
6386
|
var RE13 = new RegExp(
|
|
5657
6387
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s*\\(?\\s*([A-Z])\\s*\\)?\\s+ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?(?:c\xE1c\\s+c\u1EA1nh\\s+|c\u1EA1nh\\s+)?([A-Z]{2})\\s*,\\s*([A-Z]{2})(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*,\\s*([A-Z])(?!\\p{L})\\s+v\xE0\\s+ti\u1EBFp\\s*x\xFAc\\s+trong\\s+(?:v\u1EDBi\\s+)?\\(?\\s*[A-Z]\\s*\\)?\\s+(?:\u1EDF|t\u1EA1i)\\s+([A-Z])(?![A-Z])",
|
|
5658
6388
|
"gu"
|
|
@@ -5665,7 +6395,7 @@ var tangentTwoSidesCircleRule = {
|
|
|
5665
6395
|
id: "tangentTwoSidesCircle",
|
|
5666
6396
|
priority: 61,
|
|
5667
6397
|
languages: ["vi"],
|
|
5668
|
-
patterns: [
|
|
6398
|
+
patterns: [PREFILTER38],
|
|
5669
6399
|
match(ctx) {
|
|
5670
6400
|
const out = [];
|
|
5671
6401
|
for (const c of ctx.clauses) {
|
|
@@ -5701,14 +6431,22 @@ var tangentTwoSidesCircleRule = {
|
|
|
5701
6431
|
};
|
|
5702
6432
|
|
|
5703
6433
|
// src/stamps/geometry-2d/ai/rules/interiorPoint.ts
|
|
5704
|
-
var
|
|
5705
|
-
var
|
|
5706
|
-
var
|
|
6434
|
+
var SHAPE = "(?:(?:tam\\s*gi\xE1c|t\u1EE9\\s*gi\xE1c|\u0111\u01B0\u1EDDng\\s*tr\xF2n|h\xECnh(?:\\s+(?:ch\u1EEF\\s*nh\u1EADt|vu\xF4ng|thoi|b\xECnh\\s*h\xE0nh|thang))?)(?:\\s+(?:[A-Z]{3,4}(?![A-Z])|\\([^)]*\\)))?|\\([^)]*\\))";
|
|
6435
|
+
var QUAL = "(?:\\s+(?:b\u1EA5t\\s*k[i\xECy\u1EF3]|t[u\xF9][y\u1EF3]\\s*\xFD))?";
|
|
6436
|
+
var PREFILTER39 = /(?:nằm\s+)?(?:bên\s+)?trong\s+(?:tam\s*giác|tứ\s*giác|đường\s*tròn|hình|mặt\s*phẳng|\()/u;
|
|
6437
|
+
var RE14 = new RegExp(
|
|
6438
|
+
"(?:[Gg]\u1ECDi\\s+|[Ll]\u1EA5y\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:l\xE0\\s+)?(?:m\u1ED9t\\s+)?\u0111i\u1EC3m" + QUAL + "\\s+(?:n\u1EB1m\\s+)?(?:b\xEAn\\s+)?trong\\s+(?:m\u1EB7t\\s*ph\u1EB3ng\\s+(?:ch\u1EE9a\\s+)?)?" + SHAPE,
|
|
6439
|
+
"u"
|
|
6440
|
+
);
|
|
6441
|
+
var RE22 = new RegExp(
|
|
6442
|
+
"(?:m\u1ED9t\\s+)?[\u0110\u0111]i\u1EC3m\\s+([A-Z])(?!\\p{L})" + QUAL + "\\s+n\u1EB1m\\s+(?:b\xEAn\\s+)?trong\\s+" + SHAPE,
|
|
6443
|
+
"u"
|
|
6444
|
+
);
|
|
5707
6445
|
var interiorPointRule = {
|
|
5708
6446
|
id: "interiorPoint",
|
|
5709
6447
|
priority: 30,
|
|
5710
6448
|
languages: ["vi"],
|
|
5711
|
-
patterns: [
|
|
6449
|
+
patterns: [PREFILTER39],
|
|
5712
6450
|
match(ctx) {
|
|
5713
6451
|
const out = [];
|
|
5714
6452
|
for (const c of ctx.clauses) {
|
|
@@ -5726,7 +6464,7 @@ var interiorPointRule = {
|
|
|
5726
6464
|
};
|
|
5727
6465
|
|
|
5728
6466
|
// src/stamps/geometry-2d/ai/rules/parallelCutsTangentAt.ts
|
|
5729
|
-
var
|
|
6467
|
+
var PREFILTER40 = /[Đđ]ường\s*thẳng\s+qua\s+[A-Z][^.]{0,40}?tiếp\s*tuyến\s+tại\s+[A-Z]/u;
|
|
5730
6468
|
var CIRCLE_REF5 = /đường\s*tròn\s*\(\s*([A-Z])\s*\)|\(\s*([A-Z])\s*\)/u;
|
|
5731
6469
|
var RE15 = new RegExp(
|
|
5732
6470
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(song\\s*song|vu\xF4ng\\s*g\xF3c)\\s+(?:v\u1EDBi\\s+)?([A-Z]{2})(?!\\p{L})\\s+c\u1EAFt\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+t\u1EA1i\\s+([A-Z])(?!\\p{L})(?:\\s+c\u1EE7a\\s+[^.]{0,12}?)?\\s+(?:\u1EDF|t\u1EA1i)\\s+([A-Z])(?![A-Z])",
|
|
@@ -5740,7 +6478,7 @@ var parallelCutsTangentAtRule = {
|
|
|
5740
6478
|
id: "parallelCutsTangentAt",
|
|
5741
6479
|
priority: 52,
|
|
5742
6480
|
languages: ["vi"],
|
|
5743
|
-
patterns: [
|
|
6481
|
+
patterns: [PREFILTER40],
|
|
5744
6482
|
match(ctx) {
|
|
5745
6483
|
const circle = circleName4(ctx.problem);
|
|
5746
6484
|
if (!circle) return [];
|
|
@@ -5771,7 +6509,7 @@ var parallelCutsTangentAtRule = {
|
|
|
5771
6509
|
};
|
|
5772
6510
|
|
|
5773
6511
|
// src/stamps/geometry-2d/ai/rules/inExCircleTangentBC.ts
|
|
5774
|
-
var
|
|
6512
|
+
var PREFILTER41 = /[Đđ]ường\s*tròn\s+(?:nội|bàng)\s*tiếp[^.]{0,60}?tâm\s+[A-Z][^.]{0,30}?tiếp\s*xúc/u;
|
|
5775
6513
|
var TANGENT_TAIL = "\\s*,?\\s*ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?(?:c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?([A-Z]{2})(?!\\p{L})\\s+(?:\u1EDF|t\u1EA1i)\\s+([A-Z])(?![A-Z])";
|
|
5776
6514
|
var INCIRCLE = new RegExp(
|
|
5777
6515
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+n\u1ED9i\\s*ti\u1EBFp\\s+tam\\s*gi\xE1c\\s+([A-Z]{3})(?![A-Z])[^.]{0,20}?t\xE2m\\s+([A-Z])(?!\\p{L})" + TANGENT_TAIL,
|
|
@@ -5785,7 +6523,7 @@ var inExCircleTangentBCRule = {
|
|
|
5785
6523
|
id: "inExCircleTangentBC",
|
|
5786
6524
|
priority: 69,
|
|
5787
6525
|
languages: ["vi"],
|
|
5788
|
-
patterns: [
|
|
6526
|
+
patterns: [PREFILTER41],
|
|
5789
6527
|
match(ctx) {
|
|
5790
6528
|
const out = [];
|
|
5791
6529
|
for (const c of ctx.clauses) {
|
|
@@ -5827,7 +6565,7 @@ var inExCircleTangentBCRule = {
|
|
|
5827
6565
|
};
|
|
5828
6566
|
|
|
5829
6567
|
// src/stamps/geometry-2d/ai/rules/tangentPointsFromExt.ts
|
|
5830
|
-
var
|
|
6568
|
+
var PREFILTER42 = /[Tt]ừ\s+(?:điểm\s+)?[A-Z][^.]{0,60}?tiếp\s*tuyến\s+[A-Z]{2}\s*,\s*[A-Z]{2}/u;
|
|
5831
6569
|
var RE16 = new RegExp(
|
|
5832
6570
|
"[Tt]\u1EEB\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,60}?ti\u1EBFp\\s*tuy\u1EBFn\\s+([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])(?!\\p{L})",
|
|
5833
6571
|
"gu"
|
|
@@ -5837,7 +6575,7 @@ var tangentPointsFromExtRule = {
|
|
|
5837
6575
|
id: "tangentPointsFromExt",
|
|
5838
6576
|
priority: 50,
|
|
5839
6577
|
languages: ["vi"],
|
|
5840
|
-
patterns: [
|
|
6578
|
+
patterns: [PREFILTER42],
|
|
5841
6579
|
match(ctx) {
|
|
5842
6580
|
const out = [];
|
|
5843
6581
|
for (const c of ctx.clauses) {
|
|
@@ -5872,7 +6610,11 @@ var VN_FORM2 = new RegExp(
|
|
|
5872
6610
|
"u"
|
|
5873
6611
|
);
|
|
5874
6612
|
var VN_REV = new RegExp(
|
|
5875
|
-
String.raw`điểm\s+([A-Z])(?:['′]?)
|
|
6613
|
+
String.raw`điểm\s+([A-Z])(?:['′]?)[^.A-Z]{0,18}?ngoài\s+đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-Z])?`,
|
|
6614
|
+
"u"
|
|
6615
|
+
);
|
|
6616
|
+
var VN_REV_BARE = new RegExp(
|
|
6617
|
+
String.raw`(?<![\p{L}])([A-Z])(?:['′]?)[^.A-Z]{0,18}?ngoài\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)`,
|
|
5876
6618
|
"u"
|
|
5877
6619
|
);
|
|
5878
6620
|
var RESOLVE_CENTER2 = /đường\s*tròn\s*(?:\(\s*)?(?:tâm\s+)?([A-Z])(?![A-Za-z])|\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)/u;
|
|
@@ -5901,17 +6643,23 @@ var circleExternalPointRule = {
|
|
|
5901
6643
|
} else if (VN_METRIC.test(c.text)) {
|
|
5902
6644
|
const mm = VN_METRIC.exec(c.text);
|
|
5903
6645
|
const [ctr, r, pt, pair, d] = [mm[1], mm[2], mm[3], mm[4], mm[5]];
|
|
5904
|
-
const
|
|
5905
|
-
if (pair.includes(ctr) && pair.includes(pt) &&
|
|
6646
|
+
const norm5 = (s) => parseFloat(s.replace(",", "."));
|
|
6647
|
+
if (pair.includes(ctr) && pair.includes(pt) && norm5(d) > norm5(r)) {
|
|
5906
6648
|
center = ctr;
|
|
5907
6649
|
ext = pt;
|
|
5908
6650
|
}
|
|
5909
6651
|
} else {
|
|
5910
6652
|
const rv = VN_REV.exec(c.text);
|
|
5911
|
-
if (
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
6653
|
+
if (rv) {
|
|
6654
|
+
ext = rv[1];
|
|
6655
|
+
const rc = rv[2] ? void 0 : RESOLVE_CENTER2.exec(ctx.problem);
|
|
6656
|
+
center = rv[2] ?? rc?.[1] ?? rc?.[2] ?? "O";
|
|
6657
|
+
} else {
|
|
6658
|
+
const rb = VN_REV_BARE.exec(c.text);
|
|
6659
|
+
if (!rb) continue;
|
|
6660
|
+
ext = rb[1];
|
|
6661
|
+
center = rb[2];
|
|
6662
|
+
}
|
|
5915
6663
|
}
|
|
5916
6664
|
if (!center || !ext || center === ext) continue;
|
|
5917
6665
|
out.push({
|
|
@@ -5928,7 +6676,7 @@ var circleExternalPointRule = {
|
|
|
5928
6676
|
};
|
|
5929
6677
|
|
|
5930
6678
|
// src/stamps/geometry-2d/ai/rules/tangentNamedFromExt.ts
|
|
5931
|
-
var
|
|
6679
|
+
var PREFILTER43 = /(?:[Kk]ẻ|[Vv]ẽ)\s+(?:các\s+|hai\s+|2\s+)?tiếp\s*tuyến\s+(?:thứ\s+\S+\s+)?[A-Z]{2}|[Cc]ác\s+tiếp\s*tuyến\s+với|[Tt]iếp\s*tuyến\s+[A-Z]{2}\s*(?:,|và)\s*[A-Z]{2}|[Tt]ừ\s+[A-Z][^.;]{0,20}?(?:kẻ|vẽ)\s+(?:hai|2|các)\s+tiếp\s*tuyến|[A-Z]{2}\s*(?:,|và)\s*[A-Z]{2}\s+là\s+(?:hai\s+|2\s+|các\s+)?tiếp\s*tuyến/u;
|
|
5932
6680
|
var TWO = new RegExp(
|
|
5933
6681
|
"(?:[Kk]\u1EBB|[Vv]\u1EBD)\\s+(?:c\xE1c\\s+|hai\\s+|2\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+([A-Z])([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])([A-Z])(?!\\p{L})",
|
|
5934
6682
|
"gu"
|
|
@@ -5941,6 +6689,18 @@ var FROM_TOUCH = new RegExp(
|
|
|
5941
6689
|
"[Cc]\xE1c\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+v\u1EDBi\\s+\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+k\u1EBB\\s+t\u1EEB\\s+([A-Z])(?!\\p{L})[^.;]{0,40}?t\u1EA1i\\s+([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])(?!\\p{L})",
|
|
5942
6690
|
"gu"
|
|
5943
6691
|
);
|
|
6692
|
+
var FROM_DRAW_TOUCH = new RegExp(
|
|
6693
|
+
"[Tt]\u1EEB\\s+([A-Z])(?!\\p{L})[^.;]{0,20}?(?:k\u1EBB|v\u1EBD)\\s+(?:hai|2|c\xE1c)\\s+ti\u1EBFp\\s*tuy\u1EBFn[^.;]{0,40}?t\u1EA1i\\s+([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])(?!\\p{L})",
|
|
6694
|
+
"gu"
|
|
6695
|
+
);
|
|
6696
|
+
var TWO_BARE = new RegExp(
|
|
6697
|
+
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+([A-Z])([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])([A-Z])(?!\\p{L})",
|
|
6698
|
+
"gu"
|
|
6699
|
+
);
|
|
6700
|
+
var NAME_BEFORE_TWO = new RegExp(
|
|
6701
|
+
"([A-Z])([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])([A-Z])(?!\\p{L})\\s+l\xE0\\s+(?:hai\\s+|2\\s+|c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn",
|
|
6702
|
+
"gu"
|
|
6703
|
+
);
|
|
5944
6704
|
var TOUCH_APPOS = new RegExp(
|
|
5945
6705
|
"^(?:v\u1EDBi\\s+)?([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])\\s+l\xE0\\s+(?:c\xE1c\\s+|hai\\s+|2\\s+)?ti\u1EBFp\\s*\u0111i\u1EC3m",
|
|
5946
6706
|
"u"
|
|
@@ -5950,7 +6710,7 @@ var tangentNamedFromExtRule = {
|
|
|
5950
6710
|
id: "tangentNamedFromExt",
|
|
5951
6711
|
priority: 50,
|
|
5952
6712
|
languages: ["vi"],
|
|
5953
|
-
patterns: [
|
|
6713
|
+
patterns: [PREFILTER43],
|
|
5954
6714
|
match(ctx) {
|
|
5955
6715
|
const par = PAREN_CIRCLE2.exec(ctx.problem);
|
|
5956
6716
|
const circle = par ? par[1] : "O";
|
|
@@ -5979,14 +6739,43 @@ var tangentNamedFromExtRule = {
|
|
|
5979
6739
|
});
|
|
5980
6740
|
}
|
|
5981
6741
|
}
|
|
6742
|
+
for (const c of ctx.clauses) {
|
|
6743
|
+
if (twoClauses.has(c.id)) continue;
|
|
6744
|
+
TWO_BARE.lastIndex = 0;
|
|
6745
|
+
NAME_BEFORE_TWO.lastIndex = 0;
|
|
6746
|
+
const hits = [...c.text.matchAll(TWO_BARE), ...c.text.matchAll(NAME_BEFORE_TWO)];
|
|
6747
|
+
for (const m of hits) {
|
|
6748
|
+
if (twoClauses.has(c.id)) continue;
|
|
6749
|
+
const ext = m[1];
|
|
6750
|
+
if (m[3] !== ext) continue;
|
|
6751
|
+
const p = m[2];
|
|
6752
|
+
const q = m[4];
|
|
6753
|
+
if (p === q || p === ext || q === ext) continue;
|
|
6754
|
+
twoClauses.add(c.id);
|
|
6755
|
+
whichOf.set(ext, 1);
|
|
6756
|
+
out.push({
|
|
6757
|
+
ruleId: "tangentNamedFromExt",
|
|
6758
|
+
clauseIds: [c.id],
|
|
6759
|
+
intents: [
|
|
6760
|
+
addPoint(p, { kind: "tangentPoint", from: ext, circle, which: 0 }),
|
|
6761
|
+
addPoint(q, { kind: "tangentPoint", from: ext, circle, which: 1 }),
|
|
6762
|
+
connect(ext, p, "segment"),
|
|
6763
|
+
connect(ext, q, "segment")
|
|
6764
|
+
]
|
|
6765
|
+
});
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
5982
6768
|
for (const c of ctx.clauses) {
|
|
5983
6769
|
if (twoClauses.has(c.id)) continue;
|
|
5984
6770
|
FROM_TOUCH.lastIndex = 0;
|
|
5985
|
-
|
|
6771
|
+
FROM_DRAW_TOUCH.lastIndex = 0;
|
|
6772
|
+
const hits = [...c.text.matchAll(FROM_TOUCH), ...c.text.matchAll(FROM_DRAW_TOUCH)];
|
|
6773
|
+
for (const m of hits) {
|
|
5986
6774
|
const ext = m[1];
|
|
5987
6775
|
const p = m[2];
|
|
5988
6776
|
const q = m[3];
|
|
5989
6777
|
if (p === q || p === ext || q === ext) continue;
|
|
6778
|
+
if (twoClauses.has(c.id)) continue;
|
|
5990
6779
|
twoClauses.add(c.id);
|
|
5991
6780
|
whichOf.set(ext, 1);
|
|
5992
6781
|
out.push({
|
|
@@ -6040,11 +6829,16 @@ var tangentNamedFromExtRule = {
|
|
|
6040
6829
|
// src/stamps/geometry-2d/ai/rules/secant.ts
|
|
6041
6830
|
var CAT_TUYEN = /cát\s*tuyến\s+([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
6042
6831
|
var LINE_THROUGH = new RegExp(
|
|
6043
|
-
String.raw`[Qq]ua\s+(?:điểm\s+)?([A-Z])(?!\p{L})[^.]{0,30}?cắt\s+(?:đường\s*tròn\s*)?(?:\(\s*([A-Z])\s*\)\s
|
|
6832
|
+
String.raw`[Qq]ua\s+(?:điểm\s+)?([A-Z])(?!\p{L})[^.]{0,30}?cắt\s+(?:lại\s+)?(?:nửa\s+)?(?:đường\s*tròn\s*)?(?:\(\s*([A-Z])\s*\)\s*|[A-Z]\s+)?(?:tại|ở)\s+(?:hai\s+|2\s+)?(?:điểm\s+)?([A-Z])\s*(?:và|,)\s*([A-Z])(?![A-Z])`,
|
|
6833
|
+
"gu"
|
|
6834
|
+
);
|
|
6835
|
+
var CAT_CUTS = new RegExp(
|
|
6836
|
+
String.raw`cát\s*tuyến\s+(?:[a-z][a-z0-9]?\s+)?cắt\s+(?:lại\s+)?(?:nửa\s+)?(?:đường\s*tròn\s*)?(?:\(\s*([A-Z])\s*\)\s*|[A-Z]\s+)?(?:tại|ở)\s+(?:hai\s+|2\s+)?(?:điểm\s+)?([A-Z])\s*(?:và|,)\s*([A-Z])(?![A-Z])`,
|
|
6044
6837
|
"gu"
|
|
6045
6838
|
);
|
|
6046
6839
|
var PAREN_CIRCLE3 = /\(\s*([A-Z])\s*\)/u;
|
|
6047
|
-
var
|
|
6840
|
+
var EXT_RESOLVE = /([A-Z])(?!\p{L})[^.A-Z]{0,25}?(?:ở\s+|nằm\s+)?ngoài\s+(?:\(|nửa|đường\s*tròn)/u;
|
|
6841
|
+
var PREFILTER44 = /cát\s*tuyến|[Qq]ua\s+(?:điểm\s+)?[A-Z][^.]{0,30}?cắt/u;
|
|
6048
6842
|
function secantIntents(ext, near, far, circle, theta) {
|
|
6049
6843
|
if ((/* @__PURE__ */ new Set([ext, near, far])).size !== 3) return null;
|
|
6050
6844
|
const line = ext + near;
|
|
@@ -6058,13 +6852,26 @@ var secantRule = {
|
|
|
6058
6852
|
id: "secant",
|
|
6059
6853
|
priority: 58,
|
|
6060
6854
|
languages: ["vi"],
|
|
6061
|
-
patterns: [
|
|
6855
|
+
patterns: [PREFILTER44],
|
|
6062
6856
|
match(ctx) {
|
|
6063
6857
|
const par = PAREN_CIRCLE3.exec(ctx.problem);
|
|
6064
6858
|
const fallbackCircle = par ? par[1] : "O";
|
|
6859
|
+
const extM = EXT_RESOLVE.exec(ctx.problem);
|
|
6860
|
+
const resolvedExt = extM ? extM[1] : void 0;
|
|
6065
6861
|
const out = [];
|
|
6066
6862
|
let theta = 2.4;
|
|
6067
6863
|
for (const c of ctx.clauses) {
|
|
6864
|
+
if (resolvedExt) {
|
|
6865
|
+
CAT_CUTS.lastIndex = 0;
|
|
6866
|
+
for (const m of c.text.matchAll(CAT_CUTS)) {
|
|
6867
|
+
const circle = m[1] ?? fallbackCircle;
|
|
6868
|
+
const intents = secantIntents(resolvedExt, m[2], m[3], circle, theta);
|
|
6869
|
+
if (intents) {
|
|
6870
|
+
out.push({ ruleId: "secant", clauseIds: [c.id], intents });
|
|
6871
|
+
theta += 0.7;
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6068
6875
|
CAT_TUYEN.lastIndex = 0;
|
|
6069
6876
|
for (const m of c.text.matchAll(CAT_TUYEN)) {
|
|
6070
6877
|
const intents = secantIntents(m[1], m[2], m[3], fallbackCircle, theta);
|
|
@@ -6089,14 +6896,21 @@ var secantRule = {
|
|
|
6089
6896
|
|
|
6090
6897
|
// src/stamps/geometry-2d/ai/rules/oppositeRayPoint.ts
|
|
6091
6898
|
var RE17 = new RegExp(
|
|
6092
|
-
String.raw`[Tt]rên\s+tia\s+đối\s+(?:của\s+)?tia\s+([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?lấy\s+(?:một\s+)?(?:điểm\s+)?([A-Z])(?![A-Z])`,
|
|
6899
|
+
String.raw`[Tt]rên\s+tia\s+đối\s+(?:của\s+)?(?:tia\s+)?([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?lấy\s+(?:một\s+)?(?:điểm\s+)?([A-Z])(?![A-Z])`,
|
|
6093
6900
|
"gu"
|
|
6094
6901
|
);
|
|
6095
6902
|
var RE_NAME_FIRST = new RegExp(
|
|
6096
|
-
String.raw`[Ll]ấy\s+(?:điểm\s+)?([A-Z])(?![A-Z])\s+trên\s+tia\s+đối\s+(?:của\s+)?tia\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
6903
|
+
String.raw`[Ll]ấy\s+(?:điểm\s+)?([A-Z])(?![A-Z])\s+trên\s+tia\s+đối\s+(?:của\s+)?(?:tia\s+)?([A-Z])([A-Z])(?![A-Z])`,
|
|
6097
6904
|
"gu"
|
|
6098
6905
|
);
|
|
6906
|
+
var RE_DISTRIB = new RegExp(
|
|
6907
|
+
String.raw`[Ll]ấy\s+(?:các\s+)?(?:điểm\s+)?((?:[A-Z]\s*(?:,|và)\s*)+[A-Z])(?![A-Z])\s+(?:lần\s*lượt\s+|(?:theo\s+)?thứ\s+tự\s+)?(?:thuộc|trên|nằm\s+trên)\s+(?:các\s+)?tia\s+đối\s+(?:của\s+)?(?:các\s+)?(?:tia\s+)?((?:[A-Z]{2}\s*(?:,|và)\s*)+[A-Z]{2})(?![A-Z])`,
|
|
6908
|
+
"u"
|
|
6909
|
+
);
|
|
6099
6910
|
var CANON = 2.5;
|
|
6911
|
+
function splitNames2(blob) {
|
|
6912
|
+
return blob.split(/\s*,\s*|\s+và\s+/u).map((s) => s.trim()).filter(Boolean);
|
|
6913
|
+
}
|
|
6100
6914
|
var oppositeRayPointRule = {
|
|
6101
6915
|
id: "oppositeRayPoint",
|
|
6102
6916
|
priority: 56,
|
|
@@ -6105,6 +6919,35 @@ var oppositeRayPointRule = {
|
|
|
6105
6919
|
match(ctx) {
|
|
6106
6920
|
const out = [];
|
|
6107
6921
|
for (const c of ctx.clauses) {
|
|
6922
|
+
const dm = RE_DISTRIB.exec(c.text);
|
|
6923
|
+
if (dm) {
|
|
6924
|
+
const names = splitNames2(dm[1]);
|
|
6925
|
+
const rays = splitNames2(dm[2]);
|
|
6926
|
+
if (names.length >= 2 && names.length === rays.length) {
|
|
6927
|
+
const intents = [];
|
|
6928
|
+
let valid3 = true;
|
|
6929
|
+
for (let i = 0; i < names.length; i++) {
|
|
6930
|
+
const a = rays[i][0];
|
|
6931
|
+
const b = rays[i][1];
|
|
6932
|
+
if ((/* @__PURE__ */ new Set([a, b, names[i]])).size !== 3) {
|
|
6933
|
+
valid3 = false;
|
|
6934
|
+
break;
|
|
6935
|
+
}
|
|
6936
|
+
intents.push(
|
|
6937
|
+
addPoint(names[i], {
|
|
6938
|
+
kind: "pointAtDistance",
|
|
6939
|
+
from: b,
|
|
6940
|
+
through: a,
|
|
6941
|
+
distance: { kind: "literal", value: CANON }
|
|
6942
|
+
})
|
|
6943
|
+
);
|
|
6944
|
+
}
|
|
6945
|
+
if (valid3) {
|
|
6946
|
+
out.push({ ruleId: "oppositeRayPoint", clauseIds: [c.id], intents });
|
|
6947
|
+
continue;
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
}
|
|
6108
6951
|
RE17.lastIndex = 0;
|
|
6109
6952
|
for (const m of c.text.matchAll(RE17)) {
|
|
6110
6953
|
const a = m[1];
|
|
@@ -6160,7 +7003,7 @@ var CENTER3 = /\(\s*([A-Z])\s*[;,)]|tâm\s+([A-Z])(?![A-Za-z])/u;
|
|
|
6160
7003
|
function appearsBefore(problem, chordText, d) {
|
|
6161
7004
|
const pos = problem.indexOf(chordText);
|
|
6162
7005
|
if (pos < 0) return true;
|
|
6163
|
-
return new RegExp(`(?<![A-Za-z])${d}(?![a-z])`, "u").test(problem.slice(0, pos));
|
|
7006
|
+
return new RegExp(`(?<![A-Za-z])${escapeRe(d)}(?![a-z])`, "u").test(problem.slice(0, pos));
|
|
6164
7007
|
}
|
|
6165
7008
|
var perpChordAtFootRule = {
|
|
6166
7009
|
id: "perpChordAtFoot",
|
|
@@ -6242,12 +7085,12 @@ var diameterCircleSecantRule = {
|
|
|
6242
7085
|
CUTS.lastIndex = 0;
|
|
6243
7086
|
for (const m of c.text.matchAll(CUTS)) {
|
|
6244
7087
|
const line = m[1];
|
|
6245
|
-
const
|
|
6246
|
-
if (line.includes(
|
|
7088
|
+
const z47 = m[2];
|
|
7089
|
+
if (line.includes(z47) || onCircle.has(z47)) continue;
|
|
6247
7090
|
const other = onCircle.has(line[0]) ? line[0] : onCircle.has(line[1]) ? line[1] : void 0;
|
|
6248
7091
|
if (!other) continue;
|
|
6249
|
-
onCircle.add(
|
|
6250
|
-
const intent = addPoint(
|
|
7092
|
+
onCircle.add(z47);
|
|
7093
|
+
const intent = addPoint(z47, { kind: "secondIntersection", line, circle, other });
|
|
6251
7094
|
out.push({ ruleId: "diameterCircleSecant", clauseIds: [c.id], intents: [intent] });
|
|
6252
7095
|
}
|
|
6253
7096
|
}
|
|
@@ -6256,7 +7099,7 @@ var diameterCircleSecantRule = {
|
|
|
6256
7099
|
};
|
|
6257
7100
|
|
|
6258
7101
|
// src/stamps/geometry-2d/ai/rules/tangentsAtMeet.ts
|
|
6259
|
-
var
|
|
7102
|
+
var PREFILTER45 = /[Tt]iếp\s*tuyến[^.]{0,40}?tại\s+[A-Z][^.]{0,30}?(?:và|,)\s*[A-Z][^.]{0,30}?cắt\s+nhau/u;
|
|
6260
7103
|
var CIRCLE_REF6 = /đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-ZωΩ])(?:\s*[;,]\s*[Rr])?\s*\)?|\(\s*([A-ZωΩ])\s*\)/u;
|
|
6261
7104
|
var RE19 = new RegExp(
|
|
6262
7105
|
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+(?:c\u1EE7a\\s+[^.]{0,14}?\\s+)?t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:v\xE0|,)\\s*([A-Z])(?!\\p{L})(?:\\s+c\u1EE7a\\s+[^.]{0,14}?)?\\s+c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
@@ -6273,7 +7116,7 @@ var tangentsAtMeetRule = {
|
|
|
6273
7116
|
priority: 55,
|
|
6274
7117
|
// trên intersection(45) — 2 tiếp tuyến + giao trong CÙNG match nên tự đủ thứ tự
|
|
6275
7118
|
languages: ["vi"],
|
|
6276
|
-
patterns: [
|
|
7119
|
+
patterns: [PREFILTER45],
|
|
6277
7120
|
match(ctx) {
|
|
6278
7121
|
const circle = circleName5(ctx.problem);
|
|
6279
7122
|
if (!circle) return [];
|
|
@@ -6301,7 +7144,7 @@ var tangentsAtMeetRule = {
|
|
|
6301
7144
|
};
|
|
6302
7145
|
|
|
6303
7146
|
// src/stamps/geometry-2d/ai/rules/angleBisectorCutsSideCircle.ts
|
|
6304
|
-
var
|
|
7147
|
+
var PREFILTER46 = /phân\s*giác[^.]{0,40}?cắt/u;
|
|
6305
7148
|
var FOOT = new RegExp(
|
|
6306
7149
|
"ph\xE2n\\s*gi\xE1c\\s+(?:trong\\s+)?(?:c\u1EE7a\\s+)?g\xF3c\\s+([A-Z])([A-Z])?([A-Z])?(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+(?:c\u1EA1nh\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
6307
7150
|
"u"
|
|
@@ -6316,7 +7159,7 @@ var angleBisectorCutsSideCircleRule = {
|
|
|
6316
7159
|
priority: 48,
|
|
6317
7160
|
// sau triangle/circle/cevian; trước intersection(45)
|
|
6318
7161
|
languages: ["vi"],
|
|
6319
|
-
patterns: [
|
|
7162
|
+
patterns: [PREFILTER46],
|
|
6320
7163
|
match(ctx) {
|
|
6321
7164
|
const out = [];
|
|
6322
7165
|
for (const c of ctx.clauses) {
|
|
@@ -6362,7 +7205,7 @@ var angleBisectorCutsSideCircleRule = {
|
|
|
6362
7205
|
};
|
|
6363
7206
|
|
|
6364
7207
|
// src/stamps/geometry-2d/ai/rules/circleCutsArcSecond.ts
|
|
6365
|
-
var
|
|
7208
|
+
var PREFILTER47 = /đường\s*tròn\s+đường\s*kính\s+[A-Z]{2}[^.]{0,40}?cắt\s+(?:cung|(?:nửa\s+)?đường\s*tròn)/u;
|
|
6366
7209
|
var CUTS_ARC = new RegExp(
|
|
6367
7210
|
"\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?([A-Z])([A-Z])(?![A-Z])[^.]{0,24}?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
6368
7211
|
"u"
|
|
@@ -6377,7 +7220,7 @@ var circleCutsArcSecondRule = {
|
|
|
6377
7220
|
priority: 45,
|
|
6378
7221
|
// sau mọi rule dựng đường tròn + điểm chung
|
|
6379
7222
|
languages: ["vi"],
|
|
6380
|
-
patterns: [
|
|
7223
|
+
patterns: [PREFILTER47],
|
|
6381
7224
|
match(ctx) {
|
|
6382
7225
|
const out = [];
|
|
6383
7226
|
for (const c of ctx.clauses) {
|
|
@@ -6416,7 +7259,7 @@ var circleCutsArcSecondRule = {
|
|
|
6416
7259
|
};
|
|
6417
7260
|
|
|
6418
7261
|
// src/stamps/geometry-2d/ai/rules/angleBisectorCutsCircleLine.ts
|
|
6419
|
-
var
|
|
7262
|
+
var PREFILTER48 = /[Pp]hân\s*giác[^.]{0,40}?cắt\s+(?:đường\s*tròn|\()/u;
|
|
6420
7263
|
var RE20 = new RegExp(
|
|
6421
7264
|
"[Pp]h\xE2n\\s*gi\xE1c\\s+(?:trong\\s+)?(?:c\u1EE7a\\s+)?g\xF3c\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])\\s+c\u1EAFt\\s+(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*)?(?:\\(\\s*([A-Z])(?:['\u2032]?)\\s*\\))?[^.]{0,16}?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+[A-Z](?![A-Z]))?(?:\\s*,?\\s*v\xE0\\s+(?:c\u1EAFt\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{2})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z]))?",
|
|
6422
7265
|
"u"
|
|
@@ -6429,7 +7272,7 @@ var angleBisectorCutsCircleLineRule = {
|
|
|
6429
7272
|
// perpFoot 65 dựng tia/C/H trước).
|
|
6430
7273
|
priority: 49,
|
|
6431
7274
|
languages: ["vi"],
|
|
6432
|
-
patterns: [
|
|
7275
|
+
patterns: [PREFILTER48],
|
|
6433
7276
|
match(ctx) {
|
|
6434
7277
|
const out = [];
|
|
6435
7278
|
for (const c of ctx.clauses) {
|
|
@@ -6461,17 +7304,34 @@ var angleBisectorCutsCircleLineRule = {
|
|
|
6461
7304
|
};
|
|
6462
7305
|
|
|
6463
7306
|
// src/stamps/geometry-2d/ai/rules/tangentLineNamedAtPoint.ts
|
|
6464
|
-
var
|
|
7307
|
+
var PREFILTER49 = /(?:trên|thuộc)\s+\(\s*[A-Z][^)]*\)[^.]{0,24}?kẻ\s+tiếp\s*tuyến\s+[a-z]|tiếp\s*tuyến\s+[a-z][0-9]?\s+(?:của|với)\s+|(?:[Qq]ua|[Tt]ừ)\s+[A-Z]\s+(?:vẽ|kẻ)\s+tiếp\s*tuyến\s+[a-z]/u;
|
|
6465
7308
|
var RE21 = new RegExp(
|
|
6466
7309
|
"(?:t\u1EEB\\s+|qua\\s+)?(?:m\u1ED9t\\s+)?\u0111i\u1EC3m\\s+([A-Z])(?![A-Z])\\s+(?:tr\xEAn|thu\u1ED9c)\\s+\\(\\s*([A-Z])(?:['\u2032]?)\\s*\\)[^.]{0,24}?k\u1EBB\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+([a-z][0-9]?)(?!\\p{L})",
|
|
6467
7310
|
"gu"
|
|
6468
7311
|
);
|
|
7312
|
+
var RE_NAMED_OF_AT = new RegExp(
|
|
7313
|
+
"(?:[VvKk]\u1EBD|[Kk]\u1EBB|[Dd]\u1EF1ng)?\\s*ti\u1EBFp\\s*tuy\u1EBFn\\s+([a-z][0-9]?)(?!\\p{L})\\s+(?:c\u1EE7a|v\u1EDBi)\\s+(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*)?\\(\\s*([A-Z])(?:['\u2032]?)\\s*\\)\\s+t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
7314
|
+
"gu"
|
|
7315
|
+
);
|
|
7316
|
+
var RE_THROUGH_NAMED = new RegExp(
|
|
7317
|
+
"(?:[Qq]ua|[Tt]\u1EEB)\\s+([A-Z])(?![A-Z])\\s+(?:v\u1EBD|k\u1EBB)\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+([a-z]{1,2}[0-9]?)(?!\\p{L})",
|
|
7318
|
+
"gu"
|
|
7319
|
+
);
|
|
7320
|
+
var PAREN_CIRCLE4 = /\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)|đường\s*tròn\s+tâm\s+([A-Z])(?![A-Za-z])/u;
|
|
7321
|
+
function circleDslName(problem, center) {
|
|
7322
|
+
return /đường\s*kính/u.test(problem) ? `${center}_c` : center;
|
|
7323
|
+
}
|
|
7324
|
+
function resolveProblemCircle(problem) {
|
|
7325
|
+
const m = PAREN_CIRCLE4.exec(problem);
|
|
7326
|
+
const center = m?.[1] ?? m?.[2];
|
|
7327
|
+
return center ? circleDslName(problem, center) : void 0;
|
|
7328
|
+
}
|
|
6469
7329
|
var tangentLineNamedAtPointRule = {
|
|
6470
7330
|
id: "tangentLineNamedAtPoint",
|
|
6471
7331
|
priority: 63,
|
|
6472
7332
|
// cao như tangentAt — đường d phải dựng trước điểm-trên-d
|
|
6473
7333
|
languages: ["vi"],
|
|
6474
|
-
patterns: [
|
|
7334
|
+
patterns: [PREFILTER49],
|
|
6475
7335
|
match(ctx) {
|
|
6476
7336
|
const out = [];
|
|
6477
7337
|
for (const c of ctx.clauses) {
|
|
@@ -6486,6 +7346,30 @@ var tangentLineNamedAtPointRule = {
|
|
|
6486
7346
|
intents: [drawLine(line, "tangentAt", { through, circle })]
|
|
6487
7347
|
});
|
|
6488
7348
|
}
|
|
7349
|
+
RE_NAMED_OF_AT.lastIndex = 0;
|
|
7350
|
+
for (const m of c.text.matchAll(RE_NAMED_OF_AT)) {
|
|
7351
|
+
const line = m[1];
|
|
7352
|
+
const circle = circleDslName(ctx.problem, m[2]);
|
|
7353
|
+
const through = m[3];
|
|
7354
|
+
out.push({
|
|
7355
|
+
ruleId: "tangentLineNamedAtPoint",
|
|
7356
|
+
clauseIds: [c.id],
|
|
7357
|
+
intents: [drawLine(line, "tangentAt", { through, circle })]
|
|
7358
|
+
});
|
|
7359
|
+
}
|
|
7360
|
+
RE_THROUGH_NAMED.lastIndex = 0;
|
|
7361
|
+
const probCircle = resolveProblemCircle(ctx.problem);
|
|
7362
|
+
if (probCircle) {
|
|
7363
|
+
for (const m of c.text.matchAll(RE_THROUGH_NAMED)) {
|
|
7364
|
+
const through = m[1];
|
|
7365
|
+
const line = m[2];
|
|
7366
|
+
out.push({
|
|
7367
|
+
ruleId: "tangentLineNamedAtPoint",
|
|
7368
|
+
clauseIds: [c.id],
|
|
7369
|
+
intents: [drawLine(line, "tangentAt", { through, circle: probCircle })]
|
|
7370
|
+
});
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
6489
7373
|
}
|
|
6490
7374
|
return out;
|
|
6491
7375
|
}
|
|
@@ -6531,13 +7415,13 @@ var givenDiameterCircleRule = {
|
|
|
6531
7415
|
|
|
6532
7416
|
// src/stamps/geometry-2d/ai/rules/multiDiameterCircles.ts
|
|
6533
7417
|
var RE23 = /đường\s*kính\s+(?:theo\s+thứ\s+tự\s+)?(?:là\s+)?((?:[A-Z]{2}\s*,\s*)+[A-Z]{2})(?![A-Z])[^.]{0,30}?tâm\s+(?:theo\s+thứ\s+tự\s+)?(?:là\s+)?((?:[A-Z](?:['′])?\s*,\s*)+[A-Z](?:['′])?)(?![A-Z])/u;
|
|
6534
|
-
var
|
|
7418
|
+
var PREFILTER50 = /đường\s*kính[^.]{0,40}?tâm/u;
|
|
6535
7419
|
var multiDiameterCirclesRule = {
|
|
6536
7420
|
id: "multiDiameterCircles",
|
|
6537
7421
|
priority: 67,
|
|
6538
7422
|
// như circleDiameter — dựng đầu mút + tâm trước điểm phái sinh
|
|
6539
7423
|
languages: ["vi"],
|
|
6540
|
-
patterns: [
|
|
7424
|
+
patterns: [PREFILTER50],
|
|
6541
7425
|
match(ctx) {
|
|
6542
7426
|
for (const c of ctx.clauses) {
|
|
6543
7427
|
const m = RE23.exec(c.text);
|
|
@@ -6589,12 +7473,12 @@ function diameterMap(problem) {
|
|
|
6589
7473
|
}
|
|
6590
7474
|
return map;
|
|
6591
7475
|
}
|
|
6592
|
-
var
|
|
7476
|
+
var PREFILTER51 = /giao\s*điểm\s+(?:của\s+)?[A-Z]{2}\s*,\s*[A-Z]{2}[^.]{0,30}?(?:với|cắt)\s+(?:các\s+)?(?:(?:nửa\s+)?đường\s*tròn\s*)?\(/u;
|
|
6593
7477
|
var cutCirclesDistribRule = {
|
|
6594
7478
|
id: "cutCirclesDistrib",
|
|
6595
7479
|
priority: 45,
|
|
6596
7480
|
languages: ["vi"],
|
|
6597
|
-
patterns: [
|
|
7481
|
+
patterns: [PREFILTER51],
|
|
6598
7482
|
match(ctx) {
|
|
6599
7483
|
const map = diameterMap(ctx.problem);
|
|
6600
7484
|
const out = [];
|
|
@@ -6622,6 +7506,7 @@ var cutCirclesDistribRule = {
|
|
|
6622
7506
|
// src/stamps/geometry-2d/ai/rules/givenNamedCircle.ts
|
|
6623
7507
|
var BARE_PAREN2 = /\(\s*([A-Z])\s*\)/gu;
|
|
6624
7508
|
var HAS_POINTS_ON = /(?:trên|thuộc)\s+(?:nửa\s+)?(?:đường\s*tròn|cung|\(\s*[A-Z]\s*\))/u;
|
|
7509
|
+
var HAS_TANGENT_OR_SECANT = /tiếp\s*tuyến|cát\s*tuyến/u;
|
|
6625
7510
|
var givenNamedCircleRule = {
|
|
6626
7511
|
id: "givenNamedCircle",
|
|
6627
7512
|
priority: 74,
|
|
@@ -6629,7 +7514,7 @@ var givenNamedCircleRule = {
|
|
|
6629
7514
|
languages: ["vi"],
|
|
6630
7515
|
patterns: [/\(\s*[A-Z]\s*\)/u],
|
|
6631
7516
|
match(ctx) {
|
|
6632
|
-
if (!HAS_POINTS_ON.test(ctx.problem)) return [];
|
|
7517
|
+
if (!HAS_POINTS_ON.test(ctx.problem) && !HAS_TANGENT_OR_SECANT.test(ctx.problem)) return [];
|
|
6633
7518
|
const out = [];
|
|
6634
7519
|
const seen = /* @__PURE__ */ new Set();
|
|
6635
7520
|
BARE_PAREN2.lastIndex = 0;
|
|
@@ -6637,12 +7522,14 @@ var givenNamedCircleRule = {
|
|
|
6637
7522
|
const center = m[1];
|
|
6638
7523
|
if (seen.has(center)) continue;
|
|
6639
7524
|
const c = center;
|
|
6640
|
-
if (new RegExp(`\\(\\s*${c}\\s*[;,]`, "u").test(ctx.problem)) continue;
|
|
6641
|
-
if (new RegExp(`\\(\\s*${c}\\s*\\)[^.]{0,30}?\u0111\u01B0\u1EDDng\\s*k\xEDnh|\u0111\u01B0\u1EDDng\\s*k\xEDnh[^.]{0,30}?\\(\\s*${c}\\s*\\)`, "u").test(ctx.problem)) continue;
|
|
6642
|
-
if (new RegExp(`(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp[^.]{0,30}?\\(\\s*${c}\\s*\\)|\\(\\s*${c}\\s*\\)[^.]{0,30}?(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp`, "u").test(ctx.problem)) continue;
|
|
6643
|
-
|
|
7525
|
+
if (new RegExp(`\\(\\s*${escapeRe(c)}\\s*[;,]`, "u").test(ctx.problem)) continue;
|
|
7526
|
+
if (new RegExp(`\\(\\s*${escapeRe(c)}\\s*\\)[^.]{0,30}?\u0111\u01B0\u1EDDng\\s*k\xEDnh|\u0111\u01B0\u1EDDng\\s*k\xEDnh[^.]{0,30}?\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(ctx.problem)) continue;
|
|
7527
|
+
if (new RegExp(`(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp[^.]{0,30}?\\(\\s*${escapeRe(c)}\\s*\\)|\\(\\s*${escapeRe(c)}\\s*\\)[^.]{0,30}?(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp`, "u").test(ctx.problem)) continue;
|
|
7528
|
+
const isNamedCircle = new RegExp(`${CIRCLE_KW}\\s*\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(ctx.problem);
|
|
7529
|
+
const isOpenedBare = new RegExp(`^[Cc]ho\\s+\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(ctx.problem.trimStart());
|
|
7530
|
+
if (!isNamedCircle && !isOpenedBare) continue;
|
|
6644
7531
|
seen.add(center);
|
|
6645
|
-
const owner = ctx.clauses.find((cl) => new RegExp(`\\(\\s*${c}\\s*\\)`, "u").test(cl.text));
|
|
7532
|
+
const owner = ctx.clauses.find((cl) => new RegExp(`\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(cl.text));
|
|
6646
7533
|
out.push({
|
|
6647
7534
|
ruleId: "givenNamedCircle",
|
|
6648
7535
|
clauseIds: owner ? [owner.id] : [],
|
|
@@ -6654,12 +7541,16 @@ var givenNamedCircleRule = {
|
|
|
6654
7541
|
};
|
|
6655
7542
|
|
|
6656
7543
|
// src/stamps/geometry-2d/ai/rules/twoPerpLinesMeet.ts
|
|
6657
|
-
var
|
|
7544
|
+
var PREFILTER52 = /(?:[Đđ]ường\s*thẳng\s+qua|(?:Qua|qua|Từ|từ)\s+[A-Z][^.]{0,20}?(?:vẽ|kẻ|dựng)\s+đường\s*thẳng)\s*[^.]{0,30}?(?:vuông\s*góc|⊥)[^.]{0,40}?cắt\s+(?:[Đđ]ường\s*thẳng\s+qua|nhau)/u;
|
|
6658
7545
|
var RE25 = new RegExp(
|
|
6659
7546
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
6660
7547
|
"gu"
|
|
6661
7548
|
);
|
|
6662
|
-
var
|
|
7549
|
+
var RE_DRAW_FIRST = new RegExp(
|
|
7550
|
+
"(?:Qua|qua|T\u1EEB|t\u1EEB)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s*[^.]{0,16}?(?:v\u1EBD|k\u1EBB|d\u1EF1ng)\\s+\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s*[^.]{0,16}?c\u1EAFt\\s+[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
7551
|
+
"gu"
|
|
7552
|
+
);
|
|
7553
|
+
var RE_DISTRIB2 = new RegExp(
|
|
6663
7554
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s*,\\s*([A-Z])(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s*th\u1EE9\\s*t\u1EF1\\s+)?(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s*,\\s*([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
6664
7555
|
"gu"
|
|
6665
7556
|
);
|
|
@@ -6668,7 +7559,7 @@ var twoPerpLinesMeetRule = {
|
|
|
6668
7559
|
priority: 49,
|
|
6669
7560
|
// sau parallelPerp (dựng đường), trước/đủ để S tham chiếu 2 đường
|
|
6670
7561
|
languages: ["vi"],
|
|
6671
|
-
patterns: [
|
|
7562
|
+
patterns: [PREFILTER52],
|
|
6672
7563
|
match(ctx) {
|
|
6673
7564
|
const out = [];
|
|
6674
7565
|
const emit = (c, p1, l1, p2, l2, s) => {
|
|
@@ -6690,8 +7581,12 @@ var twoPerpLinesMeetRule = {
|
|
|
6690
7581
|
for (const m of c.text.matchAll(RE25)) {
|
|
6691
7582
|
emit(c, m[1], m[2] + m[3], m[4], m[5] + m[6], m[7]);
|
|
6692
7583
|
}
|
|
6693
|
-
|
|
6694
|
-
for (const m of c.text.matchAll(
|
|
7584
|
+
RE_DRAW_FIRST.lastIndex = 0;
|
|
7585
|
+
for (const m of c.text.matchAll(RE_DRAW_FIRST)) {
|
|
7586
|
+
emit(c, m[1], m[2] + m[3], m[4], m[5] + m[6], m[7]);
|
|
7587
|
+
}
|
|
7588
|
+
RE_DISTRIB2.lastIndex = 0;
|
|
7589
|
+
for (const m of c.text.matchAll(RE_DISTRIB2)) {
|
|
6695
7590
|
emit(c, m[1], m[3] + m[4], m[2], m[5] + m[6], m[7]);
|
|
6696
7591
|
}
|
|
6697
7592
|
}
|
|
@@ -6700,27 +7595,57 @@ var twoPerpLinesMeetRule = {
|
|
|
6700
7595
|
};
|
|
6701
7596
|
|
|
6702
7597
|
// src/stamps/geometry-2d/ai/rules/circumcircleCutsLine.ts
|
|
6703
|
-
var
|
|
7598
|
+
var PREFILTER53 = /[Đđ]ường\s*tròn\s+ngoại\s*tiếp\s+(?:tam\s*giác\s+)?[A-Z]{3}[^.]{0,20}?cắt\s+[A-Z]{2}|[Đđ]ường\s*tròn\s+\([A-Z]{3}\)[^.]{0,20}?cắt\s+[A-Z]{2}|\([A-Z]{3}\)\s*∩\s*[A-Z]{2}\s*=\s*\{/u;
|
|
6704
7599
|
var RE26 = new RegExp(
|
|
6705
7600
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+ngo\u1EA1i\\s*ti\u1EBFp\\s+(?:tam\\s*gi\xE1c\\s+)?([A-Z]{3})(?![A-Z])\\s+c\u1EAFt\\s+(?:l\u1EA1i\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n\\s+|tia\\s+|c\u1EA1nh\\s+)?([A-Z]{2})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
6706
7601
|
"gu"
|
|
6707
7602
|
);
|
|
7603
|
+
var RE_PAREN = new RegExp(
|
|
7604
|
+
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+\\(([A-Z]{3})\\)\\s+c\u1EAFt\\s+(?:l\u1EA1i\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n\\s+|tia\\s+|c\u1EA1nh\\s+)?([A-Z]{2})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+(?:hai|2)\\s+)?)?(?:l\xE0\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
7605
|
+
"gu"
|
|
7606
|
+
);
|
|
7607
|
+
var RE_SET = new RegExp(
|
|
7608
|
+
"\\(([A-Z]{3})\\)\\s*\u2229\\s*(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n\\s+|tia\\s+|c\u1EA1nh\\s+)?([A-Z]{2})(?![A-Z])\\s*=\\s*\\{\\s*([A-Z])(?![A-Z])\\s*,\\s*([A-Z])(?![A-Z])\\s*\\}",
|
|
7609
|
+
"gu"
|
|
7610
|
+
);
|
|
6708
7611
|
var circumcircleCutsLineRule = {
|
|
6709
7612
|
id: "circumcircleCutsLine",
|
|
6710
7613
|
priority: 58,
|
|
6711
7614
|
// sau circleTriangle/cevian; trước intersection(45)
|
|
6712
7615
|
languages: ["vi"],
|
|
6713
|
-
patterns: [
|
|
7616
|
+
patterns: [PREFILTER53],
|
|
6714
7617
|
match(ctx) {
|
|
6715
7618
|
const out = [];
|
|
6716
7619
|
for (const c of ctx.clauses) {
|
|
6717
|
-
RE26
|
|
6718
|
-
|
|
7620
|
+
for (const re of [RE26, RE_PAREN]) {
|
|
7621
|
+
re.lastIndex = 0;
|
|
7622
|
+
for (const m of c.text.matchAll(re)) {
|
|
7623
|
+
const tri = m[1].split("");
|
|
7624
|
+
const line = m[2];
|
|
7625
|
+
const k = m[3];
|
|
7626
|
+
const shared = m[4] ?? line.split("").find((v) => tri.includes(v));
|
|
7627
|
+
if (!shared || tri.includes(k) || line.includes(k)) continue;
|
|
7628
|
+
const w = `w${m[1]}`;
|
|
7629
|
+
out.push({
|
|
7630
|
+
ruleId: "circumcircleCutsLine",
|
|
7631
|
+
clauseIds: [c.id],
|
|
7632
|
+
intents: [
|
|
7633
|
+
drawCircle(w, "through3", { points: tri }),
|
|
7634
|
+
addPoint(k, { kind: "secondIntersection", line, circle: w, other: shared })
|
|
7635
|
+
]
|
|
7636
|
+
});
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
RE_SET.lastIndex = 0;
|
|
7640
|
+
for (const m of c.text.matchAll(RE_SET)) {
|
|
6719
7641
|
const tri = m[1].split("");
|
|
6720
7642
|
const line = m[2];
|
|
6721
|
-
const
|
|
6722
|
-
const
|
|
6723
|
-
if (
|
|
7643
|
+
const set = [m[3], m[4]];
|
|
7644
|
+
const onLine = set.filter((v) => line.includes(v));
|
|
7645
|
+
if (onLine.length !== 1) continue;
|
|
7646
|
+
const shared = onLine[0];
|
|
7647
|
+
const k = set.find((v) => v !== shared);
|
|
7648
|
+
if (tri.includes(k) || line.includes(k)) continue;
|
|
6724
7649
|
const w = `w${m[1]}`;
|
|
6725
7650
|
out.push({
|
|
6726
7651
|
ruleId: "circumcircleCutsLine",
|
|
@@ -6750,7 +7675,7 @@ var LINE_TYPES = [
|
|
|
6750
7675
|
var CONCURRENCY = /(?:cắt\s+nhau|đồng\s*quy|gặp\s+nhau|cùng\s+đi\s+qua)\s+(?:tại\s+|qua\s+)?(?:điểm\s+)?([A-Z])(?!\p{L})/u;
|
|
6751
7676
|
var VERTEX_PAIR = /(?<!\p{L})[A-Z][A-Z](?:['′])?(?!\p{L})/gu;
|
|
6752
7677
|
var TRI_G9 = /tam\s*giác\s+(?:(?:nhọn|cân|đều|vuông|tù)\s+)*([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
6753
|
-
var
|
|
7678
|
+
var PREFILTER54 = LINE_TYPES.map((t) => t.kw);
|
|
6754
7679
|
function trianglesIn4(text) {
|
|
6755
7680
|
const out = [];
|
|
6756
7681
|
TRI_G9.lastIndex = 0;
|
|
@@ -6783,7 +7708,7 @@ var lineConcurrencyRule = {
|
|
|
6783
7708
|
id: "lineConcurrency",
|
|
6784
7709
|
priority: 69,
|
|
6785
7710
|
languages: ["vi"],
|
|
6786
|
-
patterns:
|
|
7711
|
+
patterns: PREFILTER54,
|
|
6787
7712
|
match(ctx) {
|
|
6788
7713
|
const fallbackTri = uniqueTriangle5(ctx.problem);
|
|
6789
7714
|
const out = [];
|
|
@@ -6815,7 +7740,7 @@ var lineConcurrencyRule = {
|
|
|
6815
7740
|
};
|
|
6816
7741
|
|
|
6817
7742
|
// src/stamps/geometry-2d/ai/rules/twoCirclesMeet.ts
|
|
6818
|
-
var
|
|
7743
|
+
var PREFILTER55 = /hai\s+đường\s*tròn[^.]{0,70}?cắt\s+nhau/u;
|
|
6819
7744
|
var CIRCLE2 = "\\(\\s*([A-Z]['\u2032]?)(?:\\s*[;,]\\s*(?:[Rr]['\u2032]?|\\d+(?:[.,]\\d+)?\\s*[a-z]*))?\\s*\\)";
|
|
6820
7745
|
var TWO_MEET2 = new RegExp(
|
|
6821
7746
|
"hai\\s+\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*" + CIRCLE2 + "\\s*(?:[;,]\\s*)?v\xE0\\s*" + CIRCLE2 + "[^.]{0,50}?c\u1EAFt\\s+nhau\\s+(?:\u1EDF|t\u1EA1i)\\s+(?:hai\\s+\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Za-z])",
|
|
@@ -6828,7 +7753,7 @@ var twoCirclesMeetRule = {
|
|
|
6828
7753
|
// phái sinh downstream → dựng sớm. DƯỚI circleRadius (75) cho nhất quán.
|
|
6829
7754
|
priority: 74,
|
|
6830
7755
|
languages: ["vi"],
|
|
6831
|
-
patterns: [
|
|
7756
|
+
patterns: [PREFILTER55],
|
|
6832
7757
|
match(ctx) {
|
|
6833
7758
|
const m = TWO_MEET2.exec(ctx.problem);
|
|
6834
7759
|
if (!m) return [];
|
|
@@ -6855,7 +7780,7 @@ var twoCirclesMeetRule = {
|
|
|
6855
7780
|
};
|
|
6856
7781
|
|
|
6857
7782
|
// src/stamps/geometry-2d/ai/rules/radius.ts
|
|
6858
|
-
var
|
|
7783
|
+
var PREFILTER56 = /bán\s*kính\s+[A-Z]{2}/u;
|
|
6859
7784
|
var DRAW_RADII = new RegExp(
|
|
6860
7785
|
"(?:[Vv]\u1EBD|[Kk]\u1EBB|[Dd]\u1EF1ng)\\s+(?:hai\\s+|ba\\s+|2\\s+|3\\s+|c\xE1c\\s+)?b\xE1n\\s*k\xEDnh\\s+([A-Z]{2}(?:\\s*(?:,|v\xE0)\\s*[A-Z]{2})*)(?![A-Z])",
|
|
6861
7786
|
"u"
|
|
@@ -6866,7 +7791,7 @@ var radiusRule = {
|
|
|
6866
7791
|
id: "radius",
|
|
6867
7792
|
priority: 60,
|
|
6868
7793
|
languages: ["vi"],
|
|
6869
|
-
patterns: [
|
|
7794
|
+
patterns: [PREFILTER56],
|
|
6870
7795
|
match(ctx) {
|
|
6871
7796
|
const out = [];
|
|
6872
7797
|
for (const c of ctx.clauses) {
|
|
@@ -6890,17 +7815,17 @@ var radiusRule = {
|
|
|
6890
7815
|
};
|
|
6891
7816
|
|
|
6892
7817
|
// src/stamps/geometry-2d/ai/rules/perpNamedLine.ts
|
|
6893
|
-
var
|
|
7818
|
+
var PREFILTER57 = /(?:[Kk]ẻ|[Vv]ẽ|[Dd]ựng)\s+(?:đường\s*thẳng\s+|tia\s+)?[A-Z][a-z]\s*(?:⊥|vuông\s*góc)/u;
|
|
6894
7819
|
var RE27 = new RegExp(
|
|
6895
7820
|
// `⊥\\s*` (KHÔNG `\\s+`): OCR hay dính "Ax⊥MN" (vao10:11). Dạng chữ giữ `\\s+`.
|
|
6896
|
-
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?([A-Z][a-z])(?![A-Za-z])\\s*(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2})(?![A-Z])(?:\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z]))?",
|
|
7821
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|tia\\s+)?([A-Z][a-z])(?![A-Za-z])\\s*(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2})(?![A-Z])(?:\\s*,?\\s*c\u1EAFt\\s+[A-Z]{1,2})?(?:\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z]))?",
|
|
6897
7822
|
"gu"
|
|
6898
7823
|
);
|
|
6899
7824
|
var perpNamedLineRule = {
|
|
6900
7825
|
id: "perp-named-line",
|
|
6901
7826
|
priority: 60,
|
|
6902
7827
|
languages: ["vi"],
|
|
6903
|
-
patterns: [
|
|
7828
|
+
patterns: [PREFILTER57],
|
|
6904
7829
|
match(ctx) {
|
|
6905
7830
|
const out = [];
|
|
6906
7831
|
for (const c of ctx.clauses) {
|
|
@@ -6926,7 +7851,7 @@ var perpNamedLineRule = {
|
|
|
6926
7851
|
};
|
|
6927
7852
|
|
|
6928
7853
|
// src/stamps/geometry-2d/ai/rules/parallelSidePoints.ts
|
|
6929
|
-
var
|
|
7854
|
+
var PREFILTER58 = /thuộc\s+[A-Z]{2}\s+và\s+[A-Z]{2}\s+sao\s+cho[^.]*?(?:∥|\/\/|song\s*song)/u;
|
|
6930
7855
|
var PAR2 = "(?:\u2225|\\/\\/|song\\s*song(?:\\s+v\u1EDBi)?)";
|
|
6931
7856
|
var RE28 = new RegExp(
|
|
6932
7857
|
"([A-Z])\\s*,\\s*([A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?thu\u1ED9c\\s+([A-Z]{2})\\s+v\xE0\\s+([A-Z]{2})(?![A-Z])\\s+sao\\s+cho\\s+([A-Z])([A-Z])(?![A-Z])\\s*" + PAR2 + "\\s*([A-Z]{2})(?![A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])([A-Z])(?![A-Z])\\s*" + PAR2 + "\\s*([A-Z]{2})(?![A-Z])",
|
|
@@ -6936,7 +7861,7 @@ var parallelSidePointsRule = {
|
|
|
6936
7861
|
id: "parallel-side-points",
|
|
6937
7862
|
priority: 55,
|
|
6938
7863
|
languages: ["vi"],
|
|
6939
|
-
patterns: [
|
|
7864
|
+
patterns: [PREFILTER58],
|
|
6940
7865
|
match(ctx) {
|
|
6941
7866
|
const out = [];
|
|
6942
7867
|
for (const c of ctx.clauses) {
|
|
@@ -6959,36 +7884,370 @@ var parallelSidePointsRule = {
|
|
|
6959
7884
|
}
|
|
6960
7885
|
};
|
|
6961
7886
|
|
|
6962
|
-
// src/stamps/geometry-2d/ai/
|
|
6963
|
-
var
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
7887
|
+
// src/stamps/geometry-2d/ai/intent-builders/shared.ts
|
|
7888
|
+
var SQRT3 = 1.7320508075688772;
|
|
7889
|
+
function triangleCanonical(variant) {
|
|
7890
|
+
switch (variant) {
|
|
7891
|
+
case "equilateral":
|
|
7892
|
+
return [[0, 0], [4, 0], [2, 2 * SQRT3]];
|
|
7893
|
+
case "isoceles-AB":
|
|
7894
|
+
return [[0, 0], [4, 0], [2, 3]];
|
|
7895
|
+
case "isoceles-BC":
|
|
7896
|
+
return [[0, 3], [-2, 0], [2, 0]];
|
|
7897
|
+
case "isoceles-CA":
|
|
7898
|
+
return [[0, 0], [2, 3], [4, 0]];
|
|
7899
|
+
case "right-at-A":
|
|
7900
|
+
return [[0, 0], [4, 0], [0, 3]];
|
|
7901
|
+
case "right-at-B":
|
|
7902
|
+
return [[0, 0], [4, 0], [4, 3]];
|
|
7903
|
+
case "right-at-C":
|
|
7904
|
+
return [[0, 0], [6, 0], [3, 3]];
|
|
7905
|
+
case "any":
|
|
7906
|
+
default:
|
|
7907
|
+
return [[0, 0], [5, 0], [2, 3]];
|
|
7908
|
+
}
|
|
6978
7909
|
}
|
|
6979
|
-
function
|
|
6980
|
-
|
|
6981
|
-
if (!m) return void 0;
|
|
6982
|
-
return m[1] ?? m[2];
|
|
7910
|
+
function squareCanonical() {
|
|
7911
|
+
return [[0, 0], [4, 0], [4, 4], [0, 4]];
|
|
6983
7912
|
}
|
|
6984
|
-
function
|
|
6985
|
-
|
|
7913
|
+
function rectangleCanonical(variant) {
|
|
7914
|
+
if (variant === "tall") return [[0, 0], [2.5, 0], [2.5, 4], [0, 4]];
|
|
7915
|
+
return [[0, 0], [4, 0], [4, 2.5], [0, 2.5]];
|
|
6986
7916
|
}
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
7917
|
+
function rhombusCanonical() {
|
|
7918
|
+
return [[-2, 0], [0, -1.5], [2, 0], [0, 1.5]];
|
|
7919
|
+
}
|
|
7920
|
+
function trapezoidCanonical(variant) {
|
|
7921
|
+
switch (variant) {
|
|
7922
|
+
case "right":
|
|
7923
|
+
return [[0, 0], [5, 0], [3, 3], [0, 3]];
|
|
7924
|
+
case "isoceles":
|
|
7925
|
+
return [[0, 0], [5, 0], [4, 3], [1, 3]];
|
|
7926
|
+
case "general":
|
|
7927
|
+
default:
|
|
7928
|
+
return [[0, 0], [5, 0], [3.5, 3], [1, 3]];
|
|
7929
|
+
}
|
|
7930
|
+
}
|
|
7931
|
+
function parallelogramCanonical() {
|
|
7932
|
+
return [[0, 0], [4, 0], [5, 3], [1, 3]];
|
|
7933
|
+
}
|
|
7934
|
+
function quadrilateralCanonical() {
|
|
7935
|
+
return [[0, 0], [5, 0], [4.5, 3.5], [0.5, 3]];
|
|
7936
|
+
}
|
|
7937
|
+
function addPoint2(s, p) {
|
|
7938
|
+
if (s.pointNames.has(p.name)) {
|
|
7939
|
+
if (p.kind !== "free") {
|
|
7940
|
+
const idx = s.points.findIndex((q) => q.name === p.name);
|
|
7941
|
+
if (idx >= 0 && s.points[idx].kind === "free") s.points[idx] = p;
|
|
7942
|
+
}
|
|
7943
|
+
return;
|
|
7944
|
+
}
|
|
7945
|
+
s.points.push(p);
|
|
7946
|
+
s.pointNames.add(p.name);
|
|
7947
|
+
}
|
|
7948
|
+
var FREE_DEFAULT_SPREAD = [
|
|
7949
|
+
[3, 3],
|
|
7950
|
+
[-3, 3],
|
|
7951
|
+
[3, -3],
|
|
7952
|
+
[-3, -3],
|
|
7953
|
+
[4, 0],
|
|
7954
|
+
[-4, 0],
|
|
7955
|
+
[0, 4],
|
|
7956
|
+
[0, -4]
|
|
7957
|
+
];
|
|
7958
|
+
function defaultFreeCoord(s) {
|
|
7959
|
+
let count = 0;
|
|
7960
|
+
for (const p of s.points) if (p.kind === "free") count++;
|
|
7961
|
+
return [...FREE_DEFAULT_SPREAD[count % FREE_DEFAULT_SPREAD.length]];
|
|
7962
|
+
}
|
|
7963
|
+
function addShape(s, sh) {
|
|
7964
|
+
if (s.shapeNames.has(sh.name)) return;
|
|
7965
|
+
s.shapes.push(sh);
|
|
7966
|
+
s.shapeNames.add(sh.name);
|
|
7967
|
+
}
|
|
7968
|
+
function uniqueShapeName(s, base) {
|
|
7969
|
+
if (!s.shapeNames.has(base)) return base;
|
|
7970
|
+
let i = 2;
|
|
7971
|
+
while (s.shapeNames.has(`${base}${i}`)) i++;
|
|
7972
|
+
return `${base}${i}`;
|
|
7973
|
+
}
|
|
7974
|
+
function uniquePointName(s, base) {
|
|
7975
|
+
if (!s.pointNames.has(base)) return base;
|
|
7976
|
+
let i = 2;
|
|
7977
|
+
while (s.pointNames.has(`${base}${i}`)) i++;
|
|
7978
|
+
return `${base}${i}`;
|
|
7979
|
+
}
|
|
7980
|
+
function ensureSegment(s, a, b) {
|
|
7981
|
+
const k1 = `${a}-${b}`;
|
|
7982
|
+
const k2 = `${b}-${a}`;
|
|
7983
|
+
const existing = s.segmentByEnds.get(k1) ?? s.segmentByEnds.get(k2);
|
|
7984
|
+
if (existing) return existing;
|
|
7985
|
+
const name = uniqueShapeName(s, `${a}${b}`);
|
|
7986
|
+
addShape(s, { name, kind: "segment", p1: a, p2: b });
|
|
7987
|
+
s.segmentByEnds.set(k1, name);
|
|
7988
|
+
return name;
|
|
7989
|
+
}
|
|
7990
|
+
function resolveLineRefWithFallback(s, ref, anchor) {
|
|
7991
|
+
if (/^[A-Za-z]$/.test(ref) && /^[A-Za-z]$/.test(anchor) && ref !== anchor && s.pointNames.has(ref) && s.pointNames.has(anchor)) {
|
|
7992
|
+
const [a, b] = ref < anchor ? [ref, anchor] : [anchor, ref];
|
|
7993
|
+
return ensureSegment(s, a, b);
|
|
7994
|
+
}
|
|
7995
|
+
return resolveSegmentRef(s, ref);
|
|
7996
|
+
}
|
|
7997
|
+
function resolveSegmentRef(s, ref) {
|
|
7998
|
+
if (s.shapeNames.has(ref)) return ref;
|
|
7999
|
+
if (/^[A-Z][A-Z]$/u.test(ref)) {
|
|
8000
|
+
return ensureSegment(s, ref[0], ref[1]);
|
|
8001
|
+
}
|
|
8002
|
+
if (ref.length === 2 && s.pointNames.has(ref[0]) && s.pointNames.has(ref[1])) {
|
|
8003
|
+
return ensureSegment(s, ref[0], ref[1]);
|
|
8004
|
+
}
|
|
8005
|
+
const dashMatch = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
8006
|
+
if (dashMatch && s.pointNames.has(dashMatch[1]) && s.pointNames.has(dashMatch[2])) {
|
|
8007
|
+
return ensureSegment(s, dashMatch[1], dashMatch[2]);
|
|
8008
|
+
}
|
|
8009
|
+
const sub2 = splitKnownPair(s, ref);
|
|
8010
|
+
if (sub2) return ensureSegment(s, sub2[0], sub2[1]);
|
|
8011
|
+
return ref;
|
|
8012
|
+
}
|
|
8013
|
+
function splitKnownPair(s, ref) {
|
|
8014
|
+
for (let i = ref.length - 1; i >= 1; i--) {
|
|
8015
|
+
const a = ref.slice(0, i);
|
|
8016
|
+
const b = ref.slice(i);
|
|
8017
|
+
if (s.pointNames.has(a) && s.pointNames.has(b)) return [a, b];
|
|
8018
|
+
}
|
|
8019
|
+
return null;
|
|
8020
|
+
}
|
|
8021
|
+
function parseEnds(ref) {
|
|
8022
|
+
if (ref.length === 2) return [ref[0], ref[1]];
|
|
8023
|
+
const m = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
8024
|
+
if (m) return [m[1], m[2]];
|
|
8025
|
+
return null;
|
|
8026
|
+
}
|
|
8027
|
+
var SHAPE_VARIANTS = {
|
|
8028
|
+
triangle: ["any", "equilateral", "isoceles-AB", "isoceles-BC", "isoceles-CA", "right-at-A", "right-at-B", "right-at-C"],
|
|
8029
|
+
square: ["standard"],
|
|
8030
|
+
rectangle: ["standard", "wide", "tall"],
|
|
8031
|
+
rhombus: ["standard"],
|
|
8032
|
+
trapezoid: ["right", "isoceles", "general"],
|
|
8033
|
+
parallelogram: ["standard"],
|
|
8034
|
+
quadrilateral: ["any"]
|
|
8035
|
+
};
|
|
8036
|
+
|
|
8037
|
+
// src/stamps/geometry-2d/ai/rules/inscribedSquare.ts
|
|
8038
|
+
function sub(p, q) {
|
|
8039
|
+
return [p[0] - q[0], p[1] - q[1]];
|
|
8040
|
+
}
|
|
8041
|
+
function add(p, q) {
|
|
8042
|
+
return [p[0] + q[0], p[1] + q[1]];
|
|
8043
|
+
}
|
|
8044
|
+
function mul(p, k) {
|
|
8045
|
+
return [p[0] * k, p[1] * k];
|
|
8046
|
+
}
|
|
8047
|
+
function dot(p, q) {
|
|
8048
|
+
return p[0] * q[0] + p[1] * q[1];
|
|
8049
|
+
}
|
|
8050
|
+
function cross(p, q) {
|
|
8051
|
+
return p[0] * q[1] - p[1] * q[0];
|
|
8052
|
+
}
|
|
8053
|
+
function len(p) {
|
|
8054
|
+
return Math.hypot(p[0], p[1]);
|
|
8055
|
+
}
|
|
8056
|
+
function norm2(p) {
|
|
8057
|
+
const l = len(p) || 1;
|
|
8058
|
+
return [p[0] / l, p[1] / l];
|
|
8059
|
+
}
|
|
8060
|
+
function inscribedSquareCoords(A, B, C) {
|
|
8061
|
+
const u = norm2(sub(C, B));
|
|
8062
|
+
const a = len(sub(C, B));
|
|
8063
|
+
const tA = dot(sub(A, B), u);
|
|
8064
|
+
const foot = add(B, mul(u, tA));
|
|
8065
|
+
const h = len(sub(A, foot));
|
|
8066
|
+
const s = a * h / (a + h);
|
|
8067
|
+
const n = norm2(sub(A, foot));
|
|
8068
|
+
const BA = sub(B, A);
|
|
8069
|
+
const q0 = -(s * cross(n, BA)) / cross(u, BA);
|
|
8070
|
+
const Q = add(B, mul(u, q0));
|
|
8071
|
+
const P = add(B, mul(u, q0 + s));
|
|
8072
|
+
const M = add(Q, mul(n, s));
|
|
8073
|
+
const N = add(P, mul(n, s));
|
|
8074
|
+
return { M, N, P, Q };
|
|
8075
|
+
}
|
|
8076
|
+
function paramOn(pt, X, Y) {
|
|
8077
|
+
const d = sub(Y, X);
|
|
8078
|
+
return dot(sub(pt, X), d) / (dot(d, d) || 1);
|
|
8079
|
+
}
|
|
8080
|
+
function inscribedSquareParams(A, B, C) {
|
|
8081
|
+
const { M, N, P, Q } = inscribedSquareCoords(A, B, C);
|
|
8082
|
+
return {
|
|
8083
|
+
tM: paramOn(M, A, B),
|
|
8084
|
+
tN: paramOn(N, A, C),
|
|
8085
|
+
tP: paramOn(P, B, C),
|
|
8086
|
+
tQ: paramOn(Q, B, C)
|
|
8087
|
+
};
|
|
8088
|
+
}
|
|
8089
|
+
var PREFILTER59 = /hình\s*vuông[^.]*?(?:trên|∈|thuộc|\bNe\b|\bEe\b)/iu;
|
|
8090
|
+
var SQUARE_LABELS = /hình\s*vuông\s+([A-Z])\s*([A-Z])\s*([A-Z])\s*([A-Z])(?![A-Z])/u;
|
|
8091
|
+
var RIGHT_AT2 = /vuông\s+tại\s+([A-Z])(?![A-Za-z])/u;
|
|
8092
|
+
var RIGHT_BY_IDX2 = ["right-at-A", "right-at-B", "right-at-C"];
|
|
8093
|
+
var MEMBERSHIP_G = /(?:([A-Z])\s*(?:∈|trên|thuộc)|\b([A-Z])e\b)\s*([A-Z])([A-Z])(?![A-Z])/gu;
|
|
8094
|
+
var LIST_MEMBERSHIP_G = /([A-Z])\s*,\s*([A-Z])\s*(?:∈|trên|thuộc)\s*([A-Z])([A-Z])(?![A-Z])/gu;
|
|
8095
|
+
function parseMemberships(text) {
|
|
8096
|
+
const out = [];
|
|
8097
|
+
let m;
|
|
8098
|
+
LIST_MEMBERSHIP_G.lastIndex = 0;
|
|
8099
|
+
const listSpans = [];
|
|
8100
|
+
while ((m = LIST_MEMBERSHIP_G.exec(text)) !== null) {
|
|
8101
|
+
const side = [m[3], m[4]];
|
|
8102
|
+
out.push({ pt: m[1], side });
|
|
8103
|
+
out.push({ pt: m[2], side });
|
|
8104
|
+
listSpans.push([m.index, m.index + m[0].length]);
|
|
8105
|
+
}
|
|
8106
|
+
MEMBERSHIP_G.lastIndex = 0;
|
|
8107
|
+
while ((m = MEMBERSHIP_G.exec(text)) !== null) {
|
|
8108
|
+
if (listSpans.some(([a, b]) => m.index >= a && m.index < b)) continue;
|
|
8109
|
+
const pt = m[1] ?? m[2];
|
|
8110
|
+
out.push({ pt, side: [m[3], m[4]] });
|
|
8111
|
+
}
|
|
8112
|
+
return out;
|
|
8113
|
+
}
|
|
8114
|
+
var inscribedSquareRule = {
|
|
8115
|
+
id: "inscribed-square",
|
|
8116
|
+
priority: 56,
|
|
8117
|
+
languages: ["vi"],
|
|
8118
|
+
patterns: [PREFILTER59],
|
|
8119
|
+
match(ctx) {
|
|
8120
|
+
const out = [];
|
|
8121
|
+
for (const c of ctx.clauses) {
|
|
8122
|
+
const labelM = SQUARE_LABELS.exec(c.text);
|
|
8123
|
+
if (!labelM) continue;
|
|
8124
|
+
const labels = [labelM[1], labelM[2], labelM[3], labelM[4]];
|
|
8125
|
+
if (new Set(labels).size !== 4) continue;
|
|
8126
|
+
const memberships = parseMemberships(c.text);
|
|
8127
|
+
if (memberships.length < 4) continue;
|
|
8128
|
+
const bySide = /* @__PURE__ */ new Map();
|
|
8129
|
+
const ptSide = /* @__PURE__ */ new Map();
|
|
8130
|
+
for (const mem of memberships) {
|
|
8131
|
+
if (!labels.includes(mem.pt)) continue;
|
|
8132
|
+
ptSide.set(mem.pt, mem.side);
|
|
8133
|
+
const key = [...mem.side].sort().join("");
|
|
8134
|
+
const arr = bySide.get(key) ?? [];
|
|
8135
|
+
if (!arr.includes(mem.pt)) arr.push(mem.pt);
|
|
8136
|
+
bySide.set(key, arr);
|
|
8137
|
+
}
|
|
8138
|
+
if (labels.some((l) => !ptSide.has(l))) continue;
|
|
8139
|
+
const sideKeys = [...bySide.keys()];
|
|
8140
|
+
const hypKey = sideKeys.find((k) => (bySide.get(k) ?? []).length === 2);
|
|
8141
|
+
if (!hypKey) continue;
|
|
8142
|
+
const legKeys = sideKeys.filter((k) => k !== hypKey);
|
|
8143
|
+
if (legKeys.length !== 2) continue;
|
|
8144
|
+
if ((bySide.get(legKeys[0]) ?? []).length !== 1) continue;
|
|
8145
|
+
if ((bySide.get(legKeys[1]) ?? []).length !== 1) continue;
|
|
8146
|
+
const triVerts = /* @__PURE__ */ new Set();
|
|
8147
|
+
for (const [, side] of ptSide) side.forEach((v) => triVerts.add(v));
|
|
8148
|
+
if (triVerts.size !== 3) continue;
|
|
8149
|
+
const legVerts = legKeys.map((k) => k.split(""));
|
|
8150
|
+
const apex = legVerts[0].find((v) => legVerts[1].includes(v));
|
|
8151
|
+
if (!apex) continue;
|
|
8152
|
+
const triLabels = [apex, ...[...triVerts].filter((v) => v !== apex).sort()];
|
|
8153
|
+
const explicitRight = collectRightAt(ctx.clauses);
|
|
8154
|
+
if (explicitRight && explicitRight !== apex) continue;
|
|
8155
|
+
const tri = triangleCanonical("right-at-A");
|
|
8156
|
+
const params = inscribedSquareParams(tri[0], tri[1], tri[2]);
|
|
8157
|
+
const placement = assignSquarePlacement(
|
|
8158
|
+
labels,
|
|
8159
|
+
ptSide,
|
|
8160
|
+
bySide.get(hypKey),
|
|
8161
|
+
triLabels[0],
|
|
8162
|
+
// apex (đỉnh góc vuông)
|
|
8163
|
+
triLabels[1],
|
|
8164
|
+
// đỉnh B của tam giác (đầu mút cạnh huyền gần Q)
|
|
8165
|
+
triLabels[2],
|
|
8166
|
+
// đỉnh C của tam giác (đầu mút cạnh huyền gần P)
|
|
8167
|
+
params
|
|
8168
|
+
);
|
|
8169
|
+
if (!placement) continue;
|
|
8170
|
+
const rightIdx = triLabels.indexOf(apex);
|
|
8171
|
+
const variant = RIGHT_BY_IDX2[rightIdx] ?? "right-at-A";
|
|
8172
|
+
const intents = [drawShape("triangle", triLabels, variant)];
|
|
8173
|
+
for (const lbl of labels) {
|
|
8174
|
+
const { of, t } = placement.get(lbl);
|
|
8175
|
+
intents.push(addPoint(lbl, { kind: "onSegment", of, t }));
|
|
8176
|
+
}
|
|
8177
|
+
intents.push(markShape("quadrilateral", labels));
|
|
8178
|
+
const claimed = /* @__PURE__ */ new Set([c.id]);
|
|
8179
|
+
for (const cc of ctx.clauses) {
|
|
8180
|
+
const rm = RIGHT_AT2.exec(cc.text);
|
|
8181
|
+
if (rm && rm[1] === apex) claimed.add(cc.id);
|
|
8182
|
+
}
|
|
8183
|
+
out.push({ ruleId: "inscribed-square", clauseIds: [...claimed], intents });
|
|
8184
|
+
}
|
|
8185
|
+
return out;
|
|
8186
|
+
}
|
|
8187
|
+
};
|
|
8188
|
+
function collectRightAt(clauses) {
|
|
8189
|
+
for (const c of clauses) {
|
|
8190
|
+
const m = RIGHT_AT2.exec(c.text);
|
|
8191
|
+
if (m) return m[1];
|
|
8192
|
+
}
|
|
8193
|
+
return void 0;
|
|
8194
|
+
}
|
|
8195
|
+
function assignSquarePlacement(labels, ptSide, hypPts, triA, triB, triC, params) {
|
|
8196
|
+
const place = /* @__PURE__ */ new Map();
|
|
8197
|
+
const onAB = labels.find((l) => !hypPts.includes(l) && ptSide.get(l).includes(triB));
|
|
8198
|
+
const onAC = labels.find((l) => !hypPts.includes(l) && ptSide.get(l).includes(triC));
|
|
8199
|
+
if (!onAB || !onAC) return null;
|
|
8200
|
+
place.set(onAB, { of: `${triA}${triB}`, t: params.tM });
|
|
8201
|
+
place.set(onAC, { of: `${triA}${triC}`, t: params.tN });
|
|
8202
|
+
const neighborInHyp = (anchor) => {
|
|
8203
|
+
const i = labels.indexOf(anchor);
|
|
8204
|
+
const next = labels[(i + 1) % labels.length];
|
|
8205
|
+
const prev = labels[(i - 1 + labels.length) % labels.length];
|
|
8206
|
+
if (hypPts.includes(next)) return next;
|
|
8207
|
+
if (hypPts.includes(prev)) return prev;
|
|
8208
|
+
return void 0;
|
|
8209
|
+
};
|
|
8210
|
+
const qVtx = neighborInHyp(onAB);
|
|
8211
|
+
const pVtx = neighborInHyp(onAC);
|
|
8212
|
+
const hyp = `${triB}${triC}`;
|
|
8213
|
+
if (qVtx) place.set(qVtx, { of: hyp, t: params.tQ });
|
|
8214
|
+
if (pVtx) place.set(pVtx, { of: hyp, t: params.tP });
|
|
8215
|
+
for (const hp of hypPts) {
|
|
8216
|
+
if (!place.has(hp)) place.set(hp, { of: hyp, t: hp === qVtx ? params.tQ : params.tP });
|
|
8217
|
+
}
|
|
8218
|
+
return labels.every((l) => place.has(l)) ? place : null;
|
|
8219
|
+
}
|
|
8220
|
+
|
|
8221
|
+
// src/stamps/geometry-2d/ai/rules/parallelThroughCutsCircle.ts
|
|
8222
|
+
var DISTRIB4 = new RegExp(
|
|
8223
|
+
String.raw`(?:[CcNn]ác\s+)?đường\s*thẳng\s+qua\s+(?:hai\s+điểm\s+)?([A-Z])(?!\p{L})\s+và\s+([A-Z])(?!\p{L})` + String.raw`\s+song\s*song\s+(?:với\s+)?(?:đường\s*thẳng\s+)?([A-Z])([A-Z])(?!\p{L})` + String.raw`[^.]{0,30}?cắt\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)` + String.raw`[^.]{0,30}?lần\s*lượt\s+(?:ở|tại)\s+([A-Z])\s+và\s+([A-Z])(?!\p{L})`,
|
|
8224
|
+
"gu"
|
|
8225
|
+
);
|
|
8226
|
+
var DECL_NAMED = new RegExp(
|
|
8227
|
+
String.raw`(?:[Qq]ua|[Tt]ừ)\s+(?:điểm\s+)?([A-Z])(?!\p{L})` + String.raw`\s+(?:kẻ|vẽ|dựng)\s+đường\s*thẳng\s+([a-z])(?!\p{L})` + String.raw`\s+song\s*song\s+(?:với\s+)?(?:đường\s*thẳng\s+)?([A-Z])([A-Z])(?!\p{L})`,
|
|
8228
|
+
"u"
|
|
8229
|
+
);
|
|
8230
|
+
var NAMED_CUT_BOTH = new RegExp(
|
|
8231
|
+
String.raw`[Đđ]ường\s*thẳng\s+([a-z])(?!\p{L})` + String.raw`\s+cắt\s+(?:đường\s*thẳng\s+|cạnh\s+|đoạn\s+)?([A-Z])([A-Z])(?!\p{L})\s+(?:ở|tại)\s+(?:điểm\s+)?([A-Z])(?!\p{L})` + String.raw`[^.]{0,8}?cắt\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?!\p{L})`,
|
|
8232
|
+
"u"
|
|
8233
|
+
);
|
|
8234
|
+
var PREFILTER60 = /(?:[Qq]ua|đường\s*thẳng\s+qua)\s+[^.]{0,60}?song\s*song[^.]{0,80}?cắt|(?:[Qq]ua|[Tt]ừ)\s+(?:điểm\s+)?[A-Z][^.]{0,20}?(?:kẻ|vẽ|dựng)\s+đường\s*thẳng\s+[a-z]\s+song\s*song/u;
|
|
8235
|
+
function lineNameOf2(through) {
|
|
8236
|
+
return "par" + through;
|
|
8237
|
+
}
|
|
8238
|
+
function resolveCircle4(problem) {
|
|
8239
|
+
const m = /đường\s*tròn\s*\(\s*([A-Z])\s*\)|\(\s*([A-Z])\s*\)/u.exec(problem);
|
|
8240
|
+
if (!m) return void 0;
|
|
8241
|
+
return m[1] ?? m[2];
|
|
8242
|
+
}
|
|
8243
|
+
function secondIntersection2(name, line, circle, other) {
|
|
8244
|
+
return addPoint(name, { kind: "secondIntersection", line, circle, other });
|
|
8245
|
+
}
|
|
8246
|
+
var parallelThroughCutsCircleRule = {
|
|
8247
|
+
id: "parallelThroughCutsCircle",
|
|
8248
|
+
priority: 46,
|
|
8249
|
+
languages: ["vi"],
|
|
8250
|
+
patterns: [PREFILTER60],
|
|
6992
8251
|
match(ctx) {
|
|
6993
8252
|
const circle = resolveCircle4(ctx.problem);
|
|
6994
8253
|
if (!circle) return [];
|
|
@@ -7064,12 +8323,12 @@ var RE29 = new RegExp(
|
|
|
7064
8323
|
"(?:[Mm]\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+(?:b\u1EA5t\\s*k[i\xECy\u1EF3]|t\xF9y\\s*\xFD)\\s+(?:n\xE0o\\s+)?(?:\u0111i\\s+)?qua\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,20}?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?([A-Z]\\s*[A-Z])(?![A-Z])\\s*(?:,|v\xE0)\\s*([A-Z]\\s*[A-Z])(?![A-Z])[^.]{0,20}?(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
7065
8324
|
"gu"
|
|
7066
8325
|
);
|
|
7067
|
-
var
|
|
8326
|
+
var PREFILTER61 = [/đường\s*thẳng\s+(?:bất\s*k[iìyỳ]|tùy\s*ý)\s+(?:nào\s+)?(?:đi\s+)?qua\s+[A-Z]/u];
|
|
7068
8327
|
var lineThroughCutsTwoLinesRule = {
|
|
7069
8328
|
id: "lineThroughCutsTwoLines",
|
|
7070
8329
|
priority: 44,
|
|
7071
8330
|
languages: ["vi"],
|
|
7072
|
-
patterns:
|
|
8331
|
+
patterns: PREFILTER61,
|
|
7073
8332
|
match(ctx) {
|
|
7074
8333
|
const out = [];
|
|
7075
8334
|
for (const c of ctx.clauses) {
|
|
@@ -7154,47 +8413,644 @@ function detectSpec(problem) {
|
|
|
7154
8413
|
}
|
|
7155
8414
|
return null;
|
|
7156
8415
|
}
|
|
7157
|
-
var rightAngleViewingRule = {
|
|
7158
|
-
id: "rightAngleViewing",
|
|
7159
|
-
priority: 63,
|
|
8416
|
+
var rightAngleViewingRule = {
|
|
8417
|
+
id: "rightAngleViewing",
|
|
8418
|
+
priority: 63,
|
|
8419
|
+
languages: ["vi"],
|
|
8420
|
+
patterns: [
|
|
8421
|
+
/(?:[Gg]óc|∠)[^.;\n]{0,18}90/u,
|
|
8422
|
+
/(?:[Gg]óc|∠)\s*[A-Z]{3}\s*vuông/u,
|
|
8423
|
+
/[A-Z]{2}\s*(?:⊥|vuông\s*góc)\s*[A-Z]{2}/u
|
|
8424
|
+
],
|
|
8425
|
+
match(ctx) {
|
|
8426
|
+
const spec = detectSpec(ctx.problem);
|
|
8427
|
+
if (!spec) return [];
|
|
8428
|
+
const clauseIds = ctx.clauses.filter(
|
|
8429
|
+
(c) => ANGLE_EQ_RE.test(c.text) || ANGLE_VUONG_RE.test(c.text) || PERP_RE.test(c.text) || findOnLine(c.text, spec.vertex) !== null
|
|
8430
|
+
).map((c) => c.id);
|
|
8431
|
+
const out = [
|
|
8432
|
+
{
|
|
8433
|
+
ruleId: "rightAngleViewing",
|
|
8434
|
+
clauseIds,
|
|
8435
|
+
intents: [
|
|
8436
|
+
addPoint(spec.vertex, {
|
|
8437
|
+
kind: "rightAngleViewing",
|
|
8438
|
+
a: spec.a,
|
|
8439
|
+
b: spec.b,
|
|
8440
|
+
onLine: spec.onLine
|
|
8441
|
+
})
|
|
8442
|
+
]
|
|
8443
|
+
}
|
|
8444
|
+
];
|
|
8445
|
+
return out;
|
|
8446
|
+
}
|
|
8447
|
+
};
|
|
8448
|
+
|
|
8449
|
+
// src/stamps/geometry-2d/ai/rules/collinearPoints.ts
|
|
8450
|
+
var PREFILTER62 = /thẳng\s+hàng|(?:cùng\s+)?(?:thuộc|nằm\s+trên|ở\s+trên)\s+(?:một\s+)?(?:cùng\s+)?(?:một\s+)?[Đđ]ư[ờơ]ng\s*thẳng/u;
|
|
8451
|
+
var NAMES = "([A-Z](?:['\u2032])?(?:\\s*(?:,|v\xE0)\\s*[A-Z](?:['\u2032])?){2,})";
|
|
8452
|
+
var ON_LINE = new RegExp(
|
|
8453
|
+
"(?:[Cc]\xE1c\\s+|[Cc]ho\\s+)?(?:\\d+\\s+|(?:hai|ba|b\u1ED1n|n\u0103m|s\xE1u)\\s+)?\u0111i\u1EC3m\\s+" + NAMES + "\\s+(?:c\xF9ng\\s+)?(?:thu\u1ED9c|n\u1EB1m\\s+tr\xEAn|\u1EDF\\s+tr\xEAn)\\s+(?:m\u1ED9t\\s+)?[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*th\u1EB3ng",
|
|
8454
|
+
"u"
|
|
8455
|
+
);
|
|
8456
|
+
var COLLINEAR = new RegExp(
|
|
8457
|
+
"(?:[Cc]\xE1c\\s+|[Cc]ho\\s+)?(?:\\d+\\s+|(?:hai|ba|b\u1ED1n|n\u0103m|s\xE1u)\\s+)?\u0111i\u1EC3m\\s+" + NAMES + "\\s+th\u1EB3ng\\s+h\xE0ng",
|
|
8458
|
+
"u"
|
|
8459
|
+
);
|
|
8460
|
+
function parseNames(blob) {
|
|
8461
|
+
return blob.split(/\s*(?:,|và)\s*/u).map((s) => s.trim().replace("\u2032", "'")).filter((s) => /^[A-Z]'?$/u.test(s));
|
|
8462
|
+
}
|
|
8463
|
+
var collinearPointsRule = {
|
|
8464
|
+
id: "collinear-points",
|
|
8465
|
+
priority: 95,
|
|
8466
|
+
// cao: dựng "bộ khung" điểm thẳng hàng sớm (như triangle/quad).
|
|
8467
|
+
languages: ["vi"],
|
|
8468
|
+
patterns: [PREFILTER62],
|
|
8469
|
+
match(ctx) {
|
|
8470
|
+
const out = [];
|
|
8471
|
+
for (const c of ctx.clauses) {
|
|
8472
|
+
const m = ON_LINE.exec(c.text) ?? COLLINEAR.exec(c.text);
|
|
8473
|
+
if (!m) continue;
|
|
8474
|
+
const names = parseNames(m[1]);
|
|
8475
|
+
if (names.length < 3) continue;
|
|
8476
|
+
if (new Set(names).size !== names.length) continue;
|
|
8477
|
+
const first = names[0];
|
|
8478
|
+
const last = names[names.length - 1];
|
|
8479
|
+
const span = 12;
|
|
8480
|
+
const intents = [
|
|
8481
|
+
addPoint(first, { kind: "free", at: [-span / 2, 0] }),
|
|
8482
|
+
addPoint(last, { kind: "free", at: [span / 2, 0] }),
|
|
8483
|
+
// Điểm giữa: onSegment trên đoạn đầu-cuối, t cách đều theo thứ tự liệt kê.
|
|
8484
|
+
...names.slice(1, -1).map(
|
|
8485
|
+
(nm, i) => addPoint(nm, { kind: "onSegment", of: `${first}${last}`, t: (i + 1) / (names.length - 1) })
|
|
8486
|
+
),
|
|
8487
|
+
connect(first, last, "segment")
|
|
8488
|
+
];
|
|
8489
|
+
out.push({ ruleId: "collinear-points", clauseIds: [c.id], intents });
|
|
8490
|
+
}
|
|
8491
|
+
return out;
|
|
8492
|
+
}
|
|
8493
|
+
};
|
|
8494
|
+
|
|
8495
|
+
// src/stamps/geometry-2d/ai/rules/quadDiagonals.ts
|
|
8496
|
+
var PREFILTER63 = /[A-Z]{4}\s+là\s+(?:một\s+)?tứ\s*giác|tứ\s*giác\s+[A-Z]{4}[^.]{0,60}?đường\s*chéo/u;
|
|
8497
|
+
var QUAD_DECL = /([A-Z])([A-Z])([A-Z])([A-Z])(?![A-Z])\s+là\s+(?:một\s+)?tứ\s*giác/u;
|
|
8498
|
+
var DIAGONALS = new RegExp(
|
|
8499
|
+
"(?:hai\\s+|c\xE1c\\s+)?\u0111\u01B0\u1EDDng\\s*ch\xE9o\\s+([A-Z]{2})\\s+(?:v\xE0|,)\\s+([A-Z]{2})[^.]{0,40}?c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
8500
|
+
"u"
|
|
8501
|
+
);
|
|
8502
|
+
var quadDiagonalsRule = {
|
|
8503
|
+
id: "quad-diagonals",
|
|
8504
|
+
priority: 99,
|
|
8505
|
+
// ~quad (100): vẽ tứ giác sớm; intersection (45) dựng E sau.
|
|
8506
|
+
languages: ["vi"],
|
|
8507
|
+
patterns: [PREFILTER63],
|
|
8508
|
+
match(ctx) {
|
|
8509
|
+
const out = [];
|
|
8510
|
+
for (const c of ctx.clauses) {
|
|
8511
|
+
const q = QUAD_DECL.exec(c.text);
|
|
8512
|
+
if (!q) continue;
|
|
8513
|
+
const labels = [q[1], q[2], q[3], q[4]];
|
|
8514
|
+
if (new Set(labels).size !== 4) continue;
|
|
8515
|
+
const intents = [drawShape("quadrilateral", labels, "any")];
|
|
8516
|
+
const d = DIAGONALS.exec(c.text);
|
|
8517
|
+
if (d) {
|
|
8518
|
+
const diag1 = d[1];
|
|
8519
|
+
const diag2 = d[2];
|
|
8520
|
+
const e = d[3];
|
|
8521
|
+
const ends = [diag1[0], diag1[1], diag2[0], diag2[1]];
|
|
8522
|
+
const okDiag = new Set(ends).size === 4 && ends.every((x) => labels.includes(x)) && !labels.includes(e);
|
|
8523
|
+
if (okDiag) {
|
|
8524
|
+
intents.push(connect(diag1[0], diag1[1], "segment"));
|
|
8525
|
+
intents.push(connect(diag2[0], diag2[1], "segment"));
|
|
8526
|
+
intents.push(addPoint(e, { kind: "intersection", of: [diag1, diag2] }));
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8529
|
+
out.push({ ruleId: "quad-diagonals", clauseIds: [c.id], intents });
|
|
8530
|
+
}
|
|
8531
|
+
return out;
|
|
8532
|
+
}
|
|
8533
|
+
};
|
|
8534
|
+
|
|
8535
|
+
// src/stamps/geometry-2d/ai/rules/twoCirclesTangent.ts
|
|
8536
|
+
var PREFILTER64 = /hai\s+[Đđ]ư[ờơ]ng\s*tròn[^.]{0,70}?tiếp\s*xúc/u;
|
|
8537
|
+
var CIRCLE3 = "\\(\\s*([A-Z]['\u2032]?)(?:\\s*[;,]\\s*(?:[Rr]['\u2032]?|\\d+(?:[.,]\\d+)?\\s*[a-z]*))?\\s*\\)";
|
|
8538
|
+
var TWO_TANGENT = new RegExp(
|
|
8539
|
+
"hai\\s+[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*tr\xF2n\\s*" + CIRCLE3 + "\\s*(?:[;,]\\s*)?v\xE0\\s*" + CIRCLE3 + "\\s*ti\u1EBFp\\s*x\xFAc\\s+(ngo\xE0i|trong|nhau)?\\s*(?:nhau\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z])",
|
|
8540
|
+
"u"
|
|
8541
|
+
);
|
|
8542
|
+
var norm3 = (s) => s.replace(/′/g, "'");
|
|
8543
|
+
var twoCirclesTangentRule = {
|
|
8544
|
+
id: "two-circles-tangent",
|
|
8545
|
+
// TRÊN chord/externalPoint, cạnh twoCirclesMeet (74): tiếp điểm A là gốc nhiều
|
|
8546
|
+
// phái sinh → dựng sớm.
|
|
8547
|
+
priority: 74,
|
|
8548
|
+
languages: ["vi"],
|
|
8549
|
+
patterns: [PREFILTER64],
|
|
8550
|
+
match(ctx) {
|
|
8551
|
+
const m = TWO_TANGENT.exec(ctx.problem);
|
|
8552
|
+
if (!m) return [];
|
|
8553
|
+
const c1 = norm3(m[1]);
|
|
8554
|
+
const c2 = norm3(m[2]);
|
|
8555
|
+
const mode = m[3];
|
|
8556
|
+
const a = m[4];
|
|
8557
|
+
if (mode === "trong") return [];
|
|
8558
|
+
if (c1 === c2) return [];
|
|
8559
|
+
if (a === c1 || a === c2) return [];
|
|
8560
|
+
const r2 = 2;
|
|
8561
|
+
const declId = ctx.clauses.find((c) => /tiếp\s*xúc/u.test(c.text))?.id;
|
|
8562
|
+
return [
|
|
8563
|
+
{
|
|
8564
|
+
ruleId: "two-circles-tangent",
|
|
8565
|
+
clauseIds: declId === void 0 ? [] : [declId],
|
|
8566
|
+
intents: [
|
|
8567
|
+
// A = gốc; O bên trái cách r1, O' bên phải cách r2 → tiếp xúc ngoài tại A.
|
|
8568
|
+
addPoint(a, { kind: "free", at: [0, 0] }),
|
|
8569
|
+
addPoint(c1, { kind: "free", at: [-3, 0] }),
|
|
8570
|
+
addPoint(c2, { kind: "free", at: [r2, 0] }),
|
|
8571
|
+
drawCircle(`${c1}_t`, "centerThrough", { center: c1, through: a }),
|
|
8572
|
+
drawCircle(`${c2}_t`, "centerThrough", { center: c2, through: a })
|
|
8573
|
+
]
|
|
8574
|
+
}
|
|
8575
|
+
];
|
|
8576
|
+
}
|
|
8577
|
+
};
|
|
8578
|
+
|
|
8579
|
+
// src/stamps/geometry-2d/ai/rules/commonTangent.ts
|
|
8580
|
+
var PREFILTER65 = /[Tt]iếp\s*tuyến\s+chung/u;
|
|
8581
|
+
var TANGENT_GLUE = /[Tt]iếp\s*tuyến\s+chung\s+(?:(?:ngoài|trong)\s+)?([A-Z])([A-Z])(?![A-Za-z])/u;
|
|
8582
|
+
var TANGENT_NAMED_BEFORE2 = /([A-Z])([A-Z])(?![A-Za-z])\s+là\s+tiếp\s*tuyến\s+chung/u;
|
|
8583
|
+
var INTERNAL = /tiếp\s*tuyến\s+chung\s+trong/u;
|
|
8584
|
+
var CIRCLE_G = /\(\s*([A-Z]['′]?)(?:\s*[;,]\s*(?:[Rr]['′]?|\d+(?:[.,]\d+)?\s*[a-z]*))?\s*\)/gu;
|
|
8585
|
+
var CIRC2 = "\\(\\s*([A-Z]['\u2032]?)(?:\\s*[;,]\\s*(?:[Rr]['\u2032]?|\\d+(?:[.,]\\d+)?\\s*[a-z]*))?\\s*\\)";
|
|
8586
|
+
var TANGENT_TOUCH = new RegExp(
|
|
8587
|
+
"ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?" + CIRC2 + "\\s*(?:,|v\xE0)\\s*" + CIRC2 + "\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|t\u01B0\u01A1ng\\s+\u1EE9ng\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z]['\u2032]?)(?![A-Za-z])\\s*(?:,|v\xE0)\\s*([A-Z]['\u2032]?)(?![A-Za-z])",
|
|
8588
|
+
"u"
|
|
8589
|
+
);
|
|
8590
|
+
var norm4 = (s) => s.replace(/′/g, "'");
|
|
8591
|
+
function membership(problem, pt) {
|
|
8592
|
+
const re = new RegExp(
|
|
8593
|
+
pt + "\\s*(?:\u2208|thu\u1ED9c|n\u1EB1m\\s+tr\xEAn)\\s*(?:[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*tr\xF2n\\s*)?(?:\\(\\s*)?(?:t\xE2m\\s+)?([A-Z]['\u2032]?)",
|
|
8594
|
+
"u"
|
|
8595
|
+
);
|
|
8596
|
+
const m = re.exec(problem);
|
|
8597
|
+
return m ? norm4(m[1]) : void 0;
|
|
8598
|
+
}
|
|
8599
|
+
var commonTangentRule = {
|
|
8600
|
+
id: "common-tangent",
|
|
8601
|
+
// Cạnh twoCirclesTangent (74): 2 đtròn + tiếp điểm là gốc nhiều phái sinh.
|
|
8602
|
+
priority: 73,
|
|
8603
|
+
languages: ["vi"],
|
|
8604
|
+
patterns: [PREFILTER65],
|
|
8605
|
+
match(ctx) {
|
|
8606
|
+
const p = ctx.problem;
|
|
8607
|
+
let t1;
|
|
8608
|
+
let t2;
|
|
8609
|
+
let c1;
|
|
8610
|
+
let c2;
|
|
8611
|
+
const mt = TANGENT_TOUCH.exec(p);
|
|
8612
|
+
if (mt) {
|
|
8613
|
+
c1 = norm4(mt[1]);
|
|
8614
|
+
c2 = norm4(mt[2]);
|
|
8615
|
+
t1 = norm4(mt[3]);
|
|
8616
|
+
t2 = norm4(mt[4]);
|
|
8617
|
+
} else {
|
|
8618
|
+
const mg = TANGENT_GLUE.exec(p);
|
|
8619
|
+
if (mg) {
|
|
8620
|
+
t1 = mg[1];
|
|
8621
|
+
t2 = mg[2];
|
|
8622
|
+
} else {
|
|
8623
|
+
const mb = TANGENT_NAMED_BEFORE2.exec(p);
|
|
8624
|
+
if (mb) {
|
|
8625
|
+
t1 = mb[1];
|
|
8626
|
+
t2 = mb[2];
|
|
8627
|
+
}
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8630
|
+
if (!t1 || !t2 || t1 === t2) return [];
|
|
8631
|
+
const m1 = c1 ? void 0 : membership(p, t1);
|
|
8632
|
+
const m2 = c2 ? void 0 : membership(p, t2);
|
|
8633
|
+
if (c1 && c2) ; else if (m1 && m2 && m1 !== m2) {
|
|
8634
|
+
c1 = m1;
|
|
8635
|
+
c2 = m2;
|
|
8636
|
+
} else {
|
|
8637
|
+
const seen = [];
|
|
8638
|
+
CIRCLE_G.lastIndex = 0;
|
|
8639
|
+
let cm;
|
|
8640
|
+
while ((cm = CIRCLE_G.exec(p)) !== null) {
|
|
8641
|
+
const nm = norm4(cm[1]);
|
|
8642
|
+
if (!seen.includes(nm)) seen.push(nm);
|
|
8643
|
+
if (seen.length === 2) break;
|
|
8644
|
+
}
|
|
8645
|
+
if (seen.length === 2) {
|
|
8646
|
+
[c1, c2] = seen;
|
|
8647
|
+
}
|
|
8648
|
+
}
|
|
8649
|
+
if (!c1 || !c2 || c1 === c2) return [];
|
|
8650
|
+
if (t1 === c1 || t1 === c2 || t2 === c1 || t2 === c2) return [];
|
|
8651
|
+
const variant = INTERNAL.test(p) ? "internal" : "external";
|
|
8652
|
+
const r1 = 3;
|
|
8653
|
+
const r2 = 2;
|
|
8654
|
+
const declId = ctx.clauses.find((c) => /tiếp\s*tuyến\s+chung/u.test(c.text))?.id;
|
|
8655
|
+
const intents = [
|
|
8656
|
+
addPoint(c1, { kind: "free", at: [0, 0] }),
|
|
8657
|
+
addPoint(c2, { kind: "free", at: [12, 0] }),
|
|
8658
|
+
drawCircle(c1, "centerRadius", { center: c1, radius: r1 }),
|
|
8659
|
+
drawCircle(c2, "centerRadius", { center: c2, radius: r2 }),
|
|
8660
|
+
addPoint(t1, { kind: "commonTangentPoint", circles: [c1, c2], on: 0, variant, side: 0 }),
|
|
8661
|
+
addPoint(t2, { kind: "commonTangentPoint", circles: [c1, c2], on: 1, variant, side: 0 }),
|
|
8662
|
+
connect(t1, t2, "segment")
|
|
8663
|
+
];
|
|
8664
|
+
return [
|
|
8665
|
+
{
|
|
8666
|
+
ruleId: "common-tangent",
|
|
8667
|
+
clauseIds: declId === void 0 ? [] : [declId],
|
|
8668
|
+
intents
|
|
8669
|
+
}
|
|
8670
|
+
];
|
|
8671
|
+
}
|
|
8672
|
+
};
|
|
8673
|
+
|
|
8674
|
+
// src/stamps/geometry-2d/ai/rules/twoCirclesCenterRadiusMeet.ts
|
|
8675
|
+
var CENTER_RADIUS = new RegExp(
|
|
8676
|
+
"([A-Z])(?![A-Za-z])\\s+l\xE0m\\s+t\xE2m\\s*,?\\s*(?:v\u1EBD|d\u1EF1ng|k\u1EBB)\\s+" + CIRCLE_KW + "\\s+b\xE1n\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Za-z])",
|
|
8677
|
+
"gu"
|
|
8678
|
+
);
|
|
8679
|
+
var MEET = new RegExp(
|
|
8680
|
+
"[Hh]ai\\s+" + CIRCLE_KW + "\\s*(?:n\xE0y\\s+)?c\u1EAFt\\s+nhau\\s+(?:(?:\u1EDF|t\u1EA1i)\\s+)?(?:\u0111i\u1EC3m\\s+th\u1EE9\\s+hai\\s+)?(?:l\xE0|t\u1EA1i)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z])",
|
|
8681
|
+
"u"
|
|
8682
|
+
);
|
|
8683
|
+
var PREFILTER66 = /làm\s+tâm[^.]{0,40}?bán\s*kính/u;
|
|
8684
|
+
var twoCirclesCenterRadiusMeetRule = {
|
|
8685
|
+
id: "two-circles-center-radius-meet",
|
|
8686
|
+
// Cạnh twoCirclesMeet (74): 2 đtròn + giao điểm là gốc phái sinh.
|
|
8687
|
+
priority: 73,
|
|
8688
|
+
languages: ["vi"],
|
|
8689
|
+
patterns: [PREFILTER66],
|
|
8690
|
+
match(ctx) {
|
|
8691
|
+
const p = ctx.problem;
|
|
8692
|
+
CENTER_RADIUS.lastIndex = 0;
|
|
8693
|
+
const circles = [];
|
|
8694
|
+
let cm;
|
|
8695
|
+
while ((cm = CENTER_RADIUS.exec(p)) !== null) {
|
|
8696
|
+
const center = cm[1];
|
|
8697
|
+
const a = cm[2];
|
|
8698
|
+
const b = cm[3];
|
|
8699
|
+
if (a !== center || b === center) continue;
|
|
8700
|
+
circles.push({ center, through: b });
|
|
8701
|
+
}
|
|
8702
|
+
if (circles.length !== 2) return [];
|
|
8703
|
+
const [k1, k2] = circles;
|
|
8704
|
+
if (k1.center === k2.center) return [];
|
|
8705
|
+
if (k1.through !== k2.through) return [];
|
|
8706
|
+
const mm = MEET.exec(p);
|
|
8707
|
+
if (!mm) return [];
|
|
8708
|
+
const d = mm[1];
|
|
8709
|
+
if (d === k1.center || d === k2.center || d === k1.through) return [];
|
|
8710
|
+
const clauseIds = ctx.clauses.filter((c) => /làm\s+tâm/u.test(c.text) || /cắt\s+nhau/u.test(c.text)).map((c) => c.id);
|
|
8711
|
+
const intents = [
|
|
8712
|
+
drawCircle(k1.center, "centerThrough", { center: k1.center, through: k1.through }),
|
|
8713
|
+
drawCircle(k2.center, "centerThrough", { center: k2.center, through: k2.through }),
|
|
8714
|
+
addPoint(d, { kind: "circleIntersection", c1: k1.center, c2: k2.center, which: 1 })
|
|
8715
|
+
];
|
|
8716
|
+
return [
|
|
8717
|
+
{
|
|
8718
|
+
ruleId: "two-circles-center-radius-meet",
|
|
8719
|
+
clauseIds,
|
|
8720
|
+
intents
|
|
8721
|
+
}
|
|
8722
|
+
];
|
|
8723
|
+
}
|
|
8724
|
+
};
|
|
8725
|
+
|
|
8726
|
+
// src/stamps/geometry-2d/ai/rules/rightAngleVertexCircles.ts
|
|
8727
|
+
var PREFILTER67 = /góc\s+vuông\s+[a-z][A-Z][a-z]/u;
|
|
8728
|
+
var RIGHT_ANGLE = /góc\s+vuông\s+([a-z])([A-Z])([a-z])(?![\p{L}])/u;
|
|
8729
|
+
var TWO_ON_RAYS = new RegExp(
|
|
8730
|
+
"L\u1EA5y\\s+(?:c\xE1c\\s+|hai\\s+)?\u0111i\u1EC3m\\s+([A-Z])\\s+v\xE0\\s+([A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?tr\xEAn\\s+(?:c\xE1c\\s+|hai\\s+)?tia\\s+[A-Z][a-z]\\s+v\xE0\\s+[A-Z][a-z]",
|
|
8731
|
+
"u"
|
|
8732
|
+
);
|
|
8733
|
+
var CIRCLE_CUTS_RAY = new RegExp(
|
|
8734
|
+
"[Vv]\u1EBD\\s+[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*tr\xF2n\\s*\\(\\s*([A-Z])\\s*[;,]\\s*([A-Z])([A-Z])\\s*\\)\\s*c\u1EAFt\\s+tia\\s+([A-Z])([a-z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
8735
|
+
"gu"
|
|
8736
|
+
);
|
|
8737
|
+
var rightAngleVertexCirclesRule = {
|
|
8738
|
+
id: "right-angle-vertex-circles",
|
|
8739
|
+
priority: 76,
|
|
8740
|
+
// dựng bộ khung góc + điểm sớm (trên twoCirclesMeet/tangent 74).
|
|
8741
|
+
languages: ["vi"],
|
|
8742
|
+
patterns: [PREFILTER67],
|
|
8743
|
+
match(ctx) {
|
|
8744
|
+
const ra = RIGHT_ANGLE.exec(ctx.problem);
|
|
8745
|
+
if (!ra) return [];
|
|
8746
|
+
const o = ra[2];
|
|
8747
|
+
const xRay = ra[1];
|
|
8748
|
+
const yRay = ra[3];
|
|
8749
|
+
let pI = "";
|
|
8750
|
+
let pK = "";
|
|
8751
|
+
for (const c of ctx.clauses) {
|
|
8752
|
+
const t = TWO_ON_RAYS.exec(c.text);
|
|
8753
|
+
if (t) {
|
|
8754
|
+
pI = t[1];
|
|
8755
|
+
pK = t[2];
|
|
8756
|
+
break;
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8759
|
+
if (!pI || !pK || pI === pK || pI === o || pK === o) return [];
|
|
8760
|
+
const xEnd = xRay.toUpperCase() === o ? `${o}x` : xRay.toUpperCase();
|
|
8761
|
+
const yEnd = yRay.toUpperCase() === o ? `${o}y` : yRay.toUpperCase();
|
|
8762
|
+
const used = /* @__PURE__ */ new Set([o, pI, pK]);
|
|
8763
|
+
const ax = used.has(xEnd) ? "X9" : xEnd;
|
|
8764
|
+
const ay = used.has(yEnd) ? "Y9" : yEnd;
|
|
8765
|
+
const intents = [
|
|
8766
|
+
addPoint(o, { kind: "free", at: [0, 0] }),
|
|
8767
|
+
addPoint(ax, { kind: "free", at: [8, 0] }),
|
|
8768
|
+
addPoint(ay, { kind: "free", at: [0, 8] }),
|
|
8769
|
+
addPoint(pI, { kind: "onSegment", of: `${o}${ax}`, t: 0.35 }),
|
|
8770
|
+
addPoint(pK, { kind: "onSegment", of: `${o}${ay}`, t: 0.35 }),
|
|
8771
|
+
connect(o, ax, "segment"),
|
|
8772
|
+
connect(o, ay, "segment")
|
|
8773
|
+
];
|
|
8774
|
+
for (const c of ctx.clauses) {
|
|
8775
|
+
CIRCLE_CUTS_RAY.lastIndex = 0;
|
|
8776
|
+
for (const m of c.text.matchAll(CIRCLE_CUTS_RAY)) {
|
|
8777
|
+
const center = m[1];
|
|
8778
|
+
const rayVertex = m[4];
|
|
8779
|
+
const rayDir = m[5];
|
|
8780
|
+
const hit = m[6];
|
|
8781
|
+
if (![pI, pK, o].includes(center)) continue;
|
|
8782
|
+
if (hit === center || hit === o) continue;
|
|
8783
|
+
const rayAxisEnd = rayDir.toUpperCase() === xRay.toUpperCase() ? ax : rayDir.toUpperCase() === yRay.toUpperCase() ? ay : "";
|
|
8784
|
+
if (!rayAxisEnd || rayVertex !== o) continue;
|
|
8785
|
+
const otherCenter = center === pI ? pK : pI;
|
|
8786
|
+
const circ = `${center}_c`;
|
|
8787
|
+
intents.push(drawCircle(circ, "centerThrough", { center, through: otherCenter }));
|
|
8788
|
+
intents.push(
|
|
8789
|
+
addPoint(hit, { kind: "secondIntersection", line: `${o}${rayAxisEnd}`, circle: circ, other: o })
|
|
8790
|
+
);
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
return [{ ruleId: "right-angle-vertex-circles", clauseIds: ctx.clauses.map((c) => c.id), intents }];
|
|
8794
|
+
}
|
|
8795
|
+
};
|
|
8796
|
+
|
|
8797
|
+
// src/stamps/geometry-2d/ai/rules/namedLine.ts
|
|
8798
|
+
var LC = String.raw`([a-z]{1,2}[0-9]?)(?!\p{L})`;
|
|
8799
|
+
var REF2 = String.raw`(?:${DUONG_KW}\s*thẳng\s+|đoạn(?:\s+thẳng)?\s+|cạnh\s+|tia\s+)?([A-Z]\s*[A-Z])(?![A-Z])`;
|
|
8800
|
+
var PT3 = String.raw`([A-Z])(?![A-Za-z])`;
|
|
8801
|
+
var VERB = String.raw`(?:[Cc]ho|[Vv]ẽ|[Kk]ẻ|[Dd]ựng)\s+(?:một\s+)?${DUONG_KW}\s*thẳng\s+`;
|
|
8802
|
+
var DECL = String.raw`(?:(?:và|[Cc]ho|[Vv]ẽ|[Kk]ẻ|[Dd]ựng)\s+)?(?:một\s+)?${DUONG_KW}\s*thẳng\s+`;
|
|
8803
|
+
var PERP = new RegExp(
|
|
8804
|
+
DECL + LC + String.raw`\s*(?:⊥\s*|vuông\s*góc(?:\s+với)?\s+)(?:với\s+)?` + REF2 + String.raw`(?:\s*,?\s*cắt\s+[A-Z]{2})?\s+(?:tại|ở)\s+(?:điểm\s+)?` + PT3,
|
|
8805
|
+
"gu"
|
|
8806
|
+
);
|
|
8807
|
+
var PARA_AFTER = new RegExp(
|
|
8808
|
+
VERB + LC + String.raw`\s*(?://\s*|song\s*song(?:\s+với)?\s+)(?:với\s+)?` + REF2 + String.raw`\s+(?:đi\s+)?qua\s+(?:điểm\s+)?` + PT3,
|
|
8809
|
+
"gu"
|
|
8810
|
+
);
|
|
8811
|
+
var PARA_BEFORE = new RegExp(
|
|
8812
|
+
String.raw`(?:[Qq]ua|[Tt]ừ)\s+(?:điểm\s+)?` + PT3 + String.raw`[^.]{0,12}?` + VERB + LC + String.raw`\s*(?://\s*|song\s*song(?:\s+với)?\s+)(?:với\s+)?` + REF2,
|
|
8813
|
+
"gu"
|
|
8814
|
+
);
|
|
8815
|
+
var THRU_TWO = new RegExp(
|
|
8816
|
+
VERB + LC + String.raw`\s+(?:đi\s+)?qua\s+(?:hai\s+điểm\s+)?` + PT3 + String.raw`\s+và\s+(?:điểm\s+)?` + PT3,
|
|
8817
|
+
"gu"
|
|
8818
|
+
);
|
|
8819
|
+
var SECANT_LINE = new RegExp(
|
|
8820
|
+
DECL + LC + String.raw`[^.]{0,40}?cắt[^.]{0,30}?(?:tại|ở)\s+(?:2\s+|hai\s+)?(?:điểm\s+)?` + PT3 + String.raw`\s+và\s+(?:điểm\s+)?` + PT3,
|
|
8821
|
+
"gu"
|
|
8822
|
+
);
|
|
8823
|
+
var FREE_DECL = new RegExp(
|
|
8824
|
+
String.raw`(?:[Cc]ho|[Vv]ẽ|[Kk]ẻ|[Dd]ựng)\s+(?:một\s+)?${DUONG_KW}\s*thẳng\s+` + LC + // KHÔNG theo sau bởi ràng buộc: ⊥/vuông góc, //|song song, (đi)?qua, cắt,
|
|
8825
|
+
// tiếp xúc. Cho phép ranh giới câu / "và" / "," / "(".
|
|
8826
|
+
String.raw`(?!\s*(?:⊥|//|vuông\s*góc|song\s*song|(?:đi\s+)?qua|cắt|tiếp\s*xúc|tại|ở)\b)`,
|
|
8827
|
+
"gu"
|
|
8828
|
+
);
|
|
8829
|
+
function lineReferenced(problem, name) {
|
|
8830
|
+
const esc = escapeRe(name);
|
|
8831
|
+
const N = String.raw`(?<![\p{L}\d])${esc}(?![\p{L}\d])`;
|
|
8832
|
+
const REFS = [
|
|
8833
|
+
// trước "d": "⊥ d", "song song d", "thuộc/trên/nằm trên d", "cắt … d", "của d"
|
|
8834
|
+
new RegExp(String.raw`(?:⊥|vuông\s*góc(?:\s+với)?|//|song\s*song(?:\s+với)?|thuộc|trên|cắt|tiếp\s*tuyến\s+(?:của|với)?)\s+(?:${N})`, "u"),
|
|
8835
|
+
// sau "d": "d cắt", "d tại", "d lấy", "d, "
|
|
8836
|
+
new RegExp(N + String.raw`\s+(?:cắt|tại|ở|lấy)`, "u")
|
|
8837
|
+
];
|
|
8838
|
+
return REFS.some((re) => re.test(problem));
|
|
8839
|
+
}
|
|
8840
|
+
var PREFILTER68 = new RegExp(
|
|
8841
|
+
String.raw`${DUONG_KW}\s*thẳng\s+[a-z]{1,2}[0-9]?(?![\p{L}])`,
|
|
8842
|
+
"u"
|
|
8843
|
+
);
|
|
8844
|
+
var namedLineRule = {
|
|
8845
|
+
id: "named-line",
|
|
8846
|
+
// Cao (63) như tangentLineNamedAtPoint — đường <d> phải dựng TRƯỚC điểm-trên-d
|
|
8847
|
+
// / intersection tham chiếu d.
|
|
8848
|
+
priority: 63,
|
|
8849
|
+
languages: ["vi"],
|
|
8850
|
+
patterns: [PREFILTER68],
|
|
8851
|
+
match(ctx) {
|
|
8852
|
+
const out = [];
|
|
8853
|
+
const push = (clauseId, intents) => {
|
|
8854
|
+
out.push({ ruleId: "named-line", clauseIds: [clauseId], intents });
|
|
8855
|
+
};
|
|
8856
|
+
for (const c of ctx.clauses) {
|
|
8857
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8858
|
+
PERP.lastIndex = 0;
|
|
8859
|
+
for (const m of c.text.matchAll(PERP)) {
|
|
8860
|
+
const line = m[1];
|
|
8861
|
+
const to = m[2].replace(/\s+/g, "");
|
|
8862
|
+
const anchor = m[3];
|
|
8863
|
+
if (seen.has(line)) continue;
|
|
8864
|
+
seen.add(line);
|
|
8865
|
+
push(c.id, [drawLine(line, "perpThrough", { through: anchor, to })]);
|
|
8866
|
+
}
|
|
8867
|
+
PARA_AFTER.lastIndex = 0;
|
|
8868
|
+
for (const m of c.text.matchAll(PARA_AFTER)) {
|
|
8869
|
+
const line = m[1];
|
|
8870
|
+
const to = m[2].replace(/\s+/g, "");
|
|
8871
|
+
const anchor = m[3];
|
|
8872
|
+
if (seen.has(line) || to.includes(anchor)) continue;
|
|
8873
|
+
seen.add(line);
|
|
8874
|
+
push(c.id, [drawLine(line, "parallelThrough", { through: anchor, to })]);
|
|
8875
|
+
}
|
|
8876
|
+
PARA_BEFORE.lastIndex = 0;
|
|
8877
|
+
for (const m of c.text.matchAll(PARA_BEFORE)) {
|
|
8878
|
+
const anchor = m[1];
|
|
8879
|
+
const line = m[2];
|
|
8880
|
+
const to = m[3].replace(/\s+/g, "");
|
|
8881
|
+
if (seen.has(line) || to.includes(anchor)) continue;
|
|
8882
|
+
seen.add(line);
|
|
8883
|
+
push(c.id, [drawLine(line, "parallelThrough", { through: anchor, to })]);
|
|
8884
|
+
}
|
|
8885
|
+
THRU_TWO.lastIndex = 0;
|
|
8886
|
+
for (const m of c.text.matchAll(THRU_TWO)) {
|
|
8887
|
+
const line = m[1];
|
|
8888
|
+
const a = m[2];
|
|
8889
|
+
const b = m[3];
|
|
8890
|
+
if (seen.has(line) || a === b) continue;
|
|
8891
|
+
seen.add(line);
|
|
8892
|
+
push(c.id, [drawLine(line, "lineThrough", { points: [a, b] })]);
|
|
8893
|
+
}
|
|
8894
|
+
SECANT_LINE.lastIndex = 0;
|
|
8895
|
+
for (const m of c.text.matchAll(SECANT_LINE)) {
|
|
8896
|
+
const line = m[1];
|
|
8897
|
+
const a = m[2];
|
|
8898
|
+
const b = m[3];
|
|
8899
|
+
if (seen.has(line) || a === b) continue;
|
|
8900
|
+
seen.add(line);
|
|
8901
|
+
push(c.id, [drawLine(line, "lineThrough", { points: [a, b] })]);
|
|
8902
|
+
}
|
|
8903
|
+
FREE_DECL.lastIndex = 0;
|
|
8904
|
+
for (const m of c.text.matchAll(FREE_DECL)) {
|
|
8905
|
+
const line = m[1];
|
|
8906
|
+
if (seen.has(line) || !lineReferenced(ctx.problem, line)) continue;
|
|
8907
|
+
seen.add(line);
|
|
8908
|
+
const p1 = `${line}_p1`;
|
|
8909
|
+
const p2 = `${line}_p2`;
|
|
8910
|
+
push(c.id, [
|
|
8911
|
+
addPoint(p1, { kind: "free" }),
|
|
8912
|
+
addPoint(p2, { kind: "free" }),
|
|
8913
|
+
drawLine(line, "lineThrough", { points: [p1, p2] })
|
|
8914
|
+
]);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
return out;
|
|
8918
|
+
}
|
|
8919
|
+
};
|
|
8920
|
+
|
|
8921
|
+
// src/stamps/geometry-2d/ai/rules/bisectorsMeet.ts
|
|
8922
|
+
var ANG_HEAD = "(?:\u2220\\s*|g\xF3c\\s+)([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
8923
|
+
var ANG_TAIL = "(?:\u2220\\s*|g\xF3c\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
8924
|
+
var RE30 = new RegExp(
|
|
8925
|
+
"[Pp]h\xE2n\\s*gi\xE1c\\s+" + ANG_HEAD + "\\s+v\xE0\\s+" + ANG_TAIL + "\\s+(?:c\u1EAFt|giao)\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
8926
|
+
"gu"
|
|
8927
|
+
);
|
|
8928
|
+
var PREFILTER69 = /[Pp]hân\s*giác[^.]{0,80}?(?:cắt|giao)\s+nhau\s+(?:tại|ở)/u;
|
|
8929
|
+
var bisectorsMeetRule = {
|
|
8930
|
+
id: "bisectorsMeet",
|
|
8931
|
+
// Trên intersection (45) để các tia phân giác (draw-line) + điểm giao gom cùng
|
|
8932
|
+
// 1 match; intent-layer order-retry topo lo thứ tự build. 49 = ngang
|
|
8933
|
+
// twoPerpLinesMeet (cùng họ "2 đường đặt-tên giao nhau").
|
|
8934
|
+
priority: 49,
|
|
8935
|
+
languages: ["vi"],
|
|
8936
|
+
patterns: [PREFILTER69],
|
|
8937
|
+
match(ctx) {
|
|
8938
|
+
const out = [];
|
|
8939
|
+
for (const c of ctx.clauses) {
|
|
8940
|
+
RE30.lastIndex = 0;
|
|
8941
|
+
for (const m of c.text.matchAll(RE30)) {
|
|
8942
|
+
const a1 = [m[1], m[2], m[3]];
|
|
8943
|
+
const a2 = [m[4], m[5], m[6]];
|
|
8944
|
+
const p = m[7];
|
|
8945
|
+
const n1 = `bis${a1[0]}${a1[1]}${a1[2]}`;
|
|
8946
|
+
const n2 = `bis${a2[0]}${a2[1]}${a2[2]}`;
|
|
8947
|
+
const intents = [
|
|
8948
|
+
drawLine(n1, "angleBisector", { p1: a1[0], vertex: a1[1], p2: a1[2] }),
|
|
8949
|
+
drawLine(n2, "angleBisector", { p1: a2[0], vertex: a2[1], p2: a2[2] })
|
|
8950
|
+
];
|
|
8951
|
+
const angleLetters = /* @__PURE__ */ new Set([...a1, ...a2]);
|
|
8952
|
+
if (n1 !== n2 && !angleLetters.has(p)) {
|
|
8953
|
+
intents.push(addPoint(p, { kind: "intersection", of: [n1, n2] }));
|
|
8954
|
+
}
|
|
8955
|
+
out.push({ ruleId: "bisectorsMeet", clauseIds: [c.id], intents });
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
return out;
|
|
8959
|
+
}
|
|
8960
|
+
};
|
|
8961
|
+
|
|
8962
|
+
// src/stamps/geometry-2d/ai/rules/hexagon.ts
|
|
8963
|
+
var HEX = "([A-Z])([A-Z])([A-Z])([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
8964
|
+
var HEXAGON_DECL = new RegExp("[Ll]\u1EE5c\\s+gi\xE1c\\s+" + HEX, "gu");
|
|
8965
|
+
var PREFILTER70 = /[Ll]ục\s+giác/u;
|
|
8966
|
+
var HEX_RADIUS = 5;
|
|
8967
|
+
var HEX_APOTHEM = HEX_RADIUS * Math.cos(Math.PI / 6);
|
|
8968
|
+
var HEX_THETAS = Array.from(
|
|
8969
|
+
{ length: 6 },
|
|
8970
|
+
(_, i) => Math.PI / 2 - i * Math.PI / 3
|
|
8971
|
+
);
|
|
8972
|
+
function hexVertexCoord(i) {
|
|
8973
|
+
const t = HEX_THETAS[i];
|
|
8974
|
+
return [
|
|
8975
|
+
+(HEX_RADIUS * Math.cos(t)).toFixed(4),
|
|
8976
|
+
+(HEX_RADIUS * Math.sin(t)).toFixed(4)
|
|
8977
|
+
];
|
|
8978
|
+
}
|
|
8979
|
+
var CENTER4 = "(?:\\(\\s*([A-Z])\\s*\\)|t\xE2m\\s+([A-Z]))";
|
|
8980
|
+
var HEX_CIRCUMSCRIBES = new RegExp(
|
|
8981
|
+
"^[\\s,]*ngo\u1EA1i\\s*ti\u1EBFp\\s+(?:(?:" + CIRCLE_KW + ")\\s*)?" + CENTER4,
|
|
8982
|
+
"u"
|
|
8983
|
+
);
|
|
8984
|
+
var HEX_INSCRIBED_BY = new RegExp(
|
|
8985
|
+
"(?:" + CIRCLE_KW + ")\\s*" + CENTER4 + "\\s*n\u1ED9i\\s*ti\u1EBFp\\s+$",
|
|
8986
|
+
"u"
|
|
8987
|
+
);
|
|
8988
|
+
function detectInscribedCircle(text, start, afterEnd) {
|
|
8989
|
+
const a = HEX_CIRCUMSCRIBES.exec(text.slice(afterEnd));
|
|
8990
|
+
if (a) return a[1] ?? a[2] ?? "O";
|
|
8991
|
+
const b = HEX_INSCRIBED_BY.exec(text.slice(0, start));
|
|
8992
|
+
if (b) return b[1] ?? b[2] ?? "O";
|
|
8993
|
+
return void 0;
|
|
8994
|
+
}
|
|
8995
|
+
var hexagonRule = {
|
|
8996
|
+
id: "hexagon",
|
|
8997
|
+
priority: 100,
|
|
8998
|
+
// ngang quad (đa giác cơ sở) — gate riêng prefilter "lục giác".
|
|
7160
8999
|
languages: ["vi"],
|
|
7161
|
-
patterns: [
|
|
7162
|
-
/(?:[Gg]óc|∠)[^.;\n]{0,18}90/u,
|
|
7163
|
-
/(?:[Gg]óc|∠)\s*[A-Z]{3}\s*vuông/u,
|
|
7164
|
-
/[A-Z]{2}\s*(?:⊥|vuông\s*góc)\s*[A-Z]{2}/u
|
|
7165
|
-
],
|
|
9000
|
+
patterns: [PREFILTER70],
|
|
7166
9001
|
match(ctx) {
|
|
7167
|
-
const
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
(
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
9002
|
+
const out = [];
|
|
9003
|
+
for (const c of ctx.clauses) {
|
|
9004
|
+
HEXAGON_DECL.lastIndex = 0;
|
|
9005
|
+
for (const m of c.text.matchAll(HEXAGON_DECL)) {
|
|
9006
|
+
const labels = [m[1], m[2], m[3], m[4], m[5], m[6]];
|
|
9007
|
+
const start = m.index ?? 0;
|
|
9008
|
+
const afterEnd = start + m[0].length;
|
|
9009
|
+
const vertices = labels.map(
|
|
9010
|
+
(lbl, i) => addPoint(lbl, { kind: "free", at: hexVertexCoord(i) })
|
|
9011
|
+
);
|
|
9012
|
+
const center = detectInscribedCircle(c.text, start, afterEnd);
|
|
9013
|
+
const intents = [...vertices];
|
|
9014
|
+
if (center !== void 0) {
|
|
9015
|
+
const centerName = escapeRe(center) === center ? center : "O";
|
|
9016
|
+
intents.unshift(
|
|
9017
|
+
addPoint(centerName, {
|
|
9018
|
+
kind: "circumcenter",
|
|
9019
|
+
of: [labels[0], labels[2], labels[4]]
|
|
9020
|
+
})
|
|
9021
|
+
);
|
|
9022
|
+
intents.push(
|
|
9023
|
+
drawCircle(centerName, "centerRadius", {
|
|
9024
|
+
center: centerName,
|
|
9025
|
+
radius: HEX_APOTHEM
|
|
9026
|
+
})
|
|
9027
|
+
);
|
|
9028
|
+
}
|
|
9029
|
+
intents.push(markShape("polygon", labels));
|
|
9030
|
+
out.push({ ruleId: "hexagon", clauseIds: [c.id], intents });
|
|
7184
9031
|
}
|
|
7185
|
-
|
|
9032
|
+
}
|
|
7186
9033
|
return out;
|
|
7187
9034
|
}
|
|
7188
9035
|
};
|
|
7189
9036
|
|
|
7190
9037
|
// src/stamps/geometry-2d/ai/rules/registry.ts
|
|
7191
9038
|
var RULES = [
|
|
9039
|
+
namedLineRule,
|
|
9040
|
+
collinearPointsRule,
|
|
9041
|
+
quadDiagonalsRule,
|
|
9042
|
+
twoCirclesTangentRule,
|
|
9043
|
+
commonTangentRule,
|
|
9044
|
+
twoCirclesCenterRadiusMeetRule,
|
|
9045
|
+
rightAngleVertexCirclesRule,
|
|
7192
9046
|
circumcircleCutsLineRule,
|
|
7193
9047
|
lineConcurrencyRule,
|
|
7194
9048
|
parallelThroughCutsCircleRule,
|
|
7195
9049
|
lineThroughCutsTwoLinesRule,
|
|
7196
9050
|
rightAngleViewingRule,
|
|
7197
9051
|
triangleRule,
|
|
9052
|
+
inscribedSquareRule,
|
|
9053
|
+
hexagonRule,
|
|
7198
9054
|
quadRule,
|
|
7199
9055
|
connectRule,
|
|
7200
9056
|
midpointRule,
|
|
@@ -7207,6 +9063,7 @@ var RULES = [
|
|
|
7207
9063
|
circleTriangleRule,
|
|
7208
9064
|
tangentFromExtRule,
|
|
7209
9065
|
externalPointRule,
|
|
9066
|
+
externalPointAtRadiusRule,
|
|
7210
9067
|
arcMidpointRule,
|
|
7211
9068
|
excenterRule,
|
|
7212
9069
|
parallelPerpRule,
|
|
@@ -7246,6 +9103,7 @@ var RULES = [
|
|
|
7246
9103
|
tangentAtCutsLinesRule,
|
|
7247
9104
|
diameterEndpointRule,
|
|
7248
9105
|
circleThroughTwoCutsSidesRule,
|
|
9106
|
+
circleCenterRadiusCutsSidesRule,
|
|
7249
9107
|
circumcircleNamedDistribRule,
|
|
7250
9108
|
circumcirclePairMeetRule,
|
|
7251
9109
|
tangentCircleAtPointRule,
|
|
@@ -7274,7 +9132,8 @@ var RULES = [
|
|
|
7274
9132
|
twoCirclesMeetRule,
|
|
7275
9133
|
radiusRule,
|
|
7276
9134
|
perpNamedLineRule,
|
|
7277
|
-
parallelSidePointsRule
|
|
9135
|
+
parallelSidePointsRule,
|
|
9136
|
+
bisectorsMeetRule
|
|
7278
9137
|
];
|
|
7279
9138
|
var ALL_RULES = [...RULES].sort(
|
|
7280
9139
|
(a, b) => b.priority - a.priority
|
|
@@ -7289,20 +9148,329 @@ function runRules(ctx) {
|
|
|
7289
9148
|
}
|
|
7290
9149
|
|
|
7291
9150
|
// src/stamps/geometry-2d/ai/deterministic/normalizeText.ts
|
|
7292
|
-
var TRIANGLE_SYMBOL = /[
|
|
9151
|
+
var TRIANGLE_SYMBOL = /[Δ∆]\s*(?=[A-Z])/gu;
|
|
7293
9152
|
var CIRCLE_SYNONYM = /vòng\s+tròn/giu;
|
|
9153
|
+
var CIRCLE_PAREN_RESTORE = /(đường\s*tròn\s+)([A-Z])(['′]?)\s*;\s*([Rr])(['′]?)(?![A-Za-z'′])/gu;
|
|
7294
9154
|
var QUAD_SYMBOL = /[◊▱□]\s*(?=[A-Z])/gu;
|
|
9155
|
+
var MIDPOINT_TYPO = /trung\s*điển(?!\p{L})/gu;
|
|
7295
9156
|
var SQRT_NOISE = /√/gu;
|
|
7296
9157
|
var PRIME_VARIANT = /([A-Za-z0-9])[’′´]/gu;
|
|
9158
|
+
var DIAERESIS_O = /(?<!\p{Ll})Ö(?!\p{Ll})/gu;
|
|
9159
|
+
var INTERSECT_PAREN = /(\([A-Z]{3}\))\s+N\s+([A-Z]{2})(\s*=\s*\{)/gu;
|
|
7297
9160
|
var GLUE_WORD_LABEL = /(cắt|tâm|điểm|cạnh|tia|dây|cung|qua|của|và|tại|trên|đến)([A-Z])/gu;
|
|
7298
9161
|
var GLUE_LABEL_WORD = /([A-Z])(sao|nằm|lần|thuộc|cắt|của|đến|trên|tại|và)/gu;
|
|
9162
|
+
var GLUE_RAYTOKEN_WORD = /([A-Z][a-z])(tại|cắt|và|của|đến|trên|thuộc)(?![\p{L}])/gu;
|
|
9163
|
+
var GLUE_VOCAB = [
|
|
9164
|
+
"\u0111\u01B0\u1EDDng",
|
|
9165
|
+
"ngo\u1EA1i",
|
|
9166
|
+
"trung",
|
|
9167
|
+
"ph\xE2n",
|
|
9168
|
+
"\u0111i\u1EC3m",
|
|
9169
|
+
"gi\xE1c",
|
|
9170
|
+
"tr\xF2n",
|
|
9171
|
+
"c\u1EA1nh",
|
|
9172
|
+
"k\xEDnh",
|
|
9173
|
+
"n\u1ED9i",
|
|
9174
|
+
"ti\u1EBFp",
|
|
9175
|
+
"cung",
|
|
9176
|
+
"d\xE2y",
|
|
9177
|
+
"tam",
|
|
9178
|
+
"t\u1EE9",
|
|
9179
|
+
"g\xF3c",
|
|
9180
|
+
"vu\xF4ng",
|
|
9181
|
+
"c\xE2n",
|
|
9182
|
+
"nh\u1ECDn",
|
|
9183
|
+
"n\u1EEDa",
|
|
9184
|
+
"\u0111o\u1EA1n",
|
|
9185
|
+
"h\xECnh",
|
|
9186
|
+
"qua",
|
|
9187
|
+
"c\u1EAFt",
|
|
9188
|
+
"l\u1EA1i",
|
|
9189
|
+
"c\xE1c",
|
|
9190
|
+
"v\xE0",
|
|
9191
|
+
"t\u1EA1i",
|
|
9192
|
+
"m\u1ED9t",
|
|
9193
|
+
"ba",
|
|
9194
|
+
"b\u1EA5t",
|
|
9195
|
+
"k\xEC",
|
|
9196
|
+
"k\u1EF3",
|
|
9197
|
+
"c\xF3",
|
|
9198
|
+
"\u0111i",
|
|
9199
|
+
"v\u1EDBi",
|
|
9200
|
+
"l\u1EA5y",
|
|
9201
|
+
"c\u1EE7a",
|
|
9202
|
+
"l\u1EA7n",
|
|
9203
|
+
"l\u01B0\u1EE3t",
|
|
9204
|
+
"th\u1EE9",
|
|
9205
|
+
"hai",
|
|
9206
|
+
"g\u1ECDi",
|
|
9207
|
+
"k\u1EBB",
|
|
9208
|
+
"v\u1EBD",
|
|
9209
|
+
"cho",
|
|
9210
|
+
"t\xE2m",
|
|
9211
|
+
"b\xE1n",
|
|
9212
|
+
// 'là' (hệ từ) + từ-vựng hình-chiếu/⊥ — tách glue "làtrung"/"làhình"/
|
|
9213
|
+
// "chiếucủa"/"xuốngđường" (OCR HHP dày). Whitelist↔whitelist nên 'là' chỉ tách
|
|
9214
|
+
// khi theo NGAY bởi một từ-khoá hình-học khác (an toàn, regression-gated).
|
|
9215
|
+
"l\xE0",
|
|
9216
|
+
"chi\u1EBFu",
|
|
9217
|
+
"xu\u1ED1ng",
|
|
9218
|
+
"l\xEAn",
|
|
9219
|
+
"\u0111\u1ED1i",
|
|
9220
|
+
"x\u1EE9ng",
|
|
9221
|
+
"song",
|
|
9222
|
+
"th\u1EB3ng"
|
|
9223
|
+
];
|
|
9224
|
+
var GLUE_VOCAB_SORTED = [...GLUE_VOCAB].sort((a, b) => b.length - a.length);
|
|
9225
|
+
var GLUE_ALT = GLUE_VOCAB_SORTED.join("|");
|
|
9226
|
+
var GLUE_VOCAB_RE = new RegExp(`(${GLUE_ALT})(?=${GLUE_ALT})`, "gu");
|
|
9227
|
+
var GLUE_VOCAB_LABEL = new RegExp(
|
|
9228
|
+
`(gi\xE1c|tr\xF2n|ti\u1EBFp|x\xFAc|k\xEDnh|\u0111i\u1EC3m|\u0111o\u1EA1n|c\u1EA1nh|d\xE2y|cung|t\u1EA1i|qua|\u0111i|v\u1EDBi|c\xF3|t\xE2m|th\u1EB3ng|l\xEAn|xu\u1ED1ng)([A-Z])`,
|
|
9229
|
+
"gu"
|
|
9230
|
+
);
|
|
9231
|
+
var CAP_GLUE = /(Cho|Đường|Gọi|Trên|Vẽ|Kẻ|Lấy|Các|Một|Trung|Tâm|Nửa|Hình)(đường|tam|tứ|nửa|hình|điểm|các|cạnh|đoạn|trung|tròn|giác|một|ba|hai|tia|dây|cung)/gu;
|
|
9232
|
+
var GLUE_LABEL_VOCAB = new RegExp(`([A-Z])(${GLUE_ALT})(?![\\p{L}])`, "gu");
|
|
9233
|
+
var GLUE_PAREN_OPEN = /(\p{Ll})\(/gu;
|
|
9234
|
+
var GLUE_PAREN_CLOSE = /\)(\p{L})/gu;
|
|
9235
|
+
function deglueMultiWord(s) {
|
|
9236
|
+
let out = s.replace(CAP_GLUE, "$1 $2");
|
|
9237
|
+
for (let k = 0; k < 8; k++) {
|
|
9238
|
+
const next = out.replace(GLUE_VOCAB_RE, "$1 ");
|
|
9239
|
+
if (next === out) break;
|
|
9240
|
+
out = next;
|
|
9241
|
+
}
|
|
9242
|
+
out = out.replace(GLUE_VOCAB_LABEL, "$1 $2").replace(GLUE_LABEL_VOCAB, "$1 $2").replace(GLUE_PAREN_OPEN, "$1 (").replace(GLUE_PAREN_CLOSE, ") $1");
|
|
9243
|
+
return out;
|
|
9244
|
+
}
|
|
7299
9245
|
function normalizeProblemText(problem) {
|
|
7300
|
-
|
|
9246
|
+
let s = problem.replace(TRIANGLE_SYMBOL, "tam gi\xE1c ").replace(QUAD_SYMBOL, "t\u1EE9 gi\xE1c ").replace(MIDPOINT_TYPO, "trung \u0111i\u1EC3m").replace(DIAERESIS_O, "O").replace(INTERSECT_PAREN, "$1 \u2229 $2$3").replace(CIRCLE_SYNONYM, "\u0111\u01B0\u1EDDng tr\xF2n");
|
|
9247
|
+
if (!/\(\s*[A-Z]/u.test(s)) s = s.replace(CIRCLE_PAREN_RESTORE, "$1($2$3;$4$5)");
|
|
9248
|
+
return deglueMultiWord(
|
|
9249
|
+
s.replace(SQRT_NOISE, " ").replace(PRIME_VARIANT, "$1'").replace(GLUE_WORD_LABEL, "$1 $2").replace(GLUE_LABEL_WORD, "$1 $2").replace(GLUE_RAYTOKEN_WORD, "$1 $2")
|
|
9250
|
+
);
|
|
9251
|
+
}
|
|
9252
|
+
|
|
9253
|
+
// src/stamps/geometry-2d/ai/deterministic/correctUserInput.ts
|
|
9254
|
+
function foldVietnamese(s) {
|
|
9255
|
+
return s.toLowerCase().replace(/đ/g, "d").normalize("NFD").replace(/[̀-ͯ]/g, "");
|
|
9256
|
+
}
|
|
9257
|
+
function levenshtein(a, b) {
|
|
9258
|
+
const m = a.length;
|
|
9259
|
+
const n = b.length;
|
|
9260
|
+
if (m === 0) return n;
|
|
9261
|
+
if (n === 0) return m;
|
|
9262
|
+
let prev = new Array(n + 1);
|
|
9263
|
+
for (let j = 0; j <= n; j++) prev[j] = j;
|
|
9264
|
+
for (let i = 1; i <= m; i++) {
|
|
9265
|
+
const cur = new Array(n + 1);
|
|
9266
|
+
cur[0] = i;
|
|
9267
|
+
for (let j = 1; j <= n; j++) {
|
|
9268
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
9269
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
9270
|
+
}
|
|
9271
|
+
prev = cur;
|
|
9272
|
+
}
|
|
9273
|
+
return prev[n];
|
|
9274
|
+
}
|
|
9275
|
+
var CORRECTION_VOCAB = [
|
|
9276
|
+
// circle / line core
|
|
9277
|
+
"\u0111\u01B0\u1EDDng",
|
|
9278
|
+
"tr\xF2n",
|
|
9279
|
+
"b\xE1n",
|
|
9280
|
+
"k\xEDnh",
|
|
9281
|
+
"v\xF2ng",
|
|
9282
|
+
// polygon / triangle (TAM before TÂM, THANG before THẲNG — see collision-winner warning above)
|
|
9283
|
+
"tam",
|
|
9284
|
+
"t\xE2m",
|
|
9285
|
+
"thang",
|
|
9286
|
+
"th\u1EB3ng",
|
|
9287
|
+
"gi\xE1c",
|
|
9288
|
+
"t\u1EE9",
|
|
9289
|
+
"g\xF3c",
|
|
9290
|
+
"c\u1EA1nh",
|
|
9291
|
+
"\u0111o\u1EA1n",
|
|
9292
|
+
"h\xECnh",
|
|
9293
|
+
"ch\u1EEF",
|
|
9294
|
+
"nh\u1EADt",
|
|
9295
|
+
"vu\xF4ng",
|
|
9296
|
+
"c\xE2n",
|
|
9297
|
+
"nh\u1ECDn",
|
|
9298
|
+
"\u0111\u1EC1u",
|
|
9299
|
+
"thoi",
|
|
9300
|
+
"b\xECnh",
|
|
9301
|
+
"h\xE0nh",
|
|
9302
|
+
// chord / arc / point
|
|
9303
|
+
"d\xE2y",
|
|
9304
|
+
"cung",
|
|
9305
|
+
"\u0111i\u1EC3m",
|
|
9306
|
+
"tia",
|
|
9307
|
+
"n\u1EEDa",
|
|
9308
|
+
// tangent / inscribed
|
|
9309
|
+
"ti\u1EBFp",
|
|
9310
|
+
"tuy\u1EBFn",
|
|
9311
|
+
"n\u1ED9i",
|
|
9312
|
+
"ngo\u1EA1i",
|
|
9313
|
+
"x\xFAc",
|
|
9314
|
+
// cevians / projection
|
|
9315
|
+
"trung",
|
|
9316
|
+
"tr\u1EF1c",
|
|
9317
|
+
"cao",
|
|
9318
|
+
"ph\xE2n",
|
|
9319
|
+
"chi\u1EBFu",
|
|
9320
|
+
"\u0111\u1ED1i",
|
|
9321
|
+
"x\u1EE9ng",
|
|
9322
|
+
// verbs / connectors phổ biến trong đề
|
|
9323
|
+
"cho",
|
|
9324
|
+
"g\u1ECDi",
|
|
9325
|
+
"v\u1EBD",
|
|
9326
|
+
"k\u1EBB",
|
|
9327
|
+
"l\u1EA5y",
|
|
9328
|
+
"qua",
|
|
9329
|
+
"c\u1EAFt",
|
|
9330
|
+
"tr\xEAn",
|
|
9331
|
+
"d\u01B0\u1EDBi",
|
|
9332
|
+
"\u0111\u1EBFn",
|
|
9333
|
+
// 'đi' ĐÃ GỠ: "di"→"đi" net-harmful trên corpus (regress 7 bài locus "di động"
|
|
9334
|
+
// → "đi động"). Từ TRẦN 2 ký tự đa-nghĩa: rủi ro cao, lợi ích thấp.
|
|
9335
|
+
"song",
|
|
9336
|
+
"v\u1EDBi",
|
|
9337
|
+
"c\u1EE7a",
|
|
9338
|
+
"v\xE0",
|
|
9339
|
+
"l\xE0",
|
|
9340
|
+
"c\xF3",
|
|
9341
|
+
"n\u1EB1m",
|
|
9342
|
+
"thu\u1ED9c",
|
|
9343
|
+
"t\u1EA1i",
|
|
9344
|
+
"m\u1ED9t",
|
|
9345
|
+
"hai",
|
|
9346
|
+
"ba",
|
|
9347
|
+
"c\xE1c",
|
|
9348
|
+
"l\u1EA7n",
|
|
9349
|
+
"l\u01B0\u1EE3t",
|
|
9350
|
+
"th\u1EE9",
|
|
9351
|
+
"b\u1EA5t",
|
|
9352
|
+
"k\xEC",
|
|
9353
|
+
"k\u1EF3",
|
|
9354
|
+
"trong",
|
|
9355
|
+
"ngo\xE0i",
|
|
9356
|
+
"l\xEAn",
|
|
9357
|
+
"xu\u1ED1ng"
|
|
9358
|
+
];
|
|
9359
|
+
var FOLDED_VOCAB = (() => {
|
|
9360
|
+
const m = /* @__PURE__ */ new Map();
|
|
9361
|
+
for (const w of CORRECTION_VOCAB) {
|
|
9362
|
+
const f = foldVietnamese(w);
|
|
9363
|
+
if (!m.has(f)) m.set(f, w);
|
|
9364
|
+
}
|
|
9365
|
+
return m;
|
|
9366
|
+
})();
|
|
9367
|
+
var LETTERS_ONLY = /^\p{L}+$/u;
|
|
9368
|
+
var UPPER_LABEL = /^\p{Lu}{1,4}$/u;
|
|
9369
|
+
var HAS_UPPER = /\p{Lu}/u;
|
|
9370
|
+
function classifyToken(token) {
|
|
9371
|
+
if (!LETTERS_ONLY.test(token)) return "protected";
|
|
9372
|
+
if (UPPER_LABEL.test(token)) return "protected";
|
|
9373
|
+
return HAS_UPPER.test(token) ? "upper" : "lower";
|
|
9374
|
+
}
|
|
9375
|
+
var DEFAULT_CORRECT_CONFIG = {
|
|
9376
|
+
structure: true,
|
|
9377
|
+
accents: true,
|
|
9378
|
+
// Tầng 3 (fuzzy typo) MẶC ĐỊNH TẮT: trên corpus thật nó băm từ TRẦN hợp lệ
|
|
9379
|
+
// ("nhau"→"nhật", "giao"→"giác", "di"→"đi") vì vocab hình-học trùng không-gian
|
|
9380
|
+
// âm với văn xuôi Việt → net-harmful (đo qua diag-all). Vẫn cài đặt đầy đủ + bật
|
|
9381
|
+
// được qua flag (mutation test / opt-in tương lai). accents+structure đủ an toàn.
|
|
9382
|
+
typo: false,
|
|
9383
|
+
maxTypoDistance: 1,
|
|
9384
|
+
minTypoLen: 4
|
|
9385
|
+
};
|
|
9386
|
+
var SYMBOL_MAP = [
|
|
9387
|
+
// "độ" có dấu = độ-góc rõ ràng → luôn đổi. "do" trần mơ hồ (do đó/do dai/lý do)
|
|
9388
|
+
// → CHỈ đổi khi KHÔNG theo sau bởi một từ-chữ (cuối cụm / trước dấu câu), tránh
|
|
9389
|
+
// băm "2 do dai"→"2° dai". "90 do"/"90 do." vẫn đổi.
|
|
9390
|
+
[/(\d+)\s*độ(?!\p{L})/giu, "$1\xB0"],
|
|
9391
|
+
[/(\d+)\s*do(?!\s*\p{L})/giu, "$1\xB0"]
|
|
9392
|
+
];
|
|
9393
|
+
function applyStructure(s) {
|
|
9394
|
+
let out = s;
|
|
9395
|
+
for (const [re, to] of SYMBOL_MAP) out = out.replace(re, to);
|
|
9396
|
+
return out.replace(/\s+/g, " ").trim();
|
|
9397
|
+
}
|
|
9398
|
+
function applyLeadingCase(canonical, original) {
|
|
9399
|
+
if (/^\p{Lu}/u.test(original)) {
|
|
9400
|
+
return canonical.charAt(0).toUpperCase() + canonical.slice(1);
|
|
9401
|
+
}
|
|
9402
|
+
return canonical;
|
|
9403
|
+
}
|
|
9404
|
+
function fuzzyLookup(folded, cfg) {
|
|
9405
|
+
if (folded.length < cfg.minTypoLen) return null;
|
|
9406
|
+
let best = null;
|
|
9407
|
+
let bestDist = cfg.maxTypoDistance + 1;
|
|
9408
|
+
let tie = false;
|
|
9409
|
+
for (const [key, canonical] of FOLDED_VOCAB) {
|
|
9410
|
+
if (canonical === key) continue;
|
|
9411
|
+
if (Math.abs(key.length - folded.length) > cfg.maxTypoDistance) continue;
|
|
9412
|
+
const d = levenshtein(folded, key);
|
|
9413
|
+
if (d < bestDist) {
|
|
9414
|
+
bestDist = d;
|
|
9415
|
+
best = canonical;
|
|
9416
|
+
tie = false;
|
|
9417
|
+
} else if (d === bestDist) {
|
|
9418
|
+
tie = true;
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
if (best === null || bestDist > cfg.maxTypoDistance || tie) return null;
|
|
9422
|
+
return best;
|
|
9423
|
+
}
|
|
9424
|
+
function letterFold(token) {
|
|
9425
|
+
return foldVietnamese(token).replace(/[^a-z]+/g, "");
|
|
9426
|
+
}
|
|
9427
|
+
var DISAMBIG = {
|
|
9428
|
+
// "tam giác" giữ "tam"; còn lại (tâm O, tâm đường tròn…) → "tâm".
|
|
9429
|
+
tam: (_p, n) => n === "giac" ? "tam" : "t\xE2m",
|
|
9430
|
+
// "hình thang" giữ "thang"; còn lại (đường thẳng, thẳng hàng…) → "thẳng".
|
|
9431
|
+
thang: (p, _n) => p === "hinh" ? "thang" : "th\u1EB3ng",
|
|
9432
|
+
// "ngoại tiếp" → "ngoại"; còn lại (nằm ngoài, điểm ngoài…) → "ngoài".
|
|
9433
|
+
ngoai: (_p, n) => n === "tiep" ? "ngo\u1EA1i" : "ngo\xE0i"
|
|
9434
|
+
};
|
|
9435
|
+
function correctToken(token, cfg, prevFold = "", nextFold = "") {
|
|
9436
|
+
const klass = classifyToken(token);
|
|
9437
|
+
if (klass === "protected") return token;
|
|
9438
|
+
const folded = foldVietnamese(token);
|
|
9439
|
+
if (folded !== token.toLowerCase()) return token;
|
|
9440
|
+
if (cfg.accents) {
|
|
9441
|
+
const dis = DISAMBIG[folded];
|
|
9442
|
+
if (dis) return applyLeadingCase(dis(prevFold, nextFold), token);
|
|
9443
|
+
const exact = FOLDED_VOCAB.get(folded);
|
|
9444
|
+
if (exact) return applyLeadingCase(exact, token);
|
|
9445
|
+
}
|
|
9446
|
+
if (cfg.typo && klass === "lower") {
|
|
9447
|
+
const near = fuzzyLookup(folded, cfg);
|
|
9448
|
+
if (near) return near;
|
|
9449
|
+
}
|
|
9450
|
+
return token;
|
|
9451
|
+
}
|
|
9452
|
+
function correctUserInput(input, cfg = DEFAULT_CORRECT_CONFIG) {
|
|
9453
|
+
let s = input;
|
|
9454
|
+
if (cfg.structure) s = applyStructure(s);
|
|
9455
|
+
if (!cfg.accents && !cfg.typo) return s;
|
|
9456
|
+
const parts = s.split(/(\s+)/);
|
|
9457
|
+
const wordIdx = [];
|
|
9458
|
+
for (let i = 0; i < parts.length; i++) {
|
|
9459
|
+
if (parts[i] !== "" && !/\s/.test(parts[i])) wordIdx.push(i);
|
|
9460
|
+
}
|
|
9461
|
+
const out = parts.slice();
|
|
9462
|
+
for (let w = 0; w < wordIdx.length; w++) {
|
|
9463
|
+
const i = wordIdx[w];
|
|
9464
|
+
const prevFold = w > 0 ? letterFold(parts[wordIdx[w - 1]]) : "";
|
|
9465
|
+
const nextFold = w < wordIdx.length - 1 ? letterFold(parts[wordIdx[w + 1]]) : "";
|
|
9466
|
+
out[i] = correctToken(parts[i], cfg, prevFold, nextFold);
|
|
9467
|
+
}
|
|
9468
|
+
return out.join("");
|
|
7301
9469
|
}
|
|
7302
9470
|
|
|
7303
9471
|
// src/stamps/geometry-2d/ai/deterministic/runDeterministicIntents.ts
|
|
7304
9472
|
function collectDeterministic(rawProblem) {
|
|
7305
|
-
const problem = normalizeProblemText(rawProblem);
|
|
9473
|
+
const problem = normalizeProblemText(correctUserInput(rawProblem));
|
|
7306
9474
|
const clauses = segmentClauses(problem);
|
|
7307
9475
|
const drawableClauses = clauses.filter((c) => c.hasGeometry);
|
|
7308
9476
|
let drawableProblem = problem;
|
|
@@ -7351,7 +9519,7 @@ var SHAPE_TRI = /tam giác\s+([A-Z]{3})(?![A-Z])/gu;
|
|
|
7351
9519
|
var SHAPE_QUAD = /(?:tứ giác|hình\s+(?:vuông|chữ nhật|bình hành|thoi|thang))\s+([A-Z]{4})(?![A-Z])/gu;
|
|
7352
9520
|
function collectExpectedNames(problem) {
|
|
7353
9521
|
const names = /* @__PURE__ */ new Set();
|
|
7354
|
-
const
|
|
9522
|
+
const add2 = (re, groups) => {
|
|
7355
9523
|
re.lastIndex = 0;
|
|
7356
9524
|
let m;
|
|
7357
9525
|
while ((m = re.exec(problem)) !== null) {
|
|
@@ -7361,9 +9529,9 @@ function collectExpectedNames(problem) {
|
|
|
7361
9529
|
}
|
|
7362
9530
|
}
|
|
7363
9531
|
};
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
9532
|
+
add2(NAMED_INTRO, [[1, 2]]);
|
|
9533
|
+
add2(NAMED_LA, [[1, 2]]);
|
|
9534
|
+
add2(NAMED_LANLUOT, [[1, 2], [3, 4]]);
|
|
7367
9535
|
for (const re of [SHAPE_TRI, SHAPE_QUAD]) {
|
|
7368
9536
|
re.lastIndex = 0;
|
|
7369
9537
|
let m;
|
|
@@ -7614,6 +9782,12 @@ function rewriteCircleRefs(intent, rename) {
|
|
|
7614
9782
|
if (newC1 !== c.c1 || newC2 !== c.c2) {
|
|
7615
9783
|
return { ...intent, constraint: { ...c, c1: newC1, c2: newC2 } };
|
|
7616
9784
|
}
|
|
9785
|
+
} else if (c.kind === "commonTangentPoint") {
|
|
9786
|
+
const n0 = rename.get(c.circles[0]) ?? c.circles[0];
|
|
9787
|
+
const n1 = rename.get(c.circles[1]) ?? c.circles[1];
|
|
9788
|
+
if (n0 !== c.circles[0] || n1 !== c.circles[1]) {
|
|
9789
|
+
return { ...intent, constraint: { ...c, circles: [n0, n1] } };
|
|
9790
|
+
}
|
|
7617
9791
|
} else if (c.kind === "intersection") {
|
|
7618
9792
|
const newOf = c.of.map((r) => rename.get(r) ?? r);
|
|
7619
9793
|
if (newOf[0] !== c.of[0] || newOf[1] !== c.of[1]) {
|
|
@@ -7681,14 +9855,14 @@ function repairCircleIntersections(points, shapes) {
|
|
|
7681
9855
|
const targetD = (lower + upper) / 2;
|
|
7682
9856
|
let dx = mover.x - anchor[0];
|
|
7683
9857
|
let dy = mover.y - anchor[1];
|
|
7684
|
-
const
|
|
7685
|
-
if (
|
|
9858
|
+
const len2 = Math.hypot(dx, dy);
|
|
9859
|
+
if (len2 < 1e-9) {
|
|
7686
9860
|
dx = 1;
|
|
7687
9861
|
dy = 0;
|
|
7688
9862
|
}
|
|
7689
|
-
const
|
|
7690
|
-
mover.x = anchor[0] + targetD * dx /
|
|
7691
|
-
mover.y = anchor[1] + targetD * dy /
|
|
9863
|
+
const norm5 = Math.hypot(dx, dy) || 1;
|
|
9864
|
+
mover.x = anchor[0] + targetD * dx / norm5;
|
|
9865
|
+
mover.y = anchor[1] + targetD * dy / norm5;
|
|
7692
9866
|
}
|
|
7693
9867
|
}
|
|
7694
9868
|
var NameZ = zod.z.string().regex(/^[A-Za-z][A-Za-z0-9_'₀-₉]{0,11}$/);
|
|
@@ -7785,6 +9959,7 @@ var onPerpBisectorModule = defineModule({
|
|
|
7785
9959
|
t: zod.z.number().optional()
|
|
7786
9960
|
}),
|
|
7787
9961
|
collectRefs: (e) => [e.p1, e.p2],
|
|
9962
|
+
refSpecs: [{ field: "p1", role: "point" }, { field: "p2", role: "point" }],
|
|
7788
9963
|
emit: (e, ctx) => [{
|
|
7789
9964
|
role: "primary",
|
|
7790
9965
|
object: emitPointObject(ctx.resolveId(e.name), e.name, {
|
|
@@ -8428,6 +10603,7 @@ var circleDiameterModule = defineModule({
|
|
|
8428
10603
|
visible: zod.z.boolean().optional()
|
|
8429
10604
|
}),
|
|
8430
10605
|
collectRefs: (e) => [e.p1, e.p2],
|
|
10606
|
+
refSpecs: [{ field: "p1", role: "point" }, { field: "p2", role: "point" }],
|
|
8431
10607
|
emit: (e, ctx) => [{
|
|
8432
10608
|
role: "primary",
|
|
8433
10609
|
object: {
|
|
@@ -8774,6 +10950,7 @@ var mixtilinearPointModule = defineModule({
|
|
|
8774
10950
|
which: zod.z.union([zod.z.literal("center"), zod.z.literal("touch")])
|
|
8775
10951
|
}),
|
|
8776
10952
|
collectRefs: (e) => [...e.vertices],
|
|
10953
|
+
refSpecs: [{ field: "vertices", role: "point", many: true }],
|
|
8777
10954
|
emit: (e, ctx) => [{
|
|
8778
10955
|
role: "primary",
|
|
8779
10956
|
object: emitPointObject(ctx.resolveId(e.name), e.name, {
|
|
@@ -8888,6 +11065,32 @@ var pointAtDistanceModule = defineModule({
|
|
|
8888
11065
|
}];
|
|
8889
11066
|
}
|
|
8890
11067
|
});
|
|
11068
|
+
var commonTangentPointModule = defineModule({
|
|
11069
|
+
kind: "commonTangentPoint",
|
|
11070
|
+
role: "point",
|
|
11071
|
+
category: "points",
|
|
11072
|
+
prefix: "p",
|
|
11073
|
+
schema: zod.z.object({
|
|
11074
|
+
name: NameZ,
|
|
11075
|
+
kind: zod.z.literal("commonTangentPoint"),
|
|
11076
|
+
circles: zod.z.tuple([NameZ, NameZ]),
|
|
11077
|
+
on: zod.z.union([zod.z.literal(0), zod.z.literal(1)]),
|
|
11078
|
+
variant: zod.z.enum(["external", "internal"]),
|
|
11079
|
+
side: zod.z.union([zod.z.literal(0), zod.z.literal(1)])
|
|
11080
|
+
}),
|
|
11081
|
+
collectRefs: (e) => [e.circles[0], e.circles[1]],
|
|
11082
|
+
refSpecs: [{ field: "circles", role: "circle", many: true }],
|
|
11083
|
+
emit: (e, ctx) => [{
|
|
11084
|
+
role: "primary",
|
|
11085
|
+
object: emitPointObject(ctx.resolveId(e.name), e.name, {
|
|
11086
|
+
kind: "commonTangentPoint",
|
|
11087
|
+
circles: [ctx.resolveId(e.circles[0]), ctx.resolveId(e.circles[1])],
|
|
11088
|
+
on: e.on,
|
|
11089
|
+
variant: e.variant,
|
|
11090
|
+
side: e.side
|
|
11091
|
+
})
|
|
11092
|
+
}]
|
|
11093
|
+
});
|
|
8891
11094
|
|
|
8892
11095
|
// src/stamps/geometry-2d/dsl/registry.ts
|
|
8893
11096
|
var ALL_MODULES = [
|
|
@@ -8934,7 +11137,9 @@ var ALL_MODULES = [
|
|
|
8934
11137
|
reflectPointModule,
|
|
8935
11138
|
reflectLineModule,
|
|
8936
11139
|
// Cụm B points
|
|
8937
|
-
pointAtDistanceModule
|
|
11140
|
+
pointAtDistanceModule,
|
|
11141
|
+
// 2-circle relations (spec mục A)
|
|
11142
|
+
commonTangentPointModule
|
|
8938
11143
|
];
|
|
8939
11144
|
var KIND_REGISTRY = new Map(ALL_MODULES.map((m) => [m.kind, m]));
|
|
8940
11145
|
var POINT_KINDS = new Set(
|
|
@@ -9137,156 +11342,6 @@ var IntentBuilderError = class extends Error {
|
|
|
9137
11342
|
}
|
|
9138
11343
|
};
|
|
9139
11344
|
|
|
9140
|
-
// src/stamps/geometry-2d/ai/intent-builders/shared.ts
|
|
9141
|
-
var SQRT3 = 1.7320508075688772;
|
|
9142
|
-
function triangleCanonical(variant) {
|
|
9143
|
-
switch (variant) {
|
|
9144
|
-
case "equilateral":
|
|
9145
|
-
return [[0, 0], [4, 0], [2, 2 * SQRT3]];
|
|
9146
|
-
case "isoceles-AB":
|
|
9147
|
-
return [[0, 0], [4, 0], [2, 3]];
|
|
9148
|
-
case "isoceles-BC":
|
|
9149
|
-
return [[0, 3], [-2, 0], [2, 0]];
|
|
9150
|
-
case "isoceles-CA":
|
|
9151
|
-
return [[0, 0], [2, 3], [4, 0]];
|
|
9152
|
-
case "right-at-A":
|
|
9153
|
-
return [[0, 0], [4, 0], [0, 3]];
|
|
9154
|
-
case "right-at-B":
|
|
9155
|
-
return [[0, 0], [4, 0], [4, 3]];
|
|
9156
|
-
case "right-at-C":
|
|
9157
|
-
return [[0, 0], [6, 0], [3, 3]];
|
|
9158
|
-
case "any":
|
|
9159
|
-
default:
|
|
9160
|
-
return [[0, 0], [5, 0], [2, 3]];
|
|
9161
|
-
}
|
|
9162
|
-
}
|
|
9163
|
-
function squareCanonical() {
|
|
9164
|
-
return [[0, 0], [4, 0], [4, 4], [0, 4]];
|
|
9165
|
-
}
|
|
9166
|
-
function rectangleCanonical(variant) {
|
|
9167
|
-
if (variant === "tall") return [[0, 0], [2.5, 0], [2.5, 4], [0, 4]];
|
|
9168
|
-
return [[0, 0], [4, 0], [4, 2.5], [0, 2.5]];
|
|
9169
|
-
}
|
|
9170
|
-
function rhombusCanonical() {
|
|
9171
|
-
return [[-2, 0], [0, -1.5], [2, 0], [0, 1.5]];
|
|
9172
|
-
}
|
|
9173
|
-
function trapezoidCanonical(variant) {
|
|
9174
|
-
switch (variant) {
|
|
9175
|
-
case "right":
|
|
9176
|
-
return [[0, 0], [5, 0], [3, 3], [0, 3]];
|
|
9177
|
-
case "isoceles":
|
|
9178
|
-
return [[0, 0], [5, 0], [4, 3], [1, 3]];
|
|
9179
|
-
case "general":
|
|
9180
|
-
default:
|
|
9181
|
-
return [[0, 0], [5, 0], [3.5, 3], [1, 3]];
|
|
9182
|
-
}
|
|
9183
|
-
}
|
|
9184
|
-
function parallelogramCanonical() {
|
|
9185
|
-
return [[0, 0], [4, 0], [5, 3], [1, 3]];
|
|
9186
|
-
}
|
|
9187
|
-
function quadrilateralCanonical() {
|
|
9188
|
-
return [[0, 0], [5, 0], [4.5, 3.5], [0.5, 3]];
|
|
9189
|
-
}
|
|
9190
|
-
function addPoint2(s, p) {
|
|
9191
|
-
if (s.pointNames.has(p.name)) {
|
|
9192
|
-
if (p.kind !== "free") {
|
|
9193
|
-
const idx = s.points.findIndex((q) => q.name === p.name);
|
|
9194
|
-
if (idx >= 0 && s.points[idx].kind === "free") s.points[idx] = p;
|
|
9195
|
-
}
|
|
9196
|
-
return;
|
|
9197
|
-
}
|
|
9198
|
-
s.points.push(p);
|
|
9199
|
-
s.pointNames.add(p.name);
|
|
9200
|
-
}
|
|
9201
|
-
var FREE_DEFAULT_SPREAD = [
|
|
9202
|
-
[3, 3],
|
|
9203
|
-
[-3, 3],
|
|
9204
|
-
[3, -3],
|
|
9205
|
-
[-3, -3],
|
|
9206
|
-
[4, 0],
|
|
9207
|
-
[-4, 0],
|
|
9208
|
-
[0, 4],
|
|
9209
|
-
[0, -4]
|
|
9210
|
-
];
|
|
9211
|
-
function defaultFreeCoord(s) {
|
|
9212
|
-
let count = 0;
|
|
9213
|
-
for (const p of s.points) if (p.kind === "free") count++;
|
|
9214
|
-
return [...FREE_DEFAULT_SPREAD[count % FREE_DEFAULT_SPREAD.length]];
|
|
9215
|
-
}
|
|
9216
|
-
function addShape(s, sh) {
|
|
9217
|
-
if (s.shapeNames.has(sh.name)) return;
|
|
9218
|
-
s.shapes.push(sh);
|
|
9219
|
-
s.shapeNames.add(sh.name);
|
|
9220
|
-
}
|
|
9221
|
-
function uniqueShapeName(s, base) {
|
|
9222
|
-
if (!s.shapeNames.has(base)) return base;
|
|
9223
|
-
let i = 2;
|
|
9224
|
-
while (s.shapeNames.has(`${base}${i}`)) i++;
|
|
9225
|
-
return `${base}${i}`;
|
|
9226
|
-
}
|
|
9227
|
-
function uniquePointName(s, base) {
|
|
9228
|
-
if (!s.pointNames.has(base)) return base;
|
|
9229
|
-
let i = 2;
|
|
9230
|
-
while (s.pointNames.has(`${base}${i}`)) i++;
|
|
9231
|
-
return `${base}${i}`;
|
|
9232
|
-
}
|
|
9233
|
-
function ensureSegment(s, a, b) {
|
|
9234
|
-
const k1 = `${a}-${b}`;
|
|
9235
|
-
const k2 = `${b}-${a}`;
|
|
9236
|
-
const existing = s.segmentByEnds.get(k1) ?? s.segmentByEnds.get(k2);
|
|
9237
|
-
if (existing) return existing;
|
|
9238
|
-
const name = uniqueShapeName(s, `${a}${b}`);
|
|
9239
|
-
addShape(s, { name, kind: "segment", p1: a, p2: b });
|
|
9240
|
-
s.segmentByEnds.set(k1, name);
|
|
9241
|
-
return name;
|
|
9242
|
-
}
|
|
9243
|
-
function resolveLineRefWithFallback(s, ref, anchor) {
|
|
9244
|
-
if (/^[A-Za-z]$/.test(ref) && /^[A-Za-z]$/.test(anchor) && ref !== anchor && s.pointNames.has(ref) && s.pointNames.has(anchor)) {
|
|
9245
|
-
const [a, b] = ref < anchor ? [ref, anchor] : [anchor, ref];
|
|
9246
|
-
return ensureSegment(s, a, b);
|
|
9247
|
-
}
|
|
9248
|
-
return resolveSegmentRef(s, ref);
|
|
9249
|
-
}
|
|
9250
|
-
function resolveSegmentRef(s, ref) {
|
|
9251
|
-
if (s.shapeNames.has(ref)) return ref;
|
|
9252
|
-
if (/^[A-Z][A-Z]$/u.test(ref)) {
|
|
9253
|
-
return ensureSegment(s, ref[0], ref[1]);
|
|
9254
|
-
}
|
|
9255
|
-
if (ref.length === 2 && s.pointNames.has(ref[0]) && s.pointNames.has(ref[1])) {
|
|
9256
|
-
return ensureSegment(s, ref[0], ref[1]);
|
|
9257
|
-
}
|
|
9258
|
-
const dashMatch = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
9259
|
-
if (dashMatch && s.pointNames.has(dashMatch[1]) && s.pointNames.has(dashMatch[2])) {
|
|
9260
|
-
return ensureSegment(s, dashMatch[1], dashMatch[2]);
|
|
9261
|
-
}
|
|
9262
|
-
const sub = splitKnownPair(s, ref);
|
|
9263
|
-
if (sub) return ensureSegment(s, sub[0], sub[1]);
|
|
9264
|
-
return ref;
|
|
9265
|
-
}
|
|
9266
|
-
function splitKnownPair(s, ref) {
|
|
9267
|
-
for (let i = ref.length - 1; i >= 1; i--) {
|
|
9268
|
-
const a = ref.slice(0, i);
|
|
9269
|
-
const b = ref.slice(i);
|
|
9270
|
-
if (s.pointNames.has(a) && s.pointNames.has(b)) return [a, b];
|
|
9271
|
-
}
|
|
9272
|
-
return null;
|
|
9273
|
-
}
|
|
9274
|
-
function parseEnds(ref) {
|
|
9275
|
-
if (ref.length === 2) return [ref[0], ref[1]];
|
|
9276
|
-
const m = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
9277
|
-
if (m) return [m[1], m[2]];
|
|
9278
|
-
return null;
|
|
9279
|
-
}
|
|
9280
|
-
var SHAPE_VARIANTS = {
|
|
9281
|
-
triangle: ["any", "equilateral", "isoceles-AB", "isoceles-BC", "isoceles-CA", "right-at-A", "right-at-B", "right-at-C"],
|
|
9282
|
-
square: ["standard"],
|
|
9283
|
-
rectangle: ["standard", "wide", "tall"],
|
|
9284
|
-
rhombus: ["standard"],
|
|
9285
|
-
trapezoid: ["right", "isoceles", "general"],
|
|
9286
|
-
parallelogram: ["standard"],
|
|
9287
|
-
quadrilateral: ["any"]
|
|
9288
|
-
};
|
|
9289
|
-
|
|
9290
11345
|
// src/stamps/geometry-2d/ai/intent-builders/draw-shape.ts
|
|
9291
11346
|
var buildDrawShape = (s, intent) => {
|
|
9292
11347
|
const labels = intent.labels;
|
|
@@ -9559,6 +11614,25 @@ var buildPointAtDistance = (s, intent) => {
|
|
|
9559
11614
|
addPoint2(s, { name: intent.name, kind: "pointAtDistance", from: c.from, through: c.through, distance: c.distance });
|
|
9560
11615
|
};
|
|
9561
11616
|
|
|
11617
|
+
// src/stamps/geometry-2d/ai/intent-builders/add-point/commonTangentPoint.ts
|
|
11618
|
+
var buildCommonTangentPoint = (s, intent) => {
|
|
11619
|
+
const c = intent.constraint;
|
|
11620
|
+
if (c.kind !== "commonTangentPoint") return;
|
|
11621
|
+
const isCircle = (name) => {
|
|
11622
|
+
const sh = s.shapes.find((x) => x.name === name);
|
|
11623
|
+
return !!sh && CIRCLE_KINDS.has(sh.kind);
|
|
11624
|
+
};
|
|
11625
|
+
if (!isCircle(c.circles[0]) || !isCircle(c.circles[1])) return;
|
|
11626
|
+
addPoint2(s, {
|
|
11627
|
+
name: intent.name,
|
|
11628
|
+
kind: "commonTangentPoint",
|
|
11629
|
+
circles: [c.circles[0], c.circles[1]],
|
|
11630
|
+
on: c.on,
|
|
11631
|
+
variant: c.variant,
|
|
11632
|
+
side: c.side
|
|
11633
|
+
});
|
|
11634
|
+
};
|
|
11635
|
+
|
|
9562
11636
|
// src/stamps/geometry-2d/ai/intent-builders/add-point/externalToCircle.ts
|
|
9563
11637
|
var buildExternalToCircle = (s, intent) => {
|
|
9564
11638
|
const c = intent.constraint;
|
|
@@ -9628,6 +11702,7 @@ var ADD_POINT_BUILDERS = {
|
|
|
9628
11702
|
reflectPoint: buildReflectPoint,
|
|
9629
11703
|
reflectLine: buildReflectLine,
|
|
9630
11704
|
pointAtDistance: buildPointAtDistance,
|
|
11705
|
+
commonTangentPoint: buildCommonTangentPoint,
|
|
9631
11706
|
externalToCircle: buildExternalToCircle,
|
|
9632
11707
|
onCircle: buildOnCircle,
|
|
9633
11708
|
mixtilinearPoint: buildMixtilinearPoint
|
|
@@ -10283,6 +12358,8 @@ function constraintKey(c) {
|
|
|
10283
12358
|
return `externalToCircle:${c.circle}`;
|
|
10284
12359
|
case "onCircle":
|
|
10285
12360
|
return `onCircle:${c.circle}:${c.theta ?? ""}`;
|
|
12361
|
+
case "commonTangentPoint":
|
|
12362
|
+
return `commonTangentPoint:${c.circles.join(",")}:${c.on}:${c.variant}:${c.side}`;
|
|
10286
12363
|
}
|
|
10287
12364
|
}
|
|
10288
12365
|
function samePrefix(a, b, depth) {
|
|
@@ -10310,11 +12387,11 @@ function verifyGeometry(intents, dsl) {
|
|
|
10310
12387
|
const other2 = apex === "A" ? pC : apex === "B" ? pC : pB;
|
|
10311
12388
|
const v1 = [other1[0] - apexP[0], other1[1] - apexP[1]];
|
|
10312
12389
|
const v2 = [other2[0] - apexP[0], other2[1] - apexP[1]];
|
|
10313
|
-
const
|
|
10314
|
-
if (Math.abs(
|
|
12390
|
+
const dot2 = v1[0] * v2[0] + v1[1] * v2[1];
|
|
12391
|
+
if (Math.abs(dot2) > 1e-6) {
|
|
10315
12392
|
issues.push({
|
|
10316
12393
|
axis: "wrong",
|
|
10317
|
-
detail: `triangle ${A}${B}${C} variant=right-at-${apex} kh\xF4ng vu\xF4ng (dot=${
|
|
12394
|
+
detail: `triangle ${A}${B}${C} variant=right-at-${apex} kh\xF4ng vu\xF4ng (dot=${dot2.toFixed(3)})`
|
|
10318
12395
|
});
|
|
10319
12396
|
}
|
|
10320
12397
|
}
|
|
@@ -10362,7 +12439,7 @@ function buildAndTranspile(intents) {
|
|
|
10362
12439
|
return second.ok ? second : first;
|
|
10363
12440
|
}
|
|
10364
12441
|
function tryDeterministicFigure(rawProblem) {
|
|
10365
|
-
const problem = normalizeProblemText(rawProblem);
|
|
12442
|
+
const problem = normalizeProblemText(correctUserInput(rawProblem));
|
|
10366
12443
|
const det = runDeterministicIntents(problem);
|
|
10367
12444
|
if (!det.ok) return { ok: false, reason: det.reason, coverage: det.coverage };
|
|
10368
12445
|
const built = buildAndTranspile(
|
|
@@ -10437,7 +12514,7 @@ function describeDeterministicMiss(miss) {
|
|
|
10437
12514
|
// src/stamps/geometry-2d/ai/deterministic/partialFigure.ts
|
|
10438
12515
|
var MAX_SALVAGE_ITERS = 6;
|
|
10439
12516
|
function tryPartialFigure(rawProblem) {
|
|
10440
|
-
const problem = normalizeProblemText(rawProblem);
|
|
12517
|
+
const problem = normalizeProblemText(correctUserInput(rawProblem));
|
|
10441
12518
|
const part = tryPartialDeterministic(problem);
|
|
10442
12519
|
if (part.detIntents.length === 0) return null;
|
|
10443
12520
|
const intents = resolveCircleNameCollisions(normalizeIntents(part.detIntents, problem));
|
|
@@ -10821,6 +12898,16 @@ var AddPointIntentZ = zod.z.object({
|
|
|
10821
12898
|
zod.z.object({ kind: zod.z.literal("segmentLength"), p1: LabelZ, p2: LabelZ, scale: zod.z.number().positive().optional(), offset: zod.z.number().optional() }),
|
|
10822
12899
|
zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.number().positive(), scale: zod.z.number().positive().optional(), offset: zod.z.number().optional() })
|
|
10823
12900
|
])
|
|
12901
|
+
}),
|
|
12902
|
+
// Tiếp điểm tiếp tuyến CHUNG 2 đường tròn (spec mục A). circles = 2 tên đtròn;
|
|
12903
|
+
// on = tiếp điểm trên đtròn 0 hay 1; variant = ngoài/trong; side = chọn 1 trong
|
|
12904
|
+
// 2 tiếp tuyến cùng loại.
|
|
12905
|
+
zod.z.object({
|
|
12906
|
+
kind: zod.z.literal("commonTangentPoint"),
|
|
12907
|
+
circles: zod.z.tuple([LabelZ, LabelZ]),
|
|
12908
|
+
on: zod.z.union([zod.z.literal(0), zod.z.literal(1)]),
|
|
12909
|
+
variant: zod.z.enum(["external", "internal"]),
|
|
12910
|
+
side: zod.z.union([zod.z.literal(0), zod.z.literal(1)])
|
|
10824
12911
|
})
|
|
10825
12912
|
])
|
|
10826
12913
|
});
|
|
@@ -10871,8 +12958,8 @@ var DrawLineIntentZ = zod.z.object({
|
|
|
10871
12958
|
});
|
|
10872
12959
|
var MarkShapeIntentZ = zod.z.object({
|
|
10873
12960
|
op: zod.z.literal("mark-shape"),
|
|
10874
|
-
shape: zod.z.enum(["triangle", "quadrilateral"]),
|
|
10875
|
-
labels: zod.z.array(LabelZ).min(3).max(
|
|
12961
|
+
shape: zod.z.enum(["triangle", "quadrilateral", "polygon"]),
|
|
12962
|
+
labels: zod.z.array(LabelZ).min(3).max(8)
|
|
10876
12963
|
});
|
|
10877
12964
|
var IntentZ = zod.z.discriminatedUnion("op", [
|
|
10878
12965
|
DrawShapeIntentZ,
|
|
@@ -11184,7 +13271,7 @@ ho\u1EB7c
|
|
|
11184
13271
|
- quadrilateral: any
|
|
11185
13272
|
|
|
11186
13273
|
## Constraint kinds (cho add-point)
|
|
11187
|
-
midpoint, perpFoot, centroid, circumcenter, incenter, orthocenter, intersection, onSegment, free, secondIntersection, circleIntersection, tangencyPoint, tangentPoint, angleBisectorFoot, externalAngleBisectorFoot, arcMidpoint, reflectPoint, reflectLine, excenter, rightAngleViewing, pointAtDistance
|
|
13274
|
+
midpoint, perpFoot, centroid, circumcenter, incenter, orthocenter, intersection, onSegment, free, secondIntersection, circleIntersection, tangencyPoint, tangentPoint, angleBisectorFoot, externalAngleBisectorFoot, arcMidpoint, reflectPoint, reflectLine, excenter, rightAngleViewing, pointAtDistance, commonTangentPoint
|
|
11188
13275
|
|
|
11189
13276
|
## Style enum (cho connect)
|
|
11190
13277
|
segment, line, ray, perpBisector
|
|
@@ -11328,6 +13415,131 @@ async function runTesseractOcr(image, opts = {}) {
|
|
|
11328
13415
|
}
|
|
11329
13416
|
}
|
|
11330
13417
|
|
|
13418
|
+
// src/stamps/geometry-2d/ai/vision/repairOcrSymbols.ts
|
|
13419
|
+
var PT4 = `[A-Z]{2,3}['\u2032]?`;
|
|
13420
|
+
var PERP_RE2 = new RegExp(
|
|
13421
|
+
`(?<![\\p{L}\\d])(${PT4})\\s+([1|L])\\s+(${PT4})(?![\\p{L}\\d])`,
|
|
13422
|
+
"gu"
|
|
13423
|
+
);
|
|
13424
|
+
var TRI_STRICT_RE = /(?<!giác )(?<!thang )(?<![\p{L}\d])A([A-Z]{3})\s+(đều|cân|nhọn|vuông)(?![\p{L}])/gu;
|
|
13425
|
+
var TRI_DOUBLE_RE = /(?<![\p{L}\d])AA([A-Z]{2})(?![A-Z])/gu;
|
|
13426
|
+
var CIRCLE_O_RE = /\(0\)/gu;
|
|
13427
|
+
var ELEM_RE = /(?<![\p{L}\d])([A-Z](?:\s*,\s*[A-Z])+)\s*e\s*\((?:O|0)\)/gu;
|
|
13428
|
+
var INTERSECT_RE = /([A-Z]{2,3})N\s+([A-Z]\S{0,2})\s*=\s*\{/gu;
|
|
13429
|
+
var SQUARE_RE = /([A-Z])\?(?=\s*[-=+)])/gu;
|
|
13430
|
+
var HORN_O_C_RE = /(?<!\p{Ll})Ơ(?!\p{Ll})/gu;
|
|
13431
|
+
var DUONG_DBAR_RE = /D(?:ường|uong)(?= (?:tròn|tron|thẳng|kính|cao|chéo|trung|phân|vuông|nối|gấp|tâm))/gu;
|
|
13432
|
+
var DUONG_TRON_RE = /([Đđ]ường\s+)tron(?![\p{L}])/gu;
|
|
13433
|
+
var FLORIN_F_RE = /Ƒ/gu;
|
|
13434
|
+
var TAI_TAI_RE = /(?<![\p{L}])tai(\s+)(?=[A-Z(]|điểm)/gu;
|
|
13435
|
+
var TAM_TAM_RE = /(?<![\p{L}])tam(\s+)(?=đường|\(|đối xứng)/gu;
|
|
13436
|
+
var EURO_ELEM_RE = /€/gu;
|
|
13437
|
+
var PARALLEL_RE = /\|\|/gu;
|
|
13438
|
+
var CENT_C_RE = /¢/gu;
|
|
13439
|
+
var ETH_D_RE = /(?<!\p{Ll})Ð(?!\p{Ll})/gu;
|
|
13440
|
+
var SLASH_O_RE = /(?<!\p{Ll})Ø(?!\p{Ll})/gu;
|
|
13441
|
+
var GOI_RE = /(?<!\p{L})Goi(?!\p{L})/gu;
|
|
13442
|
+
var LAY_RE = /(?<!\p{L})Lay(?!\p{L})/gu;
|
|
13443
|
+
var DIQUA_RE = /(?<!\p{L})di qua(?!\p{L})/gu;
|
|
13444
|
+
var VA_VAND_RE = /(?<!\p{L})va (?=[A-Z(]|dây|đường)/gu;
|
|
13445
|
+
var TREN_RE = /([Tt])rén(?![\p{L}])/gu;
|
|
13446
|
+
var DUONG_LOWER_RE = /(?<![\p{L}])dường(?= (?:tròn|tron|thẳng|kính|cao|chéo|trung|phân|vuông|nối|gấp|tâm))/gu;
|
|
13447
|
+
var DAY_RE = /(?<![\p{L}])day(?=\s+(?:cung(?![\p{L}])|[A-Z]{2}))/gu;
|
|
13448
|
+
var CHUNG_MINH_RE = /([Cc]hứng )mình(?![\p{L}])/gu;
|
|
13449
|
+
var THANG_HANG_RE = /thẳng hang(?![\p{L}])/gu;
|
|
13450
|
+
var TINH_RE = /(?<![\p{L}])Tinh(?= (?:[A-Z]|do(?![\p{L}])|độ|diện|bán|chu|số|giá))/gu;
|
|
13451
|
+
var DO_DAI_RE = /(?<![\p{L}])do dai(?![\p{L}])/gu;
|
|
13452
|
+
var NIM_RE = /(?<![\p{L}])nim(?= (?:giữa|trên|trong)(?![\p{L}]))/gu;
|
|
13453
|
+
var NAM_GIUA_RE = /(?<![\p{L}])năm(?= (?:giữa|trên) [A-Z])/gu;
|
|
13454
|
+
var ANGLE_Z_RE = /(?<![\p{L}\d])4?Z([A-Z]{2,3})(?![A-Z])/gu;
|
|
13455
|
+
var DE_PAREN_RE = /\(Dé /gu;
|
|
13456
|
+
var DE_VERB_RE = /(\(Đề )\S+( bởi)/gu;
|
|
13457
|
+
var DE_THI_LOWER_RE = /(?<![\p{L}])dé(?= thi)/gu;
|
|
13458
|
+
var DI_DONG_RE = /(?<![\p{L}])di dong(?![\p{L}])/gu;
|
|
13459
|
+
var POUND_E_RE = /£/gu;
|
|
13460
|
+
var TRI_SPACE_2_1 = /(tam\s*giác\s+)([A-Z])([A-Z])\s+([A-Z])(?![A-Z\p{Ll}])/gu;
|
|
13461
|
+
var TRI_SPACE_1_2 = /(tam\s*giác\s+)([A-Z])\s+([A-Z])([A-Z])(?![A-Z\p{Ll}])/gu;
|
|
13462
|
+
var DIAERESIS_O_RE = /(?<!\p{Ll})Ö(?!\p{Ll})/gu;
|
|
13463
|
+
var INTERSECT_PAREN_RE = /(\([A-Z]{3}\))\s+N\s+([A-Z]{2})(\s*=\s*\{)/gu;
|
|
13464
|
+
function repairOcrSymbols(text) {
|
|
13465
|
+
let t = text;
|
|
13466
|
+
t = t.replace(PERP_RE2, "$1 \u22A5 $3");
|
|
13467
|
+
t = t.replace(TRI_STRICT_RE, "tam gi\xE1c $1 $2");
|
|
13468
|
+
t = t.replace(TRI_DOUBLE_RE, "tam gi\xE1c A$1");
|
|
13469
|
+
t = t.replace(CIRCLE_O_RE, "(O)");
|
|
13470
|
+
t = t.replace(ELEM_RE, "$1 \u2208 (O)");
|
|
13471
|
+
t = t.replace(INTERSECT_RE, "$1 \u2229 $2 = {");
|
|
13472
|
+
t = t.replace(SQUARE_RE, "$1\xB2");
|
|
13473
|
+
t = t.replace(HORN_O_C_RE, "C");
|
|
13474
|
+
t = t.replace(DUONG_DBAR_RE, "\u0110\u01B0\u1EDDng");
|
|
13475
|
+
t = t.replace(DUONG_LOWER_RE, "\u0111\u01B0\u1EDDng");
|
|
13476
|
+
t = t.replace(DUONG_TRON_RE, "$1tr\xF2n");
|
|
13477
|
+
t = t.replace(FLORIN_F_RE, "F");
|
|
13478
|
+
t = t.replace(TAI_TAI_RE, "t\u1EA1i$1");
|
|
13479
|
+
t = t.replace(TAM_TAM_RE, "t\xE2m$1");
|
|
13480
|
+
t = t.replace(EURO_ELEM_RE, "\u2208");
|
|
13481
|
+
t = t.replace(PARALLEL_RE, "\u2225");
|
|
13482
|
+
t = t.replace(CENT_C_RE, "c");
|
|
13483
|
+
t = t.replace(ETH_D_RE, "D");
|
|
13484
|
+
t = t.replace(SLASH_O_RE, "O");
|
|
13485
|
+
t = t.replace(GOI_RE, "G\u1ECDi");
|
|
13486
|
+
t = t.replace(LAY_RE, "L\u1EA5y");
|
|
13487
|
+
t = t.replace(DIQUA_RE, "\u0111i qua");
|
|
13488
|
+
t = t.replace(VA_VAND_RE, "v\xE0 ");
|
|
13489
|
+
t = t.replace(TREN_RE, "$1r\xEAn");
|
|
13490
|
+
t = t.replace(DAY_RE, "d\xE2y");
|
|
13491
|
+
t = t.replace(CHUNG_MINH_RE, "$1minh");
|
|
13492
|
+
t = t.replace(THANG_HANG_RE, "th\u1EB3ng h\xE0ng");
|
|
13493
|
+
t = t.replace(TINH_RE, "T\xEDnh");
|
|
13494
|
+
t = t.replace(DO_DAI_RE, "\u0111\u1ED9 d\xE0i");
|
|
13495
|
+
t = t.replace(NIM_RE, "n\u1EB1m");
|
|
13496
|
+
t = t.replace(NAM_GIUA_RE, "n\u1EB1m");
|
|
13497
|
+
t = t.replace(ANGLE_Z_RE, "\u2220$1");
|
|
13498
|
+
t = t.replace(DE_PAREN_RE, "(\u0110\u1EC1 ");
|
|
13499
|
+
t = t.replace(DE_VERB_RE, "$1xu\u1EA5t$2");
|
|
13500
|
+
t = t.replace(DE_THI_LOWER_RE, "\u0111\u1EC1");
|
|
13501
|
+
t = t.replace(DI_DONG_RE, "di \u0111\u1ED9ng");
|
|
13502
|
+
t = t.replace(POUND_E_RE, "E");
|
|
13503
|
+
t = t.replace(TRI_SPACE_2_1, "$1$2$3$4");
|
|
13504
|
+
t = t.replace(TRI_SPACE_1_2, "$1$2$3$4");
|
|
13505
|
+
t = t.replace(DIAERESIS_O_RE, "O");
|
|
13506
|
+
t = t.replace(INTERSECT_PAREN_RE, "$1 \u2229 $2$3");
|
|
13507
|
+
return t;
|
|
13508
|
+
}
|
|
13509
|
+
|
|
13510
|
+
// src/stamps/geometry-2d/ai/vision/detectFormulaRisk.ts
|
|
13511
|
+
var SUP_RESIDUE = /[A-Z0-9]\?(?=\s*[-=+)(])/u;
|
|
13512
|
+
var ANGLE_DEG = /([\p{L}]{1,6})\s*=\s*\d+\s*°/gu;
|
|
13513
|
+
function hasManglingLtGt(text) {
|
|
13514
|
+
const re = /[<>]/gu;
|
|
13515
|
+
let m;
|
|
13516
|
+
while ((m = re.exec(text)) !== null) {
|
|
13517
|
+
const prev = text.slice(0, m.index).replace(/\s+$/u, "").slice(-1);
|
|
13518
|
+
const next = text.slice(m.index + 1).replace(/^\s+/u, "").slice(0, 1);
|
|
13519
|
+
const prevOperand = /[A-Za-z0-9]/.test(prev);
|
|
13520
|
+
const nextOperand = /[A-Za-z0-9]/.test(next);
|
|
13521
|
+
if (!(prevOperand && nextOperand)) return true;
|
|
13522
|
+
}
|
|
13523
|
+
return false;
|
|
13524
|
+
}
|
|
13525
|
+
function detectFormulaRisk(text) {
|
|
13526
|
+
const reasons = [];
|
|
13527
|
+
if (hasManglingLtGt(text)) {
|
|
13528
|
+
reasons.push('ph\xE2n s\u1ED1/bi\u1EC3u th\u1EE9c nghi b\u1ECB nu\u1ED1t (k\xFD hi\u1EC7u "<"/">")');
|
|
13529
|
+
}
|
|
13530
|
+
if (SUP_RESIDUE.test(text)) {
|
|
13531
|
+
reasons.push('s\u1ED1 m\u0169 ho\u1EB7c \u0111\u1ED9 nghi b\u1ECB m\u1EA5t (d\u1EA5u "?")');
|
|
13532
|
+
}
|
|
13533
|
+
for (const m of text.matchAll(ANGLE_DEG)) {
|
|
13534
|
+
const tok = m[1];
|
|
13535
|
+
if (/[A-Z]/.test(tok) && /[^\x00-\x7F]/u.test(tok)) {
|
|
13536
|
+
reasons.push("t\xEAn g\xF3c nghi b\u1ECB nh\u1EADn d\u1EA1ng sai (k\xFD t\u1EF1 c\xF3 d\u1EA5u)");
|
|
13537
|
+
break;
|
|
13538
|
+
}
|
|
13539
|
+
}
|
|
13540
|
+
return reasons;
|
|
13541
|
+
}
|
|
13542
|
+
|
|
11331
13543
|
// src/stamps/geometry-2d/ai/vision/extractProblem.ts
|
|
11332
13544
|
var MIN_HIGH_CONFIDENCE_CHARS = 10;
|
|
11333
13545
|
var MAX_TEXT_CHARS = 2e3;
|
|
@@ -11358,6 +13570,7 @@ async function extractProblemFromImage(image, opts = {}) {
|
|
|
11358
13570
|
ok: true,
|
|
11359
13571
|
text,
|
|
11360
13572
|
confidence,
|
|
13573
|
+
warnings: detectFormulaRisk(text),
|
|
11361
13574
|
usage: { inputTokens: 0, outputTokens: 0 }
|
|
11362
13575
|
};
|
|
11363
13576
|
}
|
|
@@ -11369,6 +13582,7 @@ function postProcess(raw) {
|
|
|
11369
13582
|
t = t.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1");
|
|
11370
13583
|
t = t.replace(/\s+/g, " ").trim();
|
|
11371
13584
|
t = t.normalize("NFC");
|
|
13585
|
+
t = repairOcrSymbols(t);
|
|
11372
13586
|
if (t.length > MAX_TEXT_CHARS) t = t.slice(0, MAX_TEXT_CHARS);
|
|
11373
13587
|
return t;
|
|
11374
13588
|
}
|
|
@@ -11378,6 +13592,14 @@ async function handleExtractProblem(image, opts = {}) {
|
|
|
11378
13592
|
try {
|
|
11379
13593
|
const r = await extractProblemFromImage(image, opts);
|
|
11380
13594
|
if (r.ok) {
|
|
13595
|
+
if (r.warnings.length > 0) {
|
|
13596
|
+
return {
|
|
13597
|
+
kind: "low-confidence",
|
|
13598
|
+
text: r.text,
|
|
13599
|
+
warning: `C\xF3 th\u1EC3 c\xF3 c\xF4ng th\u1EE9c/k\xFD hi\u1EC7u b\u1ECB nh\u1EADn d\u1EA1ng sai (${r.warnings.join("; ")}). Ki\u1EC3m tra k\u1EF9 tr\u01B0\u1EDBc khi v\u1EBD.`,
|
|
13600
|
+
usage: r.usage
|
|
13601
|
+
};
|
|
13602
|
+
}
|
|
11381
13603
|
if (r.confidence === "low") {
|
|
11382
13604
|
return {
|
|
11383
13605
|
kind: "low-confidence",
|