@lydt/geogebra-studio 0.0.0-stage → 0.4.0-rc.2
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/.agents/plugins/marketplace.json +21 -0
- package/.codex-plugin/plugin.json +21 -0
- package/.mcp.json +18 -0
- package/LICENSE +339 -0
- package/LICENSE-MIT +21 -0
- package/README.md +108 -2
- package/README.zh-CN.md +89 -0
- package/THIRD_PARTY_NOTICES.md +35 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +234 -0
- package/dist/cli.js.map +1 -0
- package/dist/export/files.d.ts +4 -0
- package/dist/export/files.d.ts.map +1 -0
- package/dist/export/files.js +44 -0
- package/dist/export/files.js.map +1 -0
- package/dist/export/gikz.d.ts +11 -0
- package/dist/export/gikz.d.ts.map +1 -0
- package/dist/export/gikz.js +28 -0
- package/dist/export/gikz.js.map +1 -0
- package/dist/export/html.d.ts +14 -0
- package/dist/export/html.d.ts.map +1 -0
- package/dist/export/html.js +97 -0
- package/dist/export/html.js.map +1 -0
- package/dist/export/semantic.d.ts +43 -0
- package/dist/export/semantic.d.ts.map +1 -0
- package/dist/export/semantic.js +212 -0
- package/dist/export/semantic.js.map +1 -0
- package/dist/export/viewer-themes.d.ts +76 -0
- package/dist/export/viewer-themes.d.ts.map +1 -0
- package/dist/export/viewer-themes.js +169 -0
- package/dist/export/viewer-themes.js.map +1 -0
- package/dist/export/workspace.d.ts +4 -0
- package/dist/export/workspace.d.ts.map +1 -0
- package/dist/export/workspace.js +25 -0
- package/dist/export/workspace.js.map +1 -0
- package/dist/gikz-browser.js +770 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +35 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +187 -0
- package/dist/server.js.map +1 -0
- package/dist/studio/artifact.d.ts +37 -0
- package/dist/studio/artifact.d.ts.map +1 -0
- package/dist/studio/artifact.js +61 -0
- package/dist/studio/artifact.js.map +1 -0
- package/dist/studio/browser.d.ts +7 -0
- package/dist/studio/browser.d.ts.map +1 -0
- package/dist/studio/browser.js +67 -0
- package/dist/studio/browser.js.map +1 -0
- package/dist/studio/install.d.ts +4 -0
- package/dist/studio/install.d.ts.map +1 -0
- package/dist/studio/install.js +60 -0
- package/dist/studio/install.js.map +1 -0
- package/dist/tools/apps-api-tools.d.ts +4 -0
- package/dist/tools/apps-api-tools.d.ts.map +1 -0
- package/dist/tools/apps-api-tools.js +96 -0
- package/dist/tools/apps-api-tools.js.map +1 -0
- package/dist/tools/educational-templates.d.ts +33 -0
- package/dist/tools/educational-templates.d.ts.map +1 -0
- package/dist/tools/educational-templates.js +261 -0
- package/dist/tools/educational-templates.js.map +1 -0
- package/dist/tools/export-tools.d.ts +3 -0
- package/dist/tools/export-tools.d.ts.map +1 -0
- package/dist/tools/export-tools.js +118 -0
- package/dist/tools/export-tools.js.map +1 -0
- package/dist/tools/geogebra-tools.d.ts +15 -0
- package/dist/tools/geogebra-tools.d.ts.map +1 -0
- package/dist/tools/geogebra-tools.js +3442 -0
- package/dist/tools/geogebra-tools.js.map +1 -0
- package/dist/tools/index.d.ts +43 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +183 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/performance-tools.d.ts +7 -0
- package/dist/tools/performance-tools.d.ts.map +1 -0
- package/dist/tools/performance-tools.js +312 -0
- package/dist/tools/performance-tools.js.map +1 -0
- package/dist/tools/templates/algebra-templates.d.ts +7 -0
- package/dist/tools/templates/algebra-templates.d.ts.map +1 -0
- package/dist/tools/templates/algebra-templates.js +568 -0
- package/dist/tools/templates/algebra-templates.js.map +1 -0
- package/dist/tools/templates/geometry-templates.d.ts +7 -0
- package/dist/tools/templates/geometry-templates.d.ts.map +1 -0
- package/dist/tools/templates/geometry-templates.js +492 -0
- package/dist/tools/templates/geometry-templates.js.map +1 -0
- package/dist/types/geogebra.d.ts +103 -0
- package/dist/types/geogebra.d.ts.map +1 -0
- package/dist/types/geogebra.js +34 -0
- package/dist/types/geogebra.js.map +1 -0
- package/dist/types/mcp.d.ts +125 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +19 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/utils/app-info.d.ts +12 -0
- package/dist/utils/app-info.d.ts.map +1 -0
- package/dist/utils/app-info.js +30 -0
- package/dist/utils/app-info.js.map +1 -0
- package/dist/utils/errors.d.ts +37 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +72 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/gemini-compatibility.d.ts +39 -0
- package/dist/utils/gemini-compatibility.d.ts.map +1 -0
- package/dist/utils/gemini-compatibility.js +154 -0
- package/dist/utils/gemini-compatibility.js.map +1 -0
- package/dist/utils/geogebra-instance.d.ts +252 -0
- package/dist/utils/geogebra-instance.d.ts.map +1 -0
- package/dist/utils/geogebra-instance.js +1560 -0
- package/dist/utils/geogebra-instance.js.map +1 -0
- package/dist/utils/geogebra-mock.d.ts +172 -0
- package/dist/utils/geogebra-mock.d.ts.map +1 -0
- package/dist/utils/geogebra-mock.js +732 -0
- package/dist/utils/geogebra-mock.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/performance/index.d.ts +76 -0
- package/dist/utils/performance/index.d.ts.map +1 -0
- package/dist/utils/performance/index.js +163 -0
- package/dist/utils/performance/index.js.map +1 -0
- package/dist/utils/performance/instance-pool.d.ts +78 -0
- package/dist/utils/performance/instance-pool.d.ts.map +1 -0
- package/dist/utils/performance/instance-pool.js +268 -0
- package/dist/utils/performance/instance-pool.js.map +1 -0
- package/dist/utils/validation.d.ts +92 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +557 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/widget.html +110 -0
- package/docs/APPS-API-BRIDGE.md +28 -0
- package/docs/MODERNIZATION.md +15 -0
- package/docs/TANGENT-REGRESSION.md +34 -0
- package/docs/VALIDATION.md +26 -0
- package/docs/VISUAL-STANDARD.md +26 -0
- package/docs/images/native-construction.png +0 -0
- package/examples/tangent-construction.json +26 -0
- package/gikz/LICENSE +339 -0
- package/gikz/PROVENANCE.md +8 -0
- package/gikz/browser.cjs +2 -0
- package/gikz/cli.cjs +195 -0
- package/gikz/core.cjs +806 -0
- package/mcp.json +20 -0
- package/package.json +107 -4
- package/plugin.json +35 -0
- package/runtime/package.json +4 -0
- package/scripts/build-plugin.cjs +15 -0
- package/scripts/build-widget.cjs +7 -0
- package/skills/geogebra-construct/SKILL.md +40 -0
- package/skills/geogebra-setup/SKILL.md +37 -0
- package/skills/geometry-export/SKILL.md +31 -0
- package/src/cli.ts +212 -0
- package/src/export/files.ts +26 -0
- package/src/export/gikz.ts +22 -0
- package/src/export/html.ts +108 -0
- package/src/export/semantic.ts +180 -0
- package/src/export/viewer-themes.ts +165 -0
- package/src/export/workspace.ts +15 -0
- package/src/index.ts +71 -0
- package/src/server.ts +168 -0
- package/src/studio/artifact.ts +50 -0
- package/src/studio/browser.ts +38 -0
- package/src/studio/install.ts +43 -0
- package/src/tools/apps-api-tools.ts +96 -0
- package/src/tools/educational-templates.ts +299 -0
- package/src/tools/export-tools.ts +110 -0
- package/src/tools/geogebra-tools.ts +3836 -0
- package/src/tools/index.ts +192 -0
- package/src/tools/performance-tools.ts +322 -0
- package/src/tools/templates/algebra-templates.ts +619 -0
- package/src/tools/templates/geometry-templates.ts +550 -0
- package/src/types/geogebra.ts +140 -0
- package/src/types/global.d.ts +31 -0
- package/src/types/mcp.ts +141 -0
- package/src/utils/app-info.ts +35 -0
- package/src/utils/errors.ts +97 -0
- package/src/utils/gemini-compatibility.ts +193 -0
- package/src/utils/geogebra-instance.ts +1712 -0
- package/src/utils/geogebra-mock.ts +867 -0
- package/src/utils/logger.ts +60 -0
- package/src/utils/performance/index.ts +211 -0
- package/src/utils/performance/instance-pool.ts +323 -0
- package/src/utils/validation.ts +647 -0
- package/tsconfig.json +35 -0
- package/widget/main.ts +12 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
5
|
+
try {
|
|
6
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw mod = 0, e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// gikz/core.cjs
|
|
13
|
+
var require_core = __commonJS({
|
|
14
|
+
"gikz/core.cjs"(exports, module) {
|
|
15
|
+
"use strict";
|
|
16
|
+
function roundToThreeDecimals(num) {
|
|
17
|
+
return Math.round(num * 1e3) / 1e3;
|
|
18
|
+
}
|
|
19
|
+
function formatCoordinate(coordStr, shouldRound) {
|
|
20
|
+
const match = coordStr.match(/\(([^,]+),([^)]+)\)/);
|
|
21
|
+
if (!match) return coordStr;
|
|
22
|
+
let x = parseFloat(match[1]);
|
|
23
|
+
let y = parseFloat(match[2]);
|
|
24
|
+
if (shouldRound) {
|
|
25
|
+
x = roundToThreeDecimals(x);
|
|
26
|
+
y = roundToThreeDecimals(y);
|
|
27
|
+
}
|
|
28
|
+
return `(${x},${y})`;
|
|
29
|
+
}
|
|
30
|
+
function getSmartLabelPosition(x, y, allPoints) {
|
|
31
|
+
const xCoords = allPoints.map((p) => p.x);
|
|
32
|
+
const yCoords = allPoints.map((p) => p.y);
|
|
33
|
+
const minX = Math.min(...xCoords);
|
|
34
|
+
const maxX = Math.max(...xCoords);
|
|
35
|
+
const minY = Math.min(...yCoords);
|
|
36
|
+
const maxY = Math.max(...yCoords);
|
|
37
|
+
const centerX = (minX + maxX) / 2;
|
|
38
|
+
const centerY = (minY + maxY) / 2;
|
|
39
|
+
let position = "above";
|
|
40
|
+
if (Math.abs(x - minX) < 0.1) {
|
|
41
|
+
position = "left";
|
|
42
|
+
} else if (Math.abs(x - maxX) < 0.1) {
|
|
43
|
+
position = "right";
|
|
44
|
+
} else if (Math.abs(y - minY) < 0.1) {
|
|
45
|
+
position = "below";
|
|
46
|
+
} else if (Math.abs(y - maxY) < 0.1) {
|
|
47
|
+
position = "above";
|
|
48
|
+
} else {
|
|
49
|
+
if (x > centerX && y > centerY) position = "above right";
|
|
50
|
+
else if (x > centerX && y <= centerY) position = "below right";
|
|
51
|
+
else if (x <= centerX && y > centerY) position = "above left";
|
|
52
|
+
else position = "below left";
|
|
53
|
+
}
|
|
54
|
+
return position;
|
|
55
|
+
}
|
|
56
|
+
var convertLineStyle = (style) => {
|
|
57
|
+
if (!style) return "";
|
|
58
|
+
let lineStyle = style.replace(/line width=[^,\]]+,?/g, "");
|
|
59
|
+
lineStyle = lineStyle.replace(/,\s*$/g, "");
|
|
60
|
+
lineStyle = lineStyle.replace(/\[\s*,\s*/g, "[");
|
|
61
|
+
if (lineStyle.includes("dash pattern=on 1pt off 1pt on 1pt off 4pt")) return "dash dot";
|
|
62
|
+
if (lineStyle.includes("dash pattern=on 1pt off 1pt")) return "dashed";
|
|
63
|
+
if (lineStyle.includes("dotted")) return "dotted";
|
|
64
|
+
if (lineStyle.includes("dash")) return "dashed";
|
|
65
|
+
return "";
|
|
66
|
+
};
|
|
67
|
+
function extractQuadraticFunctions(code, shouldRound) {
|
|
68
|
+
try {
|
|
69
|
+
const quadraticRegex = /\\draw\s*\[([^\]]+)\]\s*plot\s*\(\\x,\{\(\\x\)\^2\/2\/([^}]+)\}\)/g;
|
|
70
|
+
const quadraticMatches = [...code.matchAll(quadraticRegex)];
|
|
71
|
+
const quadraticFunctions = [];
|
|
72
|
+
quadraticMatches.forEach((match) => {
|
|
73
|
+
const options = match[1];
|
|
74
|
+
let denominator = match[2];
|
|
75
|
+
const samplesMatch = options.match(/samples=(\d+)/);
|
|
76
|
+
const rotateMatch = options.match(/rotate around=\{([^:]+):\(([^,]+),([^)]+)\)\}/);
|
|
77
|
+
const xshiftMatch = options.match(/xshift=([^,]+)cm/);
|
|
78
|
+
const yshiftMatch = options.match(/yshift=([^,]+)cm/);
|
|
79
|
+
const domainMatch = options.match(/domain=([^:)]+):([^)]+)\)?/);
|
|
80
|
+
if (domainMatch) {
|
|
81
|
+
let angle = "0", centerX = "0", centerY = "0";
|
|
82
|
+
if (rotateMatch) {
|
|
83
|
+
angle = rotateMatch[1];
|
|
84
|
+
centerX = rotateMatch[2];
|
|
85
|
+
centerY = rotateMatch[3];
|
|
86
|
+
}
|
|
87
|
+
let xshift = "0", yshift = "0";
|
|
88
|
+
if (xshiftMatch) xshift = xshiftMatch[1];
|
|
89
|
+
if (yshiftMatch) yshift = yshiftMatch[1];
|
|
90
|
+
let domainStart = domainMatch[1];
|
|
91
|
+
let domainEnd = domainMatch[2];
|
|
92
|
+
if (domainEnd.includes(")")) domainEnd = domainEnd.replace(")", "");
|
|
93
|
+
if (shouldRound) {
|
|
94
|
+
if (centerX !== "0") centerX = roundToThreeDecimals(parseFloat(centerX));
|
|
95
|
+
if (centerY !== "0") centerY = roundToThreeDecimals(parseFloat(centerY));
|
|
96
|
+
if (xshift !== "0") xshift = roundToThreeDecimals(parseFloat(xshift));
|
|
97
|
+
if (yshift !== "0") yshift = roundToThreeDecimals(parseFloat(yshift));
|
|
98
|
+
denominator = roundToThreeDecimals(parseFloat(denominator));
|
|
99
|
+
domainStart = roundToThreeDecimals(parseFloat(domainStart));
|
|
100
|
+
domainEnd = roundToThreeDecimals(parseFloat(domainEnd));
|
|
101
|
+
xshift += "cm";
|
|
102
|
+
yshift += "cm";
|
|
103
|
+
}
|
|
104
|
+
let newOptions = "smooth,samples=500";
|
|
105
|
+
if (rotateMatch) newOptions += `,rotate around={${angle}:(${centerX},${centerY})}`;
|
|
106
|
+
if (xshiftMatch && xshift !== "0cm" || yshiftMatch && yshift !== "0cm")
|
|
107
|
+
newOptions += `,xshift=${xshift},yshift=${yshift}`;
|
|
108
|
+
newOptions += `,domain=${domainStart}:${domainEnd}`;
|
|
109
|
+
quadraticFunctions.push({ original: match[0], options: newOptions, expression: `(\\x)^2/2/${denominator}` });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return { hasQuadratic: quadraticFunctions.length > 0, quadraticFunctions };
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return { hasQuadratic: false, quadraticFunctions: [] };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function extractFunctionPlots(code, shouldRound) {
|
|
118
|
+
try {
|
|
119
|
+
const clipRegex = /\\clip\(([^,]+),([^)]+)\)\s*rectangle\s*\(([^,]+),([^)]+)\);/;
|
|
120
|
+
const clipMatch = code.match(clipRegex);
|
|
121
|
+
if (!clipMatch) {
|
|
122
|
+
return { hasFunctions: false, scopeCode: "", clipArea: "", normalFunctionCount: 0, quadraticFunctionCount: 0 };
|
|
123
|
+
}
|
|
124
|
+
const clipX1 = shouldRound ? roundToThreeDecimals(parseFloat(clipMatch[1])) : parseFloat(clipMatch[1]);
|
|
125
|
+
const clipY1 = shouldRound ? roundToThreeDecimals(parseFloat(clipMatch[2])) : parseFloat(clipMatch[2]);
|
|
126
|
+
const clipX2 = shouldRound ? roundToThreeDecimals(parseFloat(clipMatch[3])) : parseFloat(clipMatch[3]);
|
|
127
|
+
const clipY2 = shouldRound ? roundToThreeDecimals(parseFloat(clipMatch[4])) : parseFloat(clipMatch[4]);
|
|
128
|
+
const functionPlotRegex = /\\draw\[([^\]]*)\]\s*plot\s*\(\\x,\{([^}]*)\}\)/g;
|
|
129
|
+
const functionMatches = [...code.matchAll(functionPlotRegex)];
|
|
130
|
+
const functions = [];
|
|
131
|
+
const containsLnRegex = /ln\s*\(/i;
|
|
132
|
+
functionMatches.forEach((match) => {
|
|
133
|
+
const options = match[1];
|
|
134
|
+
const functionExpression = match[2];
|
|
135
|
+
const domainRegex = /domain=([^:]+):([^,\]]+)/;
|
|
136
|
+
const domainMatch = options.match(domainRegex);
|
|
137
|
+
if (domainMatch) {
|
|
138
|
+
let domainStart = domainMatch[1], domainEnd = domainMatch[2];
|
|
139
|
+
if (containsLnRegex.test(functionExpression)) {
|
|
140
|
+
if (Math.abs(parseFloat(domainStart)) < 1e-3) domainStart = "0.001";
|
|
141
|
+
}
|
|
142
|
+
if (shouldRound) {
|
|
143
|
+
const s = parseFloat(domainStart), e = parseFloat(domainEnd);
|
|
144
|
+
if (!isNaN(s)) domainStart = roundToThreeDecimals(s).toString();
|
|
145
|
+
if (!isNaN(e)) domainEnd = roundToThreeDecimals(e).toString();
|
|
146
|
+
}
|
|
147
|
+
let lineStyle = "";
|
|
148
|
+
if (options.includes("dash pattern=on 1pt off 1pt on 1pt off 4pt")) lineStyle = "dash dot";
|
|
149
|
+
else if (options.includes("dash pattern=on 1pt off 1pt")) lineStyle = "dashed";
|
|
150
|
+
else if (options.includes("dotted")) lineStyle = "dotted";
|
|
151
|
+
let newOptions = "smooth,samples=500";
|
|
152
|
+
if (lineStyle) newOptions = lineStyle + "," + newOptions;
|
|
153
|
+
functions.push({ original: match[0], options: newOptions, domainStart, domainEnd, expression: functionExpression, lineStyle });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
const quadraticResult = extractQuadraticFunctions(code, shouldRound);
|
|
157
|
+
let result = " \\begin{scope}\n";
|
|
158
|
+
result += ` \\clip(${clipX1},${clipY1}) rectangle (${clipX2},${clipY2});
|
|
159
|
+
`;
|
|
160
|
+
functions.forEach((func) => {
|
|
161
|
+
result += ` \\draw[${func.options},domain=${func.domainStart}:${func.domainEnd}] plot(\\x,{${func.expression}});
|
|
162
|
+
`;
|
|
163
|
+
});
|
|
164
|
+
if (quadraticResult.hasQuadratic) {
|
|
165
|
+
quadraticResult.quadraticFunctions.forEach((quad) => {
|
|
166
|
+
result += ` \\draw[${quad.options}] plot(\\x,{${quad.expression}});
|
|
167
|
+
`;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
result += " \\end{scope}\n";
|
|
171
|
+
return {
|
|
172
|
+
hasFunctions: functions.length > 0 || quadraticResult.hasQuadratic,
|
|
173
|
+
scopeCode: result,
|
|
174
|
+
clipArea: `(${clipX1},${clipY1}) rectangle (${clipX2},${clipY2})`,
|
|
175
|
+
normalFunctionCount: functions.length,
|
|
176
|
+
quadraticFunctionCount: quadraticResult.quadraticFunctions.length
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return { hasFunctions: false, scopeCode: "", clipArea: "", normalFunctionCount: 0, quadraticFunctionCount: 0 };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function extractParametricPlots(code, shouldRound) {
|
|
183
|
+
try {
|
|
184
|
+
const parametricRegex = /\\draw\[([^\]]*)\]\s*plot\[parametric\]\s*function\{([^}]+)\};/g;
|
|
185
|
+
const matches = [...code.matchAll(parametricRegex)];
|
|
186
|
+
const plots = [];
|
|
187
|
+
matches.forEach((match) => {
|
|
188
|
+
const options = match[1];
|
|
189
|
+
const functionStr = match[2];
|
|
190
|
+
const commaMatch = functionStr.match(/([^,]+),([^,]+)/);
|
|
191
|
+
if (!commaMatch) return;
|
|
192
|
+
let xExpr = commaMatch[1].trim(), yExpr = commaMatch[2].trim();
|
|
193
|
+
if (shouldRound) {
|
|
194
|
+
xExpr = xExpr.replace(/(\d+\.\d{4,})/g, (m) => roundToThreeDecimals(parseFloat(m)).toString());
|
|
195
|
+
yExpr = yExpr.replace(/(\d+\.\d{4,})/g, (m) => roundToThreeDecimals(parseFloat(m)).toString());
|
|
196
|
+
}
|
|
197
|
+
xExpr = xExpr.replace(/t\*\*\((\d+)\)/g, "\\t^$1");
|
|
198
|
+
xExpr = xExpr.replace(/\(1-t\)\*\*\((\d+)\)/g, "(1-\\t)^$1");
|
|
199
|
+
yExpr = yExpr.replace(/t\*\*\((\d+)\)/g, "\\t^$1");
|
|
200
|
+
yExpr = yExpr.replace(/\(1-t\)\*\*\((\d+)\)/g, "(1-\\t)^$1");
|
|
201
|
+
xExpr = xExpr.replace(/([ (+\-*/,])t(?!\w)/g, "$1\\t");
|
|
202
|
+
yExpr = yExpr.replace(/([ (+\-*/,])t(?!\w)/g, "$1\\t");
|
|
203
|
+
xExpr = xExpr.replace(/^t(?!\w)/, "\\t");
|
|
204
|
+
yExpr = yExpr.replace(/^t(?!\w)/, "\\t");
|
|
205
|
+
const lineStyle = convertLineStyle(options);
|
|
206
|
+
plots.push({ original: match[0], options, xExpression: xExpr, yExpression: yExpr, lineStyle });
|
|
207
|
+
});
|
|
208
|
+
return { hasParametricPlots: plots.length > 0, plots };
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return { hasParametricPlots: false, plots: [] };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function cleanTikZCode(code, opts = {}) {
|
|
214
|
+
code = code.replace(/\\draw\s+\[/g, "\\\\draw[");
|
|
215
|
+
const shouldRound = opts.round !== false;
|
|
216
|
+
const includePoints = opts.points !== false;
|
|
217
|
+
const includeLabels = opts.labels !== false;
|
|
218
|
+
const tikzMatch = code.match(/\\begin\{tikzpicture\}([\s\S]*?)\\end\{tikzpicture\}/);
|
|
219
|
+
if (!tikzMatch) throw new Error("\u672A\u627E\u5230 tikzpicture \u73AF\u5883");
|
|
220
|
+
const pointRegex = /\\draw\s*\[fill=[^\]]+\]\s*\(([^)]+)\)\s*circle\s*\(([^)]+)\);/g;
|
|
221
|
+
const nodeRegex = /\\draw\[color=[^\]]+\]\s*\([^)]+\)\s*node\s*\{\$(?!.*\\textrm\{\\degre\})([^$]+(?:\{[^}]*\})?[^$]*)\$\}/g;
|
|
222
|
+
const pointMap = /* @__PURE__ */ new Map();
|
|
223
|
+
const pointMatches = [...code.matchAll(pointRegex)];
|
|
224
|
+
const nodeMatches = [...code.matchAll(nodeRegex)];
|
|
225
|
+
const anchoredNodes = [...code.matchAll(/\\draw\s*(?:\[[^\]]*\])?\s*\(([^)]+)\)\s*node\s*\[([^\]]+)\]\s*\{([^\r\n]*)\};/g)];
|
|
226
|
+
const usedAnchoredNodes = /* @__PURE__ */ new Set();
|
|
227
|
+
const allPoints = [];
|
|
228
|
+
pointMatches.forEach((match) => {
|
|
229
|
+
const coords = match[1];
|
|
230
|
+
const formattedCoords = formatCoordinate(`(${coords})`, shouldRound);
|
|
231
|
+
allPoints.push({ original: coords, formatted: formattedCoords.slice(1, -1) });
|
|
232
|
+
});
|
|
233
|
+
allPoints.forEach((point, index) => {
|
|
234
|
+
const [x, y] = point.original.split(",").map(Number);
|
|
235
|
+
const candidates = anchoredNodes.map((node, i) => {
|
|
236
|
+
const label = node[3].match(/^\$\\mathit\{([A-Za-z][A-Za-z0-9_]*)\}\$$/)?.[1] || node[3].match(/^\$([A-Za-z][A-Za-z0-9_]*)\$$/)?.[1];
|
|
237
|
+
const [nx, ny] = node[1].split(",").map(Number);
|
|
238
|
+
return { i, label, distance: Math.hypot(nx - x, ny - y) };
|
|
239
|
+
}).filter((n) => n.label && !usedAnchoredNodes.has(n.i)).sort((a, b) => a.distance - b.distance);
|
|
240
|
+
if (candidates.length && candidates[0].distance < 1) {
|
|
241
|
+
pointMap.set(candidates[0].label, point.formatted);
|
|
242
|
+
usedAnchoredNodes.add(candidates[0].i);
|
|
243
|
+
} else if (index < nodeMatches.length) {
|
|
244
|
+
pointMap.set(nodeMatches[index][1], point.formatted);
|
|
245
|
+
} else {
|
|
246
|
+
pointMap.set(`gikzPoint${index}`, point.formatted);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const functionPlotsResult = extractFunctionPlots(code, shouldRound);
|
|
250
|
+
const parametricPlotResult = extractParametricPlots(code, shouldRound);
|
|
251
|
+
const textLabels = anchoredNodes.filter((_, i) => !usedAnchoredNodes.has(i)).map((match) => ({
|
|
252
|
+
coords: formatCoordinate(`(${match[1]})`, shouldRound),
|
|
253
|
+
options: match[2],
|
|
254
|
+
content: match[3]
|
|
255
|
+
}));
|
|
256
|
+
const angleLabelRegex = /\\draw\[color=([^\]]+)\]\s*\(([^)]+)\)\s*node\s*\{\$([^}]+)\\textrm\{\\degre\}\$\};/g;
|
|
257
|
+
const angleLabelMatches = [...code.matchAll(angleLabelRegex)];
|
|
258
|
+
const angleLabels = [];
|
|
259
|
+
angleLabelMatches.forEach((match) => {
|
|
260
|
+
angleLabels.push({
|
|
261
|
+
color: match[1],
|
|
262
|
+
coords: formatCoordinate(`(${match[2]})`, shouldRound),
|
|
263
|
+
content: match[3]
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
const lineRegex = /\\draw\s*(\[[^\]]+\])?\s*\(([^)]+)\)\s*--\s*\(([^)]+)\);/g;
|
|
267
|
+
const lineMatches = [...code.matchAll(lineRegex)];
|
|
268
|
+
const circleRegex = /\\draw\s*\[([^\]]*line[^\]]*)\]\s*\(([^)]+)\)\s*circle\s*\(([^)]+)\);/g;
|
|
269
|
+
const circleMatches = [...code.matchAll(circleRegex)];
|
|
270
|
+
const circles = [];
|
|
271
|
+
circleMatches.forEach((match) => {
|
|
272
|
+
const options = match[1];
|
|
273
|
+
const centerCoords = match[2];
|
|
274
|
+
const radius = match[3];
|
|
275
|
+
const formattedCenter = formatCoordinate(`(${centerCoords})`, shouldRound);
|
|
276
|
+
const lineStyle = convertLineStyle(options);
|
|
277
|
+
let centerLabel = null;
|
|
278
|
+
for (const [label, coords] of pointMap) {
|
|
279
|
+
const coordStr = coords.includes("(") ? coords : `(${coords})`;
|
|
280
|
+
if (coordStr.includes(centerCoords.trim()) || formattedCenter.includes(coords)) {
|
|
281
|
+
centerLabel = label;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
circles.push({
|
|
286
|
+
center: centerLabel ? `(${centerLabel})` : formattedCenter,
|
|
287
|
+
radius: shouldRound ? roundToThreeDecimals(parseFloat(radius)) + (radius.includes("cm") ? "cm" : "") : radius,
|
|
288
|
+
lineStyle
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
const arcRegex = /\\draw\s*(\[[^\]]*\])?\s*plot\[domain=([^:]+):([^,]+),variable=\\t\]\(\{1\*([^\*]+)\*cos\(\\t r\)\+0\*[^\*]+\*sin\(\\t r\)\},\{0\*[^\*]+\*cos\(\\t r\)\+1\*[^\*]+\*sin\(\\t r\)\}\);/g;
|
|
292
|
+
const arcMatches = [...code.matchAll(arcRegex)];
|
|
293
|
+
const arcs = [];
|
|
294
|
+
arcMatches.forEach((match) => {
|
|
295
|
+
const style = match[1] || "";
|
|
296
|
+
const rv = (val) => shouldRound ? roundToThreeDecimals(parseFloat(val)) : val;
|
|
297
|
+
let lineStyle = "";
|
|
298
|
+
if (style.includes("dash pattern=on 1pt off 1pt on 1pt off 4pt")) lineStyle = "dash dot";
|
|
299
|
+
else if (style.includes("dash pattern=on 1pt off 1pt")) lineStyle = "dashed";
|
|
300
|
+
else if (style.includes("dotted")) lineStyle = "dotted";
|
|
301
|
+
const shiftRegex = /shift=\{\(([^,]+),([^)]+)\)\}/;
|
|
302
|
+
const shiftMatch = style.match(shiftRegex);
|
|
303
|
+
let styleParts = [];
|
|
304
|
+
if (lineStyle) styleParts.push(lineStyle);
|
|
305
|
+
if (shiftMatch) styleParts.push(`shift={(${rv(shiftMatch[1])},${rv(shiftMatch[2])})}`);
|
|
306
|
+
styleParts.push("smooth");
|
|
307
|
+
let newStyle = styleParts.length > 0 ? "[" + styleParts.join(", ") + "]" : "";
|
|
308
|
+
arcs.push({ style: newStyle, startAngle: rv(match[2]), endAngle: rv(match[3]), radius: rv(match[4]) });
|
|
309
|
+
});
|
|
310
|
+
const sectorRegex = /\\draw\s*\[shift=\{\(([^,]+),([^)]+)\)\}[^\]]*\]\s*\(0,0\)\s*--\s*plot\[domain=([^:]+):([^,\]]+),variable=\\t\]\(\{1\*([^\*]+)\*cos\(\\t r\)\+0\*[^\*]+\*sin\(\\t r\)\},\{0\*[^\*]+\*cos\(\\t r\)\+1\*[^\*]+\*sin\(\\t r\)\}\)\s*--\s*cycle\s*;/g;
|
|
311
|
+
const sectorMatches = [...code.matchAll(sectorRegex)];
|
|
312
|
+
const sectors = [];
|
|
313
|
+
sectorMatches.forEach((match) => {
|
|
314
|
+
const rv = (val) => shouldRound ? roundToThreeDecimals(parseFloat(val)) : val;
|
|
315
|
+
const style = match[0].match(/\\draw\s*\[([^\]]+)\]/)[1];
|
|
316
|
+
sectors.push({
|
|
317
|
+
shiftX: rv(match[1].trim()),
|
|
318
|
+
shiftY: rv(match[2].trim()),
|
|
319
|
+
startAngle: rv(match[3].trim()),
|
|
320
|
+
endAngle: rv(match[4].trim()),
|
|
321
|
+
radius: rv(match[5].trim()),
|
|
322
|
+
lineStyle: convertLineStyle(style)
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
const ellipseRegex = /\\draw\s*\[([^\]]*)\]\s*\(([^)]+)\)\s*ellipse\s*\(([^)]+)\);/g;
|
|
326
|
+
const ellipseMatches = [...code.matchAll(ellipseRegex)];
|
|
327
|
+
const ellipses = [];
|
|
328
|
+
ellipseMatches.forEach((match) => {
|
|
329
|
+
const options = match[1], centerCoords = match[2], radii = match[3];
|
|
330
|
+
const radiusParts = radii.split(" and ");
|
|
331
|
+
let xR = radiusParts[0]?.trim() || "", yR = radiusParts[1]?.trim() || "";
|
|
332
|
+
const formattedCenter = formatCoordinate(`(${centerCoords})`, shouldRound);
|
|
333
|
+
let centerLabel = null;
|
|
334
|
+
for (const [label, coords] of pointMap) {
|
|
335
|
+
const cs = coords.includes("(") ? coords : `(${coords})`;
|
|
336
|
+
if (cs.includes(centerCoords.trim()) || formattedCenter.includes(coords)) {
|
|
337
|
+
centerLabel = label;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
let rotationAngle = 0, rotationCenter = null;
|
|
342
|
+
const rotateMatch = options.match(/rotate around=\{([^:]+):\(([^)]+)\)\}/);
|
|
343
|
+
if (rotateMatch) {
|
|
344
|
+
rotationAngle = parseFloat(rotateMatch[1]);
|
|
345
|
+
if (shouldRound) rotationAngle = roundToThreeDecimals(rotationAngle);
|
|
346
|
+
const rc = rotateMatch[2];
|
|
347
|
+
const frc = formatCoordinate(`(${rc})`, shouldRound);
|
|
348
|
+
for (const [label, coords] of pointMap) {
|
|
349
|
+
const cs = coords.includes("(") ? coords : `(${coords})`;
|
|
350
|
+
if (cs.includes(rc.trim()) || frc.includes(coords)) {
|
|
351
|
+
rotationCenter = label;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (!rotationCenter) rotationCenter = frc;
|
|
356
|
+
}
|
|
357
|
+
const lineStyle = convertLineStyle(options);
|
|
358
|
+
if (shouldRound && xR && yR) {
|
|
359
|
+
const xU = xR.replace(/[0-9.\-\s]/g, ""), yU = yR.replace(/[0-9.\-\s]/g, "");
|
|
360
|
+
xR = roundToThreeDecimals(parseFloat(xR)) + xU;
|
|
361
|
+
yR = roundToThreeDecimals(parseFloat(yR)) + yU;
|
|
362
|
+
}
|
|
363
|
+
const obj = { center: centerLabel ? `(${centerLabel})` : formattedCenter, xRadius: xR, yRadius: yR, lineStyle, hasRotation: !!rotateMatch };
|
|
364
|
+
if (rotateMatch) {
|
|
365
|
+
obj.rotationAngle = rotationAngle;
|
|
366
|
+
obj.rotationCenter = rotationCenter ? `(${rotationCenter})` : formattedCenter;
|
|
367
|
+
}
|
|
368
|
+
ellipses.push(obj);
|
|
369
|
+
});
|
|
370
|
+
const angleRegex = /\\draw\s*\[shift=\{\(([^,]+),([^)]+)\)\}[^\]]*\]\s*\(0,0\)\s*--\s*\(([^:]+):([^)]+)\)\s*arc\s*\(([^:]+):([^)]+):([^)]+)\)\s*--\s*cycle;/g;
|
|
371
|
+
const angleMatches = [...code.matchAll(angleRegex)];
|
|
372
|
+
const angles = [];
|
|
373
|
+
angleMatches.forEach((match) => {
|
|
374
|
+
const cX = match[1].trim(), cY = match[2].trim();
|
|
375
|
+
const formattedCenter = formatCoordinate(`(${cX},${cY})`, shouldRound);
|
|
376
|
+
let centerLabel = null;
|
|
377
|
+
for (const [label, coords] of pointMap) {
|
|
378
|
+
const cs = coords.includes("(") ? coords : `(${coords})`;
|
|
379
|
+
if (cs.includes(`${cX},${cY}`) || formattedCenter.includes(coords)) {
|
|
380
|
+
centerLabel = label;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const rv = (v) => shouldRound ? roundToThreeDecimals(parseFloat(v)) : v;
|
|
385
|
+
angles.push({
|
|
386
|
+
center: centerLabel ? `(${centerLabel})` : formattedCenter,
|
|
387
|
+
startAngle: rv(match[3].trim()),
|
|
388
|
+
endAngle: rv(match[6].trim()),
|
|
389
|
+
radius: rv(match[4].trim())
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
let result = "\\begin{tikzpicture}[scale=1]\n";
|
|
393
|
+
if (pointMap.size > 0) {
|
|
394
|
+
result += " % \u5750\u6807\u70B9\u5B9A\u4E49\n";
|
|
395
|
+
for (const [label, coords] of pointMap) {
|
|
396
|
+
result += ` \\coordinate (${label}) at ${coords.includes("(") ? coords : `(${coords})`};
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
result += "\n";
|
|
400
|
+
}
|
|
401
|
+
if (functionPlotsResult.hasFunctions) {
|
|
402
|
+
result += " % \u51FD\u6570\u56FE\u50CF\n" + functionPlotsResult.scopeCode + "\n";
|
|
403
|
+
}
|
|
404
|
+
if (parametricPlotResult.hasParametricPlots) {
|
|
405
|
+
result += " % \u8D1D\u585E\u5C14\u66F2\u7EBF\n";
|
|
406
|
+
parametricPlotResult.plots.forEach((plot) => {
|
|
407
|
+
const ls = plot.lineStyle ? `[${plot.lineStyle}, smooth, samples=100, domain=0:1, variable=\\t]` : "[smooth, samples=100, domain=0:1, variable=\\t]";
|
|
408
|
+
result += ` \\draw${ls} plot
|
|
409
|
+
({${plot.xExpression}},
|
|
410
|
+
{${plot.yExpression}});
|
|
411
|
+
|
|
412
|
+
`;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (lineMatches.length > 0 || circles.length > 0 || arcs.length > 0 || ellipses.length > 0 || angles.length > 0 || angleLabels.length > 0 || sectors.length > 0) {
|
|
416
|
+
result += " % \u51E0\u4F55\u5143\u7D20\n";
|
|
417
|
+
if (angles.length > 0) {
|
|
418
|
+
result += " % \u89D2\u5EA6\n";
|
|
419
|
+
angles.forEach((a) => {
|
|
420
|
+
result += ` \\fill [shift=${a.center}, gray!30] (0,0) -- (${a.startAngle}:${a.radius}) arc (${a.startAngle}:${a.endAngle}:${a.radius}) -- cycle;
|
|
421
|
+
`;
|
|
422
|
+
});
|
|
423
|
+
result += "\n";
|
|
424
|
+
}
|
|
425
|
+
if (sectors.length > 0) {
|
|
426
|
+
result += " % \u6247\u5F62\u7ED8\u5236\n";
|
|
427
|
+
sectors.forEach((s) => {
|
|
428
|
+
const st = s.lineStyle ? `[${s.lineStyle}]` : "";
|
|
429
|
+
result += ` \\draw${st} [shift={(${s.shiftX},${s.shiftY})}] (0,0) -- plot[domain=${s.startAngle}:${s.endAngle}]({1*${s.radius}*cos(\\x r)+0*${s.radius}*sin(\\x r)},{0*${s.radius}*cos(\\x r)+1*${s.radius}*sin(\\x r)}) -- cycle;
|
|
430
|
+
`;
|
|
431
|
+
});
|
|
432
|
+
if (lineMatches.length > 0) result += "\n";
|
|
433
|
+
}
|
|
434
|
+
if (circles.length > 0) {
|
|
435
|
+
circles.forEach((c) => {
|
|
436
|
+
result += c.lineStyle ? ` \\draw[${c.lineStyle}] ${c.center} circle (${c.radius});
|
|
437
|
+
` : ` \\draw ${c.center} circle (${c.radius});
|
|
438
|
+
`;
|
|
439
|
+
});
|
|
440
|
+
if (lineMatches.length > 0 || arcs.length > 0) result += "\n";
|
|
441
|
+
}
|
|
442
|
+
if (ellipses.length > 0) {
|
|
443
|
+
result += " % \u692D\u5706\n";
|
|
444
|
+
ellipses.forEach((e) => {
|
|
445
|
+
const drawOpts = [];
|
|
446
|
+
if (e.lineStyle) drawOpts.push(e.lineStyle);
|
|
447
|
+
if (e.hasRotation) drawOpts.push(`rotate around={${e.rotationAngle}:${e.rotationCenter}}`);
|
|
448
|
+
result += drawOpts.length > 0 ? ` \\draw[${drawOpts.join(", ")}] ${e.center} ellipse (${e.xRadius} and ${e.yRadius});
|
|
449
|
+
` : ` \\draw ${e.center} ellipse (${e.xRadius} and ${e.yRadius});
|
|
450
|
+
`;
|
|
451
|
+
});
|
|
452
|
+
if (lineMatches.length > 0 || arcs.length > 0) result += "\n";
|
|
453
|
+
}
|
|
454
|
+
if (arcs.length > 0) {
|
|
455
|
+
result += " % \u5706\u5F27\u7ED8\u5236\n";
|
|
456
|
+
arcs.forEach((arc) => {
|
|
457
|
+
const s = arc.style.trim() ? arc.style : "";
|
|
458
|
+
result += ` \\draw${s} plot[domain=${arc.startAngle}:${arc.endAngle}]({1*${arc.radius}*cos(\\x r)+0*${arc.radius}*sin(\\x r)},{0*${arc.radius}*cos(\\x r)+1*${arc.radius}*sin(\\x r)});
|
|
459
|
+
`;
|
|
460
|
+
});
|
|
461
|
+
if (lineMatches.length > 0) result += "\n";
|
|
462
|
+
}
|
|
463
|
+
const processedLines = /* @__PURE__ */ new Set();
|
|
464
|
+
const parseCoords = (str) => {
|
|
465
|
+
const m = str.match(/\(([^,]+),([^)]+)\)/);
|
|
466
|
+
return m ? { x: parseFloat(m[1]), y: parseFloat(m[2]) } : null;
|
|
467
|
+
};
|
|
468
|
+
lineMatches.forEach((match) => {
|
|
469
|
+
const style = match[1] || "";
|
|
470
|
+
const coord1 = match[2], coord2 = match[3];
|
|
471
|
+
const convertedStyle = convertLineStyle(style);
|
|
472
|
+
let label1 = null, label2 = null;
|
|
473
|
+
const tc1 = formatCoordinate(`(${coord1})`, shouldRound);
|
|
474
|
+
const tc2 = formatCoordinate(`(${coord2})`, shouldRound);
|
|
475
|
+
const t1 = parseCoords(tc1), t2 = parseCoords(tc2);
|
|
476
|
+
for (const [label, coords] of pointMap) {
|
|
477
|
+
const pcs = coords.includes("(") ? coords : `(${coords})`;
|
|
478
|
+
const p = parseCoords(pcs);
|
|
479
|
+
if (p && t1 && Math.abs(p.x - t1.x) < 1e-3 && Math.abs(p.y - t1.y) < 1e-3) label1 = label;
|
|
480
|
+
if (p && t2 && Math.abs(p.x - t2.x) < 1e-3 && Math.abs(p.y - t2.y) < 1e-3) label2 = label;
|
|
481
|
+
}
|
|
482
|
+
if (label1 && label2) {
|
|
483
|
+
const key = `${label1}-${label2}`, rev = `${label2}-${label1}`;
|
|
484
|
+
if (!processedLines.has(key) && !processedLines.has(rev)) {
|
|
485
|
+
result += convertedStyle ? ` \\draw[${convertedStyle}] (${label1}) -- (${label2});
|
|
486
|
+
` : ` \\draw (${label1}) -- (${label2});
|
|
487
|
+
`;
|
|
488
|
+
processedLines.add(key);
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
const fc1 = formatCoordinate(`(${coord1})`, shouldRound);
|
|
492
|
+
const fc2 = formatCoordinate(`(${coord2})`, shouldRound);
|
|
493
|
+
result += convertedStyle ? ` \\draw[${convertedStyle}] ${fc1} -- ${fc2};
|
|
494
|
+
` : ` \\draw ${fc1} -- ${fc2};
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
if (includePoints && pointMap.size > 0) {
|
|
500
|
+
result += "\n % \u70B9\u6807\u8BB0\n";
|
|
501
|
+
for (const [label] of pointMap) {
|
|
502
|
+
result += ` \\draw[fill=black] (${label}) circle (1pt);
|
|
503
|
+
`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (includeLabels && pointMap.size > 0) {
|
|
507
|
+
result += "\n % \u70B9\u6807\u7B7E\n";
|
|
508
|
+
const allPointsData = [];
|
|
509
|
+
for (const [label, coords] of pointMap) {
|
|
510
|
+
const m = coords.match(/\(?([^,]+),([^)]+)\)?/);
|
|
511
|
+
if (m) allPointsData.push({ label, x: parseFloat(m[1]), y: parseFloat(m[2]) });
|
|
512
|
+
}
|
|
513
|
+
for (const point of allPointsData) {
|
|
514
|
+
if (/^gikzPoint\d+$/.test(point.label)) continue;
|
|
515
|
+
const pos = getSmartLabelPosition(point.x, point.y, allPointsData);
|
|
516
|
+
result += ` \\node [${pos}] at (${point.label}) {$${point.label}$};
|
|
517
|
+
`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (angleLabels.length > 0) {
|
|
521
|
+
result += "\n % \u89D2\u5EA6\u6807\u7B7E\n";
|
|
522
|
+
angleLabels.forEach((l) => {
|
|
523
|
+
result += ` \\draw ${l.coords} node {$${l.content}^{\\circ}$};
|
|
524
|
+
`;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (textLabels.length > 0) {
|
|
528
|
+
result += "\n % \u6587\u672C\u6807\u7B7E\n";
|
|
529
|
+
textLabels.forEach((l) => {
|
|
530
|
+
result += ` \\draw ${l.coords} node[${l.options}] {${l.content}};
|
|
531
|
+
`;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
result += "\\end{tikzpicture}";
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
function convertGeoGebraXML(xmlStr, opts = {}) {
|
|
538
|
+
const shouldRound = opts.round !== false;
|
|
539
|
+
const includePoints = opts.points !== false;
|
|
540
|
+
const includeLabels = opts.labels !== false;
|
|
541
|
+
const rv = (v) => shouldRound ? roundToThreeDecimals(v) : v;
|
|
542
|
+
const de = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, '"');
|
|
543
|
+
const constrM = xmlStr.match(/<construction[^>]*>([\s\S]*?)<\/construction>/);
|
|
544
|
+
if (!constrM) throw new Error("XML \u4E2D\u672A\u627E\u5230 construction \u5757");
|
|
545
|
+
const constr = constrM[1];
|
|
546
|
+
const elements = /* @__PURE__ */ new Map();
|
|
547
|
+
const elRe = /<element\s+type="([^"]+)"\s+label="([^"]+)">([\s\S]*?)<\/element>/g;
|
|
548
|
+
let m;
|
|
549
|
+
while (m = elRe.exec(constr)) {
|
|
550
|
+
const label = de(m[2]), body = m[3];
|
|
551
|
+
const el = { type: m[1], label };
|
|
552
|
+
const showM = body.match(/<show\s+object="([^"]+)"\s+label="([^"]+)"/);
|
|
553
|
+
el.visible = showM ? showM[1] === "true" : false;
|
|
554
|
+
el.showLabel = showM ? showM[2] === "true" : false;
|
|
555
|
+
const coordsM = body.match(/<coords\s+x="([^"]+)"\s+y="([^"]+)"\s+z="([^"]+)"/);
|
|
556
|
+
if (coordsM) {
|
|
557
|
+
const z = parseFloat(coordsM[3]) || 1;
|
|
558
|
+
el.x = parseFloat(coordsM[1]) / z;
|
|
559
|
+
el.y = parseFloat(coordsM[2]) / z;
|
|
560
|
+
}
|
|
561
|
+
const valM = body.match(/<value\s+val="([^"]+)"/);
|
|
562
|
+
if (valM) el.value = parseFloat(valM[1]);
|
|
563
|
+
const arcM = body.match(/<arcSize\s+val="([^"]+)"/);
|
|
564
|
+
if (arcM) el.arcSize = parseInt(arcM[1]);
|
|
565
|
+
const colM = body.match(/<objColor[^>]+alpha="([^"]+)"/);
|
|
566
|
+
if (colM) el.alpha = parseFloat(colM[1]);
|
|
567
|
+
const lineM = body.match(/<lineStyle\s+thickness="([^"]+)"\s+type="([^"]+)"/);
|
|
568
|
+
if (lineM) {
|
|
569
|
+
el.lineThickness = parseInt(lineM[1]);
|
|
570
|
+
el.lineType = parseInt(lineM[2]);
|
|
571
|
+
}
|
|
572
|
+
if (el.x !== void 0 && (isNaN(el.x) || isNaN(el.y))) continue;
|
|
573
|
+
elements.set(label, el);
|
|
574
|
+
}
|
|
575
|
+
const commands = [];
|
|
576
|
+
const cmdRe = /<command\s+name="([^"]+)">([\s\S]*?)<\/command>/g;
|
|
577
|
+
while (m = cmdRe.exec(constr)) {
|
|
578
|
+
const cmd = { name: m[1], inputs: [], outputs: [] };
|
|
579
|
+
const inM = m[2].match(/<input\s+([^/]*)\/>/);
|
|
580
|
+
if (inM) {
|
|
581
|
+
const r = /a(\d+)="([^"]+)"/g;
|
|
582
|
+
let a;
|
|
583
|
+
while (a = r.exec(inM[1])) cmd.inputs[+a[1]] = de(a[2]);
|
|
584
|
+
}
|
|
585
|
+
const outM = m[2].match(/<output\s+([^/]*)\/>/);
|
|
586
|
+
if (outM) {
|
|
587
|
+
const r = /a(\d+)="([^"]+)"/g;
|
|
588
|
+
let a;
|
|
589
|
+
while (a = r.exec(outM[1])) cmd.outputs[+a[1]] = de(a[2]);
|
|
590
|
+
}
|
|
591
|
+
commands.push(cmd);
|
|
592
|
+
}
|
|
593
|
+
const cmdByOut = /* @__PURE__ */ new Map();
|
|
594
|
+
for (const cmd of commands)
|
|
595
|
+
for (const lbl of cmd.outputs) if (lbl) cmdByOut.set(lbl, cmd);
|
|
596
|
+
const polyEdges = /* @__PURE__ */ new Set();
|
|
597
|
+
for (const cmd of commands) {
|
|
598
|
+
if (cmd.name === "Polygon") {
|
|
599
|
+
for (let i = 1; i < cmd.outputs.length; i++) if (cmd.outputs[i]) polyEdges.add(cmd.outputs[i]);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const refPts = /* @__PURE__ */ new Set();
|
|
603
|
+
const lsName = (t) => {
|
|
604
|
+
switch (t) {
|
|
605
|
+
case 10:
|
|
606
|
+
case 15:
|
|
607
|
+
return "dashed";
|
|
608
|
+
case 20:
|
|
609
|
+
return "dotted";
|
|
610
|
+
case 30:
|
|
611
|
+
return "dash dot";
|
|
612
|
+
default:
|
|
613
|
+
return "";
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
const segs = [];
|
|
617
|
+
for (const [label, el] of elements) {
|
|
618
|
+
if (!el.visible || el.type !== "segment" || polyEdges.has(label)) continue;
|
|
619
|
+
const cmd = cmdByOut.get(label);
|
|
620
|
+
if (cmd && cmd.name === "Segment" && cmd.inputs[0] && cmd.inputs[1]) {
|
|
621
|
+
segs.push({ from: cmd.inputs[0], to: cmd.inputs[1], ls: lsName(el.lineType || 0) });
|
|
622
|
+
refPts.add(cmd.inputs[0]);
|
|
623
|
+
refPts.add(cmd.inputs[1]);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const circs = [];
|
|
627
|
+
for (const [label, el] of elements) {
|
|
628
|
+
if (!el.visible || el.type !== "conic") continue;
|
|
629
|
+
const cmd = cmdByOut.get(label);
|
|
630
|
+
if (cmd && cmd.name === "Circle") {
|
|
631
|
+
const cEl = elements.get(cmd.inputs[0]), tEl = elements.get(cmd.inputs[1]);
|
|
632
|
+
if (cEl && tEl && cEl.x !== void 0 && tEl.x !== void 0) {
|
|
633
|
+
const dx = tEl.x - cEl.x, dy = tEl.y - cEl.y;
|
|
634
|
+
circs.push({ center: cmd.inputs[0], radius: rv(Math.sqrt(dx * dx + dy * dy)) });
|
|
635
|
+
refPts.add(cmd.inputs[0]);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const polys = [];
|
|
640
|
+
for (const [label, el] of elements) {
|
|
641
|
+
if (!el.visible || el.type !== "polygon") continue;
|
|
642
|
+
const cmd = cmdByOut.get(label);
|
|
643
|
+
if (cmd && cmd.name === "Polygon") {
|
|
644
|
+
const verts = [];
|
|
645
|
+
for (let i = 0; cmd.inputs[i]; i++) {
|
|
646
|
+
verts.push(cmd.inputs[i]);
|
|
647
|
+
refPts.add(cmd.inputs[i]);
|
|
648
|
+
}
|
|
649
|
+
polys.push({ vertices: verts, alpha: el.alpha || 0 });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const angs = [];
|
|
653
|
+
for (const [label, el] of elements) {
|
|
654
|
+
if (!el.visible || el.type !== "angle") continue;
|
|
655
|
+
const cmd = cmdByOut.get(label);
|
|
656
|
+
if (cmd && cmd.name === "Angle") {
|
|
657
|
+
angs.push({ ptA: cmd.inputs[0], vertex: cmd.inputs[1], ptC: cmd.inputs[2], value: el.value });
|
|
658
|
+
refPts.add(cmd.inputs[0]);
|
|
659
|
+
refPts.add(cmd.inputs[1]);
|
|
660
|
+
refPts.add(cmd.inputs[2]);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
for (const [label, el] of elements)
|
|
664
|
+
if (el.type === "point" && (el.visible || el.showLabel)) refPts.add(label);
|
|
665
|
+
const coords = [];
|
|
666
|
+
for (const label of refPts) {
|
|
667
|
+
const el = elements.get(label);
|
|
668
|
+
if (el && el.type === "point" && el.x !== void 0) coords.push({ label, x: rv(el.x), y: rv(el.y) });
|
|
669
|
+
}
|
|
670
|
+
coords.sort((a, b) => a.label.localeCompare(b.label));
|
|
671
|
+
let out = "\\begin{tikzpicture}[scale=1]\n";
|
|
672
|
+
if (coords.length > 0) {
|
|
673
|
+
out += " % \u5750\u6807\u70B9\u5B9A\u4E49\n";
|
|
674
|
+
for (const c of coords) out += ` \\coordinate (${c.label}) at (${c.x},${c.y});
|
|
675
|
+
`;
|
|
676
|
+
out += "\n";
|
|
677
|
+
}
|
|
678
|
+
out += " % \u51E0\u4F55\u5143\u7D20\n";
|
|
679
|
+
if (angs.length > 0) {
|
|
680
|
+
for (const ang of angs) {
|
|
681
|
+
const vEl = elements.get(ang.vertex), aEl = elements.get(ang.ptA), cEl = elements.get(ang.ptC);
|
|
682
|
+
if (!vEl || !aEl || !cEl) continue;
|
|
683
|
+
let sa = rv(Math.atan2(aEl.y - vEl.y, aEl.x - vEl.x) * 180 / Math.PI);
|
|
684
|
+
let ea = rv(Math.atan2(cEl.y - vEl.y, cEl.x - vEl.x) * 180 / Math.PI);
|
|
685
|
+
if (ea <= sa) ea += 360;
|
|
686
|
+
out += ` \\fill[green!10] (${ang.vertex}) -- ++(${sa}:0.4) arc (${sa}:${ea}:0.4) -- cycle;
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
out += "\n";
|
|
690
|
+
}
|
|
691
|
+
if (polys.length > 0) {
|
|
692
|
+
for (const p of polys) {
|
|
693
|
+
if (p.alpha > 0) {
|
|
694
|
+
const pth = p.vertices.map((v) => `(${v})`).join(" -- ") + " -- cycle";
|
|
695
|
+
out += ` \\fill[gray!20, fill opacity=${rv(p.alpha)}] ${pth};
|
|
696
|
+
`;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
out += "\n";
|
|
700
|
+
}
|
|
701
|
+
if (circs.length > 0) {
|
|
702
|
+
for (const c of circs) out += ` \\draw (${c.center}) circle (${c.radius});
|
|
703
|
+
`;
|
|
704
|
+
out += "\n";
|
|
705
|
+
}
|
|
706
|
+
if (segs.length > 0) {
|
|
707
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
708
|
+
for (const s of segs) {
|
|
709
|
+
const k = [s.from, s.to].sort().join("|");
|
|
710
|
+
if (drawn.has(k)) continue;
|
|
711
|
+
drawn.add(k);
|
|
712
|
+
out += s.ls ? ` \\draw[${s.ls}] (${s.from}) -- (${s.to});
|
|
713
|
+
` : ` \\draw (${s.from}) -- (${s.to});
|
|
714
|
+
`;
|
|
715
|
+
}
|
|
716
|
+
out += "\n";
|
|
717
|
+
}
|
|
718
|
+
if (includePoints) {
|
|
719
|
+
const visPts = coords.filter((c) => {
|
|
720
|
+
const e = elements.get(c.label);
|
|
721
|
+
return e && e.visible;
|
|
722
|
+
});
|
|
723
|
+
if (visPts.length > 0) {
|
|
724
|
+
out += " % \u70B9\u6807\u8BB0\n";
|
|
725
|
+
for (const p of visPts) out += ` \\draw[fill=black] (${p.label}) circle (1pt);
|
|
726
|
+
`;
|
|
727
|
+
out += "\n";
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (includeLabels) {
|
|
731
|
+
const lblPts = coords.filter((c) => {
|
|
732
|
+
const e = elements.get(c.label);
|
|
733
|
+
return e && (e.visible || e.showLabel);
|
|
734
|
+
});
|
|
735
|
+
if (lblPts.length > 0) {
|
|
736
|
+
out += " % \u70B9\u6807\u7B7E\n";
|
|
737
|
+
for (const p of lblPts) {
|
|
738
|
+
const pos = getSmartLabelPosition(p.x, p.y, coords);
|
|
739
|
+
out += ` \\node[${pos}] at (${p.label}) {$${p.label}$};
|
|
740
|
+
`;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
out += "\\end{tikzpicture}";
|
|
745
|
+
return out;
|
|
746
|
+
}
|
|
747
|
+
function wrapStandalone(tikzCode) {
|
|
748
|
+
return `\\documentclass[border=5pt]{standalone}
|
|
749
|
+
\\usepackage{tikz}
|
|
750
|
+
\\usepackage{pgfplots}
|
|
751
|
+
\\pgfplotsset{compat=1.15}
|
|
752
|
+
\\usetikzlibrary{arrows.meta}
|
|
753
|
+
|
|
754
|
+
\\begin{document}
|
|
755
|
+
${tikzCode}
|
|
756
|
+
\\end{document}
|
|
757
|
+
`;
|
|
758
|
+
}
|
|
759
|
+
module.exports = { cleanTikZCode, wrapStandalone, convertGeoGebraXML };
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// gikz/browser.cjs
|
|
764
|
+
var require_browser = __commonJS({
|
|
765
|
+
"gikz/browser.cjs"() {
|
|
766
|
+
globalThis.Gikz = Object.freeze(require_core());
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
require_browser();
|
|
770
|
+
})();
|