gramfig-mcp 0.1.0 → 0.1.1
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/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/package.json +2 -1
package/dist/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../packages/formula/src/latex-svg.ts", "../src/main.ts", "../src/server.ts", "../src/tools/latex-render.ts", "../../../packages/formula/src/render.ts", "../../../packages/formula/src/sanitize.ts", "../../../packages/formula/src/latex-detect.ts", "../../../packages/formula/src/mp-render.ts", "../../../packages/formula/src/mp-html.ts", "../../../packages/formula/src/mp-samples.ts", "../src/tools/graph-plot.ts", "../../../packages/viz/src/core/numeric.ts", "../../../packages/viz/src/core/compile.ts", "../../../packages/viz/src/plot-svg/scales.ts", "../../../packages/viz/src/plot-svg/sampler.ts", "../../../packages/viz/src/plot-svg/axes.ts", "../../../packages/viz/src/plot-svg/svg-builder.ts", "../../../packages/viz/src/plot-svg/render.ts", "../src/tools/geometry-solve.ts", "../../../packages/viz/src/geometry/construction.ts", "../../../packages/viz/src/geometry/arc.ts", "../../../packages/viz/src/geometry/conic.ts", "../../../packages/viz/src/geometry/glider.ts", "../../../packages/viz/src/geometry/locus.ts", "../../../packages/viz/src/core/polyline.ts", "../../../packages/viz/src/geometry/intersections.ts", "../../../packages/viz/src/geometry/measure.ts", "../../../packages/viz/src/geometry/transform.ts", "../../../packages/viz/src/geometry/resolve.ts", "../../../packages/viz/src/geometry/angle.ts", "../../../packages/viz/src/geometry/kernel.ts", "../src/tools/geometry-measure.ts", "../src/tools/geometry-render.ts", "../../../packages/viz/src/geometry/style.ts", "../../../packages/viz/src/geometry/label-position.ts", "../../../packages/viz/src/geometry/render-theme.ts", "../../../packages/viz/src/geometry/render-svg.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Node \u7AEF TeX\u2192SVG\uFF08\u65E0 DOM\u3001\u540C\u6B65\uFF09\uFF1A\u57FA\u4E8E mathjax-full liteAdaptor\u3002\n *\n * \u4F9B geometry SVG \u6E32\u67D3\u5668\u6CE8\u5165 `latexToSvg` \u56DE\u8C03\u4F7F\u7528\uFF08MCP \u56DE\u8C03\u4E3A\u540C\u6B65\u7B7E\u540D\uFF09\uFF0C\n * \u56E0\u6B64\u4E0D\u80FD\u4F7F\u7528\u52A8\u6001 import\uFF1B\u6539\u4E3A\u9996\u6B21\u8C03\u7528\u65F6\u540C\u6B65\u521D\u59CB\u5316\u5355\u4F8B\uFF0CMathJax \u53EA\u521D\u59CB\u5316\u4E00\u6B21\u3002\n * \u8F93\u51FA\u4EC5\u542B svg/defs/g/path/text \u7B49 resvg \u53EF\u6805\u683C\u5316\u7684\u8282\u70B9\uFF0C\u4E0D\u542B foreignObject\u3002\n */\nimport { mathjax } from 'mathjax-full/js/mathjax.js';\nimport { TeX } from 'mathjax-full/js/input/tex.js';\nimport { SVG } from 'mathjax-full/js/output/svg.js';\nimport { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js';\nimport { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html.js';\nimport { AllPackages } from 'mathjax-full/js/input/tex/AllPackages.js';\n\ninterface MathJaxSvgRenderer {\n convert(latex: string): string;\n}\n\nlet renderer: MathJaxSvgRenderer | null = null;\n\nfunction getRenderer(): MathJaxSvgRenderer {\n if (renderer) return renderer;\n const adaptor = liteAdaptor();\n RegisterHTMLHandler(adaptor);\n const tex = new TeX({ packages: AllPackages });\n const svg = new SVG({ fontCache: 'local' });\n const document = mathjax.document('', { InputJax: tex, OutputJax: svg });\n renderer = {\n convert: (latex: string) => {\n const node = document.convert(latex, { display: false });\n return adaptor.innerHTML(node);\n },\n };\n return renderer;\n}\n\n/** TeX \u6E90\u7801\u8F6C\u5185\u8054 `<svg>` \u5B57\u7B26\u4E32\uFF08\u884C\u5185\u6A21\u5F0F\uFF0C\u5B57\u4F53\u7F13\u5B58\u5185\u5D4C\u5728 defs \u4E2D\uFF09 */\nexport function renderLatexToSvg(latex: string): string {\n return getRenderer().convert(latex);\n}\n", "import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createMcpServer } from './server.js';\n\n/**\n * MCP stdio \u5165\u53E3\uFF1Astdout \u4EC5\u4F9B MCP \u534F\u8BAE\u5E27\u4F7F\u7528\uFF0C\u4EFB\u4F55\u65E5\u5FD7\u5FC5\u987B\u8D70 stderr\u3002\n */\nasync function main(): Promise<void> {\n const server = createMcpServer();\n await server.connect(new StdioServerTransport());\n console.error('[gramfig-mcp] \u5DE5\u5177\u5DF2\u6CE8\u518C\uFF0C\u7B49\u5F85 stdio \u8BF7\u6C42\u2026');\n}\n\nmain().catch((error) => {\n console.error('[gramfig-mcp] \u542F\u52A8\u5931\u8D25\uFF1A', error);\n process.exit(1);\n});\n", "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerLatexRenderTool } from './tools/latex-render.js';\nimport { registerGraphPlotTool } from './tools/graph-plot.js';\nimport { registerGeometrySolveTool } from './tools/geometry-solve.js';\nimport { registerGeometryMeasureTool } from './tools/geometry-measure.js';\nimport { registerGeometryRenderTool } from './tools/geometry-render.js';\n\n/** \u521B\u5EFA MCP server\uFF08stdio \u65E0\u72B6\u6001\uFF09\u5E76\u6CE8\u518C\u5168\u90E8\u5DE5\u5177 */\nexport function createMcpServer(): McpServer {\n const server = new McpServer(\n { name: 'gramfig-mcp', version: '0.1.0' },\n { capabilities: { tools: {} } },\n );\n registerLatexRenderTool(server);\n registerGraphPlotTool(server);\n registerGeometrySolveTool(server);\n registerGeometryMeasureTool(server);\n registerGeometryRenderTool(server);\n return server;\n}\n", "/**\n * latex_render \u5DE5\u5177\uFF1A\u76F4\u8C03 @studylab/formula \u672C\u5730\u51FD\u6570\u94FE\uFF08\u4E0D\u7ECF\u7F51\u7EDC\uFF0C\u5EF6\u8FDF <50ms\uFF09\u3002\n * \u8F93\u5165 schema \u7531 registerTool \u7684 zod inputSchema \u6821\u9A8C\uFF08\u542B 4KB \u957F\u5EA6\u4E0A\u9650\uFF09\uFF1B\n * \u672C handler \u53EA\u8D1F\u8D23\u6E32\u67D3\u7F16\u6392\uFF0C\u6240\u6709\u5931\u8D25\u5C31\u5730\u8F6C\u4E3A isError \u7ED3\u679C\uFF0C\u7EDD\u4E0D throw\n * \u2014\u2014 \u9632\u6B62\u672A\u6355\u83B7\u5F02\u5E38\u5BFC\u81F4 stdio \u8FDB\u7A0B\u5D29\u6E83\u3002\n */\nimport { JSDOM } from 'jsdom';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { renderFormula, sanitizeFormulaHtml, type RenderEngine } from '@studylab/formula';\nimport { MpRenderError, renderToMpNodes, type MpTreeNode } from '@studylab/formula/mp';\n\n/** \u5355\u6761 latex \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A72.2\uFF0C\u4E0E apps/api \u7684 LatexService \u5BF9\u9F50\uFF09 */\nexport const LATEX_MAX_LENGTH = 4096;\n\n/** \u767D\u540D\u5355\u6E05\u6D17\u7528 DOM \u5355\u4F8B\uFF1Ajsdom \u5B9E\u4F8B\u6A21\u5757\u7EA7\u590D\u7528\uFF0C\u907F\u514D\u6BCF\u6B21\u8BF7\u6C42\u91CD\u5EFA\u5F00\u9500 */\nconst sanitizeDocument = new JSDOM('').window.document;\n\n/** \u5DE5\u5177\u5165\u53C2\uFF08displayMode / outputs \u7684\u9ED8\u8BA4\u503C\u7531 inputSchema \u7684 zod default \u515C\u5E95\uFF09 */\nexport interface LatexRenderArgs {\n latex: string;\n displayMode: boolean;\n outputs: ('html' | 'mp-nodes')[];\n}\n\n/** \u6210\u529F\u8F7D\u8377\uFF1A\u4EC5\u5305\u542B\u5B9E\u9645\u8BF7\u6C42\u7684\u8F93\u51FA\u5B57\u6BB5 */\ninterface LatexRenderPayload {\n html?: string;\n mpNodes?: MpTreeNode[];\n engine?: RenderEngine;\n}\n\nfunction textResult(payload: LatexRenderPayload): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\n/**\n * latex_render \u7EAF handler\uFF08\u53EF\u8131\u79BB stdio \u76F4\u63A5\u5355\u6D4B\uFF09\u3002\n * html \u8F93\u51FA\u5FC5\u8FC7 sanitizeFormulaHtml \u767D\u540D\u5355\u6E05\u6D17\uFF1Bmp-nodes \u5931\u8D25\u6355\u83B7 MpRenderError\u3002\n */\nexport async function handleLatexRender(args: LatexRenderArgs): Promise<CallToolResult> {\n const { latex, displayMode, outputs } = args;\n const payload: LatexRenderPayload = {};\n\n try {\n if (outputs.includes('html')) {\n const rendered = await renderFormula(latex, displayMode);\n if (!rendered.ok) {\n return errorResult(`LaTeX \u6E32\u67D3\u5931\u8D25\uFF1A${rendered.message}`);\n }\n payload.html = sanitizeFormulaHtml(rendered.html, { documentRef: sanitizeDocument });\n payload.engine = rendered.engine;\n }\n\n if (outputs.includes('mp-nodes')) {\n try {\n payload.mpNodes = renderToMpNodes(latex, displayMode);\n payload.engine = payload.engine ?? 'katex';\n } catch (error) {\n const detail = error instanceof MpRenderError ? error.message : String(error);\n return errorResult(`LaTeX \u6E32\u67D3\u5931\u8D25\uFF08\u5C0F\u7A0B\u5E8F\u8282\u70B9\uFF09\uFF1A${detail}`);\n }\n }\n\n return textResult(payload);\n } catch (error) {\n // \u515C\u5E95\uFF1A\u4EFB\u4F55\u672A\u9884\u671F\u5F02\u5E38\u90FD\u4E0D\u80FD\u8BA9 stdio \u8FDB\u7A0B\u9000\u51FA\n return errorResult(error instanceof Error ? error.message : String(error));\n }\n}\n\n/** \u5411 MCP server \u6CE8\u518C latex_render \u5DE5\u5177 */\nexport function registerLatexRenderTool(server: McpServer): void {\n server.registerTool(\n 'latex_render',\n {\n title: 'LaTeX \u6E32\u67D3',\n description: '\u5C06 LaTeX \u6570\u5B66\u6216\u5316\u5B66\u516C\u5F0F\u6E32\u67D3\u4E3A\u5B89\u5168 HTML \u6216\u5C0F\u7A0B\u5E8F rich-text nodes\u3002',\n inputSchema: {\n latex: z.string().max(LATEX_MAX_LENGTH, `LaTeX \u957F\u5EA6\u8D85\u9650\uFF08\u4E0A\u9650 ${LATEX_MAX_LENGTH} \u5B57\u7B26\uFF09`),\n displayMode: z.boolean().default(false),\n outputs: z.array(z.enum(['html', 'mp-nodes'])).default(['html']),\n },\n },\n (args) => handleLatexRender(args),\n );\n}\n", "/**\n * \u516C\u5F0F\u6E32\u67D3\u5206\u6D41\uFF08PRD \u00A76.1\uFF09\uFF1A\n * - \u6570\u5B66\uFF08\u9ED8\u8BA4\uFF09\uFF1AKaTeX \u540C\u6B65\u6E32\u67D3 HTML\uFF1B\u89E3\u6790\u5931\u8D25\u663E\u5F0F\u629B\u9519\uFF0C\u7531\u7F16\u8F91\u6001\u6807\u7EA2\u3001\u4E0D\u4E2D\u65AD\u8F93\u5165\u3002\n * - \u5316\u5B66\uFF08`\\ce` / mhchem\uFF09\uFF1A\u52A8\u6001\u52A0\u8F7D MathJax 3\uFF08liteAdaptor\uFF0C\u7EAF JS \u65E0\u9700 DOM\uFF09\uFF0C\n * \u8F93\u51FA\u5185\u8054 SVG \u5B57\u7B26\u4E32\u3002\u52A8\u6001 import \u4F7F mathjax-full \u4E0D\u8FDB\u4E3B\u5305\u5173\u952E\u8DEF\u5F84\u3002\n * \u6240\u6709\u4EA7\u7269\u8FD4\u56DE\u524D\u8C03\u7528\u65B9\u5E94\u8FC7 sanitizeFormulaHtml \u767D\u540D\u5355\u3002\n */\nimport katex from 'katex';\n\nexport type RenderEngine = 'katex' | 'mathjax';\n\nexport interface RenderSuccess {\n ok: true;\n engine: RenderEngine;\n /** \u5DF2\u6E05\u6D17\u7684\u5B89\u5168 HTML/SVG \u7247\u6BB5 */\n html: string;\n}\n\nexport interface RenderFailure {\n ok: false;\n engine: RenderEngine;\n /** \u9762\u5411\u6392\u67E5\u7684\u9519\u8BEF\u4FE1\u606F\uFF1B\u7981\u6B62\u76F4\u63A5\u900F\u4F20\u7ED9\u6700\u7EC8\u7528\u6237 */\n message: string;\n}\n\nexport type RenderResult = RenderSuccess | RenderFailure;\n\n/** \u5316\u5B66\u65B9\u7A0B\u5F0F\u5224\u5B9A\uFF1A\\ce{...}\u3001\\cee\u3001\\pu \u6216\u663E\u5F0F require mhchem */\nconst CHEMISTRY_PATTERN = /\\\\(?:ce|cee|pu)\\b|\\\\require\\s*\\{[^}]*mhchem/;\n\nexport function isChemistry(latex: string): boolean {\n return CHEMISTRY_PATTERN.test(latex);\n}\n\nfunction renderWithKatex(latex: string, displayMode: boolean): RenderResult {\n try {\n return {\n ok: true,\n engine: 'katex',\n html: katex.renderToString(latex, {\n displayMode,\n throwOnError: true,\n strict: 'ignore',\n trust: false,\n }),\n };\n } catch (error) {\n return {\n ok: false,\n engine: 'katex',\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n// \u2500\u2500 MathJax \u5355\u4F8B\uFF08\u9996\u6B21\u6E32\u67D3\u5316\u5B66\u516C\u5F0F\u65F6\u5F02\u6B65\u521D\u59CB\u5316\uFF09\u2500\u2500\n\ninterface MathJaxBundle {\n convert: (latex: string, displayMode: boolean) => string;\n}\n\nlet mathjaxBundlePromise: Promise<MathJaxBundle> | null = null;\n\nasync function getMathJax(): Promise<MathJaxBundle> {\n if (!mathjaxBundlePromise) {\n mathjaxBundlePromise = (async () => {\n const [{ mathjax }, { TeX }, { SVG }, { liteAdaptor }, { RegisterHTMLHandler }, { AllPackages }] =\n await Promise.all([\n import('mathjax-full/js/mathjax.js'),\n import('mathjax-full/js/input/tex.js'),\n import('mathjax-full/js/output/svg.js'),\n import('mathjax-full/js/adaptors/liteAdaptor.js'),\n import('mathjax-full/js/handlers/html.js'),\n import('mathjax-full/js/input/tex/AllPackages.js'),\n ]);\n const adaptor = liteAdaptor();\n RegisterHTMLHandler(adaptor);\n const tex = new TeX({ packages: AllPackages });\n const svg = new SVG({ fontCache: 'local' });\n const document = mathjax.document('', { InputJax: tex, OutputJax: svg });\n return {\n convert: (latex: string, displayMode: boolean) => {\n const node = document.convert(latex, { display: displayMode });\n return adaptor.outerHTML(node);\n },\n };\n })();\n }\n return mathjaxBundlePromise;\n}\n\nasync function renderWithMathJax(latex: string, displayMode: boolean): Promise<RenderResult> {\n try {\n const bundle = await getMathJax();\n return { ok: true, engine: 'mathjax', html: bundle.convert(latex, displayMode) };\n } catch (error) {\n return {\n ok: false,\n engine: 'mathjax',\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * \u7EDF\u4E00\u6E32\u67D3\u5165\u53E3\uFF1A\u81EA\u52A8\u6309\u5185\u5BB9\u5206\u6D41\u3002\n * @param displayMode \u72EC\u7ACB\u516C\u5F0F\u5757 true\uFF1B\u884C\u5185 false\n */\nexport async function renderFormula(latex: string, displayMode: boolean): Promise<RenderResult> {\n if (isChemistry(latex)) return renderWithMathJax(latex, displayMode);\n return renderWithKatex(latex, displayMode);\n}\n\n/** \u4F9B\u6027\u80FD\u57FA\u51C6\u4E0E\u540C\u6B65\u573A\u666F\u4F7F\u7528\uFF1A\u76F4\u63A5\u8D70 KaTeX\uFF08\u4E0D\u542B\u5316\u5B66\u5206\u6D41\uFF09 */\nexport function renderMathSync(latex: string, displayMode: boolean): RenderResult {\n return renderWithKatex(latex, displayMode);\n}\n", "/**\n * \u6E32\u67D3\u4EA7\u7269\u767D\u540D\u5355\u6E05\u6D17\uFF08PRD \u00A76.1 \u5B89\u5168\u6761\u6B3E\uFF09\u3002\n *\n * \u6E32\u67D3\u6765\u6E90\u867D\u53D7\u63A7\uFF08KaTeX/MathJax \u8F93\u51FA\uFF09\uFF0C\u4F46\u516C\u5F0F\u5185\u5BB9\u542B\u7528\u6237\u8F93\u5165\uFF0C\n * \u5165\u9898/\u5165\u5E93\u524D\u7EDF\u4E00\u8FC7\u767D\u540D\u5355\uFF1A\u5265\u79BB\u811A\u672C\u3001\u4E8B\u4EF6\u5C5E\u6027\u4E0E\u5371\u9669\u6807\u7B7E\uFF0C\u4EC5\u4FDD\u7559\n * KaTeX HTML \u4E0E MathJax SVG \u6E32\u67D3\u6240\u9700\u7684\u6807\u7B7E/\u5C5E\u6027/\u6837\u5F0F\u3002\n *\n * M0 POC \u57FA\u4E8E\u6D4F\u89C8\u5668 DOMParser\uFF1B\u9636\u6BB5 I \u670D\u52A1\u7AEF\u590D\u7528\u65F6\u6CE8\u5165\u7B49\u4EF7 document \u5B9E\u73B0\u3002\n */\n\nconst ALLOWED_TAGS = new Set([\n // KaTeX HTML\n 'span',\n 'math',\n 'semantics',\n 'annotation',\n 'mrow',\n 'mi',\n 'mo',\n 'mn',\n 'ms',\n 'mtext',\n 'mspace',\n 'msup',\n 'msub',\n 'msubsup',\n 'munder',\n 'mover',\n 'munderover',\n 'mfrac',\n 'msqrt',\n 'mroot',\n 'mtable',\n 'mlabeledtr',\n 'mtr',\n 'mtd',\n 'mstyle',\n // MathJax SVG\n 'svg',\n 'g',\n 'path',\n 'line',\n 'rect',\n 'polygon',\n 'ellipse',\n 'circle',\n 'use',\n 'text',\n 'tspan',\n 'defs',\n 'b',\n 'i',\n 'br',\n]);\n\n/** \u5141\u8BB8\u900F\u4F20\u7684\u5C5E\u6027\uFF08\u767D\u540D\u5355\u5236\uFF1Bdata \u4E0E aria \u524D\u7F00\u5C5E\u6027\u516C\u5F0F\u4EA7\u7269\u4E0D\u9700\u8981\uFF0C\u4E00\u5F8B\u62D2\u7EDD\uFF09 */\nconst ALLOWED_ATTRS = new Set([\n 'class',\n 'style',\n 'aria-hidden',\n // MathML\n 'mathvariant',\n 'mathsize',\n 'fence',\n 'separator',\n 'stretchy',\n 'lspace',\n 'rspace',\n 'largeop',\n 'movablelimits',\n 'accent',\n 'accentunder',\n 'columnalign',\n 'rowalign',\n 'columnspan',\n 'rowspan',\n // SVG\n 'viewbox',\n 'preserveaspectratio',\n 'width',\n 'height',\n 'x',\n 'y',\n 'x1',\n 'y1',\n 'x2',\n 'y2',\n 'cx',\n 'cy',\n 'rx',\n 'ry',\n 'd',\n 'points',\n 'transform',\n 'fill',\n 'stroke',\n 'stroke-width',\n 'stroke-linecap',\n 'xmlns',\n 'href',\n 'xlink:href',\n 'id',\n 'overflow',\n 'vertical-align',\n]);\n\n/** \u5141\u8BB8\u4FDD\u7559\u7684 CSS \u5C5E\u6027\uFF08\u6E32\u67D3\u516C\u5F0F\u6240\u9700\uFF1B\u7981\u6B62 url()/expression \u7B49\u5916\u8054\u80FD\u529B\uFF09 */\nconst ALLOWED_STYLE_PROPS = new Set([\n 'margin-left',\n 'margin-right',\n 'margin-top',\n 'margin-bottom',\n 'top',\n 'left',\n 'position',\n 'width',\n 'height',\n 'min-width',\n 'vertical-align',\n 'transform',\n 'transform-origin',\n 'color',\n 'font-size',\n 'font-family',\n 'font-style',\n 'font-weight',\n 'line-height',\n 'border-top-width',\n 'border-bottom-width',\n 'border-left-width',\n 'border-right-width',\n 'border-style',\n 'padding',\n 'padding-left',\n 'padding-right',\n 'padding-top',\n 'padding-bottom',\n 'display',\n]);\n\n/** \u4EFB\u4F55\u503C\u4E2D\u51FA\u73B0\u8FD9\u4E9B\u7247\u6BB5\u7684\u6837\u5F0F\u9879\u76F4\u63A5\u4E22\u5F03 */\nconst FORBIDDEN_STYLE_PATTERN = /url\\s*\\(|expression\\s*\\(|javascript:|@import|<\\/?script/i;\n\nfunction sanitizeStyle(cssText: string): string {\n const kept: string[] = [];\n for (const declaration of cssText.split(';')) {\n const separatorIndex = declaration.indexOf(':');\n if (separatorIndex === -1) continue;\n const prop = declaration.slice(0, separatorIndex).trim().toLowerCase();\n const value = declaration.slice(separatorIndex + 1).trim();\n if (!ALLOWED_STYLE_PROPS.has(prop)) continue;\n if (FORBIDDEN_STYLE_PATTERN.test(value)) continue;\n kept.push(`${prop}:${value}`);\n }\n return kept.join(';');\n}\n\nfunction walk(node: Element | DocumentFragment): void {\n for (const child of Array.from(node.children)) {\n walk(child);\n if (!ALLOWED_TAGS.has(child.tagName.toLowerCase())) {\n // \u975E\u767D\u540D\u5355\u6807\u7B7E\uFF1A\u4FDD\u7559\u6587\u672C\u5185\u5BB9\uFF0C\u79FB\u9664\u5143\u7D20\u672C\u8EAB\n child.replaceWith(...Array.from(child.childNodes));\n continue;\n }\n for (const attr of Array.from(child.attributes)) {\n const name = attr.name.toLowerCase();\n const value = attr.value;\n if (!ALLOWED_ATTRS.has(name)) {\n child.removeAttribute(attr.name);\n } else if (name === 'style') {\n const cleaned = sanitizeStyle(value);\n if (cleaned.length === 0) child.removeAttribute('style');\n else child.setAttribute('style', cleaned);\n }\n }\n }\n}\n\nexport interface SanitizeOptions {\n /** \u670D\u52A1\u7AEF\u590D\u7528\u65F6\u6CE8\u5165\uFF08jsdom document\uFF09\uFF1B\u9ED8\u8BA4\u53D6\u6D4F\u89C8\u5668\u5168\u5C40 */\n documentRef?: Document;\n}\n\n/**\n * \u767D\u540D\u5355\u6E05\u6D17\u6E32\u67D3 HTML/SVG\u3002\u8FD4\u56DE\u5B89\u5168\u7247\u6BB5\u5B57\u7B26\u4E32\u3002\n * \u6CE8\u610F\uFF1A\u8F93\u5165\u5E94\u4E3A\u6E32\u67D3\u5F15\u64CE\u4EA7\u7269\uFF0C\u672C\u51FD\u6570\u4E0D\u627F\u62C5\u901A\u7528\u5BCC\u6587\u672C\u51C0\u5316\u804C\u8D23\u3002\n */\nexport function sanitizeFormulaHtml(html: string, options: SanitizeOptions = {}): string {\n const docRef = options.documentRef ?? globalThis.document;\n const template = docRef.createElement('template');\n template.innerHTML = html;\n // <template> \u7684\u5B50\u8282\u70B9\u4F4D\u4E8E .content \u6587\u6863\u7247\u6BB5\u4E2D\uFF0Ctemplate.children \u6052\u4E3A\u7A7A\n walk(template.content);\n return template.innerHTML;\n}\n", "/**\n * \u6B63\u6587\u88F8 LaTeX \u68C0\u6D4B / \u5207\u5206 + \u5185\u5BB9\u6587\u6863\u5BA1\u6838\uFF08\u5355\u4E00\u771F\u76F8\u6E90\uFF09\u3002\n *\n * \u80CC\u666F\uFF1A\u9898\u76EE\u5185\u5BB9\u6309 block JSON v1 \u5B58\u50A8\uFF0CLaTeX \u53EA\u80FD\u5B58\u5728\u4E8E formula \u8282\u70B9\uFF08\u884C\u5185/\u72EC\u7ACB\u5757\uFF09\u3002\n * \u82E5 LaTeX \u547D\u4EE4\u6DF7\u8FDB\u4E86 paragraph \u7684 text \u8282\u70B9\uFF0C\u524D\u7AEF\u53EA\u80FD\u6309\u5B57\u9762\u6587\u672C\u663E\u793A\uFF08\u4E0D\u6E32\u67D3\uFF09\u3002\n *\n * \u672C\u6A21\u5757\u4F9B\u4E09\u5904\u590D\u7528\uFF1A\n * - seed \u6784\u5EFA\u5C42\uFF1A\u628A\u6587\u672C\u6BB5\u91CC\u7684\u88F8\u547D\u4EE4\u5F52\u4E00\u5316\u4E3A\u884C\u5185\u516C\u5F0F\u8282\u70B9\uFF08splitTextIntoLatex\uFF09\n * - \u7F16\u8F91\u5668\uFF1A\u6B63\u6587\u5B9E\u65F6\u7EA2\u7EBF\u6807\u6CE8\uFF08scanNakedLatex\uFF09+ \u4FDD\u5B58\u524D\u5BA1\u6838\uFF08auditQuestionContent\uFF09\n * - API\uFF1A\u4FDD\u5B58\u65F6\u5BA1\u6838\u5E76\u6301\u4E45\u5316\u9898\u76EE\u6807\u8BB0\uFF08auditQuestionContent\uFF09\n *\n * \u4FDD\u5B88\u7B56\u7565\uFF1A\u547D\u4EE4\u8D70**\u767D\u540D\u5355**\uFF0C\u5B81\u53EF\u6F0F\u68C0\u4E0D\u53EF\u8BEF\u4F24\uFF1B\u767D\u540D\u5355\u5916\u7684\u53CD\u659C\u6760\u4E00\u5F8B\u4E0D\u7BA1\u3002\n * \u8BEF\u62A5\u8FB9\u754C\uFF1A\n * - \u666E\u901A\u53CD\u659C\u6760\u4E0E\u8F6C\u4E49\u5B57\u9762\u91CF\uFF08\\$ \\% \\& \\# \\_\uFF09\u4E0D\u5728\u767D\u540D\u5355\uFF0C\u4E0D\u547D\u4E2D\uFF1B\n * - \\text{...} / \\ce{...} \u8FDE\u540C\u82B1\u62EC\u53F7\u6574\u4F53\u4F5C\u4E3A\u4E00\u4E2A\u7247\u6BB5\u6D88\u8D39\uFF0C\u4E0D\u9012\u5F52\u5207\u5185\u90E8\uFF1B\n * - \\left|\\right:\\big \u7B49\u5FC5\u987B\u540E\u968F\u5B9A\u754C\u7B26\u624D\u7B97\u547D\u4E2D\uFF1B\n * - formula \u8282\u70B9\u53EA\u8DD1 lintLatex\uFF08R1\u2013R4\uFF09\uFF0C\u4E0D\u8DD1\u88F8\u547D\u4EE4\u68C0\u67E5 \u2014\u2014 \u5408\u6CD5\u7684 `\\ldots` \u4E0D\u4F1A\u8BEF\u5224\u3002\n */\n\nimport { lintLatex } from './latex-lint.js';\n\nexport interface NakedLatexSpan {\n /** \u6587\u672C\u5185\u8D77\u59CB\u504F\u79FB */\n from: number;\n /** \u6587\u672C\u5185\u7ED3\u675F\u504F\u79FB\uFF08\u4E0D\u542B\uFF09 */\n to: number;\n snippet: string;\n kind: 'command' | 'delimiter';\n}\n\nexport type InlineSegment = { type: 'text'; text: string } | { type: 'formula'; latex: string };\n\nexport interface ContentIssue {\n /** \u4F4D\u7F6E\u8BED\u4E49\uFF1A\u9898\u5E72 / \u89E3\u6790 / \u9009\u9879A / \u53C2\u8003\u7B54\u6848 / \u586B\u7A7A1 */\n scope: string;\n /** \u7ED3\u6784\u5B9A\u4F4D\uFF1A\u5982 blocks[2].children[3] */\n path: string;\n kind: 'naked-latex-in-text' | 'delimiter-in-text' | 'rule-violation';\n /** \u4EC5 rule-violation \u643A\u5E26\uFF08R1\u2013R4\uFF09 */\n rule?: string;\n message: string;\n snippet: string;\n}\n\n/** \u6570\u5B66\u547D\u4EE4\u767D\u540D\u5355\uFF08\u786E\u5B9A\u5C5E\u4E8E LaTeX \u7684\u624D\u6536\u5F55\uFF09 */\nconst COMMANDS = [\n // \u7701\u7565\u53F7\u4E0E\u70B9\n 'ldots', 'cdots', 'dots', 'vdots', 'ddots',\n // \u5E0C\u814A\u5B57\u6BCD\n 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'varepsilon', 'zeta', 'eta', 'theta', 'vartheta',\n 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'varphi',\n 'chi', 'psi', 'omega', 'Gamma', 'Delta', 'Theta', 'Lambda', 'Xi', 'Pi', 'Sigma', 'Upsilon',\n 'Phi', 'Psi', 'Omega',\n // \u8FD0\u7B97\u7B26\u4E0E\u5173\u7CFB\n 'times', 'div', 'pm', 'mp', 'cdot', 'circ', 'bullet', 'le', 'leq', 'ge', 'geq', 'ne', 'neq',\n 'equiv', 'approx', 'sim', 'cong', 'propto', 'in', 'notin', 'subset', 'subseteq', 'supset',\n 'supseteq', 'cup', 'cap', 'emptyset', 'varnothing', 'forall', 'exists', 'nexists', 'neg',\n 'Rightarrow', 'Leftrightarrow', 'rightarrow', 'leftarrow', 'to', 'mapsto', 'angle', 'parallel',\n 'perp', 'triangle', 'because', 'therefore', 'setminus', 'mid', 'infty', 'partial', 'nabla',\n 'prime', 'ell', 'hbar',\n // \u7ED3\u6784\u4E0E\u51FD\u6570\n 'frac', 'dfrac', 'tfrac', 'sqrt', 'overline', 'underline', 'vec', 'widehat', 'widetilde',\n 'overrightarrow', 'overleftarrow', 'underbrace', 'overbrace', 'binom', 'begin', 'end',\n 'sin', 'cos', 'tan', 'cot', 'sec', 'csc', 'arcsin', 'arccos', 'arctan', 'sinh', 'cosh', 'tanh',\n 'log', 'lg', 'ln', 'exp', 'lim', 'sum', 'prod', 'int', 'oint', 'iint', 'iiint', 'max', 'min',\n 'gcd', 'det', 'dim', 'arg', 'deg', 'bmod', 'pmod', 'text', 'mathrm', 'mathbb', 'mathcal',\n 'mathbf', 'mathit', 'mathsf', 'mathtt', 'operatorname', 'ce', 'cee', 'pu', 'hspace', 'space',\n 'quad', 'qquad', 'underbrace',\n // \u89C4\u8303\u7981\u7528\u7684\u547D\u4EE4\uFF08\u51FA\u73B0\u5373\u9700\u63D0\u793A\uFF09\n 'displaystyle', 'limits', 'nolimits', 'def', 'newcommand', 'renewcommand', 'require',\n 'includegraphics', 'href', 'url',\n];\n\nconst CMD_ALT = COMMANDS.join('|');\n/** \u82B1\u62EC\u53F7\u7EC4\uFF1A\u652F\u6301\u4E00\u5C42\u5D4C\u5957\uFF08\\frac{1}{2} / \\ce{H2O}\uFF09 */\nconst GROUP = String.raw`\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}`;\n/** \\left| \\right) \\bigg[ \u7B49\uFF1A\u5FC5\u987B\u540E\u968F\u5B9A\u754C\u7B26 */\nconst DELIM_COMMAND = String.raw`\\\\(?:left|right|big|Big|bigg|Bigg)\\s*[()\\[\\]{}|./]`;\n\nconst COMMAND_SOURCE =\n String.raw`\\\\(?:` + CMD_ALT + String.raw`)\\b(?:` + GROUP + String.raw`){0,2}(?:[_^](?:` + GROUP + String.raw`|[A-Za-z0-9]))*` +\n '|' + DELIM_COMMAND;\n\n/** \u6210\u5BF9\u5B9A\u754C\u7B26\uFF1A$\u2026$ \u4E0E \\(\u2026\\)\uFF08\u524D\u7F6E\u53CD\u659C\u6760\u7684 \\$ \u89C6\u4E3A\u8F6C\u4E49\u5B57\u9762\u91CF\uFF0C\u4E0D\u547D\u4E2D\uFF09 */\nconst DELIMITER_SOURCE = String.raw`(?<!\\\\)\\$([^$\\n]{1,200})(?<!\\\\)\\$|(?<!\\\\)\\\\\\(([^()\\n]{1,200})\\\\\\)`;\n\n/**\n * \u626B\u63CF\u6587\u672C\u91CC\u6DF7\u5165\u7684\u88F8 LaTeX \u7247\u6BB5\uFF08\u547D\u4EE4\u4E0E\u5B9A\u754C\u7B26\uFF09\uFF0C\u6309\u4F4D\u7F6E\u5347\u5E8F\u3001\u53BB\u91CD\u53E0\u3002\n */\nexport function scanNakedLatex(text: string): NakedLatexSpan[] {\n if (!text) return [];\n const found: NakedLatexSpan[] = [];\n\n const commandRe = new RegExp(COMMAND_SOURCE, 'g');\n for (const match of text.matchAll(commandRe)) {\n if (match.index === undefined) continue;\n found.push({ from: match.index, to: match.index + match[0].length, snippet: match[0], kind: 'command' });\n }\n\n const delimiterRe = new RegExp(DELIMITER_SOURCE, 'g');\n for (const match of text.matchAll(delimiterRe)) {\n if (match.index === undefined) continue;\n // \u5B9A\u754C\u7B26\u5185\u5BB9\u542B\u4E2D\u65E5\u97E9\u5B57\u7B26\uFF08\u5982\u300C\u4EF7\u683C $5 \u4E0E $6\u300D\uFF09\u89C6\u4E3A\u666E\u901A\u6587\u672C\uFF0C\u4E0D\u4F5C\u4E3A\u516C\u5F0F\u5B9A\u754C\n if (/[\\u4e00-\\u9fff\\u3040-\\u30ff]/.test(match[0])) continue;\n found.push({ from: match.index, to: match.index + match[0].length, snippet: match[0], kind: 'delimiter' });\n }\n\n found.sort((a, b) => a.from - b.from || b.to - a.to);\n const spans: NakedLatexSpan[] = [];\n let cursor = -1;\n for (const span of found) {\n if (span.from < cursor) continue; // \u4E22\u5F03\u4E0E\u524D\u4E00\u4E2A\u91CD\u53E0\u7684\u547D\u4E2D\n spans.push(span);\n cursor = span.to;\n }\n return spans;\n}\n\n/** \u6587\u672C\u91CC\u662F\u5426\u6DF7\u5165\u4E86\u88F8 LaTeX\uFF08\u4F9B\u6279\u91CF\u5DE1\u68C0\u811A\u672C\u4E0E\u65AD\u8A00\u4F7F\u7528\uFF09 */\nexport function hasNakedLatex(text: string): boolean {\n return scanNakedLatex(text).length > 0;\n}\n\n/** \u5B9A\u754C\u7B26\u7247\u6BB5\u5265\u79BB\u5916\u5C42 `$` / `\\(`\uFF0C\u53D6\u51FA\u7EAF LaTeX */\nfunction unwrapDelimiter(snippet: string): string {\n const dollar = /^\\$([^$\\n]*)\\$$/.exec(snippet);\n if (dollar) return dollar[1]!;\n const paren = /^\\\\\\(([^()\\n]*)\\\\\\)$/.exec(snippet);\n if (paren) return paren[1]!;\n return snippet;\n}\n\n/**\n * \u628A\u7EAF\u6587\u672C\u5207\u6210 text / \u884C\u5185 formula \u7247\u6BB5\uFF08seed \u6784\u5EFA\u5C42\u5F52\u4E00\u5316\u7528\uFF09\u3002\n * \u8FDE\u7EED\u65E0\u95F4\u9694\u7684\u547D\u4E2D\u5408\u5E76\u4E3A\u540C\u4E00\u4E2A\u516C\u5F0F\u7247\u6BB5\u3002\n */\nexport function splitTextIntoLatex(text: string): InlineSegment[] {\n const spans = scanNakedLatex(text);\n if (!spans.length) return text ? [{ type: 'text', text }] : [];\n\n const segments: InlineSegment[] = [];\n const pushText = (value: string) => {\n if (value) segments.push({ type: 'text', text: value });\n };\n\n let cursor = 0;\n for (const span of spans) {\n pushText(text.slice(cursor, span.from));\n const latex = span.kind === 'delimiter' ? unwrapDelimiter(span.snippet) : span.snippet;\n const last = segments[segments.length - 1];\n // \u7D27\u90BB\u4E0A\u4E00\u4E2A\u516C\u5F0F\u7247\u6BB5\uFF08\u4E2D\u95F4\u6CA1\u6709\u6587\u672C\uFF09\u65F6\u5408\u5E76\uFF0C\u907F\u514D\u5207\u51FA\u788E\u7247\u516C\u5F0F\n if (last && last.type === 'formula' && cursor === span.from) {\n last.latex += latex;\n } else {\n segments.push({ type: 'formula', latex });\n }\n cursor = span.to;\n }\n pushText(text.slice(cursor));\n return segments;\n}\n\n// \u2500\u2500 \u5185\u5BB9\u6587\u6863\u5BA1\u6838 \u2500\u2500\n\nfunction lintFormula(latex: string, display: boolean, scope: string, path: string, out: ContentIssue[]): void {\n for (const issue of lintLatex(latex, { display })) {\n out.push({\n scope,\n path,\n kind: 'rule-violation',\n rule: issue.rule,\n message: issue.message,\n snippet: latex.slice(0, 60),\n });\n }\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;\n}\n\nfunction walkInline(node: unknown, scope: string, path: string, out: ContentIssue[]): void {\n const record = asRecord(node);\n if (!record) return;\n if (record['type'] === 'text') {\n const text = typeof record['text'] === 'string' ? record['text'] : '';\n for (const span of scanNakedLatex(text)) {\n out.push({\n scope,\n path,\n kind: span.kind === 'delimiter' ? 'delimiter-in-text' : 'naked-latex-in-text',\n message:\n span.kind === 'delimiter'\n ? `\u6B63\u6587\u91CC\u4E0D\u5E94\u5199\u5B9A\u754C\u7B26\u300C${span.snippet}\u300D\uFF1A\u516C\u5F0F\u8BF7\u7528\u300C\u63D2\u5165\u884C\u5185\u516C\u5F0F\u300D\u63D2\u5165`\n : `\u6B63\u6587\u91CC\u6DF7\u5165\u4E86\u672A\u6E32\u67D3\u7684 LaTeX \u547D\u4EE4\u300C${span.snippet}\u300D\uFF1A\u8BF7\u6539\u7528\u300C\u63D2\u5165\u884C\u5185\u516C\u5F0F\u300D`,\n snippet: span.snippet,\n });\n }\n return;\n }\n if (record['type'] === 'formula') {\n const latex = typeof record['latex'] === 'string' ? record['latex'] : '';\n lintFormula(latex, false, scope, path, out);\n }\n}\n\nfunction walkBlocks(blocks: unknown, scope: string, path: string, out: ContentIssue[]): void {\n if (!Array.isArray(blocks)) return;\n blocks.forEach((block, index) => {\n const record = asRecord(block);\n if (!record) return;\n const blockPath = `${path}[${index}]`;\n const type = record['type'];\n if (type === 'paragraph') {\n const children = record['children'];\n if (Array.isArray(children)) {\n children.forEach((child, i) => walkInline(child, scope, `${blockPath}.children[${i}]`, out));\n }\n return;\n }\n if (type === 'formula') {\n const latex = typeof record['latex'] === 'string' ? record['latex'] : '';\n lintFormula(latex, record['display'] === true, scope, blockPath, out);\n return;\n }\n if (type === 'table' && Array.isArray(record['rows'])) {\n record['rows'].forEach((row, r) => {\n const rowRecord = asRecord(row);\n if (!rowRecord || !Array.isArray(rowRecord['cells'])) return;\n rowRecord['cells'].forEach((cell, c) => {\n const cellRecord = asRecord(cell);\n if (cellRecord) walkBlocks(cellRecord['blocks'], scope, `${blockPath}.rows[${r}].cells[${c}].blocks`, out);\n });\n });\n return;\n }\n if (type === 'list' && Array.isArray(record['items'])) {\n record['items'].forEach((item, i) => {\n const itemRecord = asRecord(item);\n if (itemRecord) walkBlocks(itemRecord['blocks'], scope, `${blockPath}.items[${i}].blocks`, out);\n });\n }\n });\n}\n\n/** \u5BA1\u6838\u4E00\u4E2A\u5185\u5BB9\u6587\u6863\uFF08\u9898\u5E72/\u89E3\u6790/\u53C2\u8003\u7B54\u6848\u7B49\u540C\u4E00\u5F62\u72B6\uFF09 */\nexport function auditContentDocument(doc: unknown, scope = '\u5185\u5BB9'): ContentIssue[] {\n const out: ContentIssue[] = [];\n walkBlocks(asRecord(doc)?.['blocks'], scope, 'blocks', out);\n return out;\n}\n\n/**\n * \u5BA1\u6838\u4E00\u9053\u9898\u7684\u5B8C\u6574\u5185\u5BB9\uFF1A\u9898\u5E72 + \u89E3\u6790 + \u7B54\u6848\uFF08\u6309 kind \u5206\u652F\u8986\u76D6\u9009\u9879/\u586B\u7A7A/\u53C2\u8003\u89E3\u7B54\uFF09\u3002\n * \u975E\u963B\u65AD\u7528\u9014 \u2014\u2014 \u8C03\u7528\u65B9\u51B3\u5B9A\u63D0\u793A\u3001\u6807\u8BB0\u6216\u62E6\u622A\u3002\n */\nexport function auditQuestionContent(input: {\n content?: unknown;\n analysis?: unknown;\n answer?: unknown;\n}): ContentIssue[] {\n const out: ContentIssue[] = [];\n if (input.content) out.push(...auditContentDocument(input.content, '\u9898\u5E72'));\n if (input.analysis) out.push(...auditContentDocument(input.analysis, '\u89E3\u6790'));\n\n const answer = asRecord(input.answer);\n if (!answer) return out;\n const kind = answer['kind'];\n\n if (kind === 'choice' && Array.isArray(answer['options'])) {\n answer['options'].forEach((option, i) => {\n const optionRecord = asRecord(option);\n if (!optionRecord) return;\n const key = typeof optionRecord['key'] === 'string' ? optionRecord['key'] : String(i + 1);\n const scope = `\u9009\u9879${key}`;\n const content = asRecord(optionRecord['content']);\n if (!content) return;\n if (content['kind'] === 'latex' && typeof content['latex'] === 'string') {\n lintFormula(content['latex'], false, scope, `answer.options[${i}].content`, out);\n } else if (content['kind'] === 'blocks') {\n walkBlocks(content['blocks'], scope, `answer.options[${i}].content.blocks`, out);\n }\n });\n return out;\n }\n\n if (kind === 'fill' && Array.isArray(answer['blanks'])) {\n answer['blanks'].forEach((blank, i) => {\n const accept = asRecord(blank)?.['accept'];\n if (!Array.isArray(accept)) return;\n accept.forEach((value, j) => {\n if (typeof value !== 'string') return;\n lintFormula(value, false, `\u586B\u7A7A${i + 1}`, `answer.blanks[${i}].accept[${j}]`, out);\n });\n });\n return out;\n }\n\n if (kind === 'solution' && answer['reference']) {\n out.push(...auditContentDocument(answer['reference'], '\u53C2\u8003\u7B54\u6848'));\n }\n return out;\n}\n", "/**\n * \u5C0F\u7A0B\u5E8F\u9884\u6E32\u67D3\u5165\u53E3\uFF08plan-M0 \u9636\u6BB5 I \u7B2C 27 \u6761\uFF09\uFF1A\n * renderToMpNodes(latex) \u2192 \u5FAE\u4FE1 rich-text nodes\u3002\n *\n * \u4E3A\u4EC0\u4E48\u5316\u5B66\u516C\u5F0F\u4E0D\u8D70 MathJax\uFF1A\u5B98\u65B9 rich-text \u53D7\u4FE1\u4EFB\u8282\u70B9\u5217\u8868\u53EA\u542B HTML \u6807\u7B7E\uFF0C\n * \u4E0D\u542B svg/path\uFF0CMathJax \u7684 SVG \u8F93\u51FA\u4F1A\u88AB\u6574\u68F5\u79FB\u9664\u3002\u56E0\u6B64\u7EDF\u4E00\u8D70\n * KaTeX + \u5B98\u65B9 mhchem \u6269\u5C55\uFF08\u8F93\u51FA HTML\uFF09\uFF0C\u6570\u5B66/\u5316\u5B66\u540C\u4E00\u6761\u94FE\u8DEF\u3002\n *\n * \u65E0\u72B6\u6001\u3001\u53EF\u88AB API \u7AEF\uFF08BullMQ \u9884\u6E32\u67D3\u4EFB\u52A1\uFF09\u76F4\u63A5\u8C03\u7528\uFF1B\n * \u4EFB\u4F55\u5931\u8D25\u663E\u5F0F\u629B MpRenderError\uFF0C\u7531\u8C03\u7528\u65B9\u963B\u65AD rendered_mp \u5165\u5E93\u3002\n */\nimport katex from 'katex';\nimport 'katex/contrib/mhchem';\nimport { katexHtmlToNodes, MpRenderError } from './mp-html.js';\nimport type { MpElementNode, MpTextNode, MpTreeNode } from './mp-html.js';\n\nexport { MpRenderError, katexHtmlToNodes };\nexport type { MpElementNode, MpTextNode, MpTreeNode };\n\nconst KATEX_OPTIONS = {\n throwOnError: true,\n strict: 'ignore',\n trust: false,\n output: 'html',\n} as const;\n\n/**\n * KaTeX \u57FA\u51C6 HTML \u6E32\u67D3\uFF08\u4E0E renderToMpNodes \u540C\u53C2\u540C\u94FE\u8DEF\uFF09\uFF0C\n * \u4F9B Web \u5FEB\u7167\u9875\u4F5C\u4E3A\u53CC\u7AEF\u4E00\u81F4\u6027\u5BF9\u6BD4\u7684\u57FA\u51C6\u6E32\u67D3\u3002\n * @throws \u89E3\u6790\u5931\u8D25\u76F4\u63A5\u629B\u51FA\uFF08KaTeX ParseError\uFF09\n */\nexport function renderKatexHtml(latex: string, displayMode = false): string {\n return katex.renderToString(latex, { ...KATEX_OPTIONS, displayMode });\n}\n\n/**\n * \u6E32\u67D3 LaTeX\uFF08\u6570\u5B66/\u5316\u5B66\uFF09\u5E76\u8F6C\u6362\u4E3A rich-text nodes\u3002\n * @param displayMode \u72EC\u7ACB\u516C\u5F0F\u5757 true\uFF1B\u884C\u5185 false\n * @throws MpRenderError KaTeX \u6E32\u67D3\u5931\u8D25\u6216 HTML\u2192nodes \u8F6C\u6362\u5931\u8D25\n */\nexport function renderToMpNodes(latex: string, displayMode = false): MpTreeNode[] {\n let html: string;\n try {\n html = katex.renderToString(latex, { ...KATEX_OPTIONS, displayMode });\n } catch (error) {\n throw new MpRenderError(latex, 'KaTeX \u6E32\u67D3\u5931\u8D25', error);\n }\n try {\n return katexHtmlToNodes(html);\n } catch (error) {\n throw new MpRenderError(latex, 'HTML\u2192nodes \u8F6C\u6362\u5931\u8D25', error);\n }\n}\n", "/**\n * KaTeX HTML \u2192 \u5FAE\u4FE1 rich-text nodes \u8F6C\u6362\uFF08plan-M0 \u9636\u6BB5 I \u7B2C 27 \u6761\uFF09\u3002\n *\n * \u4E3A\u4EC0\u4E48\u624B\u5199\u8F7B\u91CF\u89E3\u6790\u5668\u800C\u4E0D\u5F15\u5165 DOM \u89E3\u6790\u5E93\uFF1A\n * - KaTeX renderToString(output: 'html') \u7684\u4EA7\u7269\u662F\u826F\u6784 HTML\uFF1A\u6807\u7B7E\u5FC5\u7136\u914D\u5BF9\u3001\n * \u5C5E\u6027\u6052\u4E3A\u53CC\u5F15\u53F7\u3001\u65E0\u6CE8\u91CA\u3001\u65E0\u81EA\u95ED\u5408\u3001\u65E0 CDATA\uFF0Ctokenizer \u5373\u53EF\u5B89\u5168\u89E3\u6790\uFF0C\n * Node\uFF08\u670D\u52A1\u7AEF\u9884\u6E32\u67D3\uFF09\u4E0E\u6D4F\u89C8\u5668\uFF08POC \u5DE5\u5177\u9875\uFF09\u96F6\u4F9D\u8D56\u8FD0\u884C\u3002\n * - \u5FAE\u4FE1 rich-text \u4EC5\u63A5\u53D7\"\u53D7\u4FE1\u4EFB\u7684\u8282\u70B9\u4E0E\u5C5E\u6027\"\uFF08\u5B98\u65B9\u6587\u6863\uFF09\uFF0C\u6B64\u5C42\u540C\u65F6\u5B8C\u6210\u5C5E\u6027\u6536\u655B\uFF1A\n * \u53EA\u4FDD\u7559\u5168\u5C40\u53D7\u4FE1\u4EFB\u7684 class \u4E0E style\uFF0C\u5176\u4F59\uFF08aria-hidden \u7B49\uFF09\u4E00\u5F8B\u4E22\u5F03\u3002\n *\n * \u5931\u8D25\u8BED\u4E49\uFF1A\u6807\u7B7E\u4E0D\u914D\u5BF9\u7B49\u5F02\u5E38\u663E\u5F0F\u629B MpRenderError\uFF0C\u7EDD\u4E0D\u9759\u9ED8\u964D\u7EA7\u2014\u2014\n * nodes \u4EA7\u7269\u5C06\u4F5C\u4E3A rendered_mp \u7F13\u5B58\u5165\u5E93\uFF08PRD \u00A79.2\uFF09\uFF0C\u9519\u8BEF\u5FC5\u987B\u5728\u751F\u6210\u671F\u66B4\u9732\u3002\n */\n\nexport interface MpElementNode {\n name: string;\n /** class/style \u5168\u5C40\u53D7\u4FE1\u4EFB\uFF1Bimg \u8282\u70B9\u989D\u5916\u900F\u4F20 src\uFF08rich-text \u53D7\u4FE1\u4EFB\u5C5E\u6027\uFF09 */\n attrs?: { class?: string; style?: string; src?: string };\n children?: MpTreeNode[];\n}\n\nexport interface MpTextNode {\n type: 'text';\n text: string;\n}\n\nexport type MpTreeNode = MpElementNode | MpTextNode;\n\n/** \u8F6C\u6362/\u6E32\u67D3\u5931\u8D25\uFF1A\u663E\u5F0F\u62A5\u9519\uFF0C\u8C03\u7528\u65B9\u5FC5\u987B\u6355\u83B7\u5E76\u963B\u65AD\u5165\u5E93 */\nexport class MpRenderError extends Error {\n readonly latex: string;\n readonly stage: string;\n\n constructor(latex: string, stage: string, cause?: unknown) {\n const detail = cause instanceof Error ? cause.message : String(cause ?? '\u672A\u77E5\u9519\u8BEF');\n super(`[POC-B:${stage}] ${latex.slice(0, 80)} \u2192 ${detail}`);\n this.name = 'MpRenderError';\n this.latex = latex;\n this.stage = stage;\n }\n}\n\n/** rich-text \u5168\u5C40\u53D7\u4FE1\u4EFB\u5C5E\u6027\uFF08\u5B98\u65B9\u6587\u6863\uFF09\uFF1BKaTeX HTML \u4E2D\u5176\u4F59\u5C5E\u6027\u4E00\u5F8B\u4E22\u5F03 */\nconst MP_ALLOWED_ATTRS = new Set(['class', 'style']);\n\n/** \u81EA\u95ED\u5408/\u7A7A\u5143\u7D20\uFF1A\u4E0D\u5165\u6808\uFF08KaTeX \u8F93\u51FA\u4E0D\u542B\uFF0C\u4FDD\u9669\u5904\u7406\uFF09 */\nconst VOID_TAGS = new Set(['br', 'hr', 'img']);\n\n/** \u8DE8\u73AF\u5883 base64 \u7F16\u7801\uFF08Node \u6784\u5EFA\u671F + \u6D4F\u89C8\u5668 POC \u5DE5\u5177\u9875\u53CC\u7AEF\u590D\u7528\uFF09 */\nfunction svgToBase64(svg: string): string {\n const g = globalThis as { Buffer?: { from(s: string, enc: string): { toString(enc: string): string } }; btoa?: (s: string) => string };\n if (g.Buffer) return g.Buffer.from(svg, 'utf8').toString('base64');\n const bytes = new TextEncoder().encode(svg);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n return (g.btoa ?? btoa)(binary);\n}\n\nconst TAG_RE = /<(\\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:\\s+[-\\w]+(?:\\s*=\\s*\"[^\"]*\")?)*\\s*)>/g;\n\nconst ATTR_RE = /([-\\w]+)(?:\\s*=\\s*\"([^\"]*)\")?/g;\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: '\u00A0',\n};\n\nfunction decodeEntities(text: string): string {\n if (!text.includes('&')) return text;\n return text.replace(/&(#[xX]?[0-9a-fA-F]+|[a-zA-Z]+);/g, (raw, body: string) => {\n if (body.startsWith('#')) {\n const isHex = body[1] === 'x' || body[1] === 'X';\n const code = Number.parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);\n return Number.isFinite(code) && code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : raw;\n }\n return NAMED_ENTITIES[body] ?? raw;\n });\n}\n\nfunction parseAttrs(attrText: string): { class?: string; style?: string } | undefined {\n let classValue: string | undefined;\n let styleValue: string | undefined;\n ATTR_RE.lastIndex = 0;\n for (let m = ATTR_RE.exec(attrText); m !== null; m = ATTR_RE.exec(attrText)) {\n const name = (m[1] ?? '').toLowerCase();\n if (!MP_ALLOWED_ATTRS.has(name)) continue;\n const value = decodeEntities(m[2] ?? '');\n if (name === 'class') classValue = value;\n else styleValue = value;\n }\n if (classValue === undefined && styleValue === undefined) return undefined;\n return { ...(classValue !== undefined && { class: classValue }), ...(styleValue !== undefined && { style: styleValue }) };\n}\n\nfunction appendText(stack: MpElementNode[], text: string): void {\n // \u7EAF\u7A7A\u767D\u8282\u70B9\u4E22\u5F03\uFF1AKaTeX \u7684\u95F4\u8DDD\u5168\u90E8\u7531 mspace/strut \u7B49\u53D7\u63A7 span \u5B9E\u73B0\uFF0C\n // \u7F29\u8FDB/\u6362\u884C\u7A7A\u767D\u8FDB\u5165 rich-text \u53CD\u800C\u5F15\u5165\u4E0D\u53EF\u63A7\u5360\u4F4D\u3002\n if (text.trim().length === 0) return;\n const top = stack[stack.length - 1];\n if (!top) throw new MpRenderError('', '\u7ED3\u6784\u5F02\u5E38\uFF1A\u7A7A\u6808');\n (top.children ??= []).push({ type: 'text', text: decodeEntities(text) });\n}\n\n/**\n * \u771F\u673A rich-text \u9009\u62E9\u5668\u517C\u5BB9\u6CE8\u5165\uFF08POC-B \u771F\u673A\u5B9E\u6D4B\u7ED3\u8BBA\uFF09\uFF1A\n * rich-text \u5185\u90E8\u4EC5\"\u9009\u62E9\u5668\u6700\u540E\u4E00\u6BB5\u4EE5\u76EE\u6807\u81EA\u8EAB\u7C7B\u540D\u7ED3\u5C3E\"\u7684\u89C4\u5219\u53EF\u9760\u547D\u4E2D\uFF1B\n * \u6807\u7B7E\u540D\u7ED3\u5C3E\u7684\u540E\u4EE3/\u5B50\u9009\u62E9\u5668\uFF08\u5982 `.vlist>span`\uFF09\u4E00\u5F8B\u5931\u6548\uFF0C\u4E14 `>` \u5B50\u7EC4\u5408\u5668\n * \u4E0D\u88AB\u652F\u6301\u3002KaTeX \u7684 vlist \u884C\u5BB9\u5668\u7B49\u4E2D\u95F4\u5C42 span \u6CA1\u6709\u7C7B\u540D\uFF0C\u5FC5\u987B\u5728\u8F6C\u6362\u671F\u6309\n * \u56FA\u5B9A DOM \u6A21\u5F0F\u6CE8\u5165\u7ED3\u6784\u7C7B\uFF0C\u914D\u5957\u7684\u6539\u5199\u540E\u89C4\u5219\u89C1 generate-mp-poc.mjs \u7684\n * MP_RULE_REWRITES\u3002\u4EC5\u4F9D\u8D56\u76F4\u63A5\u7236/\u7956\u7236\u7C7B\uFF0CKaTeX DOM \u7ED3\u6784\u5728 0.16 \u7248\u672C\u7A33\u5B9A\u3002\n */\nfunction classListOf(node: MpElementNode): string[] {\n return node.attrs?.class?.split(/\\s+/).filter(Boolean) ?? [];\n}\n\nfunction addClass(node: MpElementNode, name: string): void {\n const list = classListOf(node);\n if (list.includes(name)) return;\n list.push(name);\n node.attrs = {\n ...node.attrs,\n class: list.join(' '),\n ...(node.attrs?.style !== undefined && { style: node.attrs.style }),\n };\n}\n\ninterface MpInheritCtx {\n /** \u7956\u5148 .katex-display \u5E26 leqno\uFF08\u516C\u5F0F\u7F16\u53F7\u5728\u5DE6\uFF09 */\n leqno: boolean;\n /** \u7956\u5148 .katex-display \u5E26 fleqn\uFF08\u516C\u5F0F\u5DE6\u5BF9\u9F50\uFF09 */\n fleqn: boolean;\n}\n\nfunction injectStructureClasses(\n node: MpElementNode,\n parent: MpElementNode | null,\n grand: MpElementNode | null,\n ctx: MpInheritCtx,\n): void {\n const self = classListOf(node);\n const pCls = parent && parent.name !== '#root' ? classListOf(parent) : [];\n const gCls = grand && grand.name !== '#root' ? classListOf(grand) : [];\n const has = (c: string): boolean => self.includes(c);\n const pHas = (c: string): boolean => pCls.includes(c);\n const gHas = (c: string): boolean => gCls.includes(c);\n\n // .vlist>span\uFF08\u7CBE\u786E\u5339\u914D vlist\uFF0C\u6392\u9664 vlist-t/vlist-r/vlist-s\uFF09\n if (pHas('vlist')) addClass(node, 'mp-vrow');\n // .vlist>span>span\n if (pHas('mp-vrow')) addClass(node, 'mp-vcell');\n // .mfrac>span>span\uFF1Amfrac\u2192vlist-t\u2192vlist-r\uFF08\u542B vlist-t2 \u955C\u50CF\u884C\uFF09\n if (gHas('mfrac')) addClass(node, 'mp-mfrac-r');\n // .llap/.clap/.rlap > .inner \u7684\u65B9\u4F4D\u533A\u5206\uFF08position:absolute \u7EDF\u4E00\u7531 .inner \u89C4\u5219\u627F\u8F7D\uFF09\n if (has('inner') && pHas('llap')) addClass(node, 'mp-inner-r');\n if (has('inner') && (pHas('clap') || pHas('rlap'))) addClass(node, 'mp-inner-l');\n // .clap>.inner>span\n if (pHas('inner') && gHas('clap')) addClass(node, 'mp-clap-s');\n // .delimsizing.mult .delim-size1/4 > span\n if (pHas('delim-size1')) addClass(node, 'mp-ds1');\n if (pHas('delim-size4')) addClass(node, 'mp-ds4');\n // .accent .accent-body\uFF1A\u4E09\u6BB5\u540E\u4EE3\u9009\u62E9\u5668\u5728 rich-text \u4E0D\u547D\u4E2D\uFF08\u94C1\u5F8B\u4EC5\u9A8C\u8BC1\u4E24\u6BB5\uFF09\uFF0C\n // \u6CE8\u5165\u7ED3\u6784\u7C7B\u8D70\u5355\u7C7B\u89C4\u5219\uFF1Baccent-full\uFF08\\widehat/\\widetilde\uFF09\u53E6\u8D70 mp-accent-full \u4FDD\u7559\u539F\u5BBD\n if (has('accent-body')) {\n addClass(node, has('accent-full') ? 'mp-accent-full' : 'mp-accent-body');\n }\n // .accent/.op-limits/.col-align-* > .vlist-t\n if (has('vlist-t') && (pHas('accent') || pHas('op-limits'))) addClass(node, 'mp-vac');\n if (has('vlist-t') && pHas('col-align-c')) addClass(node, 'mp-cac');\n if (has('vlist-t') && pHas('col-align-l')) addClass(node, 'mp-cal');\n if (has('vlist-t') && pHas('col-align-r')) addClass(node, 'mp-car');\n // \u5757\u7EA7\u516C\u5F0F\uFF1A.katex-display>.katex \u53CA\u5176 .katex-html\n if (has('katex') && pHas('katex-display')) addClass(node, 'mp-kd');\n if (has('katex-html') && pHas('mp-kd')) addClass(node, 'mp-kdh');\n if (ctx.leqno && has('tag')) addClass(node, 'mp-tagl');\n if (has('katex') && pHas('katex-display') && ctx.fleqn) addClass(node, 'mp-kfl');\n\n const nextCtx: MpInheritCtx = {\n leqno: ctx.leqno || (has('katex-display') && has('leqno')),\n fleqn: ctx.fleqn || (has('katex-display') && has('fleqn')),\n };\n for (const child of node.children ?? []) {\n if ('type' in child) continue;\n injectStructureClasses(child, node, parent, nextCtx);\n }\n}\n\n/**\n * \u5C06 KaTeX HTML \u7247\u6BB5\u8F6C\u6362\u4E3A\u5FAE\u4FE1 rich-text nodes \u6811\u3002\n * \u8F93\u5165\u5E94\u4E3A renderToMpNodes \u7684 KaTeX \u4EA7\u7269\uFF08\u826F\u6784 HTML\uFF09\u3002\n *\n * svg \u5904\u7406\uFF08POC-B \u771F\u673A\u5B9E\u6D4B\u7ED3\u8BBA\uFF09\uFF1Arich-text \u53D7\u4FE1\u4EFB\u8282\u70B9\u4E0D\u542B svg\uFF0CKaTeX \u7684\n * sqrt/\u62C9\u4F38\u7BAD\u5934/vec/\u8D85\u9AD8\u5B9A\u754C\u7B26\u5168\u90E8\u7528 svg path \u6E32\u67D3\uFF0C\u771F\u673A\u88AB\u6574\u68F5\u79FB\u9664\u3002\u6B64\u5904\n * \u5728\u89E3\u6790\u4E3B\u6D41\u6C34\u7EBF\u5185\u8054\u66FF\u6362\u4E3A\u7B49\u4E49\u5B57\u7B26\uFF08SVG_PATH_SPECS\uFF09\uFF0Csvg \u8282\u70B9\u4E0D\u5165\u6811\u3002\n * \u81EA\u95ED\u5408 <path .../> \u4E0D\u5339\u914D TAG_RE\uFF0C\u6574\u6BB5\u843D\u5728 svg \u5F00\u95ED\u6807\u7B7E\u4E4B\u95F4\u7684\u6587\u672C\u91CC\u5904\u7406\u3002\n * @throws MpRenderError \u6807\u7B7E\u4E0D\u914D\u5BF9\u6216\u8868\u5916 svg path \u65F6\n */\nexport function katexHtmlToNodes(html: string): MpTreeNode[] {\n const root: MpElementNode = { name: '#root', children: [] };\n const stack: MpElementNode[] = [root];\n /** \u975E null \u8868\u793A\u5F53\u524D\u5904\u4E8E <svg> \u5185\uFF08\u8BB0\u5F55 svg \u76D2\u5C3A\u5BF8+viewBox \u4F9B\u5B57\u7B26\u66FF\u6362/img \u91CD\u5EFA\u8BA1\u7B97\uFF09 */\n let svgBox: { width: number; height: number; viewBox: string; preserveAspectRatio: string } | null = null;\n let last = 0;\n TAG_RE.lastIndex = 0;\n for (let m = TAG_RE.exec(html); m !== null; m = TAG_RE.exec(html)) {\n const textChunk = html.slice(last, m.index);\n if (svgBox) emitSvgPaths(stack, textChunk, svgBox, html);\n else appendText(stack, textChunk);\n const [, close, rawName, attrText] = m;\n const name = (rawName ?? '').toLowerCase();\n if (svgBox) {\n // svg \u5185\u90E8\uFF1A\u4EC5\u6D88\u8D39 </svg> \u95ED\u5408\uFF1B\u5176\u4F59\u6807\u7B7E\uFF08path \u7B49\uFF09\u5DF2\u88AB emitSvgPaths \u5904\u7406\n if (close && name === 'svg') svgBox = null;\n } else if (!close && name === 'svg') {\n svgBox = parseSvgBox(attrText ?? '', html);\n } else if (close) {\n const top = stack[stack.length - 1];\n if (!top || top.name !== name) {\n throw new MpRenderError(html, `\u6807\u7B7E\u4E0D\u914D\u5BF9\uFF1A\u671F\u671B </${top?.name ?? '#root'}> \u5B9E\u9645 </${name}>`);\n }\n stack.pop();\n } else {\n const node: MpElementNode = { name };\n const attrs = parseAttrs(attrText ?? '');\n if (attrs) node.attrs = attrs;\n const top = stack[stack.length - 1];\n if (!top) throw new MpRenderError(html, '\u7ED3\u6784\u5F02\u5E38\uFF1A\u7A7A\u6808');\n (top.children ??= []).push(node);\n if (!VOID_TAGS.has(name)) stack.push(node);\n }\n last = TAG_RE.lastIndex;\n }\n const tail = html.slice(last);\n if (svgBox) emitSvgPaths(stack, tail, svgBox, html);\n else appendText(stack, tail);\n if (svgBox) throw new MpRenderError(html, '\u5B58\u5728\u672A\u95ED\u5408\u6807\u7B7E <svg>');\n if (stack.length > 1) {\n const unclosed = stack[stack.length - 1]?.name ?? '#root';\n throw new MpRenderError(html, `\u5B58\u5728\u672A\u95ED\u5408\u6807\u7B7E <${unclosed}>`);\n }\n for (const child of root.children ?? []) {\n if (!('type' in child)) injectStructureClasses(child, root, null, { leqno: false, fleqn: false });\n }\n return root.children ?? [];\n}\n\n/**\n * svg path \u2192 \u5B57\u7B26\u66FF\u6362\u89C4\u683C\u8868\u3002\n *\n * \u6570\u636E\u6765\u6E90\uFF08\u6784\u5EFA\u671F\u811A\u672C\u5B9E\u8BC1\uFF0C\u52FF\u51ED\u8BB0\u5FC6\u4FEE\u6539\uFF09\uFF1A\n * - path d \u503C\uFF1Aanalyze \u811A\u672C\u5BF9 105 \u6837\u672C\u5168\u91CF\u63D0\u53D6\uFF08\u4E0E KaTeX src/svgGeometry.ts \u6838\u5BF9\u547D\u540D\uFF09\uFF1B\n * - \u5B57\u5F62\u5EA6\u91CF height/depth/width\uFF1AKaTeX src/fontMetricsData.js\uFF08font-size:1em \u57FA\u51C6\uFF09\u3002\n *\n * \u5339\u914D\u7528\"\u53BB\u7A7A\u767D\u524D\u7F00 + \u53EF\u9009\u6807\u8BB0\u5B50\u4E32\"\uFF1A\u8D85\u9AD8\u5B9A\u754C\u7B26 path \u4E2D\u6BB5\u542B\u9AD8\u5EA6\u63D2\u503C\n * \uFF08vert \u7684 v${midHeight}\u3001paren \u7684 l0,${midHeight}\uFF09\uFF0C\u524D\u7F00\u5339\u914D\u4F7F\u4EFB\u610F\u9AD8\u5EA6\u901A\u7528\u3002\n * \u8868\u5916 path \u663E\u5F0F\u629B MpRenderError\uFF08\u963B\u65AD rendered_mp \u5165\u5E93\uFF09\uFF0C\u7EDD\u4E0D\u9759\u9ED8\u4E22\u56FE\u5F62\u3002\n */\ninterface SvgPathSpec {\n /** path d \u53BB\u7A7A\u767D\u540E\u7684\u524D\u7F00 */\n prefix: string;\n /** \u989D\u5916\u5FC5\u987B\u5305\u542B\u7684\u5B50\u4E32\uFF08\u533A\u5206\u540C\u524D\u7F00\u7684 vert/doublevert \u7B49\uFF09 */\n marker?: string;\n kind: 'drop' | 'char';\n /** \u66FF\u6362\u5B57\u7B26\uFF08kind='char' \u65F6\u5FC5\u586B\uFF09 */\n char?: string;\n /** \u5B57\u4F53\u7C7B\uFF08rich-text \u5185\u8054 font-family \u4E0D\u53EF\u9760\uFF0C\u5B57\u4F53\u9009\u62E9\u8D70 WXSS \u7C7B\uFF09 */\n fontClass?: 'mp-svg-main' | 'mp-svg-size1' | 'mp-svg-size2' | 'mp-svg-size3' | 'mp-svg-size4';\n /**\n * fill\uFF1A\u5B57\u7B26 ink \u586B\u6EE1 svg \u76D2\u2014\u2014F=H/(glyphHeight+glyphDepth)\u3001va=glyphDepth\u00B7F\uFF1B\n * \u5E26 rule \u7684\u6839\u53F7\u6539\u7528 F=(H-0.08)/(gh+gd)\uFF08\u76D2\u9876\u542B hLinePad\uFF0Cink \u533A\u95F4 [0.08,H]\uFF09\u3002\n * \uFF08sqrt/\u5B9A\u754C\u7B26/vec\uFF1Asvg ink \u5145\u6EE1\u76D2\u5B50\uFF09\u3002\n * natural\uFF1A\u5B57\u5F62\u539F\u5C3A\u5BF8\uFF08\u62C9\u4F38\u7BAD\u5934\uFF1Asvg \u76D2 0.522em \u5185 ink \u4EC5 0.234em \u4E14\u81EA\u7136\n * \u5B57\u5F62\u7684\u8F74\u7EBF\u4F4D\u7F6E\u4E0E\u539F\u59CB\u6E32\u67D3\u4EC5\u5DEE 0.01em\uFF0C\u586B\u76D2\u516C\u5F0F\u53CD\u800C\u5931\u771F\uFF09\u3002\n */\n mode?: 'fill' | 'natural';\n glyphHeight?: number;\n glyphDepth?: number;\n glyphWidth?: number;\n /** \u5B9A\u5BBD svg\uFF08\u5B9A\u754C\u7B26/vec\uFF09\uFF1A\u8D1F margin \u628A\u5B57\u5F62 advance \u538B\u56DE svg \u5BBD\u5EA6\uFF0C\u4FDD\u6301 vlist \u603B\u5BBD\u4E0D\u53D8 */\n fixedWidth?: boolean;\n /** \u7ED9 svg \u7236\u5BB9\u5668\u8FFD\u52A0\u7684 class\uFF08\u7BAD\u5934\u5C45\u4E2D\u3001\u534A\u5E45\u7BAD\u5934\u6539\u5168\u5E45\u7B49\uFF09 */\n parentClass?: string;\n /**\n * \u6839\u53F7\u4E0A\u6A2A\u7EBF\uFF1Ahide-tail\uFF08position:relative;overflow:hidden\uFF09\u5185\u7684\u7EDD\u5BF9\u5B9A\u4F4D\u8272\u6761\u3002\n * leftEm \u4E3A\u5B57\u5F62\u8F6E\u5ED3\u5185\u6A2A\u7EBF\u8D77\u70B9\u7684 em \u503C\uFF08\u5B57\u4F53\u4E0E svg \u540C\u4E3A 1000upm\uFF0C\u6E32\u67D3\u65F6\u4E58 F\uFF09\uFF1B\n * top/thickness \u56FA\u5B9A 0.08em/0.04em\uFF08hLinePad=80\u3001sqrtRuleThickness=40\uFF0C\n * \u5404\u6863 svg scale\u22480.00095-0.001\uFF0C\u8BEF\u5DEE\u22640.002em \u4E0D\u53EF\u89C1\uFF09\u3002\n */\n rule?: { leftEm: number };\n /**\n * 'char'\uFF08\u9ED8\u8BA4\uFF0C\u8D70 fontSize/strut/rule \u66FF\u6362\u5B57\u7B26\uFF09| 'img'\uFF08\u91CD\u5EFA SVG \u4E3A data URL\n * `<img>`\uFF0C\u4FDD\u7559 KaTeX \u539F\u59CB\u51E0\u4F55\uFF09\u3002\n * \u5B57\u7B26\u66FF\u6362\u65B9\u6848\u5728 sqrt/vec/\u21CC \u4E0A\u53CD\u590D\u51FA\u73B0 ink \u504F\u79FB\uFF08vertical-align \u516C\u5F0F\u5931\u771F\u3001\n * accent-body left \u504F\u79FB\uFF09\uFF0C\u6539\u7528 img \u76F4\u63A5\u6E32\u67D3 KaTeX SVG path \u4E00\u52B3\u6C38\u9038\uFF1B\n * \u5176\u4F59 spec\uFF08\u2192/drop/vert/paren\uFF09\u4FDD\u7559\u5B57\u7B26\u66FF\u6362\u4E0D\u53D8\u3002\n * img \u5206\u652F\u4E0D\u8BFB mode/glyphHeight/Depth/Width/fixedWidth/rule \u5B57\u6BB5\uFF0C\n * \u4F46 prefix/marker/parentClass \u4ECD\u751F\u6548\u3002\n */\n output?: 'char' | 'img';\n}\n\nconst SVG_PATH_SPECS: SvgPathSpec[] = [\n // \u5806\u53E0\u82B1\u62EC\u53F7\u7684\u63A5\u7F1D\u906E\u76D6\u53D1\u4E1D\u7EBF\uFF080.8889\u00D70.016em\uFF09\uFF1A\u5B57\u5F62\u672C\u8EAB\u8FDE\u7EED\uFF0C\u906E\u76D6\u6761\u7EAF\u6297\u63A5\u7F1D\uFF0C\u4E22\u5F03\n { prefix: 'M3840H504V16H384z', kind: 'drop' },\n // \u21CC \u53F3\u534A\uFF08righthalfarrow\uFF09\uFF1AxMaxYMin slice \u951A\u53F3\uFF0Cimg width:100% \u586B\u6EE1 .halfarrow-right\n // \uFF08position:absolute;right:0;width:50.2%;overflow:hidden\uFF09\u2192 SVG \u88C1\u526A\u663E\u793A\u53F3\u7BAD\u5934\n { prefix: 'M0241v40h399993', kind: 'char', output: 'img' },\n // \u2192 \u62C9\u4F38\u7BAD\u5934\uFF08mhchem -> / \\xrightarrow\uFF09\uFF1A\u539F svg \u968F\u6807\u7B7E\u5BBD\u5EA6\u62C9\u4F38\uFF0C\u5B57\u7B26\u7248\u5B9A\u5BBD\u5C45\u4E2D\n // \uFF08\u504F\u5DEE\u8BB0\u5F55\u8FDB POC \u62A5\u544A\uFF09\uFF1B\u7236\u5BB9\u5668 hide-tail \u5DF2\u6709 position:relative\uFF0C\u8865 text-align:center\n { prefix: 'M0241v40h399891', kind: 'char', char: '\u2192', fontClass: 'mp-svg-main', mode: 'natural', parentClass: 'mp-xarrow' },\n // \u21CC\uFF08mhchem <=> / \\rightleftharpoons\uFF09\uFF1Ahalfarrow \u5DE6\u53F3\u4E24\u534A\u5404 50.2% \u5BBD\u62FC\u63A5\uFF0C\n // \u5404\u81EA img \u6E32\u67D3\u539F SVG path\u3002\u5DE6\u534A xMinYMin slice \u951A\u5DE6\uFF08.halfarrow-left \u88C1\u526A\u663E\u793A\u5DE6\u7BAD\u5934\uFF09\uFF0C\n // \u53F3\u534A xMaxYMin slice \u951A\u53F3\uFF08.halfarrow-right \u88C1\u526A\u663E\u793A\u53F3\u7BAD\u5934\uFF09\u3002\u4E24 img \u91CD\u53E0 0.4% \u62FC\u5408 \u21CC\u3002\n // \u5B57\u7B26\u66FF\u6362\u65B9\u6848\u4E2D \u21CC \u53CD\u590D\u504F\u4E0B\u6C89\uFF08\u5B57\u5F62 baseline \u4E0E SVG \u8F74\u7EBF\u5DEE 0.322em\uFF09\uFF1B\u53CC img \u76F4\u63A5\n // \u6E32\u67D3 KaTeX SVG path\uFF0C\u51E0\u4F55\u4E0E\u6D4F\u89C8\u5668\u9010\u5355\u4F4D\u91CD\u5408\u3002\n {\n prefix: 'M7435c-44',\n kind: 'char',\n output: 'img',\n char: '\u21CC',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.671,\n glyphDepth: 0.011,\n glyphWidth: 1,\n },\n // \u221A \u6839\u53F7\u4E94\u6863\uFF08KaTeX 0.16 \u5168\u90E8\u8D70 svg\uFF0C\u89C1 delimiter.ts makeSqrtImage\uFF09\u3002\n // \u6863\u4F4D\u2192\u5B57\u5F62\u4E25\u683C\u5BF9\u5E94\uFF08svgGeometry.ts\uFF1A\u5404\u6863 path \u53D6\u81EA\u5BF9\u5E94\u5B57\u4F53 U+221A \u8F6E\u5ED3\uFF0C\n // \u9876\u90E8\u6A2A\u7EBF\u7531 path \u81EA\u8EAB\u5EF6\u4F38 H400000 \u5B9E\u73B0\uFF1B\u5B57\u4F53\u7684\u540C\u4E00\u8F6E\u5ED3\u5728\u6A2A\u7EBF\u8D77\u70B9\u5904\u6298\u8FD4\uFF09\uFF1A\n // - M95,702 sqrtMain \u2192 KaTeX_Main \u221A\uFF08h0.8/d0.2\uFF09\n // - M263,681 sqrtSize1 \u2192 KaTeX_Size1 \u221A\uFF08h0.85/d0.35001\uFF09\n // - M424, sqrtSize3 \u2192 KaTeX_Size3 \u221A\uFF08h1.45/d0.95003\uFF1BVB=2592=1080\u00D72.4\uFF09\n // - M983 90 sqrtSize2 \u2192 KaTeX_Size2 \u221A\uFF08h1.15/d0.65002\uFF1BVB=1944=1080\u00D71.8\uFF09\n // - M702\u2423 sqrtTall \u2192 KaTeX_Size4 \u23B7 U+23B7\uFF08h0.915/d0.88502/w1.05556\uFF09\uFF0C\n // VB \u968F\u5185\u5BB9\u9AD8\u53EF\u53D8\uFF08path \u53BB\u7A7A\u767D\u540E\u524D\u7F00 M702 \u6703\u4E0E\u5750\u6807\u7C98\u8FDE\uFF0Cmarker \u7528 H742v \u5224\u522B\uFF09\n // svg \u76D2\u9AD8 H = \u5B57\u5F62\u5168\u9AD8 + 0.08em \u9876\u90E8 pad\uFF08hLinePad=80\uFF0CsizeMultiplier=1 \u65F6\n // \u6052\u7B49\uFF09\uFF0C\u6545 fill \u516C\u5F0F\u53D6 F=(H-0.08)/(gh+gd)\u3001va=gd\u00B7F\uFF1A\u5B57\u5F62 ink \u6070\u843D\u5728\n // [0.08em, H]\uFF0C\u4E0E svg \u539F\u59CB surd \u9010\u5355\u4F4D\u91CD\u5408\uFF08\u5B57\u4F53\u4E0E svg \u540C\u4E3A 1000upm \u8F6E\u5ED3\uFF09\u3002\n // \u6A2A\u7EBF\u7531 rule \u8272\u6761\u8865\u9F50\uFF1Ay\u2208[0.08,0.12]em\uFF08hLinePad/sqrtRuleThickness=80/40\uFF09\uFF0C\n // leftEm \u4E3A\u5B57\u5F62\u8F6E\u5ED3\u5185\u6A2A\u7EBF\u8D77\u70B9\uFF08\u6298\u8FD4 x\uFF1AMain 834\u3001Size1-4 1001\u3001Tall 702\uFF09\uFF0C\n // \u6E32\u67D3\u65F6\u4E58 F\u2014\u2014F\u22481 \u65F6\u4E0E\u5B57\u5F62\u9876\u90E8\u77ED\u6A2A\u5C16\u7AEF\u7CBE\u786E\u76F8\u63A5\u3002\n {\n prefix: 'M95,702c-2.7,0',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.8,\n glyphDepth: 0.2,\n glyphWidth: 0.83334,\n rule: { leftEm: 0.834 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M263,681c0.7,0',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-size1',\n mode: 'fill',\n glyphHeight: 0.85,\n glyphDepth: 0.35001,\n glyphWidth: 1.0,\n rule: { leftEm: 1.001 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M424,',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-size3',\n mode: 'fill',\n glyphHeight: 1.45,\n glyphDepth: 0.95003,\n glyphWidth: 1.0,\n rule: { leftEm: 1.001 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M98390l0-0c4',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-size2',\n mode: 'fill',\n glyphHeight: 1.15,\n glyphDepth: 0.65002,\n glyphWidth: 1.0,\n rule: { leftEm: 1.001 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M702',\n marker: 'H742v',\n kind: 'char',\n output: 'img',\n char: '\u23B7',\n fontClass: 'mp-svg-size4',\n mode: 'fill',\n glyphHeight: 0.915,\n glyphDepth: 0.88502,\n glyphWidth: 1.05556,\n rule: { leftEm: 0.702 },\n parentClass: 'mp-sqrt-tail',\n },\n // \\vec \u5C0F\u7BAD\u5934\uFF08U+20D7 \u7EC4\u5408\u7BAD\u5934\uFF0CKaTeX_Main \u4E2D\u4E3A\u6709\u5BBD\u5EA6\u7684\u72EC\u7ACB\u5B57\u5F62\uFF1Baccent overlay \u5B9A\u5BBD\u5BB9\u5668\uFF09\u3002\n // \u5B57\u7B26\u66FF\u6362\u65B9\u6848\u4E2D \u20D7 \u53CD\u590D\u504F\u5DE6\uFF1Aaccent-body \u7684 left \u504F\u79FB\u516C\u5F0F\u96BE\u4EE5\u8FD8\u539F KaTeX SVG\n // \u5728\u5B9A\u5BBD\u5BB9\u5668\u5185\u7684\u5C45\u4E2D\u4F4D\u7F6E\uFF08advance 0.5em \u4E0E ink \u533A\u95F4 [0.033, 0.4]em \u7684\u8F74\u7EBF\u5DEE\uFF09\uFF1B\n // \u6539\u7528 img \u76F4\u63A5\u6E32\u67D3 SVG path\uFF0C\u51E0\u4F55\u4E0E\u6D4F\u89C8\u5668\u9010\u5355\u4F4D\u91CD\u5408\u3002\n {\n prefix: 'M37720c0-5.3331.833',\n kind: 'char',\n output: 'img',\n char: '\\u20D7',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.71444,\n glyphDepth: 0,\n glyphWidth: 0.5,\n fixedWidth: true,\n },\n // \u8D85\u9AD8\u7AD6\u7EBF\uFF08svgGeometry vertTall\uFF0C\u5982 vmatrix \u7684 |\uFF1Bmarker \u533A\u5206 doublevert\uFF09\n {\n prefix: 'M14515v585v',\n marker: 'zM18815H145',\n kind: 'char',\n char: '|',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.75,\n glyphDepth: 0.25,\n glyphWidth: 0.27778,\n fixedWidth: true,\n },\n // \u8D85\u9AD8\u5DE6\u53F3\u62EC\u53F7\uFF08makeCustomSizedDelim lparen/rparen\uFF0C\u4E2D\u6BB5 l0,${midHeight} \u968F\u9AD8\u5EA6\u63D2\u503C\uFF09\n {\n prefix: 'M863,9c0,-2,-2,-5,-6,-9',\n kind: 'char',\n char: '(',\n fontClass: 'mp-svg-size4',\n mode: 'fill',\n glyphHeight: 1.75,\n glyphDepth: 1.25003,\n glyphWidth: 0.79167,\n fixedWidth: true,\n },\n {\n prefix: 'M76,0c-16.7,0,-25,3,-25,9',\n kind: 'char',\n char: ')',\n fontClass: 'mp-svg-size4',\n mode: 'fill',\n glyphHeight: 1.75,\n glyphDepth: 1.25003,\n glyphWidth: 0.79167,\n fixedWidth: true,\n },\n];\n\nconst SVG_PATH_TAG_RE = /<path\\b[^>]*?\\bd=\"([^\"]*)\"[^>]*?\\/>/g;\n\nfunction round4(n: number): string {\n return String(Number(n.toFixed(4)));\n}\n\n/** \u89E3\u6790 <svg> \u5F00\u6807\u7B7E\u7684 width/height\uFF08\u5FC5\u586B\uFF0Cem \u5355\u4F4D\uFF09+ viewBox/preserveAspectRatio\uFF08\u53EF\u9009\uFF09\u3002\n * KaTeX \u7684 sqrt/vec/\u21CC/\u62C9\u4F38\u5B9A\u754C\u7B26 svg \u5747\u542B width/height\uFF1B\u8D85\u9AD8\u5B9A\u754C\u7B26\uFF08vert/paren\uFF09\n * \u7684 svg \u4E0D\u5E26 preserveAspectRatio\uFF08\u7528\u6D4F\u89C8\u5668\u9ED8\u8BA4 xMidYMid meet\uFF09\u2014\u2014\u6545 vb/par \u4E3A\u53EF\u9009\u3002\n * output:'img' \u5206\u652F\u91CD\u5EFA SVG \u65F6\u539F\u6837\u900F\u4F20\uFF0C\u7F3A\u5931\u7684\u5C5E\u6027\u4E0D\u5199\u5165\uFF08SVG \u9ED8\u8BA4\u503C\u751F\u6548\uFF09\u3002 */\nfunction parseSvgBox(\n attrText: string,\n html: string,\n): { width: number; height: number; viewBox: string; preserveAspectRatio: string } {\n const w = /\\bwidth=\"([0-9.]+)em\"/.exec(attrText);\n const h = /\\bheight=\"([0-9.]+)em\"/.exec(attrText);\n if (!w || !h) throw new MpRenderError(html, `svg \u7F3A\u5C11 em \u5355\u4F4D width/height \u5C5E\u6027\uFF1A${attrText.trim().slice(0, 80)}`);\n const vb = /\\bviewBox=\"([^\"]*)\"/.exec(attrText);\n const par = /\\bpreserveAspectRatio=\"([^\"]*)\"/.exec(attrText);\n return {\n width: Number.parseFloat(w[1] ?? ''),\n height: Number.parseFloat(h[1] ?? ''),\n viewBox: vb?.[1] ?? '',\n preserveAspectRatio: par?.[1] ?? '',\n };\n}\n\n/**\n * \u5904\u7406 <svg> \u4E0E </svg> \u4E4B\u95F4\u7684\u6587\u672C\u6BB5\uFF1A\u63D0\u53D6\u5168\u90E8 <path/> \u5E76\u9010\u4E2A\u66FF\u6362\u3002\n * \u6BB5\u5185\u9664 path \u6807\u7B7E\u4E0E\u7A7A\u767D\u5916\u4E0D\u5141\u8BB8\u5176\u4ED6\u5185\u5BB9\uFF08KaTeX svg \u4EC5\u542B path\uFF09\u3002\n */\nfunction emitSvgPaths(\n stack: MpElementNode[],\n chunk: string,\n box: { width: number; height: number; viewBox: string; preserveAspectRatio: string },\n html: string,\n): void {\n let residue = chunk;\n SVG_PATH_TAG_RE.lastIndex = 0;\n for (let m = SVG_PATH_TAG_RE.exec(chunk); m !== null; m = SVG_PATH_TAG_RE.exec(chunk)) {\n applySvgPath(stack, m[1] ?? '', box, html);\n residue = residue.replace(m[0], '');\n }\n if (residue.trim().length > 0) {\n throw new MpRenderError(html, `svg \u5185\u542B\u975E path \u5185\u5BB9\uFF1A${residue.trim().slice(0, 60)}`);\n }\n}\n\nfunction applySvgPath(\n stack: MpElementNode[],\n d: string,\n box: { width: number; height: number; viewBox: string; preserveAspectRatio: string },\n html: string,\n): void {\n // \u89C4\u683C\u5339\u914D\u7528\u5265\u7A7A\u767D\u526F\u672C\uFF08SVG_PATH_SPECS \u7684 prefix/marker \u5747\u4E3A\u65E0\u7A7A\u767D\u7D27\u51D1\u683C\u5F0F\uFF09\u3002\n // \u8F93\u51FA\u5FC5\u987B\u7528\u539F\u59CB d\uFF1AKaTeX \u90E8\u5206 path\uFF08vec/\u21CC/\u62C9\u4F38\u7BAD\u5934\uFF09\u4EE5\u7A7A\u683C\u5206\u9694\u5750\u6807\uFF08\u5982\n // \"M377 20c0-5.333 1.833-10\"\uFF09\uFF0C\u5265\u7A7A\u683C\u4F1A\u628A\u76F8\u90BB\u5750\u6807\u7C98\u8FDE\u6210\u4E00\u4E2A\u6570\uFF08\"M37720\"\uFF09\uFF0C\n // \u4EFB\u4F55 SVG \u89E3\u6790\u5668\u90FD\u65E0\u6CD5\u8FD8\u539F \u2192 \u771F\u673A\u6E32\u67D3\u7A7A\u767D\uFF08#79/#105 \u7B2C\u4E09\u8F6E\u6839\u56E0\uFF09\u3002\u221A \u7684 path\n // \u6070\u4E3A\u9017\u53F7\u5206\u9694\u683C\u5F0F\uFF08\u65E0\u7A7A\u683C\uFF09\u6545\u6B64\u524D\u5E78\u514D\u3002d \u5C5E\u6027\u5185\u7A7A\u683C/\u6362\u884C\u5747\u4E3A SVG \u5408\u6CD5\u5206\u9694\u7B26\u3002\n const dNorm = d.replace(/\\s+/g, '');\n const spec = SVG_PATH_SPECS.find((s) => dNorm.startsWith(s.prefix) && (!s.marker || dNorm.includes(s.marker)));\n if (!spec) {\n throw new MpRenderError(html, `\u8868\u5916 svg path\uFF08d \u524D\u7F00\uFF1A${d.slice(0, 80)}\uFF09`);\n }\n if (spec.kind === 'drop') return;\n const parent = stack[stack.length - 1];\n if (!parent || parent.name === '#root') throw new MpRenderError(html, 'svg \u7236\u5BB9\u5668\u5F02\u5E38');\n if (spec.parentClass) addClass(parent, spec.parentClass);\n\n // output:'img' \u5206\u652F\uFF1A\u91CD\u5EFA SVG \u4E3A data URL\uFF0Cimg \u590D\u523B KaTeX svg \u7684 CSS \u884C\u4E3A\n // \uFF08display:block;position:absolute;width:100%;height:inherit\u2014\u2014\u89C1 .katex svg \u89C4\u5219\uFF09\u3002\n // \u5173\u952E\uFF1Aimg \u5FC5\u987B position:absolute \u8131\u79BB\u6587\u6863\u6D41\uFF0C\u5426\u5219\u5176 intrinsic \u5BBD\u5EA6\uFF08\u221A \u7684 400em\uFF09\n // \u4F1A\u6491\u5F00\u7236\u5BB9\u5668\uFF0Cvinculum \u6A2A\u7EBF\u6EA2\u51FA\u6574\u4E2A\u516C\u5F0F\uFF08#32 \u6839\u53F7\u6A2A\u7EBF\u8FC7\u957F\u7684\u6839\u56E0\uFF09\u3002\n // \u5BBD\u5EA6\u7B56\u7565\uFF1Asvg \u5BBD \u22655em\uFF08\u221A \u7684 400em\uFF09\u7528 width:100% \u586B\u6EE1\u7236\u5BB9\u5668\uFF08\u7236 overflow:hidden \u88C1\u526A\uFF0C\n // \u7236\u5BBD\u5EA6\u7531 vlist \u5E03\u5C40\u51B3\u5B9A\uFF0C\u8986\u76D6\u88AB\u5F00\u65B9\u6570\uFF09\uFF1Bsvg \u5BBD <5em\uFF08\u20D7 \u7684 0.471em\uFF09\u7528\u663E\u5F0F\u5BBD\u5EA6\uFF0C\n // \u56E0 .overlay \u65E0 position\uFF0Cwidth:100% \u4F1A\u5411\u4E0A\u627E\u5230 .accent-body\uFF08width:0\uFF09\u5BFC\u81F4 img \u5BBD 0\u3002\n // height \u7528 box.height\uFF08= SVG height \u5C5E\u6027 = \u7236 inline height\uFF09\uFF0C\u4E0D\u7528 inherit \u907F\u514D\u6B67\u4E49\u3002\n // \u7F16\u7801\uFF1A\u63A2\u9488\u5B9E\u8BC1\uFF08I9-I14\uFF09rich-text img \u5BF9 base64 \u4E0E charset \u4E24\u79CD data URL \u7F16\u7801\u3001\n // SVG \u4F4D\u56FE\u5747\u652F\u6301\uFF08\u6781\u7B80 SVG/\u221A SVG \u4E24\u79CD\u7F16\u7801\u90FD\u80FD\u6E32\u67D3\uFF09\uFF1Bbase64 \u65E0\u4FDD\u7559\u5B57\u7B26\u6B67\u4E49\u66F4\u7A33\u3002\n // left:0;top:0 \u663E\u5F0F\u5B9A\u4F4D\u907F\u514D rich-text \u5BF9 position:absolute \u5143\u7D20\"\u9759\u6001\u4F4D\u7F6E\"\u6B67\u4E49\u3002\n if (spec.output === 'img') {\n let svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${box.width}em\" height=\"${box.height}em\"`;\n if (box.viewBox) svg += ` viewBox=\"${box.viewBox}\"`;\n if (box.preserveAspectRatio) svg += ` preserveAspectRatio=\"${box.preserveAspectRatio}\"`;\n svg += `><path d=\"${d}\"/></svg>`;\n const b64 = svgToBase64(svg);\n const src = `data:image/svg+xml;base64,${b64}`;\n const imgWidth = box.width > 5 ? '100%' : `${box.width}em`;\n (parent.children ??= []).push({\n name: 'img',\n attrs: {\n src,\n style: `display:block;position:absolute;left:0;top:0;width:${imgWidth};height:${box.height}em`,\n },\n });\n return;\n }\n\n const styles: string[] = [];\n let fontSize = 0;\n if (spec.mode === 'fill') {\n const gh = spec.glyphHeight ?? 0;\n const gd = spec.glyphDepth ?? 0;\n // \u6839\u53F7\uFF08\u5E26 rule\uFF09\uFF1Asvg \u76D2\u9876\u90E8\u542B 0.08em hLinePad\uFF0Cink \u53EA\u5360 [0.08, H]\uFF0C\n // \u6545 F=(H-0.08)/(gh+gd)\uFF1B\u5176\u4F59 fill \u89C4\u683C svg \u76D2\u5373 ink \u76D2\uFF0CF=H/(gh+gd)\u3002\n const inkHeight = spec.rule ? box.height - 0.08 : box.height;\n fontSize = inkHeight / (gh + gd);\n styles.push(`font-size:${round4(fontSize)}em`);\n // va=gd\uFF08char \u5355\u4F4D\uFF0C\u975E gd\u00B7F\uFF09\uFF1Astrut\uFF08\u89C1\u4E0B\uFF09\u628A\u884C\u76D2\u57FA\u7EBF\u9489\u5728\u76D2\u5E95\u540E\uFF0C\u5B57\u5F62 ink\n // \u7CBE\u786E\u843D\u5728 [-(gh+gd)\u00B7F, 0] = [-inkHeight, 0]\uFF0C\u4E0E svg ink \u9010\u5355\u4F4D\u91CD\u5408\u3002\n // \u5B9E\u6D4B\uFF08canvas actualBoundingBox\uFF09\uFF1Ava=gd\u00B7F \u5728 F\u22601 \u7684\u62EC\u53F7\u7C7B\u4F7F ink \u504F\u9AD8\n // gd\u00B7(F-1)\u00B7F em\uFF08pmatrix \u5927\u62EC\u53F7\u504F\u9AD8 20.65px\uFF0C\u5185\u5BB9\u76F8\u5BF9\"\u4E0B\u6C89\"\uFF09\u3002\n if (Math.abs(gd) > 1e-4) styles.push(`vertical-align:${round4(gd)}em`);\n if (spec.fixedWidth) {\n const margin = (box.width - (spec.glyphWidth ?? 0) * fontSize) / 2;\n styles.push(`margin-left:${round4(margin)}em`, `margin-right:${round4(margin)}em`);\n }\n }\n // \u884C\u76D2\u57FA\u7EBF\u9489\u5E95\uFF08\u5168\u90E8 fill \u89C4\u683C\uFF0C\u975E\u4EC5\u6839\u53F7\uFF09\uFF1Asvg \u5BB9\u5668\uFF08hide-tail / \u5B9A\u5C3A\u5BF8\n // mp-vcell\uFF09\u5185 normal line-box \u57FA\u7EBF\u4F4D\u7F6E\u4E0D\u786E\u5B9A\uFF0Cva \u516C\u5F0F\u6574\u4F53\u6F02\u79FB\uFF08\u5B9E\u6D4B\u65E0 strut\n // \u7684\u62EC\u53F7\u7C7B ink \u504F\u9AD8 20.65px\uFF09\u3002\u2460 \u5B57\u7B26\u524D\u63D2\u5185\u90E8 strut\uFF08inline-block \u9AD8 H\u3001\n // \u57FA\u7EBF=\u5E95\u8FB9\uFF09\u628A\u884C\u76D2\u6491\u5230\u76D2\u9AD8\u4E14\u884C\u57FA\u7EBF\u9489\u5728\u76D2\u5E95\uFF1B\u2461 \u5B57\u7B26\u8BBE line-height:1\n // \u6D88\u9664\u5B57\u4F53 leading \u4E0D\u786E\u5B9A\u6027\u3002\n if (spec.mode === 'fill') {\n (parent.children ??= []).push({\n name: 'span',\n attrs: {\n style: `display:inline-block;width:0;height:${round4(box.height)}em;overflow:hidden`,\n },\n });\n styles.push('line-height:1');\n }\n (parent.children ??= []).push({\n name: 'span',\n attrs: {\n ...(spec.fontClass !== undefined && { class: spec.fontClass }),\n ...(styles.length > 0 && { style: styles.join(';') }),\n },\n children: [{ type: 'text', text: spec.char ?? '' }],\n });\n if (spec.rule) {\n // \u6A2A\u7EBF\u8D77\u70B9 = \u5B57\u5F62\u8F6E\u5ED3\u6298\u8FD4 x \u00D7 F\uFF1A\u5B57\u4F53\u4E0E svg \u540C\u4E3A 1000upm \u8F6E\u5ED3\uFF0CF\u22481 \u65F6\n // \u8272\u6761\u4E0E\u5B57\u5F62\u9876\u90E8\u77ED\u6A2A\u5C16\u7AEF\u7CBE\u786E\u76F8\u63A5\uFF08KaTeX \u539F\u59CB svg \u5373\u6B64\u51E0\u4F55\uFF09\u3002\n (parent.children ??= []).push({\n name: 'span',\n attrs: {\n class: 'mp-svg-rule',\n // hLinePad=80\u3001sqrtRuleThickness=40 \u2192 top:0.08em height:0.04em\n style: [\n `left:${round4(spec.rule.leftEm * fontSize)}em`,\n 'top:0.08em',\n 'right:-1em',\n 'height:0.04em',\n ].join(';'),\n },\n });\n }\n}\n", "/**\n * POC-B \u6837\u672C\u96C6\uFF08plan-M0 \u9636\u6BB5 I \u7B2C 28 \u6761\uFF09\uFF1A\u2265100 \u4E2A\u590D\u6742\u516C\u5F0F\u3002\n *\n * 7 \u7C7B\u8986\u76D6\uFF1A\u77E9\u9635\u3001cases\u3001\u5206\u5F0F\u6839\u5F0F\u5D4C\u5957\u3001\u79EF\u5206\u6C42\u548C\u3001\u4E0A\u4E0B\u6807\u3001\u5E0C\u814A\u5B57\u6BCD\u3001\\ce \u5316\u5B66\u65B9\u7A0B\u5F0F\u3002\n * \u7EA6\u675F\uFF1A\n * - \u5168\u90E8\u7ECF KaTeX 0.16 + mhchem \u6E32\u67D3\u9A8C\u8BC1\uFF08\u751F\u6210\u811A\u672C\u9010\u6761\u6267\u884C\uFF0C\u5931\u8D25\u5373\u62A5\u9519\uFF09\uFF1B\n * - \u4E0D\u4F7F\u7528 KaTeX_Math-BoldItalic \u4E4B\u5916\u7684\u4E0D\u53EF\u63A7\u5B57\u4F53\u7279\u6027\uFF08\\mathfrak/\\boldsymbol \u5DF2\u6392\u9664\uFF0C\n * \u5B57\u4F53\u6E05\u5355\u89C1 mp-fonts.ts\uFF09\uFF1B\n * - LaTeX \u662F\u552F\u4E00\u771F\u76F8\u6E90\uFF1A\u672C\u6E05\u5355\u540C\u65F6\u9A71\u52A8 Web \u5FEB\u7167\u9875\u4E0E\u5C0F\u7A0B\u5E8F nodes \u751F\u6210\uFF0C\u4FDD\u8BC1\u53CC\u7AEF\u540C\u6E90\u3002\n */\n\nexport type MpSampleCategory =\n | 'matrix'\n | 'cases'\n | 'frac-sqrt'\n | 'integral'\n | 'script'\n | 'greek'\n | 'chem';\n\nexport interface MpSample {\n /** \u7A33\u5B9A\u7F16\u53F7\uFF0C\u53CC\u7AEF\u622A\u56FE\u5BF9\u6BD4\u7684\u951A\u70B9\uFF08#001 \u8D77\uFF09 */\n id: number;\n category: MpSampleCategory;\n latex: string;\n /** \u72EC\u7ACB\u516C\u5F0F\u5757 true\uFF1B\u884C\u5185 false */\n display: boolean;\n}\n\nconst MATRIX = [\n '\\\\begin{pmatrix}a&b\\\\\\\\c&d\\\\end{pmatrix}',\n '\\\\begin{bmatrix}1&0\\\\\\\\0&1\\\\end{bmatrix}',\n '\\\\begin{vmatrix}a&b\\\\\\\\c&d\\\\end{vmatrix}',\n '\\\\begin{Bmatrix}x\\\\\\\\y\\\\\\\\z\\\\end{Bmatrix}',\n '\\\\begin{pmatrix}\\\\frac{1}{2}&\\\\sqrt{2}\\\\\\\\\\\\sin\\\\theta&\\\\cos\\\\theta\\\\end{pmatrix}',\n '\\\\begin{pmatrix}a_{11}&a_{12}&a_{13}\\\\\\\\a_{21}&a_{22}&a_{23}\\\\\\\\a_{31}&a_{32}&a_{33}\\\\end{pmatrix}',\n '\\\\begin{pmatrix}1&\\\\cdots&n\\\\\\\\\\\\vdots&\\\\ddots&\\\\vdots\\\\\\\\m&\\\\cdots&p\\\\end{pmatrix}',\n '\\\\left(\\\\begin{array}{cc|c}1&2&3\\\\\\\\4&5&6\\\\end{array}\\\\right)',\n 'A=\\\\begin{pmatrix}\\\\lambda_1&\\\\\\\\&\\\\lambda_2\\\\end{pmatrix}',\n '\\\\begin{pmatrix}\\\\mathbf{e}_1&\\\\mathbf{e}_2&\\\\mathbf{e}_3\\\\end{pmatrix}',\n '\\\\det\\\\begin{pmatrix}1&1\\\\\\\\1&1\\\\end{pmatrix}=0',\n '\\\\begin{pmatrix}0&-i\\\\\\\\i&0\\\\end{pmatrix}',\n '\\\\operatorname{rank}\\\\begin{pmatrix}1&2\\\\\\\\2&4\\\\end{pmatrix}=1',\n '\\\\begin{pmatrix}\\\\dfrac{a}{b}&\\\\dfrac{c}{d}\\\\\\\\\\\\dfrac{e}{f}&\\\\dfrac{g}{h}\\\\end{pmatrix}',\n '\\\\begin{pmatrix}x_1&x_2\\\\end{pmatrix}^{\\\\mathsf{T}}',\n];\n\nconst CASES = [\n '|x|=\\\\begin{cases}x,&x\\\\ge 0\\\\\\\\-x,&x<0\\\\end{cases}',\n 'f(n)=\\\\begin{cases}1,&n=0\\\\\\\\n\\\\cdot f(n-1),&n>0\\\\end{cases}',\n '\\\\begin{cases}x+y=2\\\\\\\\2x-y=1\\\\end{cases}',\n 'D(x)=\\\\begin{cases}1,&x\\\\in\\\\mathbb{Q}\\\\\\\\0,&x\\\\notin\\\\mathbb{Q}\\\\end{cases}',\n '\\\\begin{cases}a_1x_1+b_1x_2=c_1\\\\\\\\a_2x_1+b_2x_2=c_2\\\\end{cases}',\n 'g(x)=\\\\begin{cases}\\\\sin x,&x>0\\\\\\\\\\\\cos x,&x\\\\le 0\\\\end{cases}',\n '\\\\begin{cases}\\\\dfrac{\\\\partial u}{\\\\partial t}=k\\\\dfrac{\\\\partial^2 u}{\\\\partial x^2}\\\\\\\\u(0,t)=0\\\\end{cases}',\n '\\\\max(0,x)=\\\\begin{cases}0,&x<0\\\\\\\\x,&x\\\\ge 0\\\\end{cases}',\n '\\\\begin{cases}v=v_0+at\\\\\\\\s=v_0t+\\\\frac{1}{2}at^2\\\\end{cases}',\n '\\\\begin{cases}\\\\text{\u53CA\u683C},&s\\\\ge 60\\\\\\\\\\\\text{\u4E0D\u53CA\u683C},&s<60\\\\end{cases}',\n '\\\\begin{cases}1,&\\\\text{\u82E5 }x=y\\\\\\\\0,&\\\\text{\u5426\u5219}\\\\end{cases}',\n '\\\\begin{cases}x\\\\equiv 1\\\\pmod{3}\\\\\\\\x\\\\equiv 2\\\\pmod{5}\\\\end{cases}',\n '\\\\begin{cases}\\\\dot{x}=y\\\\\\\\\\\\dot{y}=-x\\\\end{cases}',\n 'F(n)=\\\\begin{cases}0,&n=1\\\\\\\\F(n-1)+F(n-2),&n>1\\\\end{cases}',\n '\\\\left\\\\{\\\\begin{aligned}x&=2t\\\\\\\\y&=t^2\\\\end{aligned}\\\\right.',\n];\n\nconst FRAC_SQRT = [\n '\\\\frac{1}{2}',\n '\\\\frac{\\\\sqrt{2}}{2}',\n '\\\\frac{1}{1+\\\\frac{1}{1+\\\\frac{1}{x}}}',\n '\\\\sqrt{a^2+b^2}',\n '\\\\sqrt[3]{27}=3',\n '\\\\sqrt[3]{\\\\frac{a}{b}}',\n '\\\\frac{\\\\partial^2 u}{\\\\partial x\\\\,\\\\partial y}',\n '\\\\sqrt{\\\\frac{\\\\sigma^2}{n}}=\\\\frac{\\\\sigma}{\\\\sqrt{n}}',\n '\\\\cfrac{1}{2+\\\\cfrac{1}{3+\\\\cfrac{1}{4}}}',\n '\\\\binom{n}{k}=\\\\frac{n!}{k!(n-k)!}',\n '\\\\frac{dy}{dx}=\\\\frac{dy}{du}\\\\cdot\\\\frac{du}{dx}',\n '\\\\sqrt{2+\\\\sqrt{3}}\\\\approx\\\\frac{1+\\\\sqrt{3}}{2}',\n '\\\\frac{-b\\\\pm\\\\sqrt{b^2-4ac}}{2a}',\n '\\\\sqrt[n]{x^n}=|x|',\n '\\\\frac{1}{\\\\sqrt{2}}\\\\cdot\\\\frac{\\\\sqrt{2}}{\\\\sqrt{2}}=\\\\frac{\\\\sqrt{2}}{2}',\n];\n\nconst INTEGRAL = [\n '\\\\int_0^1 x^2\\\\,dx',\n '\\\\int_{-\\\\infty}^{+\\\\infty}e^{-x^2}dx=\\\\sqrt{\\\\pi}',\n '\\\\sum_{i=1}^{n}i=\\\\frac{n(n+1)}{2}',\n '\\\\lim_{n\\\\to\\\\infty}\\\\left(1+\\\\frac{1}{n}\\\\right)^n=e',\n '\\\\prod_{k=1}^{n}k=n!',\n '\\\\iint_D f(x,y)\\\\,dA',\n '\\\\oint_C \\\\vec{F}\\\\cdot d\\\\vec{r}',\n '\\\\sum_{k=0}^{\\\\infty}\\\\frac{x^k}{k!}=e^x',\n '\\\\int_a^b f\\'(x)dx=f(b)-f(a)',\n '\\\\lim_{x\\\\to 0}\\\\frac{\\\\sin x}{x}=1',\n '\\\\sum_{n=1}^{\\\\infty}\\\\frac{1}{n^2}=\\\\frac{\\\\pi^2}{6}',\n '\\\\int\\\\frac{dx}{1+x^2}=\\\\arctan x+C',\n '\\\\limsup_{n\\\\to\\\\infty}a_n,\\\\ \\\\bigcup_{n=1}^{\\\\infty}A_n',\n '\\\\int_0^{\\\\pi}\\\\sin^2 x\\\\,dx=\\\\frac{\\\\pi}{2}',\n '\\\\frac{d}{dx}\\\\int_0^x f(t)\\\\,dt=f(x)',\n];\n\nconst SCRIPT = [\n 'a^2+b^2=c^2',\n 'x_1+x_2+\\\\cdots+x_n',\n 'e^{i\\\\pi}+1=0',\n 'a_{ij}^{(k)}',\n 'x^{y^{z}}',\n '\\\\dot{x}+\\\\ddot{y}',\n '\\\\hat{y}=\\\\bar{x}+\\\\tilde{\\\\theta}',\n '\\\\vec{v}\\\\times\\\\vec{w}',\n \"f'(x)=nx^{n-1}\",\n '\\\\max_{x\\\\in S}f(x)',\n '\\\\arg\\\\min_{\\\\theta}\\\\|X\\\\theta-y\\\\|^2',\n '\\\\text{Si}_2(x)=\\\\int_0^x\\\\frac{\\\\sin t}{t}dt',\n '\\\\aleph_0+\\\\aleph_1',\n 'x_i\\'=\\\\lambda_i x_i',\n '2^{10}=1024',\n];\n\nconst GREEK = [\n '\\\\alpha+\\\\beta=\\\\gamma',\n '\\\\Gamma(n+1)=n!',\n '\\\\Delta=\\\\sqrt{b^2-4ac}',\n '\\\\nabla\\\\cdot\\\\vec{E}=\\\\frac{\\\\rho}{\\\\varepsilon_0}',\n '\\\\varphi+\\\\phi=2\\\\phi_0',\n '\\\\sigma=\\\\sqrt{\\\\frac{1}{N}\\\\sum(x_i-\\\\mu)^2}',\n '\\\\forall\\\\epsilon>0,\\\\ \\\\exists\\\\delta>0',\n '\\\\lambda,\\\\mu,\\\\nu,\\\\xi,\\\\pi,\\\\rho',\n '\\\\Omega=\\\\{\\\\omega_1,\\\\omega_2,\\\\ldots\\\\}',\n '\\\\hbar=\\\\frac{h}{2\\\\pi}',\n '\\\\theta\\\\in[0,2\\\\pi)',\n '\\\\zeta(s)=\\\\sum_{n=1}^{\\\\infty}\\\\frac{1}{n^s}',\n '\\\\partial\\\\Omega=\\\\emptyset',\n '\\\\top,\\\\ \\\\bot,\\\\ \\\\vdash,\\\\ \\\\models',\n '\\\\ell^2=\\\\left\\\\{\\\\sum|x_k|^2<\\\\infty\\\\right\\\\}',\n];\n\nconst CHEM = [\n '\\\\ce{CO2 + C -> 2 CO}',\n '\\\\ce{2H2 + O2 -> 2H2O}',\n '\\\\ce{N2 + 3H2 <=> 2NH3}',\n '\\\\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O}',\n '\\\\ce{Fe^3+ + 3OH- -> Fe(OH)3 v}',\n '\\\\ce{CaCO3 ->[\u9AD8\u6E29] CaO + CO2 ^}',\n '\\\\ce{Zn + Cu^2+ -> Zn^2+ + Cu}',\n '\\\\ce{CH3COOH <=> CH3COO- + H+}',\n '\\\\ce{^{227}_{90}Th+}',\n '\\\\ce{SO4^2- + Ba^2+ -> BaSO4 v}',\n '\\\\ce{CH3CH2OH ->[\u6D53H2SO4][170^\\\\circ C] CH2=CH2 + H2O}',\n '\\\\ce{2Na + 2H2O -> 2NaOH + H2 ^}',\n '\\\\ce{CH4 + 2O2 ->[\u70B9\u71C3] CO2 + 2H2O}',\n '\\\\ce{Zn | Zn^{2+}(aq) || Cu^{2+}(aq) | Cu}',\n '\\\\ce{AgCl(s) <=> Ag+(aq) + Cl-(aq)}',\n];\n\nconst GROUPS: Array<[MpSampleCategory, boolean, string[]]> = [\n ['matrix', true, MATRIX],\n ['cases', true, CASES],\n ['frac-sqrt', false, FRAC_SQRT],\n ['integral', true, INTEGRAL],\n ['script', false, SCRIPT],\n ['greek', false, GREEK],\n ['chem', true, CHEM],\n];\n\n/** \u5168\u91CF\u6837\u672C\uFF1A7 \u7C7B \u00D7 15 = 105 \u6761\uFF0C\u6309\u7EC4\u987A\u5E8F\u7EDF\u4E00\u7F16\u53F7\uFF08#001\u2013#105\uFF09 */\nexport const MP_SAMPLES: MpSample[] = GROUPS.flatMap(([category, display, list]) =>\n list.map((latex) => ({ category, latex, display })),\n).map((sample, index) => ({ ...sample, id: index + 1 }));\n", "/**\n * graph_plot \u5DE5\u5177\uFF1A\u76F4\u8C03 @studylab/viz/plot-svg \u672C\u5730\u6E32\u67D3\uFF08\u4E0D\u7ECF\u7F51\u7EDC\uFF0C\u5EF6\u8FDF <50ms SVG / <100ms PNG\uFF09\u3002\n * \u8F93\u5165 schema \u7531 registerTool \u7684 zod inputSchema \u6821\u9A8C\uFF1B\n * handler \u5185 SVG/PNG \u751F\u6210\u540C\u6B65\u5B8C\u6210\uFF0C\u6240\u6709\u5931\u8D25\u5C31\u5730 isError\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport { Resvg } from '@resvg/resvg-js';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n renderPlotSvg,\n EXPRESSION_RAW_MAX_LENGTH,\n EXPRESSIONS_MAX,\n SIZE_MAX,\n} from '@studylab/viz/plot-svg';\nimport type { PlotTheme } from '@studylab/viz/plot-svg';\n\nexport interface GraphPlotArgs {\n expressions: { raw: string; label?: string; color?: string }[];\n viewport?: { xMin: number; xMax: number; yMin: number; yMax: number };\n size?: { width: number; height: number };\n format?: 'svg' | 'png';\n theme?: PlotTheme;\n grid?: boolean;\n axis?: boolean;\n}\n\nfunction textResult(text: string): CallToolResult {\n return { content: [{ type: 'text', text }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nexport async function handleGraphPlot(args: GraphPlotArgs): Promise<CallToolResult> {\n try {\n const result = renderPlotSvg({\n expressions: args.expressions,\n viewport: args.viewport,\n size: args.size,\n theme: args.theme ?? 'light',\n grid: args.grid,\n axis: args.axis,\n });\n\n const okList = result.expressions.filter((e) => e.ok);\n const failList = result.expressions.filter((e) => !e.ok);\n const summary = `\u5171 ${result.expressions.length} \u6761\u66F2\u7EBF\uFF1A${okList.length} \u6210\u529F\uFF08${okList.map((e) => `${e.raw} ${e.segments ?? 1} \u6BB5`).join('\uFF1B')}\uFF09\uFF1B${failList.length} \u5931\u8D25\uFF08${failList.map((e) => `${e.raw}: ${e.error}`).join('\uFF1B')}\uFF09`;\n\n if (args.format === 'png') {\n const png = new Resvg(result.svg).render().asPng();\n const payload = {\n format: 'png',\n mime: 'image/png',\n dataBase64: png.toString('base64'),\n width: args.size?.width ?? 800,\n height: args.size?.height ?? 500,\n summary,\n timing: result.timing,\n };\n return textResult(JSON.stringify(payload));\n }\n\n const payload = {\n format: 'svg',\n mime: 'image/svg+xml',\n svg: result.svg,\n width: args.size?.width ?? 800,\n height: args.size?.height ?? 500,\n summary,\n timing: result.timing,\n };\n return textResult(JSON.stringify(payload));\n } catch (err) {\n if (err instanceof Error && /\u8868\u8FBE\u5F0F|compile|\u6240\u6709\u8868\u8FBE\u5F0F\u5747\u65E0\u6CD5\u7F16\u8BD1/.test(err.message)) {\n return errorResult(`\u8868\u8FBE\u5F0F\u7F16\u8BD1\u5931\u8D25\uFF1A${err.message}\uFF08\u8BF7\u68C0\u67E5 mathjs \u8BED\u6CD5\uFF0C\u5982 sin(x)\u3001x^2\u3001sqrt(x)\uFF09`);\n }\n return errorResult(err instanceof Error ? err.message : String(err));\n }\n}\n\nexport function registerGraphPlotTool(server: McpServer): void {\n server.registerTool(\n 'graph_plot',\n {\n title: '\u51FD\u6570\u56FE\u50CF\u7ED8\u5236',\n description: '\u5C06 mathjs \u6570\u5B66\u8868\u8FBE\u5F0F\u6E32\u67D3\u4E3A SVG \u6216 PNG \u56FE\u50CF\uFF08\u53EF\u9009\u4E3B\u9898\u3001\u89C6\u53E3\u3001\u7F51\u683C\u3001\u8F74\uFF09',\n inputSchema: {\n expressions: z.array(\n z.object({\n raw: z.string().max(EXPRESSION_RAW_MAX_LENGTH),\n label: z.string().optional(),\n color: z.string().optional(),\n }),\n ).min(1).max(EXPRESSIONS_MAX),\n viewport: z.object({\n xMin: z.number(),\n xMax: z.number(),\n yMin: z.number(),\n yMax: z.number(),\n }).optional(),\n size: z.object({\n width: z.number().min(1).max(SIZE_MAX),\n height: z.number().min(1).max(SIZE_MAX),\n }).optional(),\n format: z.enum(['svg', 'png']).default('svg'),\n theme: z.enum(['light', 'dark']).default('light'),\n grid: z.boolean().default(true),\n axis: z.boolean().default(true),\n },\n },\n (args) => handleGraphPlot(args),\n );\n}\n", "/**\n * \u4E00\u5143\u6570\u503C\u5206\u6790\u5185\u6838\uFF08FR-VIZ P2\uFF09\uFF1A\u53D6\u503C\u3001\u5BFC\u6570\u3001\u5B9A\u79EF\u5206\u3001\u6C42\u6839\u3001\u4EA4\u70B9\u3002\n * \u7EAF mathjs + \u6570\u503C\u65B9\u6CD5\uFF0C\u4E0D\u4F9D\u8D56 React/\u5F15\u64CE\uFF0C\u53EF\u5355\u6D4B\u3001\u53EF\u88AB\u670D\u52A1\u7AEF\u590D\u7528\u3002\n * - \u5BFC\u6570\u4F18\u5148 mathjs \u7B26\u53F7\u6C42\u5BFC\uFF0C\u4E0D\u53EF\u5FAE\u9000\u56DE\u4E2D\u5FC3\u5DEE\u5206\u3002\n * - \u79EF\u5206\u7528\u81EA\u9002\u5E94 Simpson\uFF1B\u65E0\u7A77\u9650\u51E0\u4F55\u6269\u5C55\u622A\u65AD\u5E76\u663E\u5F0F\u6807\u8BB0\u3002\n * - \u6C42\u6839\u7528\u7B26\u53F7\u626B\u63CF + \u4E8C\u5206\uFF1B\u6781\u70B9\uFF08\u4E2D\u70B9\u975E\u6709\u9650\u503C\uFF09\u533A\u95F4\u62D2\u7EDD\uFF0C1/x\u3001tan \u4E0D\u4EA7\u751F\u5047\u6839\u3002\n */\nimport { derivative, parse } from 'mathjs';\nimport { buildScope, compileExpression, makePointEvaluator } from './compile.js';\nimport type { ParameterSpec } from './types.js';\n\nexport type UnivariateFn = (x: number) => number;\n\nconst DEFAULT_TOL = 1e-7;\nconst ROOT_SCAN_SEGMENTS = 1000;\nconst ROOT_BISECT_ITERS = 60;\nconst SIMPSON_MAX_DEPTH = 20;\n/** \u65E0\u7A77\u9650\u622A\u65AD\u4E0A\u9650\uFF08\u51E0\u4F55\u6269\u5C55 1,2,4\u2026 \u5230\u7EA6 16384\uFF09 */\nconst INFINITE_CUTOFF = 2e4;\n\n/** \u7F16\u8BD1\u663E\u5F0F\u65B9\u7A0B y=f(x) \u4E3A NaN \u5B89\u5168\u7684\u4E00\u5143\u51FD\u6570\uFF1B\u8BED\u6CD5\u9519\u629B ExpressionError */\nexport function makeUnivariate(raw: string, parameters: ParameterSpec[] = []): UnivariateFn {\n const point = makePointEvaluator(compileExpression(raw), buildScope(parameters));\n return (x: number) => point({ x });\n}\n\n/** \u70B9\u6C42\u503C\uFF1B\u5B9A\u4E49\u57DF\u5916\u8FD4\u56DE NaN\uFF08\u4E0D\u629B\u5F02\u5E38\uFF09 */\nexport function evaluateAt(raw: string, x: number, parameters: ParameterSpec[] = []): number {\n return makeUnivariate(raw, parameters)(x);\n}\n\n/**\n * \u5BFC\u6570\uFF1A\u5148\u5C1D\u8BD5 mathjs \u7B26\u53F7\u5BFC\u6570\uFF08\u7CBE\u786E\uFF09\uFF0C\u5931\u8D25\u9000\u56DE\u81EA\u9002\u5E94\u4E2D\u5FC3\u5DEE\u5206\u3002\n * h = \u221Aeps\u00B7max(1,|x|) \u5E73\u8861\u622A\u65AD\u8BEF\u5DEE\u4E0E\u820D\u5165\u8BEF\u5DEE\u3002\n */\nexport function derivativeAt(raw: string, x: number, parameters: ParameterSpec[] = []): number {\n try {\n const derivativeNode = derivative(parse(raw), 'x');\n const point = makePointEvaluator(\n { evaluate: (scope) => derivativeNode.compile().evaluate(scope) },\n buildScope(parameters),\n );\n const value = point({ x });\n if (Number.isFinite(value)) return value;\n } catch {\n // \u7B26\u53F7\u5BFC\u6570\u4E0D\u652F\u6301\u8BE5\u8868\u8FBE\u5F0F\uFF0C\u8D70\u6570\u503C\u5DEE\u5206\n }\n const f = makeUnivariate(raw, parameters);\n const h = Math.sqrt(Number.EPSILON) * Math.max(1, Math.abs(x));\n const y1 = f(x + h);\n const y0 = f(x - h);\n if (![y1, y0].every(Number.isFinite)) return NaN;\n return (y1 - y0) / (2 * h);\n}\n\nfunction adaptiveSimpsonRecursive(\n f: UnivariateFn,\n a: number,\n b: number,\n fa: number,\n fm: number,\n fb: number,\n whole: number,\n tol: number,\n depth: number,\n): number {\n const m = (a + b) / 2;\n const lm = (a + m) / 2;\n const rm = (m + b) / 2;\n const flm = f(lm);\n const frm = f(rm);\n if (![flm, frm].every(Number.isFinite)) return NaN;\n const left = ((m - a) / 6) * (fa + 4 * flm + fm);\n const right = ((b - m) / 6) * (fm + 4 * frm + fb);\n if (depth <= 0 || Math.abs(left + right - whole) <= 15 * tol) {\n return left + right + (left + right - whole) / 15;\n }\n return (\n adaptiveSimpsonRecursive(f, a, m, fa, flm, fm, left, tol / 2, depth - 1)\n + adaptiveSimpsonRecursive(f, m, b, fm, frm, fb, right, tol / 2, depth - 1)\n );\n}\n\n/** \u81EA\u9002\u5E94 Simpson \u6C42\u79EF\uFF1B\u533A\u95F4\u5185\u542B\u5947\u70B9\uFF08\u5982 1/x \u8DE8 0\uFF09\u8FD4\u56DE NaN */\nexport function integrate(f: UnivariateFn, a: number, b: number, tol: number = DEFAULT_TOL): number {\n if (!(Number.isFinite(a) && Number.isFinite(b))) return NaN;\n if (a === b) return 0;\n if (a > b) return -integrate(f, b, a, tol);\n const fa = f(a);\n const fm = f((a + b) / 2);\n const fb = f(b);\n if (![fa, fm, fb].every(Number.isFinite)) return NaN;\n return adaptiveSimpsonRecursive(\n f, a, b, fa, fm, fb,\n ((b - a) / 6) * (fa + 4 * fm + fb),\n tol,\n SIMPSON_MAX_DEPTH,\n );\n}\n\nexport interface InfiniteIntegralResult {\n value: number;\n /** \u662F\u5426\u4F7F\u7528\u4E86\u65E0\u7A77\u9650\u622A\u65AD\uFF08UI \u9700\u63D0\u793A\uFF09 */\n truncated: boolean;\n /** \u622A\u65AD\u8FD1\u4F3C\u65F6\u5B9E\u9645\u79EF\u5206\u5230\u7684\u6709\u9650\u533A\u95F4\uFF08\u753B\u677F\u9AD8\u4EAE\u7528\uFF09\uFF1B\u6709\u9650\u8FB9\u754C\u4E3A null */\n renderInterval: [number, number] | null;\n}\n\n/** \u5355\u4FA7\u5E7F\u4E49\u79EF\u5206\uFF1A\u4ECE\u6709\u9650\u7AEF\u5411\u65E0\u7A77\u65B9\u5411\u51E0\u4F55\u6269\u5C55\uFF0C\u8FD4\u56DE\u79EF\u5206\u503C\u4E0E\u622A\u65AD\u7AEF\u70B9 */\nfunction integrateInfiniteSide(\n f: UnivariateFn,\n finiteEnd: number,\n direction: 1 | -1,\n tol: number,\n): { value: number; end: number } {\n let sum = 0;\n let inner = finiteEnd;\n let width = 1;\n for (let i = 0; i < 60; i += 1) {\n const outer = inner + direction * width;\n const piece = integrate(f, Math.min(inner, outer), Math.max(inner, outer), tol);\n if (!Number.isFinite(piece)) return { value: NaN, end: NaN };\n sum += piece;\n if (Math.abs(piece) < tol * Math.max(1, Math.abs(sum))) return { value: sum, end: outer };\n if (Math.abs(outer - finiteEnd) > INFINITE_CUTOFF) return { value: NaN, end: NaN }; // \u4E0D\u6536\u655B\n inner = outer;\n width *= 2;\n }\n return { value: NaN, end: NaN };\n}\n\n/**\n * \u5B9A\u79EF\u5206\uFF0C\u652F\u6301 \u00B1\u221E \u8FB9\u754C\uFF08\u6309\u622A\u65AD\u8FD1\u4F3C\uFF0Ctruncated=true\uFF09\u3002\n * \u53CC\u4FA7\u65E0\u7A77\u5728 0 \u5904\u62C6\u5206\uFF1B\u53D1\u6563\u6216\u542B\u5947\u70B9 value \u4E3A NaN\u3002\n */\nexport function integrateExpression(\n raw: string,\n a: number,\n b: number,\n parameters: ParameterSpec[] = [],\n): InfiniteIntegralResult {\n const f = makeUnivariate(raw, parameters);\n if (Number.isFinite(a) && Number.isFinite(b)) {\n return { value: integrate(f, a, b), truncated: false, renderInterval: null };\n }\n let value = NaN;\n let renderInterval: [number, number] | null = null;\n if (a === Number.NEGATIVE_INFINITY && b === Number.POSITIVE_INFINITY) {\n // \u5728 0 \u5904\u62C6\u6210\u4E24\u4E2A\u5355\u4FA7\u5E7F\u4E49\u79EF\u5206\n const left = integrateInfiniteSide(f, 0, -1, DEFAULT_TOL);\n const right = integrateInfiniteSide(f, 0, 1, DEFAULT_TOL);\n if (Number.isFinite(left.value) && Number.isFinite(right.value)) {\n value = left.value + right.value;\n renderInterval = [left.end, right.end];\n }\n } else if (a === Number.NEGATIVE_INFINITY && Number.isFinite(b)) {\n const side = integrateInfiniteSide(f, b, -1, DEFAULT_TOL);\n if (Number.isFinite(side.value)) {\n value = side.value;\n renderInterval = [side.end, b];\n }\n } else if (Number.isFinite(a) && b === Number.POSITIVE_INFINITY) {\n const side = integrateInfiniteSide(f, a, 1, DEFAULT_TOL);\n if (Number.isFinite(side.value)) {\n value = side.value;\n renderInterval = [a, side.end];\n }\n }\n return { value, truncated: true, renderInterval };\n}\n\n/** \u533A\u95F4\u5185\u4E8C\u5206\u6C42\u6839\uFF1B\u4E2D\u70B9\u843D\u5230\u975E\u6709\u9650\u503C\uFF08\u6781\u70B9\uFF09\u5219\u653E\u5F03\u8BE5\u533A\u95F4 */\nfunction bisect(f: UnivariateFn, a: number, b: number): number | null {\n let lo = a;\n let hi = b;\n let flo = f(lo);\n let fhi = f(hi);\n if (![flo, fhi].every(Number.isFinite)) return null;\n if (flo === 0) return lo;\n if (fhi === 0) return hi;\n for (let i = 0; i < ROOT_BISECT_ITERS; i += 1) {\n const mid = (lo + hi) / 2;\n const fm = f(mid);\n if (!Number.isFinite(fm)) return null; // \u8DE8\u8FC7\u6781\u70B9\uFF081/x\u3001tan\uFF09\uFF0C\u4E0D\u662F\u6839\n if (fm === 0) return mid;\n if (hi - lo < 1e-12) {\n // \u6D6E\u70B9\u6781\u70B9\uFF08tan(3\u03C0/2)\u22481.6e16\uFF09\u4E5F\u4F1A\u8BA9\u533A\u95F4\u6536\u7F29\uFF0C\u5FC5\u987B\u786E\u8BA4\u51FD\u6570\u503C\u786E\u5B9E\u63A5\u8FD1\u96F6\n return Math.abs(fm) < 1e-6 ? mid : null;\n }\n if (flo * fm <= 0) { hi = mid; fhi = fm; }\n else { lo = mid; flo = fm; }\n }\n const root = (lo + hi) / 2;\n return Math.abs(f(root)) < 1e-6 ? root : null;\n}\n\n/**\n * \u5728 [xMin,xMax] \u5185\u6C42\u5168\u90E8\u5B9E\u6839\uFF1A\u7B49\u8DDD\u7B26\u53F7\u626B\u63CF + \u4E8C\u5206\uFF0C\u7ED3\u679C\u53BB\u91CD\u6392\u5E8F\u3002\n * \u91C7\u6837\u70B9\u6070\u597D\u843D\u5728\u6839\u4E0A\u65F6\u4E5F\u4F1A\u6355\u83B7\u3002\n */\nexport function findRoots(f: UnivariateFn, xMin: number, xMax: number): number[] {\n if (!(xMax > xMin)) return [];\n const step = (xMax - xMin) / ROOT_SCAN_SEGMENTS;\n const roots: number[] = [];\n // \u8FB9\u754C\u6839\uFF1A\u6D6E\u70B9\u4E0B sin(\u03C0)\u22481.2e-16 \u975E\u96F6\uFF0C\u7B26\u53F7\u626B\u63CF\u770B\u4E0D\u5230\u8D34\u8FB9\u7684\u6839\n if (Math.abs(f(xMin)) < 1e-8) roots.push(xMin);\n let prevX = xMin;\n let prevY = f(prevX);\n for (let i = 1; i <= ROOT_SCAN_SEGMENTS; i += 1) {\n const x = xMin + i * step;\n const y = f(x);\n if (Number.isFinite(prevY) && Number.isFinite(y)) {\n if (prevY === 0) roots.push(prevX);\n else if (prevY * y < 0) {\n const root = bisect(f, prevX, x);\n if (root !== null) roots.push(root);\n }\n }\n prevX = x;\n prevY = y;\n }\n if (prevY === 0 || Math.abs(prevY) < 1e-8) roots.push(prevX);\n\n roots.sort((p, q) => p - q);\n const deduped: number[] = [];\n for (const root of roots) {\n const last = deduped[deduped.length - 1];\n if (last === undefined || Math.abs(root - last) > 1e-6 * Math.max(1, Math.abs(last))) {\n deduped.push(root);\n }\n }\n return deduped;\n}\n\n/** \u6C42\u4E24\u663E\u5F0F\u66F2\u7EBF\u4EA4\u70B9\uFF1A\u5BF9 f1\u2212f2 \u6C42\u6839\uFF0C\u8FD4\u56DE\u66F2\u7EBF\u4E0A\u7684 (x,y) */\nexport function findIntersections(\n raw1: string,\n raw2: string,\n xMin: number,\n xMax: number,\n parameters: ParameterSpec[] = [],\n): Array<{ x: number; y: number }> {\n const f1 = makeUnivariate(raw1, parameters);\n const f2 = makeUnivariate(raw2, parameters);\n const roots = findRoots((x) => f1(x) - f2(x), xMin, xMax);\n return roots\n .map((x) => ({ x, y: f1(x) }))\n .filter((point) => Number.isFinite(point.y));\n}\n", "/**\n * mathjs \u7F16\u8BD1\u5C42\uFF1A\u552F\u4E00\u8868\u8FBE\u5F0F\u5185\u6838\uFF08FR-VIZ-01\uFF09\u3002\n * - parse/compile \u751F\u6210\u7EAF\u51FD\u6570\uFF0C\u7981\u6B62 eval/Function\u3002\n * - \u9519\u8BEF\u4E00\u5F8B\u5F52\u4E00\u5316\u4E3A ExpressionError\uFF08\u8BED\u6CD5\u9519 / \u672A\u5B9A\u4E49\u7B26\u53F7 / \u5B9A\u4E49\u57DF\uFF09\uFF0CUI \u4E0D\u4E2D\u65AD\u8F93\u5165\u3002\n * - \u9010\u70B9\u91C7\u6837\u9519\u8BEF\uFF08\u5947\u70B9/\u8D8A\u754C/\u590D\u6570\uFF09\u4E00\u5F8B\u8FD4\u56DE NaN\uFF0C\u7531\u5F15\u64CE\u65AD\u7F1D\uFF0C\u7EDD\u4E0D\u753B\u7A7F\u9876\u7EBF\u3002\n */\nimport { parse, gamma } from 'mathjs';\nimport type { ParameterSpec } from './types.js';\n\nexport type ExpressionErrorKind = 'syntax' | 'undefined-symbol' | 'domain';\n\nexport class ExpressionError extends Error {\n readonly kind: ExpressionErrorKind;\n constructor(kind: ExpressionErrorKind, message: string) {\n super(message);\n this.name = 'ExpressionError';\n this.kind = kind;\n }\n}\n\n/**\n * \u9519\u8BEF\u8868\u5185\u5BB9\u7B7E\u540D\uFF08id:kind:message \u6309 id \u6392\u5E8F\u62FC\u63A5\uFF09\u3002\n * \u53C2\u6570\u52A8\u753B\u6BCF\u5E27\u91CD\u5EFA errors \u5BF9\u8C61\uFF0C\u5185\u5BB9\u672A\u53D8\u65F6\u56DE\u8C03\u4F1A\u8BA9\u9875\u9762 setState \u51FA\u65B0\u5BF9\u8C61\uFF0C\n * \u5728 passive effect\uFF08\u63D0\u4EA4\u9636\u6BB5\uFF09\u5185\u7D2F\u8BA1\u5D4C\u5957\u66F4\u65B0\u89E6\u53D1 React\u300CMaximum update depth exceeded\u300D\uFF1B\n * \u9002\u914D\u5668\u636E\u6B64\u53BB\u91CD\uFF0C\u4EC5\u7B7E\u540D\u53D8\u5316\u65F6\u56DE\u8C03\u3002\n */\nexport function expressionErrorsSignature(errors: Record<string, ExpressionError>): string {\n return Object.keys(errors)\n .sort()\n .map((id) => `${id}:${errors[id]!.kind}:${errors[id]!.message}`)\n .join('|');\n}\n\nexport interface CompiledExpression {\n evaluate(scope: Record<string, unknown>): unknown;\n}\n\nexport function compileExpression(raw: string): CompiledExpression {\n let node;\n try {\n node = parse(raw);\n } catch (err) {\n throw new ExpressionError('syntax', normalizeMessage(err));\n }\n const compiled = node.compile();\n return {\n evaluate(scope: Record<string, unknown>): unknown {\n try {\n return compiled.evaluate(scope);\n } catch (err) {\n throw normalizeThrown(err);\n }\n },\n };\n}\n\nexport type ValidationResult = { ok: true } | { ok: false; error: ExpressionError };\n\n/** mathjs \u5185\u7F6E\u51FD\u6570\u540D\uFF1A\u88AB\u5F53\u7B26\u53F7\u4F7F\u7528\uFF08sin*x\u3001\u88F8 sin\uFF09\u65F6 mathjs \u4F1A\u9759\u9ED8\u56DE\u9000\u89E3\u6790\uFF0C\u9700\u663E\u5F0F\u62E6\u622A */\nconst FUNCTION_LIKE_SYMBOLS = new Set([\n 'sin', 'cos', 'tan', 'cot', 'sec', 'csc',\n 'asin', 'acos', 'atan', 'acot', 'acsc', 'asec',\n 'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch',\n 'asinh', 'acosh', 'atanh', 'acoth', 'acsch', 'asech',\n 'log', 'log2', 'log10', 'exp', 'sqrt', 'abs',\n 'max', 'min', 'floor', 'ceil', 'round', 'sign',\n]);\n\n/**\n * \u8F93\u5165\u6846\u5B9E\u65F6\u6821\u9A8C\uFF1A\u8BED\u6CD5\u5FC5\u987B\u901A\u8FC7\uFF0C\u5E76\u7528\u5B89\u5168\u63A2\u9488\u6C42\u503C\u53D1\u73B0\u672A\u5B9A\u4E49\u7B26\u53F7\u3002\n * \u5B9A\u4E49\u57DF\u9519\u8BEF\uFF08\u5982\u67D0\u70B9\u9664\u96F6\u3001\u8D1F\u6839\u5F0F\uFF09\u4E0D\u5728\u8FD9\u91CC\u62E6\u622A\u2014\u2014\u4EA4\u7ED9\u753B\u5E03\u9010\u70B9\u6C42\u503C\u5904\u7406\u3002\n */\nexport function validateExpression(raw: string): ValidationResult {\n let node;\n try {\n node = parse(raw);\n } catch (err) {\n return { ok: false, error: new ExpressionError('syntax', normalizeMessage(err)) };\n }\n // \u51FD\u6570\u540D\u88AB\u5F53\u7B26\u53F7\uFF08\\sin x \u6F0F\u7F51\u8F6C\u6362\u6210 sin*x\uFF09\uFF1A\u63A2\u9488\u6C42\u503C\u4E0D\u62A5\u9519\u4F46\u9010\u70B9\u7ED3\u679C\u5168 NaN\uFF0C\n // \u66F2\u7EBF\u9759\u9ED8\u6D88\u5931\u3002\u8FD9\u91CC\u5728 AST \u5C42\u62E6\u622A\uFF08\u51FD\u6570\u8C03\u7528\u4F4D\u7F6E fn \u7684 SymbolNode \u9664\u5916\uFF09\u3002\n let functionLike: string | null = null;\n node.traverse((n: unknown, _path?: string, parent?: unknown) => {\n if (functionLike) return;\n const node = n as { isSymbolNode?: boolean; name?: string };\n const parentNode = parent as { isFunctionNode?: boolean; fn?: unknown } | undefined;\n if (!node.isSymbolNode) return;\n if (parentNode?.isFunctionNode && parentNode.fn === n) return;\n if (node.name && FUNCTION_LIKE_SYMBOLS.has(node.name)) functionLike = node.name;\n });\n if (functionLike) {\n const name = functionLike as string;\n return {\n ok: false,\n error: new ExpressionError('undefined-symbol', `\u51FD\u6570 ${name} \u9700\u5199\u6210 ${name}(x) \u5F62\u5F0F\u8C03\u7528`),\n };\n }\n try {\n const probe: Record<string, unknown> = {\n ...buildScope(),\n x: 1, y: 1, t: 1, theta: 1, r: 2,\n a: 1, b: 1, c: 1, n: 2, k: 1, x1: 1, x2: 2,\n };\n node.compile().evaluate(probe);\n } catch (err) {\n const normalized = normalizeThrown(err);\n if (normalized.kind === 'undefined-symbol') return { ok: false, error: normalized };\n }\n return { ok: true };\n}\n\n/** mathjs AST \u2192 LaTeX\uFF08\u539F\u6587\u6A21\u5F0F\u5B9E\u65F6\u9884\u89C8\u7528\uFF09\uFF1B\u89E3\u6790\u5931\u8D25\u8FD4\u56DE null */\nexport function tryToTex(raw: string): string | null {\n try {\n return parse(raw).toTex();\n } catch {\n return null;\n }\n}\n\n/** \u5750\u6807\u53D8\u91CF\u4FDD\u7559\u5B57\uFF1A\u7528\u6237\u53C2\u6570\u4E0D\u5141\u8BB8\u4EE5\u6B64\u547D\u540D\uFF08\u5426\u5219\u8986\u76D6\u91C7\u6837\u5750\u6807\uFF0C\u56FE\u5F62\u9519\u8BEF\uFF09 */\nexport const RESERVED_SCOPE_NAMES = new Set(['x', 'y', 'z', 't', 'theta', 'r']);\n\n/** \u4F5C\u56FE scope\uFF1A\u5750\u6807\u53D8\u91CF\u3001\u6ED1\u6746\u53C2\u6570\u3001\u5E0C\u814A\u5B57\u6BCD\u53D8\u91CF\u4E0E\u4F3D\u9A6C\u51FD\u6570\uFF08\\Gamma\uFF09 */\nexport function buildScope(parameters: ParameterSpec[] = []): Record<string, unknown> {\n const scope: Record<string, unknown> = {\n x: 0, y: 0, z: 0, t: 0, theta: 0, r: 0,\n alpha: 1, beta: 1, gamma: 1, delta: 1, epsilon: 1,\n lambda: 1, mu: 1, sigma: 1, omega: 1,\n // MathLive \u5E0C\u814A\u952E\u76D8\u5C42\u5168\u91CF\u5B57\u6BCD\uFF08\u4E0E latex-to-mathjs CONST_WORDS \u5BF9\u9F50\uFF09\n phi: 1, eta: 1, xi: 1, kappa: 1, nu: 1, rho: 1, tau: 1, upsilon: 1,\n chi: 1, psi: 1, zeta: 1, iota: 1, omicron: 1,\n vartheta: 1, varepsilon: 1, varsigma: 1, varpi: 1, varrho: 1, varkappa: 1, varphi: 1,\n gammaFn: (x: number) => gamma(x),\n };\n // \u4FDD\u7559\u5B57\u4E0E\u975E\u6CD5\u540D\u9759\u9ED8\u5FFD\u7565\uFF08UI \u5C42\u6709\u884C\u5185\u6821\u9A8C\u63D0\u793A\uFF09\uFF0C\u4FDD\u8BC1\u91C7\u6837\u5750\u6807\u4E0D\u88AB\u8986\u76D6\n for (const p of parameters) {\n if (!RESERVED_SCOPE_NAMES.has(p.name)) scope[p.name] = p.value;\n }\n return scope;\n}\n\n/**\n * \u9010\u70B9\u5B89\u5168\u6C42\u503C\uFF1A\u4EFB\u4F55\u5F02\u5E38\u3001\u65E0\u7A77\u3001\u975E\u5B9E\u6570\u7ED3\u679C \u2192 NaN\uFF08\u5F15\u64CE\u9047 NaN \u65AD\u7F1D\uFF09\u3002\n * \u6CE8\u610F\uFF1A\u6BCF\u70B9\u521B\u5EFA scope \u5BF9\u8C61\uFF0C\u57FA\u51C6\u96C6 18 \u677F\u4ECD\u5728 30ms \u5185\uFF1B\u5982 P2 \u6ED1\u6746\u5E27\u7387\u5403\u7D27\u518D\u4F18\u5316\u4E3A\u9884\u5206\u914D\u3002\n */\nexport function makePointEvaluator(expr: CompiledExpression, base: Record<string, unknown>) {\n return (vars: Record<string, number>): number => {\n try {\n const v = expr.evaluate({ ...base, ...vars }) as unknown;\n if (typeof v === 'number') return Number.isFinite(v) ? v : NaN;\n // mathjs \u9047\u8D1F\u6839\u5F0F\u7B49\u8FD4\u56DE Complex / \u5206\u6570\u5BF9\u8C61\uFF1A\u4F5C\u56FE\u53EA\u753B\u5B9E\u6570\u90E8\u5206\n if (v && typeof v === 'object' && 'im' in v && (v as { im: number }).im !== 0) return NaN;\n if (v && typeof v === 'object' && 'valueOf' in v) {\n const n = Number(v.valueOf());\n return Number.isFinite(n) ? n : NaN;\n }\n const n = Number(v);\n return Number.isFinite(n) ? n : NaN;\n } catch {\n return NaN;\n }\n };\n}\n\nfunction normalizeThrown(err: unknown): ExpressionError {\n const message = normalizeMessage(err);\n if (/Undefined symbol|\u672A\u5B9A\u4E49/.test(message)) return new ExpressionError('undefined-symbol', message);\n if (/Complex|domain|\u8303\u56F4|division by zero|\u9664\u6570/i.test(message)) {\n return new ExpressionError('domain', message);\n }\n return new ExpressionError('syntax', message);\n}\n\nfunction normalizeMessage(err: unknown): string {\n const raw = err instanceof Error ? err.message : String(err);\n return (raw.split('\\n')[0] ?? raw).slice(0, 120);\n}\n", "/**\n * \u6570\u636E\u5750\u6807 \u2194 \u50CF\u7D20\u5750\u6807\u7EBF\u6027\u6620\u5C04\uFF08\u65E0 DOM\uFF0C\u7EAF\u51FD\u6570\uFF09\u3002\n * SVG \u7684 y \u8F74\u5411\u4E0B\u589E\u957F\uFF0C\u6545 y2px \u9700\u7FFB\u8F6C\u3002\n */\nexport interface Scale {\n x2px(x: number): number;\n y2px(y: number): number;\n}\n\nexport function createScales(\n xMin: number,\n xMax: number,\n yMin: number,\n yMax: number,\n width: number,\n height: number,\n padding: number,\n): Scale {\n const innerW = Math.max(1, width - padding * 2);\n const innerH = Math.max(1, height - padding * 2);\n const xScale = innerW / (xMax - xMin);\n const yScale = innerH / (yMax - yMin);\n return {\n x2px(x: number): number {\n return padding + (x - xMin) * xScale;\n },\n y2px(y: number): number {\n return height - padding - (y - yMin) * yScale;\n },\n };\n}\n", "/**\n * \u81EA\u9002\u5E94\u91C7\u6837\u5668\uFF1A\u5747\u5300\u7C97\u91C7 \u2192 \u66F2\u7387\u52A0\u5BC6 \u2192 \u6E10\u8FD1\u7EBF\u65AD\u70B9\u3002\n * \u6E10\u8FD1\u7EBF\u7B56\u7565\u6CBF\u7528 jsxgraphAdapter \u5DF2\u9A8C\u8BC1\u7ECF\u9A8C\uFF1A\n * |f(x)| \u8D85\u53EF\u89C1 Y \u57DF 10 \u500D\u7F6E NaN\uFF0C\u8FDE\u7EED \u22652 \u4E2A NaN \u65AD\u5206\u6BB5\uFF08\u907F\u514D\u591A\u9879\u5F0F\u62DF\u5408\u8BEF\u8FDE\uFF09\u3002\n */\nimport type { UnivariateFn } from '../core/numeric.js';\n\nexport type Point2 = readonly [number, number];\n/** \u8FDE\u7EED\u6BB5\uFF1A\u6BCF\u6BB5\u662F Point2 \u6570\u7EC4\uFF0C\u6BB5\u95F4\u65AD\u5F00\u5904\u4E0D\u8FDE\u7EBF */\nexport type Segment = Point2[];\nexport type Segments = Segment[];\n\n/** \u7C97\u91C7\u70B9\u6570 */\nconst COARSE_POINTS = 200;\n/** \u9012\u5F52\u52A0\u5BC6\u6700\u5927\u6DF1\u5EA6\uFF08200 * 2^10 \u5728\u7CBE\u5EA6/\u6027\u80FD\u95F4\u8DB3\u591F\uFF09 */\nconst MAX_REFINE_DEPTH = 10;\n/** \u6E10\u8FD1\u7EBF\u9608\u503C\u500D\u6570 */\nconst ASYMPTOTE_FACTOR = 10;\n/** \u66F2\u7387\u9608\u503C\uFF08\u5F27\u5EA6\uFF09\uFF1A\u76F8\u90BB\u4E24\u7EBF\u6BB5\u7684\u8F6C\u89D2\u8D85\u8FC7\u8BE5\u503C\u65F6\u9012\u5F52\u52A0\u5BC6 */\nconst CURVATURE_RAD_THRESHOLD = 0.05;\n\n/**\n * \u91C7\u6837\u5E76\u8FD4\u56DE\u8FDE\u7EED\u6BB5\u6570\u7EC4\u3002\n * @param fn NaN \u5B89\u5168\u7684\u4E00\u5143\u51FD\u6570\uFF08x \u8D85\u51FA\u5B9A\u4E49\u57DF\u8FD4\u56DE NaN\uFF09\n * @param xMin \u91C7\u6837\u5DE6\u7AEF\u70B9\n * @param xMax \u91C7\u6837\u53F3\u7AEF\u70B9\n * @param yMin \u53EF\u89C1 Y \u6700\u5C0F\u503C\uFF08\u7528\u4E8E\u6E10\u8FD1\u7EBF\u5224\u5B9A\uFF09\n * @param yMax \u53EF\u89C1 Y \u6700\u5927\u503C\uFF08\u7528\u4E8E\u6E10\u8FD1\u7EBF\u5224\u5B9A\uFF09\n */\nexport function adaptiveSample(\n fn: UnivariateFn,\n xMin: number,\n xMax: number,\n yMin: number,\n yMax: number,\n): Segments {\n const yAbsMax = Math.max(Math.abs(yMin), Math.abs(yMax));\n const asymptoteLimit = yAbsMax * ASYMPTOTE_FACTOR;\n const points: Point2[] = [];\n let total = 0;\n\n // \u7C97\u91C7\n for (let i = 0; i <= COARSE_POINTS; i += 1) {\n const x = xMin + ((xMax - xMin) * i) / COARSE_POINTS;\n const y = fn(x);\n points.push([x, sanitizeY(y, asymptoteLimit)]);\n total += 1;\n }\n\n // \u66F2\u7387\u52A0\u5BC6\uFF08\u9012\u5F52\uFF09\uFF1BNaN \u76F8\u90BB\u533A\u95F4\u540C\u6837\u4E8C\u5206\u2014\u2014\u7528\u4E8E\u903C\u8FD1\u6E10\u8FD1\u7EBF/\u5B9A\u4E49\u57DF\u8FB9\u754C\uFF0C\u5236\u9020\u8FDE\u7EED NaN \u89E6\u53D1\u5206\u6BB5\n const refined: Point2[] = [];\n for (let i = 0; i < points.length - 1; i += 1) {\n const p0 = points[i]!;\n const p1 = points[i + 1]!;\n refined.push(p0);\n refineRecursive(p0, p1, fn, asymptoteLimit, 0, refined);\n }\n refined.push(points[points.length - 1]!);\n total = refined.length; // eslint-disable-line prefer-const\n\n // \u5206\u6BB5\uFF1A\u8FDE\u7EED NaN \u22652 \u65AD\u6BB5\uFF1B\u5355\u70B9 NaN \u89C6\u4E3A\u7A7F\u8D8A\u5B9A\u4E49\u57DF\u8FB9\u754C\uFF0C\u4E0D\u5355\u72EC\u65AD\u5F00\n return splitSegments(refined, total);\n}\n\nfunction refineRecursive(\n p0: Point2,\n p1: Point2,\n fn: UnivariateFn,\n asymptoteLimit: number,\n depth: number,\n out: Point2[],\n): void {\n if (depth >= MAX_REFINE_DEPTH) {\n out.push(p1);\n return;\n }\n const mx = (p0[0] + p1[0]) / 2;\n const my = sanitizeY(fn(mx), asymptoteLimit);\n const mid: Point2 = [mx, my];\n\n const p0Ok = Number.isFinite(p0[1]);\n const p1Ok = Number.isFinite(p1[1]);\n const midOk = Number.isFinite(my);\n\n if (!p0Ok || !p1Ok || !midOk) {\n // \u4EFB\u4E00\u7AEF\u70B9/\u4E2D\u70B9\u975E\u6709\u9650\uFF1A\u4E8C\u5206\u903C\u8FD1 NaN \u533A\u57DF\u8FB9\u754C\uFF0C\u76F4\u81F3\u6DF1\u5EA6\u4E0A\u9650\n refineRecursive(p0, mid, fn, asymptoteLimit, depth + 1, out);\n refineRecursive(mid, p1, fn, asymptoteLimit, depth + 1, out);\n return;\n }\n\n // \u66F2\u7387 = \u5411\u91CF (p0\u2192m) \u4E0E (m\u2192p1) \u7684\u8F6C\u89D2\n const dx1 = mx - p0[0];\n const dy1 = my - p0[1];\n const dx2 = p1[0] - mx;\n const dy2 = p1[1] - my;\n const angle = Math.atan2(dy2, dx2) - Math.atan2(dy1, dx1);\n const curvature = Math.abs(angle);\n\n if (curvature > CURVATURE_RAD_THRESHOLD) {\n refineRecursive(p0, mid, fn, asymptoteLimit, depth + 1, out);\n refineRecursive(mid, p1, fn, asymptoteLimit, depth + 1, out);\n } else {\n out.push(mid, p1);\n }\n}\n\nfunction sanitizeY(y: number, asymptoteLimit: number): number {\n if (!Number.isFinite(y)) return NaN;\n if (Math.abs(y) > asymptoteLimit) return NaN;\n return y;\n}\n\n/**\n * \u5C06\u70B9\u5E8F\u5217\u6309\u8FDE\u7EED NaN \u5206\u6BB5\u3002\n * \u5355\u70B9 NaN\uFF1A\u8DF3\u8FC7\uFF08\u89C6\u4E3A\u77ED\u6682\u5B9A\u4E49\u57DF\u7F3A\u53E3\uFF09\uFF1B\n * \u8FDE\u7EED \u22652 NaN\uFF1A\u65AD\u5F00\u5206\u6BB5\uFF08\u6E10\u8FD1\u7EBF\u884C\u4E3A\uFF09\u3002\n */\nfunction splitSegments(points: Point2[], _total: number): Segments {\n const segments: Segments = [];\n let current: Point2[] = [];\n let nanRun = 0;\n\n const flush = (): void => {\n if (current.length >= 2) segments.push(current);\n current = [];\n };\n\n for (const p of points) {\n if (Number.isFinite(p[1])) {\n if (nanRun === 1 && current.length > 0) {\n // \u5355\u70B9 NaN \u7A7F\u8D8A\uFF1A\u4FDD\u7559\u5F53\u524D\u6BB5\uFF0C\u7EE7\u7EED\u8854\u63A5\n } else if (nanRun >= 2) {\n flush();\n }\n nanRun = 0;\n current.push(p);\n } else {\n nanRun += 1;\n }\n }\n flush();\n return segments;\n}\n", "/**\n * \u8F74/\u7F51\u683C/\u523B\u5EA6\u5E03\u5C40\uFF1Anice-number \u6B65\u957F\uFF081/2/5\u00D710^n\uFF09\uFF0C\n * \u8F93\u51FA\u50CF\u7D20\u5750\u6807\u7531\u8C03\u7528\u65B9\u7ED3\u5408 Scale \u6362\u7B97\uFF08\u4FDD\u6301\u672C\u6A21\u5757\u7EAF\u6570\u636E\u3001\u65E0\u4F9D\u8D56\uFF09\u3002\n */\n\nexport interface Tick {\n value: number;\n label: string;\n}\n\n/** nice-number \u6B65\u957F\uFF1A1/2/5\u00D710^n\uFF0C\u53D6\u4F7F\u523B\u5EA6\u6570 \u2264 maxTicks \u7684\u6700\u7EC6\u6863 */\nexport function niceStep(range: number, maxTicks: number): number {\n if (range <= 0 || !Number.isFinite(range)) return 1;\n const rough = range / Math.max(1, maxTicks);\n const mag = 10 ** Math.floor(Math.log10(rough));\n for (const mul of [1, 2, 5, 10]) {\n if (mag * mul >= rough) return mag * mul;\n }\n return mag * 10;\n}\n\n/**\n * \u751F\u6210 [min, max] \u533A\u95F4\u5185\u7684\u523B\u5EA6\uFF08\u542B 0 \u523B\u5EA6\u82E5\u53EF\u89C1\uFF09\u3002\n * \u6807\u7B7E\u7528 Number.toPrecision \u622A\u65AD\uFF0C\u9632\u6D6E\u70B9\u566A\u58F0\u3002\n */\nexport function generateTicks(min: number, max: number, maxTicks: number): Tick[] {\n const step = niceStep(max - min, maxTicks);\n const ticks: Tick[] = [];\n const start = Math.ceil(min / step) * step;\n for (let v = start; v <= max + step * 1e-9; v += step) {\n const snapped = Math.abs(v) < step * 1e-9 ? 0 : v;\n ticks.push({ value: snapped, label: formatTickLabel(snapped) });\n }\n return ticks;\n}\n\nfunction formatTickLabel(v: number): string {\n if (v === 0) return '0';\n if (Number.isInteger(v)) return String(v);\n const abs = Math.abs(v);\n if (abs >= 1e6 || abs < 1e-4) return v.toExponential(2);\n return Number(v.toPrecision(6)).toString();\n}\n", "/**\n * SVG \u5B57\u7B26\u4E32\u62FC\u88C5\u5668\uFF1A\u4E0D\u4F9D\u8D56 XML/DOM \u5E93\uFF0C\u5168\u90E8\u5B57\u7B26\u4E32\u62FC\u63A5\u3002\n * \u9632\u5FA1\uFF1A\u6240\u6709\u7528\u6237\u8F93\u5165/\u52A8\u6001\u6587\u672C\u7ECF xmlEscape \u8F6C\u4E49\uFF1B\n * \u53EA\u751F\u6210 path/line/rect/text/circle/g \u767D\u540D\u5355\u5143\u7D20\uFF0C\u7981\u6B62 script/\u4E8B\u4EF6\u5C5E\u6027\u3002\n */\n\nexport interface SvgPath {\n type: 'path';\n d: string;\n stroke: string;\n strokeWidth: number;\n fill?: string;\n}\n\nexport interface SvgLine {\n type: 'line';\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n stroke: string;\n strokeWidth: number;\n}\n\nexport interface SvgText {\n type: 'text';\n x: number;\n y: number;\n text: string;\n fill: string;\n fontSize?: number;\n textAnchor?: 'start' | 'middle' | 'end';\n dominantBaseline?: 'hanging' | 'middle' | 'alphabetic';\n}\n\nexport interface SvgRect {\n type: 'rect';\n x: number;\n y: number;\n width: number;\n height: number;\n fill: string;\n}\n\nexport type SvgElement = SvgPath | SvgLine | SvgText | SvgRect;\n\nexport function xmlEscape(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function buildSvg(\n width: number,\n height: number,\n elements: SvgElement[],\n): string {\n const parts: string[] = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n ];\n for (const el of elements) {\n switch (el.type) {\n case 'path':\n parts.push(\n `<path d=\"${el.d}\" fill=\"none\" stroke=\"${el.stroke}\" stroke-width=\"${el.strokeWidth}\" />`,\n );\n break;\n case 'line':\n parts.push(\n `<line x1=\"${fmt(el.x1)}\" y1=\"${fmt(el.y1)}\" x2=\"${fmt(el.x2)}\" y2=\"${fmt(el.y2)}\" stroke=\"${el.stroke}\" stroke-width=\"${el.strokeWidth}\" />`,\n );\n break;\n case 'rect':\n parts.push(\n `<rect x=\"${fmt(el.x)}\" y=\"${fmt(el.y)}\" width=\"${fmt(el.width)}\" height=\"${fmt(el.height)}\" fill=\"${el.fill}\" />`,\n );\n break;\n case 'text': {\n const ta = el.textAnchor ? ` text-anchor=\"${el.textAnchor}\"` : '';\n const db = el.dominantBaseline\n ? ` dominant-baseline=\"${el.dominantBaseline}\"`\n : '';\n const fs = el.fontSize ? ` font-size=\"${el.fontSize}px\"` : '';\n parts.push(\n `<text x=\"${fmt(el.x)}\" y=\"${fmt(el.y)}\" fill=\"${el.fill}\"${fs}${ta}${db}>${xmlEscape(el.text)}</text>`,\n );\n break;\n }\n }\n }\n parts.push('</svg>');\n return parts.join('');\n}\n\nfunction fmt(n: number): string {\n return Number(n.toPrecision(6)).toString();\n}\n", "/**\n * \u670D\u52A1\u7AEF\u51FD\u6570\u56FE\u50CF\u6E32\u67D3\u4E3B\u5165\u53E3\uFF08Q4 \u4EFB\u52A1 2.3 / 4.2\uFF09\u3002\n * \u65E0 DOM \u4F9D\u8D56\uFF1Amathjs \u7F16\u8BD1 + \u81EA\u9002\u5E94\u91C7\u6837 + \u5B57\u7B26\u4E32 SVG \u751F\u6210\u3002\n * \u914D\u5957 W5\uFF1Aresvg-js \u8F6C PNG\uFF1Bdark \u4E3B\u9898\u4E00\u671F\u5DF2\u5185\u7F6E\u8C03\u8272\u677F\u3002\n */\nimport { makeUnivariate } from '../core/numeric.js';\nimport { validateExpression, ExpressionError } from '../core/compile.js';\n\nexport { ExpressionError } from '../core/compile.js';\nimport { createScales } from './scales.js';\nimport { adaptiveSample, type Segments } from './sampler.js';\nimport { generateTicks } from './axes.js';\nimport { buildSvg, type SvgElement } from './svg-builder.js';\n\nexport type PlotTheme = 'light' | 'dark';\n\nexport interface PlotExpressionInput {\n /** mathjs \u8868\u8FBE\u5F0F\uFF08\u663E\u5F0F y=f(x)\uFF09 */\n raw: string;\n /** \u53EF\u9009\u56FE\u4F8B\u6587\u672C\uFF08\u7EAF\u6587\u672C Unicode\uFF0C\u4E00\u671F\u4E0D\u652F\u6301\u5B8C\u6574 LaTeX\uFF09 */\n label?: string;\n /** \u53EF\u9009 hex \u8986\u76D6\u8C03\u8272\u677F\u989C\u8272 */\n color?: string;\n}\n\nexport interface PlotSpec {\n expressions: PlotExpressionInput[];\n viewport?: { xMin: number; xMax: number; yMin: number; yMax: number };\n size?: { width: number; height: number };\n theme?: PlotTheme;\n grid?: boolean;\n axis?: boolean;\n}\n\nexport interface PlotExpressionResult {\n raw: string;\n ok: boolean;\n sampleCount?: number;\n segments?: number;\n error?: string;\n}\n\nexport interface PlotTiming {\n compileMs: number;\n sampleMs: number;\n renderMs: number;\n}\n\nexport interface PlotResult {\n svg: string;\n expressions: PlotExpressionResult[];\n timing: PlotTiming;\n}\n\n/** \u5355\u6761\u8868\u8FBE\u5F0F mathjs \u539F\u6587\u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A7\u4E94\uFF09 */\nexport const EXPRESSION_RAW_MAX_LENGTH = 200;\n/** \u5355\u6B21\u8BF7\u6C42\u8868\u8FBE\u5F0F\u6761\u6570\u4E0A\u9650\uFF08\u5BF9\u9F50 SERIES_PALETTE 8 \u8272\uFF09 */\nexport const EXPRESSIONS_MAX = 8;\n/** \u753B\u5E03\u8FB9\u957F\u4E0A\u9650\uFF08\u65B9\u6848 \u00A72.2\uFF09 */\nexport const SIZE_MAX = 2000;\n\nconst LIGHT_PALETTE = [\n '#2563eb', '#dc2626', '#16a34a', '#d97706', '#7c3aed', '#0891b2', '#db2777', '#4b5563',\n];\nconst DARK_PALETTE = [\n '#60a5fa', '#f87171', '#4ade80', '#fbbf24', '#a78bfa', '#22d3ee', '#f472b6', '#9ca3af',\n];\n\nconst PADDING = 48;\nconst TICKS_X = 8;\nconst TICKS_Y = 6;\n\nexport function renderPlotSvg(spec: PlotSpec): PlotResult {\n const width = Math.min(spec.size?.width ?? 800, SIZE_MAX);\n const height = Math.min(spec.size?.height ?? 500, SIZE_MAX);\n const theme = spec.theme ?? 'light';\n const palette = theme === 'dark' ? DARK_PALETTE : LIGHT_PALETTE;\n\n const vp = spec.viewport ?? { xMin: -6, xMax: 6, yMin: -4, yMax: 4 };\n const scale = createScales(vp.xMin, vp.xMax, vp.yMin, vp.yMax, width, height, PADDING);\n\n const elements: SvgElement[] = [];\n const exprResults: PlotExpressionResult[] = [];\n let compileMs = 0;\n let sampleMs = 0;\n\n // \u80CC\u666F\uFF08\u542B\u5E95\u8272 rect\uFF0CPNG \u8F6C\u6362\u65F6\u81EA\u5E26\u80CC\u666F\uFF09\n elements.push({\n type: 'rect',\n x: 0,\n y: 0,\n width,\n height,\n fill: theme === 'dark' ? '#1c1f25' : '#ffffff',\n });\n\n // \u7F51\u683C\uFF08\u5148\u4E8E\u8F74\u7ED8\u5236\uFF0C\u8F74\u8986\u76D6\u7F51\u683C\uFF09\n if (spec.grid ?? true) {\n const gridColor = theme === 'dark' ? '#2d333b' : '#e5e5e5';\n const xTicks = generateTicks(vp.xMin, vp.xMax, TICKS_X);\n const yTicks = generateTicks(vp.yMin, vp.yMax, TICKS_Y);\n for (const t of xTicks) {\n const px = scale.x2px(t.value);\n elements.push({ type: 'line', x1: px, y1: PADDING, x2: px, y2: height - PADDING, stroke: gridColor, strokeWidth: 0.5 });\n }\n for (const t of yTicks) {\n const py = scale.y2px(t.value);\n elements.push({ type: 'line', x1: PADDING, y1: py, x2: width - PADDING, y2: py, stroke: gridColor, strokeWidth: 0.5 });\n }\n }\n\n // \u8F74 + \u523B\u5EA6\u6807\u7B7E\n if (spec.axis ?? true) {\n const axisColor = theme === 'dark' ? '#9ca3af' : '#666666';\n const axisY0 = scale.y2px(0);\n const axisX0 = scale.x2px(0);\n if (vp.yMin <= 0 && vp.yMax >= 0) {\n elements.push({ type: 'line', x1: PADDING, y1: axisY0, x2: width - PADDING, y2: axisY0, stroke: axisColor, strokeWidth: 1 });\n }\n if (vp.xMin <= 0 && vp.xMax >= 0) {\n elements.push({ type: 'line', x1: axisX0, y1: PADDING, x2: axisX0, y2: height - PADDING, stroke: axisColor, strokeWidth: 1 });\n }\n const tickTextFill = theme === 'dark' ? '#9ca3af' : '#374151';\n for (const t of generateTicks(vp.xMin, vp.xMax, TICKS_X)) {\n if (t.value === 0) continue;\n elements.push({\n type: 'text', x: scale.x2px(t.value), y: axisY0 + 18, text: t.label,\n fill: tickTextFill, fontSize: 12, textAnchor: 'middle', dominantBaseline: 'hanging',\n });\n }\n for (const t of generateTicks(vp.yMin, vp.yMax, TICKS_Y)) {\n if (t.value === 0) continue;\n elements.push({\n type: 'text', x: axisX0 - 10, y: scale.y2px(t.value), text: t.label,\n fill: tickTextFill, fontSize: 12, textAnchor: 'end', dominantBaseline: 'middle',\n });\n }\n if (vp.xMin <= 0 && vp.xMax >= 0 && vp.yMin <= 0 && vp.yMax >= 0) {\n elements.push({\n type: 'text', x: axisX0 - 8, y: axisY0 + 14, text: '0',\n fill: tickTextFill, fontSize: 12, textAnchor: 'end', dominantBaseline: 'hanging',\n });\n }\n }\n\n // \u66F2\u7EBF\n for (const [i, expr] of spec.expressions.entries()) {\n const raw = expr.raw;\n const color = expr.color ?? palette[i % palette.length]!;\n\n const tC = Date.now();\n const validation = validateExpression(raw);\n compileMs += Date.now() - tC;\n\n if (!validation.ok) {\n exprResults.push({\n raw,\n ok: false,\n error: `${validation.error.kind}: ${validation.error.message}`,\n });\n continue;\n }\n\n const tS = Date.now();\n const fn = makeUnivariate(raw);\n const segments = adaptiveSample(fn, vp.xMin, vp.xMax, vp.yMin, vp.yMax);\n sampleMs += Date.now() - tS;\n\n let sampleCount = 0;\n const pathParts: string[] = [];\n for (const seg of segments) {\n sampleCount += seg.length;\n if (seg.length < 2) continue;\n pathParts.push(segmentToPathD(seg, scale));\n }\n\n if (pathParts.length > 0) {\n elements.push({\n type: 'path',\n d: pathParts.join(' '),\n stroke: color,\n strokeWidth: 2,\n });\n }\n\n exprResults.push({ raw, ok: true, sampleCount, segments: segments.length });\n\n // \u56FE\u4F8B\uFF08\u7EAF\u6587\u672C Unicode\uFF0C\u4E00\u671F\uFF09\n if (expr.label) {\n elements.push({\n type: 'text',\n x: width - PADDING - 8,\n y: PADDING + 18 * (exprResults.filter((r) => r.ok).length - 1) + 12,\n text: expr.label,\n fill: color,\n fontSize: 13,\n textAnchor: 'end',\n dominantBaseline: 'hanging',\n });\n }\n }\n\n const tR = Date.now();\n const svg = buildSvg(width, height, elements);\n const renderMs = Date.now() - tR;\n\n const allFailed = exprResults.every((r) => !r.ok);\n if (allFailed && exprResults.length > 0) {\n throw new ExpressionError('syntax', '\u6240\u6709\u8868\u8FBE\u5F0F\u5747\u65E0\u6CD5\u7F16\u8BD1');\n }\n\n return {\n svg,\n expressions: exprResults,\n timing: { compileMs, sampleMs, renderMs },\n };\n}\n\nfunction segmentToPathD(seg: Segments[number], scale: ReturnType<typeof createScales>): string {\n const parts: string[] = [];\n for (const [i, [x, y]] of seg.entries()) {\n const px = scale.x2px(x);\n const py = scale.y2px(y);\n parts.push(`${i === 0 ? 'M' : 'L'}${Number(px.toPrecision(6))},${Number(py.toPrecision(6))}`);\n }\n return parts.join(' ');\n}\n", "/**\n * geometry_solve \u5DE5\u5177\uFF1A\u6821\u9A8C\u5E76\u5BF9 protocolVersion=1 \u7684\u5E73\u9762\u51E0\u4F55\u6587\u6863\u505A\u5143\u7D20\u6C42\u89E3\u3002\n * \u65E0 DOM\u3001\u65E0\u72B6\u6001\uFF0C\u76F4\u8C03 @studylab/viz/geometry-kernel\u3002\n * \u6587\u6863/\u8BCA\u65AD\u7C7B\u5931\u8D25\u4EE5 ok:false \u8FD4\u56DE\uFF08\u4FBF\u4E8E AI \u8BFB\u53D6\u8BCA\u65AD\uFF09\uFF1B\u4EC5\u53C2\u6570\u7ED3\u6784\u635F\u574F\u624D isError\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n resolveGeometryDocument,\n type GeometryDiagnostic,\n type ResolvedElement,\n} from '@studylab/viz/geometry-kernel';\n\n/** \u6587\u6863 JSON \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const DOCUMENT_JSON_MAX_LENGTH = 512 * 1024;\n\n/** \u6587\u672C\u6807\u6CE8 raw \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const TEXT_RAW_MAX_LENGTH = 2000;\n\ntype GeometryDocumentInput = Parameters<typeof resolveGeometryDocument>[0];\n\nexport interface GeometrySolveArgs {\n document: unknown;\n options?: { includeResolved?: boolean };\n}\n\n/** \u89C4\u8303\u5316\u526F\u672C\uFF1A\u53EA\u4FDD\u7559 GeometryDocument \u7684\u9876\u5C42\u5B57\u6BB5 */\ninterface NormalizedGeometryDocument {\n protocolVersion: unknown;\n elements: unknown;\n viewport: unknown;\n style?: unknown;\n}\n\ninterface GeometrySolvePayload {\n ok: boolean;\n document?: NormalizedGeometryDocument | null;\n resolved?: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n}\n\nfunction textResult(payload: GeometrySolvePayload): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalidDocument(message: string): GeometryDiagnostic {\n return { code: 'INVALID_DOCUMENT', severity: 'error', message };\n}\n\nfunction normalizeDocument(document: Record<string, unknown>): NormalizedGeometryDocument {\n const normalized: NormalizedGeometryDocument = {\n protocolVersion: document.protocolVersion,\n elements: Array.isArray(document.elements) ? document.elements : [],\n viewport: isRecord(document.viewport) ? document.viewport : {},\n };\n if (document.style !== undefined) normalized.style = document.style;\n return normalized;\n}\n\n/** \u6587\u672C raw \u8D85\u9650\u8BCA\u65AD\uFF08\u5143\u7D20\u6570\u8D85\u9650\u7531\u5185\u6838\u4EA7\u51FA\uFF09 */\nfunction textLimitDiagnostics(elements: unknown[]): GeometryDiagnostic[] {\n const diagnostics: GeometryDiagnostic[] = [];\n for (const element of elements) {\n if (!isRecord(element) || element.type !== 'text') continue;\n if (typeof element.raw !== 'string' || element.raw.length <= TEXT_RAW_MAX_LENGTH) continue;\n const elementId = typeof element.id === 'string' ? element.id : undefined;\n diagnostics.push({\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n elementId,\n message: `\u6587\u672C\u5143\u7D20 \"${elementId ?? '?'}\" \u7684 raw \u8D85\u8FC7 ${TEXT_RAW_MAX_LENGTH} \u5B57\u7B26`,\n });\n }\n return diagnostics;\n}\n\nexport async function handleGeometrySolve(args: GeometrySolveArgs): Promise<CallToolResult> {\n try {\n if (!args || typeof args !== 'object' || !('document' in args)) {\n return errorResult('\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981 { document: object, options?: { includeResolved?: boolean } }');\n }\n\n const { document, options } = args;\n const includeResolved = options?.includeResolved !== false;\n\n if (JSON.stringify(document).length > DOCUMENT_JSON_MAX_LENGTH) {\n return textResult({\n ok: false,\n diagnostics: [\n {\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n message: `\u6587\u6863 JSON \u8D85\u8FC7 ${DOCUMENT_JSON_MAX_LENGTH} \u5B57\u8282\uFF08512KB\uFF09`,\n },\n ],\n });\n }\n\n if (!isRecord(document)) {\n return textResult({\n ok: false,\n document: null,\n diagnostics: [invalidDocument('\u6587\u6863\u5FC5\u987B\u662F protocolVersion=1 \u7684 GeometryDocument \u5BF9\u8C61')],\n });\n }\n\n const normalized = normalizeDocument(document);\n if (document.protocolVersion !== 1 || 'equations' in document) {\n return textResult({\n ok: false,\n document: normalized,\n diagnostics: [invalidDocument('\u4EC5\u652F\u6301 protocolVersion=1 \u4E14\u4E0D\u542B equations \u7684 GeometryDocument')],\n });\n }\n\n const result = resolveGeometryDocument(document as unknown as GeometryDocumentInput);\n const diagnostics: GeometryDiagnostic[] = [\n ...result.diagnostics,\n ...textLimitDiagnostics(Array.isArray(document.elements) ? document.elements : []),\n ];\n\n return textResult({\n ok: diagnostics.every((d) => d.severity !== 'error'),\n document: normalized,\n ...(includeResolved ? { resolved: result.elements } : {}),\n diagnostics,\n });\n } catch (error) {\n // \u515C\u5E95\uFF1A\u53C2\u6570\u5F02\u5E38\uFF08\u5982\u5FAA\u73AF\u5F15\u7528\uFF09\u6216\u5185\u6838\u610F\u5916\u5F02\u5E38\u90FD\u4E0D\u80FD\u8BA9 stdio \u8FDB\u7A0B\u5D29\u6E83\n return errorResult(error instanceof Error ? error.message : String(error));\n }\n}\n\n/** \u5411 MCP server \u6CE8\u518C geometry_solve \u5DE5\u5177 */\nexport function registerGeometrySolveTool(server: McpServer): void {\n server.registerTool(\n 'geometry_solve',\n {\n title: '\u51E0\u4F55\u6587\u6863\u6C42\u89E3\u4E0E\u6821\u9A8C',\n description:\n '\u6821\u9A8C protocolVersion=1 \u7684\u5E73\u9762\u51E0\u4F55\u6587\u6863\u5E76\u6C42\u89E3\u5143\u7D20\uFF08\u5750\u6807\u3001\u4EA4\u70B9\u3001\u6D4B\u91CF\uFF09\uFF0C\u8FD4\u56DE\u89C4\u8303\u5316\u6587\u6863\u4E0E\u8BCA\u65AD\uFF1B\u53EF\u4F9B AI \u6784\u9020\u6587\u6863\u540E\u81EA\u68C0\uFF0C\u8FD4\u56DE\u7684 document \u53EF\u76F4\u63A5\u4F20\u7ED9\u540E\u7EED\u5DE5\u5177\u3002',\n inputSchema: {\n document: z.record(z.unknown()),\n options: z.object({ includeResolved: z.boolean().default(true) }).optional(),\n },\n },\n (args) => handleGeometrySolve(args),\n );\n}\n", "/**\n * \u51E0\u4F55\u6784\u9020\u56FE\uFF1A\u4F9D\u8D56\u62D3\u6251\u4E0E\u7EA7\u8054\u5220\u9664\u3002\n *\n * \u6784\u9020\u987A\u5E8F\u5373 elements \u6570\u7EC4\u987A\u5E8F\uFF1Bparents \u5FC5\u987B\u5F15\u7528\u5DF2\u5B58\u5728\u5143\u7D20\u3002\n * \u5220\u9664\u7236\u5143\u7D20\u65F6\uFF0C\u6240\u6709\u540E\u4EE3\uFF08\u76F4\u63A5/\u95F4\u63A5\u4F9D\u8D56\u8005\uFF09\u5FC5\u987B\u7EA7\u8054\u5220\u9664\u3002\n */\n\nimport type { GeometryElementSpec } from '../core/types-geometry';\n\n/** \u5143\u7D20\u4F9D\u8D56\u7684\u51E0\u4F55\u7236 id\uFF08\u542B\u66F2\u7EBF\u4EA4\u70B9\u7684 shapeId\uFF09 */\nfunction parentIdsOf(el: GeometryElementSpec): readonly string[] {\n if (el.type === 'transform') {\n return el.kind === 'translate'\n ? [el.source]\n : el.kind === 'reflectLine'\n ? [el.source, el.axis]\n : [el.source, el.center];\n }\n if (el.type === 'locus') return [el.driver, el.trace];\n if (el.type === 'measure') return [el.target];\n if (el.type === 'glider') return [el.parent];\n const base = 'parents' in el ? el.parents : undefined;\n if (el.type === 'curveIntersection' && el.shapeId) {\n return [...(base ?? []), el.shapeId];\n }\n return base ?? [];\n}\n\n/** \u65E0\u9700 geometry parents \u7684\u7C7B\u578B\uFF08\u81EA\u7531\u70B9/\u6587\u672C/\u66F2\u7EBF\u70B9/\u5207\u7EBF/\u7EAF\u66F2\u7EBF\u4EA4\u70B9\uFF09 */\nconst PARENTLESS_TYPES = new Set<GeometryElementSpec['type']>([\n 'point',\n 'text',\n 'curvePoint',\n 'curveTangent',\n 'curveIntersection',\n]);\n\nexport interface ConstructionIssue {\n rule: 'C1' | 'C2' | 'C3';\n message: string;\n}\n\n/**\n * \u6821\u9A8C\u6784\u9020\u56FE\u5408\u6CD5\u6027\uFF1A\n * - C1\uFF1Aid \u552F\u4E00\n * - C2\uFF1Aparents \u5F15\u7528\u5FC5\u987B\u5B58\u5728\u4E8E\u6570\u7EC4\u5F53\u524D\u5143\u7D20\u4E4B\u524D\uFF08\u62D3\u6251\u6709\u5E8F\uFF09\n * - C3\uFF1A\u9664\u81EA\u7531\u70B9/\u66F2\u7EBF\u6D3E\u751F\u5143\u7D20\u5916\u5FC5\u987B\u58F0\u660E parents\n */\nexport function validateConstruction(elements: GeometryElementSpec[]): ConstructionIssue[] {\n const issues: ConstructionIssue[] = [];\n const seen = new Set<string>();\n for (const el of elements) {\n if (seen.has(el.id)) {\n issues.push({ rule: 'C1', message: `\u5143\u7D20 id \u91CD\u590D\uFF1A\"${el.id}\"` });\n }\n const parents = parentIdsOf(el);\n const needsParents = !PARENTLESS_TYPES.has(el.type);\n if (needsParents && parents.length === 0) {\n issues.push({ rule: 'C3', message: `\u5143\u7D20 \"${el.id}\"\uFF08${el.type}\uFF09\u7F3A\u5C11 parents` });\n }\n if (el.type === 'curveIntersection' && !el.curveIdB && !el.shapeId) {\n issues.push({ rule: 'C3', message: `\u66F2\u7EBF\u4EA4\u70B9 \"${el.id}\" \u7F3A\u5C11\u7B2C\u4E8C\u6761\u66F2\u7EBF\u6216\u51E0\u4F55\u7236\u5143\u7D20` });\n }\n for (const p of parents) {\n if (!seen.has(p)) {\n issues.push({ rule: 'C2', message: `\u5143\u7D20 \"${el.id}\" \u7684 parent \"${p}\" \u5C1A\u672A\u6784\u9020` });\n }\n }\n seen.add(el.id);\n }\n return issues;\n}\n\n/**\n * \u8BA1\u7B97\u5220\u9664 ids \u7684\u7EA7\u8054\u95ED\u5305\uFF1A\u8FD4\u56DE\u6240\u6709\u5FC5\u987B\u4E00\u5E76\u5220\u9664\u7684\u5143\u7D20 id\uFF08\u542B\u5165\u53C2\uFF09\u3002\n */\nexport function cascadeDelete(elements: GeometryElementSpec[], ids: string[]): Set<string> {\n const toDelete = new Set(ids);\n let changed = true;\n while (changed) {\n changed = false;\n for (const el of elements) {\n if (toDelete.has(el.id)) continue;\n if (parentIdsOf(el).some((p) => toDelete.has(p))) {\n toDelete.add(el.id);\n changed = true;\n }\n }\n }\n return toDelete;\n}\n\n/**\n * id \u662F\u5426\uFF08\u76F4\u63A5\u6216\u95F4\u63A5\uFF09\u4F9D\u8D56 ancestorId\u3002\n * \u6CBF parentIdsOf \u9012\u5F52\uFF0C\u4F9B\u8F68\u8FF9\u6821\u9A8C\u300Ctrace \u4F9D\u8D56 driver\u300D\u7B49\u573A\u666F\u4F7F\u7528\u3002\n */\nexport function dependsOn(\n elements: GeometryElementSpec[],\n id: string,\n ancestorId: string,\n): boolean {\n const byId = new Map(elements.map((el) => [el.id, el]));\n const seen = new Set<string>();\n const stack = [id];\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (seen.has(current)) continue;\n seen.add(current);\n const spec = byId.get(current);\n if (!spec) continue;\n for (const parent of parentIdsOf(spec)) {\n if (parent === ancestorId) return true;\n stack.push(parent);\n }\n }\n return false;\n}\n\nexport interface DeletePlan {\n /** \u5B9E\u9645\u5220\u9664\u96C6\u5408\uFF08\u542B\u7EA7\u8054\u540E\u4EE3\u4E0E\u5B64\u513F\u9876\u70B9\uFF09 */\n ids: Set<string>;\n /** \u56E0\u672C\u6B21\u5220\u9664\u800C\u5931\u53BB\u5168\u90E8\u5F15\u7528\u3001\u88AB\u4E00\u5E76\u5220\u9664\u7684\u9876\u70B9 */\n orphanVertices: Set<string>;\n}\n\n/** \u5F62\u72B6\u7C7B\u5143\u7D20\uFF1A\u5220\u9664\u5B83\u65F6\uFF0C\u5176\u76F4\u63A5\u7236\u70B9\u82E5\u4E0D\u518D\u88AB\u5F15\u7528\u5219\u4E00\u5E76\u5220\u9664 */\nconst VERTEX_OWNER_TYPES = new Set<GeometryElementSpec['type']>([\n 'polygon',\n 'parallelogram',\n 'rhombus',\n 'segment',\n 'line',\n 'ray',\n 'circle',\n 'circumcircle',\n 'semicircle',\n 'sector',\n 'ellipse',\n 'ellipseArc',\n 'arc',\n]);\n\n/** \u70B9\u7C7B\u5143\u7D20\uFF08\u81EA\u7531\u70B9\u3001\u7EA6\u675F\u70B9\u4E0E\u66F2\u7EBF\u4E0A\u7684\u70B9\u90FD\u53EF\u4F5C\u4E3A\u5F62\u72B6\u9876\u70B9\uFF09 */\nfunction isPointElement(el: GeometryElementSpec): boolean {\n return el.type === 'point' || el.type === 'glider' || el.type === 'curvePoint';\n}\n\n/**\n * \u5220\u9664\u8BA1\u5212\uFF1A\u7EA7\u8054\u5220\u9664 + \u8FDE\u5E26\u5220\u9664\u300C\u53EA\u5C5E\u4E8E\u88AB\u5220\u5F62\u72B6\u300D\u7684\u9876\u70B9\u3002\n *\n * \u53EA\u5904\u7406 polygon/segment/line/ray/circle \u7684\u76F4\u63A5\u7236\u70B9\uFF0C\u4E14\u8981\u6C42\u8BE5\u70B9\u5220\u9664\u540E\u4E0D\u518D\u88AB\u4EFB\u4F55\u5E78\u5B58\u5143\u7D20\u5F15\u7528\uFF1B\n * \u6D3E\u751F\u6784\u9020\uFF08\u5782\u7EBF/\u5E73\u884C\u7EBF/\u89D2\u5E73\u5206\u7EBF/\u4EA4\u70B9/\u4E2D\u70B9/\u89D2\u5EA6\uFF09\u7684\u8F93\u5165\u70B9\u4E0D\u5728\u6B64\u5217\uFF0C\u907F\u514D\u8BEF\u5220\u7528\u6237\u5173\u952E\u70B9\u3002\n */\nexport function planDelete(elements: GeometryElementSpec[], ids: string[]): DeletePlan {\n const removed = cascadeDelete(elements, ids);\n const byId = new Map(elements.map((el) => [el.id, el]));\n const survivors = elements.filter((el) => !removed.has(el.id));\n\n const referenced = new Set<string>();\n for (const el of survivors) {\n for (const p of parentIdsOf(el)) referenced.add(p);\n }\n\n const orphanVertices = new Set<string>();\n for (const el of elements) {\n if (!removed.has(el.id) || !VERTEX_OWNER_TYPES.has(el.type)) continue;\n for (const p of parentIdsOf(el)) {\n const parent = byId.get(p);\n if (parent && isPointElement(parent) && !referenced.has(p)) orphanVertices.add(p);\n }\n }\n\n for (const id of orphanVertices) removed.add(id);\n return { ids: removed, orphanVertices };\n}\n\n/**\n * \u4F9D\u8D56\u62D3\u6251\u6392\u5E8F\uFF08Kahn \u7B97\u6CD5\uFF09\u3002\n * \u5047\u5B9A\u6784\u9020\u56FE\u5DF2\u901A\u8FC7 validateConstruction \u6821\u9A8C\uFF08C2 \u65E0\u73AF\u4FDD\u8BC1\uFF09\u3002\n */\nexport function topoSort(elements: GeometryElementSpec[]): GeometryElementSpec[] {\n const map = new Map(elements.map((el) => [el.id, el]));\n const inDegree = new Map<string, number>();\n const dependents = new Map<string, string[]>();\n\n for (const el of elements) {\n const deps = parentIdsOf(el).filter((p) => map.has(p));\n inDegree.set(el.id, deps.length);\n for (const p of deps) {\n const children = dependents.get(p) ?? [];\n children.push(el.id);\n dependents.set(p, children);\n }\n }\n\n const queue = elements.filter((el) => inDegree.get(el.id) === 0).map((el) => el.id);\n const sorted: GeometryElementSpec[] = [];\n while (queue.length > 0) {\n const id = queue.shift()!;\n const el = map.get(id);\n if (el) sorted.push(el);\n for (const childId of dependents.get(id) ?? []) {\n const d = (inDegree.get(childId) ?? 0) - 1;\n inDegree.set(childId, d);\n if (d === 0) queue.push(childId);\n }\n }\n return sorted;\n}\n", "/**\n * \u4E09\u70B9\u5706\u5F27\u7684\u7EAF\u51E0\u4F55\u8BA1\u7B97\uFF08\u5F15\u64CE\u65E0\u5173\uFF09\u3002\n * \u7528\u4E8E\u7ED8\u5236\u9884\u89C8\uFF1A\u8D77\u70B9 \u2192 \u7ECF\u8FC7\u70B9 \u2192 \u7EC8\u70B9\uFF0C\u4E09\u70B9\u4E0D\u5171\u7EBF\u65F6\u552F\u4E00\u786E\u5B9A\u4E00\u6BB5\u5706\u5F27\u3002\n */\n\nexport type ArcPoint = [number, number];\n\ninterface Circumcircle {\n center: ArcPoint;\n radius: number;\n startAngle: number;\n /** \u4ECE\u8D77\u70B9\u626B\u5230\u7EC8\u70B9\u7684\u6709\u5411\u5F27\u5EA6\uFF08\u7ECF\u8FC7\u70B9\u5728\u5F27\u4E0A\uFF09 */\n sweep: number;\n}\n\nconst TWO_PI = Math.PI * 2;\nconst EPS = 1e-9;\n\nfunction normalizeAngle(angle: number): number {\n let next = angle % TWO_PI;\n if (next < 0) next += TWO_PI;\n return next;\n}\n\n/** \u4E09\u70B9\u5916\u63A5\u5706\uFF1B\u4E09\u70B9\u5171\u7EBF\u6216\u91CD\u5408\u65F6\u8FD4\u56DE null */\nexport function circumcircleThrough(\n start: ArcPoint,\n through: ArcPoint,\n end: ArcPoint,\n): Circumcircle | null {\n const [ax, ay] = start;\n const [bx, by] = through;\n const [cx, cy] = end;\n if (\n Math.hypot(ax - bx, ay - by) < EPS ||\n Math.hypot(bx - cx, by - cy) < EPS ||\n Math.hypot(ax - cx, ay - cy) < EPS\n ) {\n return null;\n }\n const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));\n if (Math.abs(d) < EPS) return null;\n const a2 = ax * ax + ay * ay;\n const b2 = bx * bx + by * by;\n const c2 = cx * cx + cy * cy;\n const ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / d;\n const uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / d;\n const radius = Math.hypot(ax - ux, ay - uy);\n if (!Number.isFinite(radius) || radius < EPS) return null;\n const startAngle = Math.atan2(ay - uy, ax - ux);\n const throughAngle = Math.atan2(by - uy, bx - ux);\n const endAngle = Math.atan2(cy - uy, cx - ux);\n const ccwSweep = normalizeAngle(endAngle - startAngle);\n if (ccwSweep < EPS || TWO_PI - ccwSweep < EPS) return null;\n // \u7ECF\u8FC7\u70B9\u843D\u5728\u9006\u65F6\u9488\u8DEF\u5F84\u4E0A \u2192 \u9006\u65F6\u9488\uFF1B\u5426\u5219\u8D70\u987A\u65F6\u9488\n const ccwThrough = normalizeAngle(throughAngle - startAngle);\n const sweep = ccwThrough <= ccwSweep ? ccwSweep : -(TWO_PI - ccwSweep);\n return { center: [ux, uy], radius, startAngle, sweep };\n}\n\n/** \u91C7\u6837\u5706\u5F27\u4E3A\u6298\u7EBF\uFF08\u542B\u9996\u5C3E\u70B9\uFF09\uFF1B\u9000\u5316\u65F6\u8FD4\u56DE null */\nexport function sampleArcThroughPoints(\n start: ArcPoint,\n through: ArcPoint,\n end: ArcPoint,\n count = 96,\n): { dataX: number[]; dataY: number[] } | null {\n const circle = circumcircleThrough(start, through, end);\n if (!circle) return null;\n const steps = Math.max(2, Math.round(count));\n const dataX: number[] = [];\n const dataY: number[] = [];\n for (let i = 0; i <= steps; i += 1) {\n const angle = circle.startAngle + (circle.sweep * i) / steps;\n dataX.push(circle.center[0] + Math.cos(angle) * circle.radius);\n dataY.push(circle.center[1] + Math.sin(angle) * circle.radius);\n }\n return { dataX, dataY };\n}\n", "/**\n * \u5706\u9525\u66F2\u7EBF\u76F8\u5173\u7EAF\u51FD\u6570\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u692D\u5706\u53C2\u6570 \u03C6 \u4E0E JSXGraph `ellipse` \u4E00\u81F4\uFF1A\u7ED5\u7126\u70B9 F1\u3001\u76F8\u5BF9 F1\u2192F2 \u65B9\u5411\u7684\u6781\u89D2\u3002\n */\n\nexport type Point2D = [number, number];\n\nconst TAU = Math.PI * 2;\n\n/** \u692D\u5706\u5EA6\u91CF\uFF1A\u957F\u8F74\u957F\uFF08\u5230\u4E24\u7126\u70B9\u8DDD\u79BB\u4E4B\u548C\uFF09\u3001\u7126\u8DDD\u3001\u7126\u70B9\u8F74\u65B9\u5411 */\nexport interface EllipseMetrics {\n /** \u957F\u8F74\u957F r = |F1P| + |F2P| */\n major: number;\n /** \u7126\u8DDD c = |F1F2| */\n focal: number;\n /** F1\u2192F2 \u65B9\u5411\u89D2 */\n axisAngle: number;\n}\n\n/**\n * \u7531\u4E24\u7126\u70B9\u4E0E\u692D\u5706\u4E0A\u4E00\u70B9\u8BA1\u7B97\u5EA6\u91CF\uFF1B\u7126\u70B9\u91CD\u5408\u3001\u4E09\u70B9\u5171\u7EBF\uFF08\u70B9\u843D\u5728\u4E24\u7126\u70B9\u8FDE\u7EBF\u4E0A\u4E14\u7B49\u4E8E\u7126\u8DDD\uFF09\u65F6\u8FD4\u56DE null\u3002\n */\nexport function ellipseMetricsOf(f1: Point2D, f2: Point2D, p: Point2D): EllipseMetrics | null {\n const focal = Math.hypot(f2[0] - f1[0], f2[1] - f1[1]);\n const major = Math.hypot(p[0] - f1[0], p[1] - f1[1]) + Math.hypot(p[0] - f2[0], p[1] - f2[1]);\n if (!Number.isFinite(focal) || !Number.isFinite(major)) return null;\n const scale = Math.max(1, Math.abs(f1[0]), Math.abs(f1[1]), Math.abs(f2[0]), Math.abs(f2[1]), Math.abs(p[0]), Math.abs(p[1]));\n const eps = scale * 1e-9;\n if (focal <= eps) return null;\n if (major <= focal + eps) return null;\n return {\n major,\n focal,\n axisAngle: Math.atan2(f2[1] - f1[1], f2[0] - f1[0]),\n };\n}\n\n/** \u692D\u5706\u51E0\u4F55\u6846\u67B6\uFF1A\u7126\u70B9\u3001\u5EA6\u91CF\u4E0E\u4E2D\u5FC3/\u65CB\u8F6C\uFF0C\u4F9B glider\u3001\u4EA4\u70B9\u3001SVG \u5171\u7528 */\nexport interface EllipseFrame {\n f1: Point2D;\n f2: Point2D;\n metrics: EllipseMetrics;\n}\n\n/**\n * \u7531\u4E2D\u5FC3\u3001\u534A\u8F74\u4E0E\u65CB\u8F6C\u8FD8\u539F\u7126\u70B9\u4E0E\u5EA6\u91CF\u3002\n *\n * \u5185\u6838\u5B58\u50A8\u692D\u5706\u65F6\u53EA\u4FDD\u7559 center/a/b/rotation\uFF1B\u4EFB\u4F55\u9700\u8981\u7126\u70B9\u6781\u89D2\u53C2\u6570\u7684\u5730\u65B9\n * \u90FD\u5FC5\u987B\u7ECF\u6B64\u51FD\u6570\u8FD8\u539F\uFF0C\u907F\u514D\u5404\u81EA\u590D\u5236\u516C\u5F0F\u9020\u6210\u53C2\u6570\u6F02\u79FB\u3002\n */\nexport function ellipseFrameOf(\n center: Point2D,\n a: number,\n b: number,\n rotation: number,\n): EllipseFrame {\n const halfFocal = Math.sqrt(Math.max(0, a * a - b * b));\n const cos = Math.cos(rotation);\n const sin = Math.sin(rotation);\n return {\n f1: [center[0] - cos * halfFocal, center[1] - sin * halfFocal],\n f2: [center[0] + cos * halfFocal, center[1] + sin * halfFocal],\n metrics: { major: a * 2, focal: halfFocal * 2, axisAngle: rotation },\n };\n}\n\n/** \u692D\u5706\u4E0A\u53C2\u6570 \u03C6 \u5BF9\u5E94\u7684\u70B9\uFF08\u4E0E JSXGraph \u6781\u5750\u6807\u5F62\u5F0F\u4E00\u81F4\uFF09 */\nexport function ellipsePointAt(metrics: EllipseMetrics, f1: Point2D, phi: number): Point2D {\n const { major: r, focal: c, axisAngle } = metrics;\n const radius = (0.5 * (c * c - r * r)) / (c * Math.cos(phi) - r);\n const angle = axisAngle + phi;\n return [f1[0] + Math.cos(angle) * radius, f1[1] + Math.sin(angle) * radius];\n}\n\n/** \u70B9\u51FB\u65B9\u5411 \u2192 \u692D\u5706\u53C2\u6570 \u03C6\uFF080..2\u03C0\uFF09\uFF0C\u4E0D\u8981\u6C42\u70B9\u51FB\u70B9\u843D\u5728\u692D\u5706\u4E0A */\nexport function ellipseParamFromDirection(f1: Point2D, f2: Point2D, point: Point2D): number {\n const axisAngle = Math.atan2(f2[1] - f1[1], f2[0] - f1[0]);\n const direction = Math.atan2(point[1] - f1[1], point[0] - f1[0]);\n const phi = direction - axisAngle;\n return ((phi % TAU) + TAU) % TAU;\n}\n\n/**\n * \u5F52\u4E00\u5316\u692D\u5706\u5F27\u8D77\u6B62\u53C2\u6570\uFF1Astart \u843D\u56DE 0..2\u03C0\uFF0Cend \u9006\u65F6\u9488\u5927\u4E8E start\u3002\n * \u8D77\u6B62\u65B9\u5411\u91CD\u5408\uFF08\u5DEE < 1e-6\uFF09\u65F6\u8FD4\u56DE null\uFF0C\u907F\u514D\u9000\u5316\u4E3A\u6574\u692D\u5706\u6216\u7A7A\u5F27\u3002\n */\nexport function normalizeEllipseRange(start: number, end: number): [number, number] | null {\n const s = ((start % TAU) + TAU) % TAU;\n let e = ((end % TAU) + TAU) % TAU;\n if (Math.abs(e - s) < 1e-6) return null;\n if (e <= s) e += TAU;\n return [s, e];\n}\n\n/** \u91C7\u6837\u6574\u692D\u5706\uFF08\u7528\u4E8E\u692D\u5706\u5F27\u9884\u89C8\uFF09 */\nexport function sampleEllipse(\n metrics: EllipseMetrics,\n f1: Point2D,\n start: number,\n end: number,\n count = 96,\n): Point2D[] {\n const n = Math.max(2, Math.floor(count));\n const out: Point2D[] = [];\n for (let i = 0; i <= n; i += 1) {\n out.push(ellipsePointAt(metrics, f1, start + ((end - start) * i) / n));\n }\n return out;\n}\n", "/**\n * \u7EA6\u675F\u70B9\uFF08glider\uFF09\u53C2\u6570 \u2192 \u5750\u6807\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u53C2\u6570\u8BED\u4E49\u89C1\u8BBE\u8BA1\u89C4\u683C \u00A76\uFF1A\n * - line / ray\uFF1A\u4ECE\u8D77\u70B9\u6CBF\u5355\u4F4D\u65B9\u5411\u7684\u6709\u7B26\u53F7\u8DDD\u79BB\uFF1B\n * - segment / polygon \u8FB9\uFF1At\u2208[0,1]\uFF1B\n * - circle\uFF1A\u5706\u5FC3\u89D2 \u03B8\uFF08\u4E0D\u505A\u94B3\u5236\uFF09\uFF1B\n * - arc / semicircle / sector\uFF1A\u89D2\u5EA6\u94B3\u5236\u5728\u5F27\u7684\u626B\u89D2\u8303\u56F4\u5185\uFF1B\n * - ellipse / ellipseArc\uFF1A\u53C2\u6570 \u03C6\uFF0C\u6309 `ellipsePointAt` \u7684\u7126\u70B9\u6781\u89D2\u5B9A\u4E49\uFF0C\n * \u6709 startParam/endParam \u65F6\u94B3\u5236\u5230\u8BE5\u533A\u95F4\uFF0C\u6574\u692D\u5706\u6309 2\u03C0 \u5468\u671F\u5F52\u4E00\u3002\n */\n\nimport type { ParentEdge } from '../core/types-geometry';\nimport { ellipseFrameOf, ellipseParamFromDirection, ellipsePointAt } from './conic.js';\nimport type { ResolvedElement } from './resolve';\n\nconst TWO_PI = Math.PI * 2;\nconst EPS = 1e-9;\n\nexport interface GliderInput {\n parent: ResolvedElement;\n param: number;\n /** \u591A\u8FB9\u5F62\u8FB9\u53F7\uFF08parents[k] \u2192 parents[k+1]\uFF09\uFF0C\u7F3A\u7701 0 */\n parentEdge?: ParentEdge;\n}\n\nexport interface GliderParamInput {\n parent: ResolvedElement;\n /** \u70B9\u51FB/\u62D6\u52A8\u4F4D\u7F6E\uFF08\u4E16\u754C\u5750\u6807\uFF09\uFF0C\u7528\u4E8E\u53CD\u7B97\u53C2\u6570 */\n point: [number, number];\n /** \u591A\u8FB9\u5F62\u8FB9\u53F7\uFF08parents[k] \u2192 parents[k+1]\uFF09\uFF0C\u7F3A\u7701 0 */\n parentEdge?: ParentEdge;\n}\n\n/** \u7EA6\u675F\u70B9\u5750\u6807\uFF1B\u7236\u7C7B\u578B\u4E0D\u652F\u6301\u3001\u8FB9\u53F7\u8D8A\u754C\u6216\u53C2\u6570\u975E\u6CD5\u65F6\u8FD4\u56DE null */\nexport function gliderPosition(input: GliderInput): [number, number] | null {\n const { parent, param } = input;\n if (!Number.isFinite(param)) return null;\n\n switch (parent.kind) {\n case 'line': {\n if (parent.extent === 'segment') {\n const from = parent.from ?? parent.origin;\n const to = parent.to;\n if (!to) return null;\n const t = clamp01(param);\n return [\n from[0] + (to[0] - from[0]) * t,\n from[1] + (to[1] - from[1]) * t,\n ];\n }\n const distance = parent.extent === 'ray' ? Math.max(0, param) : param;\n return [\n parent.origin[0] + parent.direction[0] * distance,\n parent.origin[1] + parent.direction[1] * distance,\n ];\n }\n case 'circle':\n return [\n parent.center[0] + parent.radius * Math.cos(param),\n parent.center[1] + parent.radius * Math.sin(param),\n ];\n case 'arc': {\n const angle = clampAngleToArc(param, parent);\n return [\n parent.center[0] + parent.radius * Math.cos(angle),\n parent.center[1] + parent.radius * Math.sin(angle),\n ];\n }\n case 'ellipse': {\n const phi = clampEllipseParam(param, parent);\n return ellipsePointOn(parent, phi);\n }\n case 'polygon': {\n const edge = input.parentEdge ?? 0;\n if (!Number.isInteger(edge) || edge < 0 || edge >= parent.vertices.length) return null;\n const a = parent.vertices[edge]!;\n const b = parent.vertices[(edge + 1) % parent.vertices.length]!;\n const t = clamp01(param);\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\n }\n default:\n return null;\n }\n}\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\n/**\n * \u70B9\u51FB/\u62D6\u52A8\u4F4D\u7F6E \u2192 \u7236\u5143\u7D20\u53C2\u6570\uFF08`gliderPosition` \u7684\u53CD\u7B97\uFF09\u3002\n * \u7236\u7C7B\u578B\u4E0D\u652F\u6301\u3001\u7236\u5143\u7D20\u9000\u5316\u6216\u8FB9\u53F7\u8D8A\u754C\u65F6\u8FD4\u56DE null\uFF1B\u53C2\u6570\u59CB\u7EC8\u94B3\u5236\u5230\u5408\u6CD5\u8303\u56F4\u3002\n */\nexport function gliderParamFromPoint(input: GliderParamInput): number | null {\n const { parent, point } = input;\n if (!Number.isFinite(point[0]) || !Number.isFinite(point[1])) return null;\n\n switch (parent.kind) {\n case 'line': {\n if (parent.extent === 'segment') {\n const from = parent.from ?? parent.origin;\n const to = parent.to;\n if (!to) return null;\n return segmentParam(from, to, point);\n }\n const length = Math.hypot(parent.direction[0], parent.direction[1]);\n if (!(length > EPS)) return null;\n const distance =\n ((point[0] - parent.origin[0]) * parent.direction[0]\n + (point[1] - parent.origin[1]) * parent.direction[1])\n / (length * length);\n return parent.extent === 'ray' ? Math.max(0, distance) : distance;\n }\n case 'circle': {\n if (!(parent.radius > EPS)) return null;\n return modTwoPi(Math.atan2(point[1] - parent.center[1], point[0] - parent.center[0]));\n }\n case 'arc': {\n if (!(parent.radius > EPS)) return null;\n if (Math.abs(arcSweep(parent)) < EPS) return null;\n const angle = Math.atan2(point[1] - parent.center[1], point[0] - parent.center[0]);\n return clampAngleToArc(angle, parent);\n }\n case 'ellipse': {\n if (!(parent.a > EPS) || !(parent.b > EPS)) return null;\n const cos = Math.cos(parent.rotation);\n const sin = Math.sin(parent.rotation);\n const halfFocal = Math.sqrt(Math.max(0, parent.a * parent.a - parent.b * parent.b));\n const f1: [number, number] = [\n parent.center[0] - cos * halfFocal,\n parent.center[1] - sin * halfFocal,\n ];\n const f2: [number, number] = [\n parent.center[0] + cos * halfFocal,\n parent.center[1] + sin * halfFocal,\n ];\n return clampEllipseParam(ellipseParamFromDirection(f1, f2, point), parent);\n }\n case 'polygon': {\n const edge = input.parentEdge ?? 0;\n if (!Number.isInteger(edge) || edge < 0 || edge >= parent.vertices.length) return null;\n const a = parent.vertices[edge]!;\n const b = parent.vertices[(edge + 1) % parent.vertices.length]!;\n return segmentParam(a, b, point);\n }\n default:\n return null;\n }\n}\n\n/** \u7EBF\u6BB5 t\u2208[0,1]\uFF1B\u4E24\u7AEF\u91CD\u5408\u65F6\u8FD4\u56DE null */\nfunction segmentParam(\n from: [number, number],\n to: [number, number],\n point: [number, number],\n): number | null {\n const dx = to[0] - from[0];\n const dy = to[1] - from[1];\n const length2 = dx * dx + dy * dy;\n if (!(length2 > EPS * EPS)) return null;\n return clamp01(((point[0] - from[0]) * dx + (point[1] - from[1]) * dy) / length2);\n}\n\nfunction modTwoPi(angle: number): number {\n const next = angle % TWO_PI;\n return next < 0 ? next + TWO_PI : next;\n}\n\n/** \u5F27\u7684\u6709\u7B26\u53F7\u626B\u89D2\uFF1Accw \u4E3A\u6B63\u5411\uFF0C\u5426\u5219\u4E3A\u53CD\u5411\uFF08\u5141\u8BB8 endAngle < startAngle\uFF09 */\nfunction arcSweep(arc: Extract<ResolvedElement, { kind: 'arc' }>): number {\n return arc.ccw\n ? modTwoPi(arc.endAngle - arc.startAngle)\n : -modTwoPi(arc.startAngle - arc.endAngle);\n}\n\n/** \u89D2\u5EA6\u94B3\u5236\u5230\u5F27\u7684\u626B\u89D2\u8303\u56F4\u5185\uFF08\u542B ccw=false \u4E0E\u8DE8 2\u03C0 \u60C5\u5F62\uFF09 */\nfunction clampAngleToArc(\n angle: number,\n arc: Extract<ResolvedElement, { kind: 'arc' }>,\n): number {\n const sweep = arcSweep(arc);\n if (Math.abs(sweep) < EPS) return arc.startAngle;\n const magnitude = sweep > 0\n ? modTwoPi(angle - arc.startAngle)\n : modTwoPi(arc.startAngle - angle);\n const bounded = Math.min(Math.abs(sweep), magnitude);\n return arc.startAngle + (sweep < 0 ? -bounded : bounded);\n}\n\n/** \u692D\u5706\u53C2\u6570\u94B3\u5236\uFF1A\u6574\u692D\u5706\u6309\u5468\u671F\u5F52\u4E00\uFF0C\u692D\u5706\u5F27\u94B3\u5236\u5230 [startParam, endParam] */\nfunction clampEllipseParam(\n param: number,\n ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>,\n): number {\n const start = ellipse.startParam ?? 0;\n const end = ellipse.endParam ?? TWO_PI;\n const sweep = end - start;\n if (!Number.isFinite(sweep) || sweep <= EPS) return start;\n if (sweep >= TWO_PI - EPS) return start + modTwoPi(param - start);\n return Math.min(end, Math.max(start, param));\n}\n\n/** \u692D\u5706\u53C2\u6570 \u03C6 \u2192 \u4E16\u754C\u5750\u6807\uFF08\u7126\u70B9\u6781\u89D2\uFF0C\u4E0E JSXGraph / intersections / SVG \u4E00\u81F4\uFF09 */\nfunction ellipsePointOn(\n ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>,\n phi: number,\n): [number, number] {\n const frame = ellipseFrameOf(ellipse.center, ellipse.a, ellipse.b, ellipse.rotation);\n return ellipsePointAt(frame.metrics, frame.f1, phi);\n}\n", "/**\n * \u8F68\u8FF9\uFF08locus\uFF09\u9A71\u52A8\u57DF\uFF1A\u7EA6\u675F\u70B9\u7236\u5143\u7D20 \u2192 \u9A71\u52A8\u53C2\u6570\u533A\u95F4\u3002\n *\n * \u53EA\u6709\u6709\u754C\u7236\u5143\u7D20\uFF08\u7EBF\u6BB5/\u5706/\u5706\u5F27/\u534A\u5706/\u692D\u5706/\u692D\u5706\u5F27\uFF09\u53EF\u4F5C\u4E3A\u8F68\u8FF9\u9A71\u52A8\uFF1B\n * \u76F4\u7EBF/\u5C04\u7EBF/\u6247\u5F62\u7B49\u65E0\u754C\u6216\u975E\u626B\u63A0\u5BF9\u8C61\u8FD4\u56DE null\uFF0C\u7531\u8C03\u7528\u65B9\u8F6C\u4E3A\u8BCA\u65AD\u3002\n */\n\nimport type { ParentEdge } from '../core/types-geometry';\nimport type { ResolvedElement } from './resolve';\n\nexport interface DriverDomain {\n from: number;\n to: number;\n closed: boolean;\n}\n\nconst TWO_PI = Math.PI * 2;\n\nexport function driverDomainOf(\n parent: ResolvedElement,\n _parentEdge?: ParentEdge,\n): DriverDomain | null {\n if (parent.kind === 'line') {\n if (parent.extent !== 'segment' || !parent.from || !parent.to) return null;\n return { from: 0, to: 1, closed: false };\n }\n if (parent.kind === 'circle') {\n return { from: 0, to: TWO_PI, closed: true };\n }\n if (parent.kind === 'ellipse') {\n const start = parent.startParam ?? 0;\n const end = parent.endParam ?? TWO_PI;\n if (Math.abs(end - start - TWO_PI) < 1e-9 && Math.abs(start) < 1e-9) {\n return { from: 0, to: TWO_PI, closed: true };\n }\n return { from: start, to: end, closed: false };\n }\n if (parent.kind === 'arc') {\n if (parent.arcKind === 'sector') return null;\n if (parent.arcKind === 'semicircle') {\n return { from: parent.startAngle, to: parent.startAngle + Math.PI, closed: false };\n }\n return { from: parent.startAngle, to: parent.endAngle, closed: false };\n }\n return null;\n}\n", "/**\n * \u6298\u7EBF\u4E0E\u53C2\u6570\u66F2\u7EBF\u91C7\u6837\u7684\u7EAF\u51FD\u6570\u5DE5\u5177\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u7528\u9014\uFF1A\n * - \u6781\u5750\u6807 / \u53C2\u6570\u65B9\u7A0B\u6309\u53C2\u6570\u91C7\u6837\u4E3A\u6298\u7EBF\uFF1B\n * - JXG implicitcurve \u7684 dataX/dataY \u8F6C\u6298\u7EBF\uFF08NaN \u65AD\u6BB5\uFF09\uFF1B\n * - \u66F2\u7EBF\u5438\u9644\uFF08\u6700\u8FD1\u70B9\u6295\u5F71\uFF09\u3001\u66F2\u7EBF\u00D7\u66F2\u7EBF\u3001\u66F2\u7EBF\u00D7\u51E0\u4F55\u6C42\u4EA4\u3002\n */\n\nexport interface PolylinePoint {\n x: number;\n y: number;\n /** \u66F2\u7EBF\u53C2\u6570\uFF1Aexplicit=x\uFF0Cpolar=\u03B8\uFF0Cparametric=t\uFF0Cimplicit=\u5168\u5C40\u7D2F\u8BA1\u5F27\u957F\u5F52\u4E00\u5316 0..1 */\n t: number;\n}\n\nexport type PolylineBranch = PolylinePoint[];\n\nexport interface PolylineProjection {\n x: number;\n y: number;\n /** \u6295\u5F71\u70B9\u5BF9\u5E94\u7684\u53C2\u6570\uFF08\u6309\u6BB5\u7EBF\u6027\u63D2\u503C\uFF09 */\n param: number;\n /** \u5F52\u4E00\u5316\u692D\u5706\u8DDD\u79BB\uFF08tol \u4E3A Infinity \u65F6\u8FD1\u4F3C 0\uFF09 */\n distance: number;\n}\n\nexport type Segment = [[number, number], [number, number]];\n\nconst DEFAULT_COUNT = 480;\nconst EPS = 1e-9;\n\n/** \u53C2\u6570\u533A\u95F4\u9006\u5E8F\u65F6\u7EDF\u4E00\u4E3A\u5347\u5E8F\uFF08JXG \u5BF9\u9006\u5E8F\u533A\u95F4\u4F1A\u7A7A\u7ED8\uFF09 */\nexport function normalizeParamRange(t0: number, t1: number): [number, number] {\n if (!Number.isFinite(t0) || !Number.isFinite(t1)) return [0, 0];\n return t0 <= t1 ? [t0, t1] : [t1, t0];\n}\n\n/** \u53C2\u6570\u91C7\u6837\uFF1ApointAt \u8FD4\u56DE null / \u975E\u6709\u9650\u503C\u5904\u65AD\u6BB5 */\nexport function sampleParametricPoint(\n pointAt: (t: number) => [number, number] | null,\n t0: number,\n t1: number,\n count = DEFAULT_COUNT,\n): PolylineBranch[] {\n const [lo, hi] = normalizeParamRange(t0, t1);\n const n = Math.max(2, Math.floor(count));\n const branches: PolylineBranch[] = [];\n let current: PolylineBranch = [];\n for (let i = 0; i <= n; i += 1) {\n const t = lo + ((hi - lo) * i) / n;\n let p: [number, number] | null = null;\n try {\n p = pointAt(t);\n } catch {\n p = null;\n }\n if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) {\n if (current.length > 0) branches.push(current);\n current = [];\n continue;\n }\n current.push({ x: p[0], y: p[1], t });\n }\n if (current.length > 0) branches.push(current);\n return branches;\n}\n\n/** dataX/dataY\uFF08NaN \u5206\u9694\uFF09\u2192 \u5206\u652F\uFF1Bt \u7528\u5168\u5C40\u7D2F\u8BA1\u5F27\u957F\u5F52\u4E00\u5316 0..1 */\nexport function polylineFromData(\n dataX: readonly number[],\n dataY: readonly number[],\n): PolylineBranch[] {\n const n = Math.min(dataX.length, dataY.length);\n const raw: Array<Array<{ x: number; y: number }>> = [];\n let current: Array<{ x: number; y: number }> = [];\n for (let i = 0; i < n; i += 1) {\n const x = dataX[i]!;\n const y = dataY[i]!;\n if (!Number.isFinite(x) || !Number.isFinite(y)) {\n if (current.length > 0) raw.push(current);\n current = [];\n continue;\n }\n current.push({ x, y });\n }\n if (current.length > 0) raw.push(current);\n if (raw.length === 0) return [];\n\n // \u5148\u7B97\u5404\u5206\u652F\u7D2F\u8BA1\u5F27\u957F\uFF0C\u518D\u7EDF\u4E00\u5F52\u4E00\u5316\n let total = 0;\n const accs: number[][] = raw.map((branch) => {\n // \u6BCF\u4E2A\u5206\u652F\u7684\u7D2F\u8BA1\u5F27\u957F\u4ECE\u4E0A\u4E00\u5206\u652F\u672B\u5C3E\u7EE7\u7EED\uFF0C\u4FDD\u8BC1\u8DE8\u5206\u652F t \u5355\u8C03\n const acc: number[] = [total];\n for (let i = 1; i < branch.length; i += 1) {\n total += Math.hypot(branch[i]!.x - branch[i - 1]!.x, branch[i]!.y - branch[i - 1]!.y);\n acc.push(total);\n }\n return acc;\n });\n return raw.map((branch, bi) =>\n branch.map((p, i) => ({\n x: p.x,\n y: p.y,\n t: total > EPS ? accs[bi]![i]! / total : 0,\n })),\n );\n}\n\n/**\n * \u6298\u7EBF\u6700\u8FD1\u70B9\u6295\u5F71\u3002tolX/tolY \u7528\u4E8E\u5F52\u4E00\u5316\u692D\u5706\u8DDD\u79BB\uFF1B\u7F3A\u7701 Infinity \u8868\u793A\u4E0D\u8BBE\u5BB9\u5DEE\u3002\n * \u8FD4\u56DE null \u8868\u793A\u6CA1\u6709\u70B9\u843D\u5728\u5BB9\u5DEE\u5185\u3002\n */\nexport function projectToPolyline(\n branches: PolylineBranch[],\n x: number,\n y: number,\n tolX = Number.POSITIVE_INFINITY,\n tolY = Number.POSITIVE_INFINITY,\n): PolylineProjection | null {\n if (!(tolX > 0) || !(tolY > 0)) return null;\n const useNorm = Number.isFinite(tolX) && Number.isFinite(tolY);\n // \u65E0\u5BB9\u5DEE\u65F6\u6309\u6B27\u6C0F\u8DDD\u79BB\u6BD4\u8F83\uFF0C\u5426\u5219\u6309\u5F52\u4E00\u5316\u692D\u5706\u8DDD\u79BB\n const metric = (px: number, py: number): number =>\n useNorm\n ? ((px - x) / tolX) ** 2 + ((py - y) / tolY) ** 2\n : (px - x) ** 2 + (py - y) ** 2;\n let bestMetric = Number.POSITIVE_INFINITY;\n let best: PolylineProjection | null = null;\n for (const branch of branches) {\n if (branch.length === 1) {\n const p = branch[0]!;\n const m = metric(p.x, p.y);\n if (m < bestMetric) {\n bestMetric = m;\n best = { x: p.x, y: p.y, param: p.t, distance: 0 };\n }\n continue;\n }\n for (let i = 1; i < branch.length; i += 1) {\n const a = branch[i - 1]!;\n const b = branch[i]!;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len2 = dx * dx + dy * dy;\n const u =\n len2 <= EPS\n ? 0\n : Math.min(1, Math.max(0, ((x - a.x) * dx + (y - a.y) * dy) / len2));\n const px = a.x + dx * u;\n const py = a.y + dy * u;\n const m = metric(px, py);\n if (m < bestMetric) {\n bestMetric = m;\n best = {\n x: px,\n y: py,\n param: a.t + (b.t - a.t) * u,\n distance: 0,\n };\n }\n }\n }\n if (!best) return null;\n if (useNorm && bestMetric > 1) return null;\n return { ...best, distance: Math.sqrt(Math.max(0, bestMetric)) };\n}\n\n/** \u53D6\u6298\u7EBF\u4E0A\u53C2\u6570 t\uFF080..1\uFF09\u5BF9\u5E94\u7684\u70B9\u4E0E\u5C40\u90E8\u5355\u4F4D\u65B9\u5411 */\nexport function pointAtPolylineParam(\n branches: PolylineBranch[],\n t: number,\n): { point: [number, number]; direction: [number, number] | null } | null {\n if (branches.length === 0) return null;\n const tt = Math.min(1, Math.max(0, t));\n for (const branch of branches) {\n if (branch.length === 1) continue;\n for (let i = 1; i < branch.length; i += 1) {\n const a = branch[i - 1]!;\n const b = branch[i]!;\n if (tt < a.t - 1e-9 || tt > b.t + 1e-9) continue;\n const span = b.t - a.t;\n const u = span > EPS ? Math.min(1, Math.max(0, (tt - a.t) / span)) : 0;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n return {\n point: [a.x + dx * u, a.y + dy * u],\n direction: len > EPS ? [dx / len, dy / len] : null,\n };\n }\n }\n // \u8D85\u51FA\u8303\u56F4\uFF1A\u5939\u5230\u9996/\u5C3E\u70B9\n const firstBranch = branches.find((b) => b.length > 0);\n const lastBranch = [...branches].reverse().find((b) => b.length > 0);\n if (!firstBranch || !lastBranch) return null;\n const p = tt <= 0 ? firstBranch[0]! : lastBranch[lastBranch.length - 1]!;\n return { point: [p.x, p.y], direction: null };\n}\n\nfunction pushUnique(out: Array<[number, number]>, p: [number, number], eps: number): void {\n if (out.some((q) => Math.hypot(q[0] - p[0], q[1] - p[1]) <= eps)) return;\n out.push(p);\n}\n\nfunction projectPointToSegment(\n p: [number, number],\n a: [number, number],\n b: [number, number],\n): [number, number] {\n const dx = b[0] - a[0];\n const dy = b[1] - a[1];\n const len2 = dx * dx + dy * dy;\n const u =\n len2 <= EPS ? 0 : Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));\n return [a[0] + dx * u, a[1] + dy * u];\n}\n\nfunction segmentIntersection(\n a0: [number, number],\n a1: [number, number],\n b0: [number, number],\n b1: [number, number],\n eps: number,\n): [number, number] | null {\n // bbox \u7C97\u7B5B\n if (\n Math.max(a0[0], a1[0]) < Math.min(b0[0], b1[0]) - eps ||\n Math.max(b0[0], b1[0]) < Math.min(a0[0], a1[0]) - eps ||\n Math.max(a0[1], a1[1]) < Math.min(b0[1], b1[1]) - eps ||\n Math.max(b0[1], b1[1]) < Math.min(a0[1], a1[1]) - eps\n ) {\n return null;\n }\n const rx = a1[0] - a0[0];\n const ry = a1[1] - a0[1];\n const sx = b1[0] - b0[0];\n const sy = b1[1] - b0[1];\n const den = rx * sy - ry * sx;\n // \u5E73\u884C\u5224\u5B9A\u6309\u7EBF\u6BB5\u957F\u5EA6\u5F52\u4E00\uFF1A\u56FA\u5B9A eps \u4F1A\u628A\u5C0F\u534A\u5F84/\u5BC6\u96C6\u91C7\u6837\u7684\u771F\u5B9E\u4EA4\u53C9\u8BEF\u5224\u4E3A\u5E73\u884C\n const parallelEps = Math.max(1e-12, Math.hypot(rx, ry) * Math.hypot(sx, sy) * 1e-9);\n if (Math.abs(den) > parallelEps) {\n const qx = b0[0] - a0[0];\n const qy = b0[1] - a0[1];\n const t = (qx * sy - qy * sx) / den;\n const u = (qx * ry - qy * rx) / den;\n if (t >= -eps && t <= 1 + eps && u >= -eps && u <= 1 + eps) {\n return [a0[0] + t * rx, a0[1] + t * ry];\n }\n }\n // \u8FD1\u4F3C\u76F8\u5207\uFF1A\u4E24\u6761\u7EBF\u6BB5\u6CA1\u6709\u771F\u4EA4\u53C9\uFF0C\u4F46\u6700\u8FD1\u8DDD\u79BB\u5728 eps \u5185 \u2192 \u53D6\u6700\u8FD1\u70B9\u4E2D\u70B9\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestPoint: [number, number] | null = null;\n const pairs: Array<[[number, number], [number, number]]> = [\n [a0, projectPointToSegment(a0, b0, b1)],\n [a1, projectPointToSegment(a1, b0, b1)],\n [projectPointToSegment(b0, a0, a1), b0],\n [projectPointToSegment(b1, a0, a1), b1],\n ];\n for (const [pa, pb] of pairs) {\n const d = Math.hypot(pa[0] - pb[0], pa[1] - pb[1]);\n if (d < bestDistance) {\n bestDistance = d;\n bestPoint = [(pa[0] + pb[0]) / 2, (pa[1] + pb[1]) / 2];\n }\n }\n return bestDistance <= eps ? bestPoint : null;\n}\n\nfunction branchSegments(branch: PolylineBranch): Segment[] {\n const out: Segment[] = [];\n for (let i = 1; i < branch.length; i += 1) {\n const a = branch[i - 1]!;\n const b = branch[i]!;\n out.push([\n [a.x, a.y],\n [b.x, b.y],\n ]);\n }\n return out;\n}\n\n/** \u6298\u7EBF \u00D7 \u6298\u7EBF\u6C42\u4EA4\uFF08\u7ED3\u679C\u6309 eps \u53BB\u91CD\uFF1B\u5171\u7EBF\u91CD\u53E0\u4E0D\u5355\u72EC\u5904\u7406\uFF09 */\nexport function intersectPolylines(\n a: PolylineBranch[],\n b: PolylineBranch[],\n eps = 1e-6,\n): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n const segsA = a.flatMap(branchSegments);\n const segsB = b.flatMap(branchSegments);\n for (const sa of segsA) {\n for (const sb of segsB) {\n const hit = segmentIntersection(sa[0], sa[1], sb[0], sb[1], eps);\n if (hit) pushUnique(out, hit, eps);\n }\n }\n return out;\n}\n\n/** \u6298\u7EBF \u00D7 \u7EBF\u6BB5\u96C6\u5408\u6C42\u4EA4\uFF08\u4F9B\u66F2\u7EBF \u00D7 \u51E0\u4F55\u5F62\u72B6\uFF09 */\nexport function intersectPolylineSegments(\n a: PolylineBranch[],\n segments: Segment[],\n eps = 1e-6,\n): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n const segsA = a.flatMap(branchSegments);\n for (const sa of segsA) {\n for (const sb of segments) {\n const hit = segmentIntersection(sa[0], sa[1], sb[0], sb[1], eps);\n if (hit) pushUnique(out, hit, eps);\n }\n }\n return out;\n}\n", "/**\n * \u5143\u7D20\u6C42\u4EA4\u4E0E\u7236\u5143\u7D20\u69FD\u4F4D\u89E3\u6790\uFF08\u5F15\u64CE\u65E0\u5173\uFF09\u3002\n *\n * \u6C42\u4EA4\u4F18\u5148\u7EA7\uFF1A\n * - \u7EBF\u00D7\u7EBF / \u7EBF\u00D7\u5706 / \u7EBF\u00D7\u692D\u5706 / \u5706\u00D7\u5706\uFF1A\u89E3\u6790\u89E3\uFF1B\n * - \u7EBF\u00D7\u591A\u8FB9\u5F62\uFF1A\u9010\u8FB9\u7EBF\u6BB5\u89E3\u6790\u6C42\u4EA4\uFF1B\u7EBF\u00D7\u5706\u5F27\uFF1A\u6309 64 \u6BB5\u6298\u7EBF\u9010\u6BB5\u89E3\u6790\uFF1B\n * - \u5176\u4F59\u7EC4\u5408\uFF08\u591A\u8FB9\u5F62 / \u5706\u5F27 / \u6247\u5F62 / \u692D\u5706\u5F27 \u4E4B\u95F4\u7684\u7EC4\u5408\uFF09\uFF1A\u91C7\u6837\u4E3A\u6298\u7EBF\u540E\n * `intersectPolylines` \u6C42\u4EA4\uFF0C\u5706\u4E0E\u692D\u5706 128 \u6BB5\u3001\u5706\u5F27 64 \u6BB5\u3002\n *\n * \u91C7\u6837\u7EC4\u5408\u7CBE\u5EA6\u53D7\u5F26\u957F\u9650\u5236\uFF08\u22641e-3\uFF09\uFF1B\u89E3\u6790\u7EC4\u5408\u8BEF\u5DEE \u22641e-9\u3002\n * \u7ED3\u679C\u7EDF\u4E00\u6309 x \u5347\u5E8F\u3001\u518D\u6309 y \u5347\u5E8F\uFF0C\u5E76\u53BB\u9664\u8DDD\u79BB <1e-9 \u7684\u91CD\u590D\u70B9\u3002\n */\n\nimport { intersectPolylines, type PolylineBranch } from '../core/polyline.js';\nimport type { ParentEdge } from '../core/types-geometry';\nimport { ellipseFrameOf, ellipsePointAt } from './conic.js';\nimport type { ResolvedElement } from './resolve';\n\ntype Point = [number, number];\n\nconst EPS = 1e-9;\nconst TWO_PI = Math.PI * 2;\nconst CIRCLE_SEGMENTS = 128;\nconst ELLIPSE_SEGMENTS = 128;\nconst ARC_SEGMENTS = 64;\n\n/** \u89E3\u6790\u540E\u7684\u7EBF\u7C7B\u51E0\u4F55\uFF08\u65B9\u5411\u4E3A\u5355\u4F4D\u5411\u91CF\uFF1Bsegment \u8BB0 from/to\uFF09 */\nexport interface ResolvedLine {\n origin: Point;\n direction: Point;\n extent: 'segment' | 'line' | 'ray';\n from?: Point;\n to?: Point;\n}\n\nfunction unitVector(dx: number, dy: number): Point | null {\n const length = Math.hypot(dx, dy);\n if (length < EPS) return null;\n return [dx / length, dy / length];\n}\n\n/**\n * \u7236\u5143\u7D20 + \u69FD\u4F4D \u2192 \u5B9E\u9645\u53C2\u4E0E\u8BA1\u7B97\u7684\u5143\u7D20\u3002\n * - \u69FD\u4F4D\u4E3A\u7A7A\uFF1A\u4F7F\u7528\u7236\u5143\u7D20\u672C\u8EAB\uFF1B\n * - \u591A\u8FB9\u5F62\u7B2C k \u6761\u8FB9\uFF1AVk \u2192 V(k+1)\uFF0C\u7EBF\u6BB5\uFF1B\n * - \u89D2\u5EA6\u7B2C k \u6761\u817F\uFF080 \u2192 parents[0]\uFF0C1 \u2192 parents[2]\uFF09\uFF1A\u4ECE\u9876\u70B9\u51FA\u53D1\u7684\u5C04\u7EBF\u3002\n */\nexport function elementWithParentEdge(\n element: ResolvedElement,\n edge: ParentEdge | null | undefined,\n): ResolvedElement | null {\n if (edge == null) return element;\n\n if (element.kind === 'polygon') {\n if (!Number.isInteger(edge) || edge < 0 || edge >= element.vertices.length) return null;\n const start = element.vertices[edge]!;\n const end = element.vertices[(edge + 1) % element.vertices.length]!;\n const direction = unitVector(end[0] - start[0], end[1] - start[1]);\n if (!direction) return null;\n return {\n kind: 'line',\n id: element.id,\n origin: [start[0], start[1]],\n direction,\n extent: 'segment',\n from: [start[0], start[1]],\n to: [end[0], end[1]],\n };\n }\n\n if (element.kind === 'angle') {\n if (edge !== 0 && edge !== 1) return null;\n const angle = element.startAngle + (edge === 1 ? element.sweep : 0);\n return {\n kind: 'line',\n id: element.id,\n origin: [element.vertex[0], element.vertex[1]],\n direction: [Math.cos(angle), Math.sin(angle)],\n extent: 'ray',\n from: [element.vertex[0], element.vertex[1]],\n };\n }\n\n return element;\n}\n\n/** \u7EBF\u7C7B\u7236\u5143\u7D20\uFF08\u53EF\u5E26\u8FB9/\u817F\u69FD\u4F4D\uFF09\u2192 \u7EBF\u51E0\u4F55\uFF1B\u975E\u7EBF\u7C7B\u8FD4\u56DE null */\nexport function resolvedLineOf(\n element: ResolvedElement,\n edge: ParentEdge | null | undefined,\n): ResolvedLine | null {\n const withEdge = elementWithParentEdge(element, edge);\n if (!withEdge || withEdge.kind !== 'line') return null;\n const line: ResolvedLine = {\n origin: [withEdge.origin[0], withEdge.origin[1]],\n direction: [withEdge.direction[0], withEdge.direction[1]],\n extent: withEdge.extent,\n };\n if (withEdge.from) line.from = [withEdge.from[0], withEdge.from[1]];\n if (withEdge.to) line.to = [withEdge.to[0], withEdge.to[1]];\n return line;\n}\n\n/** \u7EBF\u53C2\u6570\u8303\u56F4\uFF1Asegment \u4E3A\u957F\u5EA6\u5F52\u4E00\u540E\u7684 [0, |to\u2212from|]\uFF0Cray \u4E3A t\u22650\uFF0Cline \u4E0D\u9650\u5236 */\nfunction parameterWithin(t: number, line: ResolvedLine): boolean {\n if (line.extent === 'line') return true;\n if (line.extent === 'ray') return t >= -EPS;\n const from = line.from ?? line.origin;\n const to = line.to ?? line.origin;\n const length = Math.hypot(to[0] - from[0], to[1] - from[1]);\n return t >= -EPS && t <= length + EPS;\n}\n\nfunction intersectLines(a: ResolvedLine, b: ResolvedLine): Point[] {\n const denominator = a.direction[0] * b.direction[1] - a.direction[1] * b.direction[0];\n // \u5E73\u884C/\u5171\u7EBF\uFF1A\u65E0\u79BB\u6563\u4EA4\u70B9\n if (Math.abs(denominator) <= 1e-12) return [];\n const qx = b.origin[0] - a.origin[0];\n const qy = b.origin[1] - a.origin[1];\n const t = (qx * b.direction[1] - qy * b.direction[0]) / denominator;\n const u = (qx * a.direction[1] - qy * a.direction[0]) / denominator;\n if (!parameterWithin(t, a) || !parameterWithin(u, b)) return [];\n return [[a.origin[0] + t * a.direction[0], a.origin[1] + t * a.direction[1]]];\n}\n\nfunction intersectLineCircle(\n line: ResolvedLine,\n circle: Extract<ResolvedElement, { kind: 'circle' }>,\n): Point[] {\n const ox = line.origin[0] - circle.center[0];\n const oy = line.origin[1] - circle.center[1];\n // \u65B9\u5411\u4E3A\u5355\u4F4D\u5411\u91CF\uFF0C\u4E8C\u6B21\u9879\u7CFB\u6570\u6052\u4E3A 1\n const b = 2 * (line.direction[0] * ox + line.direction[1] * oy);\n const c = ox * ox + oy * oy - circle.radius * circle.radius;\n const discriminant = b * b - 4 * c;\n const tolerance = 1e-12 * Math.max(1, b * b);\n if (discriminant < -tolerance) return [];\n const root = Math.sqrt(Math.max(0, discriminant));\n const offsets = root <= Math.sqrt(tolerance) ? [-b / 2] : [(-b - root) / 2, (-b + root) / 2];\n const points: Point[] = [];\n for (const t of offsets) {\n if (!parameterWithin(t, line)) continue;\n points.push([line.origin[0] + t * line.direction[0], line.origin[1] + t * line.direction[1]]);\n }\n return points;\n}\n\n/** \u692D\u5706 \u2192 \u7126\u70B9\u6846\u67B6\uFF08\u4E0E ellipsePointAt \u7684\u7126\u70B9\u6781\u89D2\u53C2\u6570\u4E00\u81F4\uFF09 */\nfunction ellipseGeometry(ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>) {\n const frame = ellipseFrameOf(ellipse.center, ellipse.a, ellipse.b, ellipse.rotation);\n return {\n f1: frame.f1,\n metrics: frame.metrics,\n cos: Math.cos(ellipse.rotation),\n sin: Math.sin(ellipse.rotation),\n };\n}\n\n/** \u4E16\u754C\u70B9 \u2192 \u7ED5 F1\u3001\u76F8\u5BF9 F1\u2192F2 \u65B9\u5411\u7684\u6781\u89D2\uFF080..2\u03C0\uFF09 */\nfunction ellipseParamOf(point: Point, f1: Point, rotation: number): number {\n const dx = point[0] - f1[0];\n const dy = point[1] - f1[1];\n let phi = Math.atan2(dy, dx) - rotation;\n phi %= TWO_PI;\n if (phi < 0) phi += TWO_PI;\n return phi;\n}\n\nfunction intersectLineEllipse(\n line: ResolvedLine,\n ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>,\n): Point[] {\n const { f1, cos, sin } = ellipseGeometry(ellipse);\n const dx = line.origin[0] - ellipse.center[0];\n const dy = line.origin[1] - ellipse.center[1];\n const localX = dx * cos + dy * sin;\n const localY = -dx * sin + dy * cos;\n const localDx = line.direction[0] * cos + line.direction[1] * sin;\n const localDy = -line.direction[0] * sin + line.direction[1] * cos;\n const invA2 = 1 / (ellipse.a * ellipse.a);\n const invB2 = 1 / (ellipse.b * ellipse.b);\n const a = localDx * localDx * invA2 + localDy * localDy * invB2;\n const b = 2 * (localX * localDx * invA2 + localY * localDy * invB2);\n const c = localX * localX * invA2 + localY * localY * invB2 - 1;\n const discriminant = b * b - 4 * a * c;\n const tolerance = 1e-12 * Math.max(1, b * b);\n if (discriminant < -tolerance) return [];\n const root = Math.sqrt(Math.max(0, discriminant));\n const offsets = root <= Math.sqrt(tolerance) ? [-b / (2 * a)] : [(-b - root) / (2 * a), (-b + root) / (2 * a)];\n const start = ellipse.startParam ?? 0;\n const end = ellipse.endParam ?? TWO_PI;\n const points: Point[] = [];\n for (const t of offsets) {\n if (!parameterWithin(t, line)) continue;\n const px = localX + t * localDx;\n const py = localY + t * localDy;\n const world: Point = [\n ellipse.center[0] + px * cos - py * sin,\n ellipse.center[1] + px * sin + py * cos,\n ];\n let param = ellipseParamOf(world, f1, ellipse.rotation);\n if (param < start - EPS) param += TWO_PI;\n if (param > end + EPS) continue;\n points.push(world);\n }\n return points;\n}\n\nfunction intersectCircles(\n a: Extract<ResolvedElement, { kind: 'circle' }>,\n b: Extract<ResolvedElement, { kind: 'circle' }>,\n): Point[] {\n const dx = b.center[0] - a.center[0];\n const dy = b.center[1] - a.center[1];\n const distance = Math.hypot(dx, dy);\n // \u540C\u5FC3\uFF08\u542B\u91CD\u5408\uFF09\uFF1A\u65E0\u79BB\u6563\u4EA4\u70B9\n if (distance < EPS) return [];\n if (distance > a.radius + b.radius + EPS) return [];\n if (distance < Math.abs(a.radius - b.radius) - EPS) return [];\n const along = (distance * distance + a.radius * a.radius - b.radius * b.radius) / (2 * distance);\n const heightSquared = a.radius * a.radius - along * along;\n const tolerance = 1e-12 * Math.max(1, a.radius * a.radius);\n if (heightSquared < -tolerance) return [];\n const height = Math.sqrt(Math.max(0, heightSquared));\n const ex = dx / distance;\n const ey = dy / distance;\n const base: Point = [a.center[0] + ex * along, a.center[1] + ey * along];\n if (height <= Math.sqrt(tolerance)) return [base];\n return [\n [base[0] - ey * height, base[1] + ex * height],\n [base[0] + ey * height, base[1] - ex * height],\n ];\n}\n\nfunction circleBranch(circle: Extract<ResolvedElement, { kind: 'circle' }>): PolylineBranch {\n const points: PolylineBranch = [];\n for (let i = 0; i <= CIRCLE_SEGMENTS; i += 1) {\n const angle = (TWO_PI * i) / CIRCLE_SEGMENTS;\n points.push({\n x: circle.center[0] + Math.cos(angle) * circle.radius,\n y: circle.center[1] + Math.sin(angle) * circle.radius,\n t: i / CIRCLE_SEGMENTS,\n });\n }\n return points;\n}\n\nfunction ellipseBranch(ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>): PolylineBranch {\n const { f1, metrics } = ellipseGeometry(ellipse);\n const start = ellipse.startParam ?? 0;\n const end = ellipse.endParam ?? TWO_PI;\n const points: PolylineBranch = [];\n for (let i = 0; i <= ELLIPSE_SEGMENTS; i += 1) {\n const param = start + ((end - start) * i) / ELLIPSE_SEGMENTS;\n const [x, y] = ellipsePointAt(metrics, f1, param);\n points.push({ x, y, t: i / ELLIPSE_SEGMENTS });\n }\n return points;\n}\n\nfunction arcBranch(arc: Extract<ResolvedElement, { kind: 'arc' }>): PolylineBranch {\n const points: PolylineBranch = [];\n for (let i = 0; i <= ARC_SEGMENTS; i += 1) {\n const angle = arc.startAngle + ((arc.endAngle - arc.startAngle) * i) / ARC_SEGMENTS;\n points.push({\n x: arc.center[0] + Math.cos(angle) * arc.radius,\n y: arc.center[1] + Math.sin(angle) * arc.radius,\n t: i / ARC_SEGMENTS,\n });\n }\n return points;\n}\n\nfunction polygonBranch(polygon: Extract<ResolvedElement, { kind: 'polygon' }>): PolylineBranch {\n const points: PolylineBranch = polygon.vertices.map(([x, y], index) => ({\n x,\n y,\n t: index / polygon.vertices.length,\n }));\n const first = polygon.vertices[0]!;\n points.push({ x: first[0], y: first[1], t: 1 });\n return points;\n}\n\nfunction shapeBranches(element: ResolvedElement): PolylineBranch[] | null {\n switch (element.kind) {\n case 'circle':\n return [circleBranch(element)];\n case 'ellipse':\n return [ellipseBranch(element)];\n case 'arc':\n return [arcBranch(element)];\n case 'polygon':\n return [polygonBranch(element)];\n default:\n return null;\n }\n}\n\nfunction polylineSegments(branches: PolylineBranch[]): ResolvedLine[] {\n const out: ResolvedLine[] = [];\n for (const branch of branches) {\n for (let i = 1; i < branch.length; i += 1) {\n const start = branch[i - 1]!;\n const end = branch[i]!;\n const direction = unitVector(end.x - start.x, end.y - start.y);\n if (!direction) continue;\n out.push({\n origin: [start.x, start.y],\n direction,\n extent: 'segment',\n from: [start.x, start.y],\n to: [end.x, end.y],\n });\n }\n }\n return out;\n}\n\nfunction intersectLineWithBranches(line: ResolvedLine, branches: PolylineBranch[]): Point[] {\n const points: Point[] = [];\n for (const segment of polylineSegments(branches)) {\n points.push(...intersectLines(line, segment));\n }\n return points;\n}\n\nfunction intersectLineWithShape(\n line: ResolvedLine,\n shape: ResolvedElement,\n): Point[] {\n switch (shape.kind) {\n case 'circle':\n return intersectLineCircle(line, shape);\n case 'ellipse':\n return intersectLineEllipse(line, shape);\n case 'polygon':\n return intersectLineWithBranches(line, [polygonBranch(shape)]);\n case 'arc':\n return intersectLineWithBranches(line, [arcBranch(shape)]);\n default:\n return [];\n }\n}\n\nfunction intersectPair(a: ResolvedElement, b: ResolvedElement): Point[] {\n if (a.kind === 'line' && b.kind === 'line') return intersectLines(a, b);\n if (a.kind === 'line') return intersectLineWithShape(a, b);\n if (b.kind === 'line') return intersectLineWithShape(b, a);\n if (a.kind === 'circle' && b.kind === 'circle') return intersectCircles(a, b);\n const branchesA = shapeBranches(a);\n const branchesB = shapeBranches(b);\n if (!branchesA || !branchesB) return [];\n return intersectPolylines(branchesA, branchesB, EPS);\n}\n\nfunction sortAndDedupe(points: Point[]): Point[] {\n const sorted = [...points].sort((p, q) => p[0] - q[0] || p[1] - q[1]);\n const out: Point[] = [];\n for (const point of sorted) {\n const last = out[out.length - 1];\n if (last && Math.hypot(point[0] - last[0], point[1] - last[1]) < EPS) continue;\n out.push([point[0], point[1]]);\n }\n return out;\n}\n\n/** \u6C42\u4E24\u89E3\u6790\u5143\u7D20\uFF08\u5DF2\u6309\u69FD\u4F4D\u5904\u7406\uFF09\u7684\u5168\u90E8\u4EA4\u70B9\uFF0C\u6309 x\u3001y \u5347\u5E8F\u5E76\u53BB\u91CD */\nexport function intersectResolved(a: ResolvedElement, b: ResolvedElement): Point[] {\n return sortAndDedupe(intersectPair(a, b));\n}\n", "/**\n * \u6D4B\u91CF\u8BA1\u7B97\uFF08\u5F15\u64CE\u65E0\u5173\uFF09\u3002\n *\n * `measureResolved` \u53EA\u8FD4\u56DE\u7EAF\u6570\u503C\uFF1B`measureLabelPosition` \u8FD4\u56DE\u4E16\u754C\u5750\u6807\u951A\u70B9\u3002\n * \u89C4\u683C\u4E2D\u7684\u50CF\u7D20\u504F\u79FB\uFF08\u5982\u7EBF\u6BB5\u4E2D\u70B9\u6CD5\u5411\u5916\u504F 14px\uFF09\u7531 UI/SVG \u6E32\u67D3\u5C42\u65BD\u52A0\uFF0C\u6B64\u5904\u4E0D\u53E0\u52A0\u5C4F\u5E55\u5355\u4F4D\u3002\n */\n\nimport type { MeasurementKind } from '../core/types-geometry';\nimport type { ResolvedElement } from './resolve';\n\nconst EPSILON = 1e-9;\nconst TWO_PI = Math.PI * 2;\n\ntype Point = [number, number];\ntype ArcElement = Extract<ResolvedElement, { kind: 'arc' }>;\n\nfunction distance(a: Point, b: Point): number {\n return Math.hypot(b[0] - a[0], b[1] - a[1]);\n}\n\nfunction midpoint(a: Point, b: Point): Point {\n return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n}\n\nfunction polygonEdge(vertices: Point[], edge: number): [Point, Point] | null {\n if (!Number.isInteger(edge) || edge < 0 || edge >= vertices.length) return null;\n const a = vertices[edge];\n const b = vertices[(edge + 1) % vertices.length];\n if (!a || !b) return null;\n return [a, b];\n}\n\n/**\n * \u5F27\u7684\u5B9E\u9645\u626B\u89D2\uFF1AendAngle \u2212 startAngle \u53EF\u80FD\u8DE8 2\u03C0\uFF08\u6216\u88AB\u5F52\u4E00\u5316\u5230\u53E6\u4E00\u4FA7\uFF09\uFF0C\n * \u56E0\u6B64\u6309 ccw \u65B9\u5411\u91CD\u65B0\u5F52\u4E00\u5316\uFF0C\u53D6 (0, 2\u03C0] \u5185\u7684\u6709\u7B26\u53F7\u626B\u89D2\uFF08ccw \u4E3A\u6B63\uFF0C\u987A\u65F6\u9488\u4E3A\u8D1F\uFF09\u3002\n */\nfunction arcSweepOf(arc: ArcElement): number | null {\n const raw = arc.endAngle - arc.startAngle;\n if (!Number.isFinite(raw) || Math.abs(raw) < EPSILON) return null;\n if (arc.ccw) {\n const sweep = ((raw % TWO_PI) + TWO_PI) % TWO_PI;\n return sweep < EPSILON ? TWO_PI : sweep;\n }\n const sweep = (((-raw % TWO_PI) + TWO_PI) % TWO_PI);\n return -(sweep < EPSILON ? TWO_PI : sweep);\n}\n\nfunction lengthOf(target: ResolvedElement, edge: number | undefined): number | null {\n if (target.kind === 'line') {\n if (target.extent !== 'segment' || !target.from || !target.to) return null;\n return distance(target.from, target.to);\n }\n if (target.kind === 'polygon') {\n const pair = polygonEdge(target.vertices, edge ?? 0);\n return pair ? distance(pair[0], pair[1]) : null;\n }\n return null;\n}\n\nfunction perimeterOf(target: ResolvedElement): number | null {\n switch (target.kind) {\n case 'polygon': {\n if (target.vertices.length < 3) return null;\n let sum = 0;\n for (let i = 0; i < target.vertices.length; i += 1) {\n const pair = polygonEdge(target.vertices, i);\n if (!pair) return null;\n sum += distance(pair[0], pair[1]);\n }\n return sum < EPSILON ? null : sum;\n }\n case 'circle':\n return target.radius > EPSILON ? TWO_PI * target.radius : null;\n case 'ellipse': {\n const { a, b } = target;\n if (a <= EPSILON || b <= EPSILON) return null;\n // Ramanujan \u8FD1\u4F3C\n return Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)));\n }\n case 'arc': {\n const sweep = arcSweepOf(target);\n if (sweep === null || target.radius <= EPSILON) return null;\n return 2 * target.radius + target.radius * Math.abs(sweep);\n }\n default:\n return null;\n }\n}\n\nfunction areaOf(target: ResolvedElement): number | null {\n switch (target.kind) {\n case 'polygon': {\n if (target.vertices.length < 3) return null;\n let sum = 0;\n for (let i = 0; i < target.vertices.length; i += 1) {\n const p = target.vertices[i];\n const q = target.vertices[(i + 1) % target.vertices.length];\n if (!p || !q) return null;\n sum += p[0] * q[1] - q[0] * p[1];\n }\n const area = Math.abs(sum) / 2;\n return area < EPSILON ? null : area;\n }\n case 'circle':\n return target.radius > EPSILON ? Math.PI * target.radius ** 2 : null;\n case 'ellipse': {\n const { a, b } = target;\n return a > EPSILON && b > EPSILON ? Math.PI * a * b : null;\n }\n case 'arc': {\n const sweep = arcSweepOf(target);\n if (sweep === null || target.radius <= EPSILON) return null;\n // \u6247\u5F62\u9762\u79EF \u00BDr\u00B2\u03B8\n return (target.radius ** 2 * Math.abs(sweep)) / 2;\n }\n default:\n return null;\n }\n}\n\n/** \u76EE\u6807\u7C7B\u578B\u4E0D\u652F\u6301\u3001\u53C2\u6570\u8D8A\u754C\u6216\u9000\u5316\u65F6\u8FD4\u56DE null */\nexport function measureResolved(input: {\n kind: MeasurementKind;\n target: ResolvedElement;\n edge?: number;\n}): number | null {\n const { kind, target, edge } = input;\n const value = kind === 'length'\n ? lengthOf(target, edge)\n : kind === 'perimeter'\n ? perimeterOf(target)\n : areaOf(target);\n if (value === null || !Number.isFinite(value) || value < 0) return null;\n return value;\n}\n\n/** \u9762\u79EF\u8D28\u5FC3\uFF1B\u9000\u5316\uFF08\u96F6\u9762\u79EF\uFF09\u65F6\u8FD4\u56DE null\uFF0C\u7531\u8C03\u7528\u65B9\u56DE\u9000\u5230\u9876\u70B9\u5747\u503C */\nfunction polygonCentroid(vertices: Point[]): Point | null {\n let twiceArea = 0;\n let x = 0;\n let y = 0;\n for (let i = 0; i < vertices.length; i += 1) {\n const p = vertices[i];\n const q = vertices[(i + 1) % vertices.length];\n if (!p || !q) return null;\n const cross = p[0] * q[1] - q[0] * p[1];\n twiceArea += cross;\n x += (p[0] + q[0]) * cross;\n y += (p[1] + q[1]) * cross;\n }\n if (Math.abs(twiceArea) < EPSILON) return null;\n const scale = 1 / (3 * twiceArea);\n return [x * scale, y * scale];\n}\n\nfunction vertexAverage(vertices: Point[]): Point | null {\n if (vertices.length === 0) return null;\n let x = 0;\n let y = 0;\n for (const vertex of vertices) {\n x += vertex[0];\n y += vertex[1];\n }\n return [x / vertices.length, y / vertices.length];\n}\n\n/**\n * \u6807\u7B7E\u951A\u70B9\uFF08\u4E16\u754C\u5750\u6807\uFF09\u3002\u957F\u5EA6\u53D6\u7EBF\u6BB5/\u591A\u8FB9\u5F62\u8FB9\u4E2D\u70B9\uFF0C\u5468\u957F\u4E0E\u9762\u79EF\u53D6\u591A\u8FB9\u5F62\u9762\u79EF\u8D28\u5FC3\n * \uFF08\u9000\u5316\u65F6\u9876\u70B9\u5747\u503C\uFF09\u3001\u5706/\u692D\u5706/\u5F27\u53D6\u5706\u5FC3\u3002\n * \u50CF\u7D20\u504F\u79FB\uFF08\u5982\u6CD5\u5411\u5916\u504F 14px\u3001\u4E2D\u5FC3\u4E0A\u65B9\u504F\u79FB\uFF09\u7531 UI/SVG \u6E32\u67D3\u5C42\u65BD\u52A0\u3002\n */\nexport function measureLabelPosition(\n kind: MeasurementKind,\n target: ResolvedElement,\n edge?: number,\n): Point | null {\n if (kind === 'length') {\n if (target.kind === 'line') {\n if (target.extent !== 'segment' || !target.from || !target.to) return null;\n return midpoint(target.from, target.to);\n }\n if (target.kind === 'polygon') {\n const pair = polygonEdge(target.vertices, edge ?? 0);\n return pair ? midpoint(pair[0], pair[1]) : null;\n }\n return null;\n }\n if (target.kind === 'polygon') {\n return polygonCentroid(target.vertices) ?? vertexAverage(target.vertices);\n }\n if (target.kind === 'circle' || target.kind === 'ellipse' || target.kind === 'arc') {\n return [target.center[0], target.center[1]];\n }\n return null;\n}\n\nconst MEASURE_PREFIX: Record<MeasurementKind, string> = {\n length: '|AB| = ',\n perimeter: 'C = ',\n area: 'S = ',\n};\n\nexport function formatMeasure(kind: MeasurementKind, value: number, precision = 2): string {\n return `${MEASURE_PREFIX[kind]}${value.toFixed(precision)}`;\n}\n", "/**\n * \u4EFF\u5C04\u53D8\u6362\u77E9\u9635\u4E0E\u5DF2\u89E3\u6790\u5143\u7D20\u7684\u57FA\u7840\u7C7B\u578B\u6620\u5C04\u3002\n *\n * \u77E9\u9635\u6309 SVG \u60EF\u4F8B\u8868\u793A\uFF1Ax' = a\u00B7x + b\u00B7y + c\uFF0Cy' = d\u00B7x + e\u00B7y + f\u3002\n * \u8986\u76D6\u70B9/\u7EBF/\u5706/\u591A\u8FB9\u5F62/\u5F27/\u692D\u5706/\u89D2\u5EA6/\u6587\u5B57\uFF1B\u5176\u4F59\u7C7B\u578B\u8FD4\u56DE null\u3002\n */\n\nimport type { TransformSpec } from '../core/types-geometry';\nimport type { ResolvedElement } from './resolve';\nimport {\n ellipseFrameOf,\n ellipseParamFromDirection,\n ellipsePointAt,\n} from './conic.js';\n\nconst TWO_PI = Math.PI * 2;\n\nexport interface AffineMatrix {\n a: number; b: number; c: number;\n d: number; e: number; f: number;\n}\n\nexport function applyPoint(m: AffineMatrix, p: [number, number]): [number, number] {\n return [m.a * p[0] + m.b * p[1] + m.c, m.d * p[0] + m.e * p[1] + m.f];\n}\n\nexport function applyVector(m: AffineMatrix, v: [number, number]): [number, number] {\n return [m.a * v[0] + m.b * v[1], m.d * v[0] + m.e * v[1]];\n}\n\nexport function similarityScale(m: AffineMatrix): number {\n return Math.sqrt(Math.abs(m.a * m.e - m.b * m.d));\n}\n\nexport function translateMatrix(dx: number, dy: number): AffineMatrix {\n return { a: 1, b: 0, c: dx, d: 0, e: 1, f: dy };\n}\n\nexport function rotateMatrix(center: [number, number], angleRad: number): AffineMatrix {\n const cos = Math.cos(angleRad);\n const sin = Math.sin(angleRad);\n return {\n a: cos,\n b: -sin,\n c: center[0] - cos * center[0] + sin * center[1],\n d: sin,\n e: cos,\n f: center[1] - sin * center[0] - cos * center[1],\n };\n}\n\nexport function reflectPointMatrix(center: [number, number]): AffineMatrix {\n return { a: -1, b: 0, c: 2 * center[0], d: 0, e: -1, f: 2 * center[1] };\n}\n\nexport function scaleMatrix(center: [number, number], factor: number): AffineMatrix {\n return {\n a: factor,\n b: 0,\n c: center[0] * (1 - factor),\n d: 0,\n e: factor,\n f: center[1] * (1 - factor),\n };\n}\n\nexport function reflectLineMatrix(\n origin: [number, number],\n direction: [number, number],\n): AffineMatrix {\n const length = Math.hypot(direction[0], direction[1]);\n const ux = direction[0] / length;\n const uy = direction[1] / length;\n const a = 2 * ux * ux - 1;\n const b = 2 * ux * uy;\n const d = 2 * ux * uy;\n const e = 2 * uy * uy - 1;\n return {\n a, b,\n c: origin[0] - (a * origin[0] + b * origin[1]),\n d, e,\n f: origin[1] - (d * origin[0] + e * origin[1]),\n };\n}\n\nexport function matrixForTransform(\n spec: TransformSpec,\n byId: ReadonlyMap<string, ResolvedElement>,\n): AffineMatrix | null {\n switch (spec.kind) {\n case 'translate':\n if (![spec.dx, spec.dy].every(Number.isFinite)) return null;\n return translateMatrix(spec.dx, spec.dy);\n case 'rotate': {\n const center = byId.get(spec.center);\n if (!center || center.kind !== 'point' || !Number.isFinite(spec.angleDeg)) return null;\n return rotateMatrix(center.position, (spec.angleDeg * Math.PI) / 180);\n }\n case 'reflectLine': {\n const axis = byId.get(spec.axis);\n if (!axis || axis.kind !== 'line') return null;\n if (!(Math.hypot(axis.direction[0], axis.direction[1]) > 1e-9)) return null;\n return reflectLineMatrix(axis.origin, axis.direction);\n }\n case 'reflectPoint': {\n const center = byId.get(spec.center);\n if (!center || center.kind !== 'point') return null;\n return reflectPointMatrix(center.position);\n }\n case 'scale': {\n const center = byId.get(spec.center);\n if (!center || center.kind !== 'point') return null;\n if (!Number.isFinite(spec.factor) || Math.abs(spec.factor) <= 1e-9) return null;\n return scaleMatrix(center.position, spec.factor);\n }\n }\n}\n\nfunction transformedAngle(m: AffineMatrix, angle: number): number {\n const v = applyVector(m, [Math.cos(angle), Math.sin(angle)]);\n return Math.atan2(v[1], v[0]);\n}\n\nexport function isFullEllipse(source: Extract<ResolvedElement, { kind: 'ellipse' }>): boolean {\n return source.startParam === 0\n && Math.abs((source.endParam ?? TWO_PI) - TWO_PI) < 1e-9;\n}\n\nfunction transformArc(\n source: Extract<ResolvedElement, { kind: 'arc' }>,\n m: AffineMatrix,\n id: string,\n): Extract<ResolvedElement, { kind: 'arc' }> {\n const det = m.a * m.e - m.b * m.d;\n const startAngle = transformedAngle(m, source.startAngle);\n const signedSweep = source.endAngle - source.startAngle;\n const sweep = det < 0 ? -signedSweep : signedSweep;\n return {\n kind: 'arc',\n id,\n center: applyPoint(m, source.center),\n radius: source.radius * similarityScale(m),\n startAngle,\n endAngle: startAngle + sweep,\n ccw: sweep > 0,\n arcKind: source.arcKind,\n };\n}\n\nfunction transformEllipse(\n source: Extract<ResolvedElement, { kind: 'ellipse' }>,\n m: AffineMatrix,\n id: string,\n): Extract<ResolvedElement, { kind: 'ellipse' }> | null {\n const det = m.a * m.e - m.b * m.d;\n const scale = similarityScale(m);\n const a = source.a * scale;\n const b = source.b * scale;\n if (!(a > 1e-9) || !(b > 1e-9)) return null;\n const center = applyPoint(m, source.center);\n const rotation = transformedAngle(m, source.rotation);\n if (isFullEllipse(source)) {\n return { kind: 'ellipse', id, center, a, b, rotation, startParam: 0, endParam: TWO_PI };\n }\n const oldFrame = ellipseFrameOf(source.center, source.a, source.b, source.rotation);\n const newFrame = ellipseFrameOf(center, a, b, rotation);\n const startPoint = applyPoint(m, ellipsePointAt(oldFrame.metrics, oldFrame.f1, source.startParam ?? 0));\n const endPoint = applyPoint(m, ellipsePointAt(oldFrame.metrics, oldFrame.f1, source.endParam ?? TWO_PI));\n let startParam = ellipseParamFromDirection(newFrame.f1, newFrame.f2, startPoint);\n let endParam = ellipseParamFromDirection(newFrame.f1, newFrame.f2, endPoint);\n if (det < 0) {\n [startParam, endParam] = [endParam, startParam];\n }\n while (endParam <= startParam + 1e-9) endParam += TWO_PI;\n return { kind: 'ellipse', id, center, a, b, rotation, startParam, endParam };\n}\n\nexport function transformResolved(\n source: ResolvedElement,\n m: AffineMatrix,\n id: string,\n): ResolvedElement | null {\n switch (source.kind) {\n case 'point':\n return { kind: 'point', id, position: applyPoint(m, source.position) };\n case 'line': {\n const origin = applyPoint(m, source.origin);\n const direction = applyVector(m, source.direction);\n const length = Math.hypot(direction[0], direction[1]);\n if (!(length > 1e-9)) return null;\n const line: Extract<ResolvedElement, { kind: 'line' }> = {\n kind: 'line',\n id,\n origin,\n direction: [direction[0] / length, direction[1] / length],\n extent: source.extent,\n };\n if (source.from) line.from = applyPoint(m, source.from);\n if (source.to) line.to = applyPoint(m, source.to);\n return line;\n }\n case 'circle':\n return {\n kind: 'circle',\n id,\n center: applyPoint(m, source.center),\n radius: source.radius * similarityScale(m),\n };\n case 'polygon':\n return { kind: 'polygon', id, vertices: source.vertices.map((p) => applyPoint(m, p)) };\n case 'arc':\n return transformArc(source, m, id);\n case 'ellipse':\n return transformEllipse(source, m, id);\n case 'angle': {\n const det = m.a * m.e - m.b * m.d;\n const sweep = det < 0 ? -source.sweep : source.sweep;\n return {\n kind: 'angle',\n id,\n vertex: applyPoint(m, source.vertex),\n startAngle: transformedAngle(m, source.startAngle),\n sweep,\n valueRad: Math.abs(sweep),\n };\n }\n case 'text':\n return {\n kind: 'text',\n id,\n position: applyPoint(m, source.position),\n raw: source.raw,\n format: source.format,\n };\n default:\n return null;\n }\n}\n", "/**\n * \u5C06 GeometryDocument \u5143\u7D20\u89E3\u6790\u4E3A\u6E32\u67D3/\u6D4B\u91CF\u5171\u7528\u7684\u51E0\u4F55\u7ED3\u679C\u3002\n *\n * \u8986\u76D6 point / midpoint / segment / line / ray / circle / circumcircle /\n * arc / semicircle / sector / ellipse / ellipseArc / polygon / parallelogram /\n * rhombus / intersection / perpendicular / parallel / angleBisector / angle /\n * circleTangent / glider / text / measure\u3002\u66F2\u7EBF\u6D3E\u751F\u7C7B\u578B\u8FD4\u56DE UNSUPPORTED_CURVE_DEPENDENT warning\uFF0C\u9000\u5316\u8F93\u5165\u8FD4\u56DE\n * DEGENERATE warning\uFF0C\u907F\u514D\u9759\u9ED8\u4E22\u5F03\u5143\u7D20\u3002\n */\n\nimport type { GeometryElementSpec, LocusSpec, MeasurementKind } from '../core/types-geometry';\nimport type { GeometryDiagnostic } from './kernel';\nimport { circumcircleThrough } from './arc.js';\nimport { ellipseMetricsOf, normalizeEllipseRange } from './conic.js';\nimport { gliderPosition } from './glider.js';\nimport { driverDomainOf } from './locus.js';\nimport { elementWithParentEdge, intersectResolved, resolvedLineOf } from './intersections.js';\nimport { formatMeasure, measureLabelPosition, measureResolved } from './measure.js';\nimport { matrixForTransform, transformResolved } from './transform.js';\n\nexport type ResolvedElement =\n | { kind: 'point'; id: string; position: [number, number] }\n | { kind: 'line'; id: string; origin: [number, number]; direction: [number, number]; extent: 'segment' | 'line' | 'ray'; from?: [number, number]; to?: [number, number] }\n | { kind: 'circle'; id: string; center: [number, number]; radius: number }\n | { kind: 'arc'; id: string; center: [number, number]; radius: number; startAngle: number; endAngle: number; ccw: boolean; arcKind?: 'arc' | 'semicircle' | 'sector' }\n | { kind: 'ellipse'; id: string; center: [number, number]; a: number; b: number; rotation: number; startParam?: number; endParam?: number }\n | { kind: 'polygon'; id: string; vertices: Array<[number, number]> }\n | { kind: 'angle'; id: string; vertex: [number, number]; startAngle: number; sweep: number; valueRad: number }\n | { kind: 'text'; id: string; position: [number, number]; raw: string; format: 'plain' | 'latex' }\n // \u8BBE\u8BA1\u89C4\u683C\u539F\u7A3F\u6B64\u5904\u7B2C\u4E8C\u4E2A\u5B57\u6BB5\u540D\u4E0E\u5224\u522B\u5B57\u6BB5 kind \u91CD\u540D\uFF0C\u6539\u7528 measurementKind\u3002\n | { kind: 'measure'; id: string; measurementKind: MeasurementKind; target: string; value: number; formatted: string; labelPosition: [number, number] }\n | { kind: 'locus'; id: string; points: Array<[number, number]>; closed: boolean };\n\nexport interface ResolveElementsResult {\n elements: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n}\n\nconst ZERO_EPSILON = 1e-9;\nconst TWO_PI = Math.PI * 2;\n\n/** \u6709\u7B26\u53F7\u89D2\u5DEE\u5F52\u4E00\u5230 (\u2212\u03C0, \u03C0]\uFF0C\u7528\u4E8E\u6247\u5F62\u53EA\u53D6\u52A3\u5F27 */\nfunction normalizeAngleDelta(delta: number): number {\n const next = ((delta + Math.PI) % TWO_PI + TWO_PI) % TWO_PI - Math.PI;\n if (next <= -Math.PI + ZERO_EPSILON) return Math.PI;\n return next;\n}\n\nfunction unitVector(dx: number, dy: number): [number, number] | null {\n const length = Math.hypot(dx, dy);\n if (length < ZERO_EPSILON) return null;\n return [dx / length, dy / length];\n}\n\ninterface ResolveCoreResult {\n elements: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n resolvedById: Map<string, ResolvedElement>;\n}\n\n/**\n * \u89E3\u6790\u975E\u8F68\u8FF9\u5143\u7D20\uFF1Boverrides \u6309\u5143\u7D20 id \u8986\u76D6\u7EA6\u675F\u70B9\u53C2\u6570\uFF08\u8F68\u8FF9\u91C7\u6837\u7528\uFF09\u3002\n */\nfunction resolveCore(\n elements: GeometryElementSpec[],\n overrides?: ReadonlyMap<string, number>,\n): ResolveCoreResult {\n const resolved: ResolvedElement[] = [];\n const diagnostics: GeometryDiagnostic[] = [];\n const positions = new Map<string, [number, number]>();\n /** \u5DF2\u89E3\u6790\u5143\u7D20\u8868\uFF1A\u4EA4\u70B9\u548C\u6D3E\u751F\u7EBF\u6309 id \u67E5\u627E\u7236\u5143\u7D20\uFF08\u542B\u7EBF\u6BB5/\u591A\u8FB9\u5F62/\u89D2\u5EA6\uFF09 */\n const resolvedById = new Map<string, ResolvedElement>();\n\n const add = (element: ResolvedElement): void => {\n resolved.push(element);\n resolvedById.set(element.id, element);\n };\n\n const pointOf = (id: string): [number, number] | null => positions.get(id) ?? null;\n const degenerate = (elementId: string, message: string): void => {\n diagnostics.push({ code: 'DEGENERATE', severity: 'warning', elementId, message });\n };\n\n for (const element of elements) {\n switch (element.type) {\n case 'point': {\n const position: [number, number] = [element.position[0], element.position[1]];\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'midpoint': {\n const [firstId, secondId] = element.parents;\n const first = pointOf(firstId);\n const second = pointOf(secondId);\n if (!first || !second) {\n degenerate(element.id, `\u4E2D\u70B9 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const position: [number, number] = [(first[0] + second[0]) / 2, (first[1] + second[1]) / 2];\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'segment':\n case 'line':\n case 'ray': {\n const origin = pointOf(element.parents[0]);\n const target = pointOf(element.parents[1]);\n if (!origin || !target) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const direction = unitVector(target[0] - origin[0], target[1] - origin[1]);\n if (!direction) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u957F\u5EA6\u4E3A\u96F6`);\n break;\n }\n const line: Extract<ResolvedElement, { kind: 'line' }> = {\n kind: 'line', id: element.id, origin: [origin[0], origin[1]], direction, extent: element.type,\n };\n if (element.type === 'segment') {\n line.from = [origin[0], origin[1]];\n line.to = [target[0], target[1]];\n } else if (element.type === 'ray') {\n line.from = [origin[0], origin[1]];\n }\n add(line);\n break;\n }\n case 'circle': {\n const center = pointOf(element.parents[0]);\n if (!center) {\n degenerate(element.id, `\u5706 \"${element.id}\" \u7684\u5706\u5FC3\u7F3A\u5931`);\n break;\n }\n let radius = 1;\n if (element.parents.length === 2) {\n const rim = pointOf(element.parents[1]);\n if (!rim) {\n degenerate(element.id, `\u5706 \"${element.id}\" \u7684\u534A\u5F84\u70B9\u7F3A\u5931`);\n break;\n }\n radius = Math.hypot(rim[0] - center[0], rim[1] - center[1]);\n }\n if (radius < ZERO_EPSILON) {\n degenerate(element.id, `\u5706 \"${element.id}\" \u534A\u5F84\u4E3A\u96F6`);\n break;\n }\n add({ kind: 'circle', id: element.id, center: [center[0], center[1]], radius });\n break;\n }\n case 'circumcircle': {\n const first = pointOf(element.parents[0]);\n const second = pointOf(element.parents[1]);\n const third = pointOf(element.parents[2]);\n if (!first || !second || !third) {\n degenerate(element.id, `\u5916\u63A5\u5706 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const circle = circumcircleThrough(first, second, third);\n if (!circle) {\n degenerate(element.id, `\u5916\u63A5\u5706 \"${element.id}\" \u7684\u4E09\u70B9\u5171\u7EBF\u6216\u91CD\u5408`);\n break;\n }\n add({\n kind: 'circle',\n id: element.id,\n center: [circle.center[0], circle.center[1]],\n radius: circle.radius,\n });\n break;\n }\n case 'arc': {\n const start = pointOf(element.parents[0]);\n const through = pointOf(element.parents[1]);\n const end = pointOf(element.parents[2]);\n if (!start || !through || !end) {\n degenerate(element.id, `\u5706\u5F27 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const circle = circumcircleThrough(start, through, end);\n if (!circle) {\n degenerate(element.id, `\u5706\u5F27 \"${element.id}\" \u7684\u4E09\u70B9\u5171\u7EBF\u6216\u91CD\u5408`);\n break;\n }\n add({\n kind: 'arc',\n id: element.id,\n center: [circle.center[0], circle.center[1]],\n radius: circle.radius,\n startAngle: circle.startAngle,\n endAngle: circle.startAngle + circle.sweep,\n ccw: circle.sweep > 0,\n arcKind: 'arc',\n });\n break;\n }\n case 'semicircle': {\n const start = pointOf(element.parents[0]);\n const end = pointOf(element.parents[1]);\n if (!start || !end) {\n degenerate(element.id, `\u534A\u5706 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const center: [number, number] = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];\n const radius = Math.hypot(end[0] - start[0], end[1] - start[1]) / 2;\n if (radius < ZERO_EPSILON) {\n degenerate(element.id, `\u534A\u5706 \"${element.id}\" \u7684\u76F4\u5F84\u4E3A\u96F6`);\n break;\n }\n const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);\n add({\n kind: 'arc',\n id: element.id,\n center,\n radius,\n startAngle,\n endAngle: startAngle + Math.PI,\n ccw: true,\n arcKind: 'semicircle',\n });\n break;\n }\n case 'sector': {\n const center = pointOf(element.parents[0]);\n const start = pointOf(element.parents[1]);\n const end = pointOf(element.parents[2]);\n if (!center || !start || !end) {\n degenerate(element.id, `\u6247\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const radius = Math.hypot(start[0] - center[0], start[1] - center[1]);\n const endRadius = Math.hypot(end[0] - center[0], end[1] - center[1]);\n if (radius < ZERO_EPSILON || endRadius < ZERO_EPSILON) {\n degenerate(element.id, `\u6247\u5F62 \"${element.id}\" \u7684\u534A\u5F84\u4E3A\u96F6`);\n break;\n }\n const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);\n const endDirection = Math.atan2(end[1] - center[1], end[0] - center[0]);\n const delta = normalizeAngleDelta(endDirection - startAngle);\n if (Math.abs(delta) < ZERO_EPSILON) {\n degenerate(element.id, `\u6247\u5F62 \"${element.id}\" \u7684\u5706\u5FC3\u89D2\u4E3A\u96F6`);\n break;\n }\n add({\n kind: 'arc',\n id: element.id,\n center: [center[0], center[1]],\n radius,\n startAngle,\n endAngle: startAngle + delta,\n ccw: delta > 0,\n arcKind: 'sector',\n });\n break;\n }\n case 'ellipse':\n case 'ellipseArc': {\n const firstFocus = pointOf(element.parents[0]);\n const secondFocus = pointOf(element.parents[1]);\n const rim = pointOf(element.parents[2]);\n if (!firstFocus || !secondFocus || !rim) {\n degenerate(element.id, `\u692D\u5706 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const metrics = ellipseMetricsOf(firstFocus, secondFocus, rim);\n if (!metrics) {\n degenerate(element.id, `\u692D\u5706 \"${element.id}\" \u7684\u7126\u70B9\u91CD\u5408\u6216\u70B9\u4F4D\u9000\u5316`);\n break;\n }\n const a = metrics.major / 2;\n const halfFocal = metrics.focal / 2;\n const b = Math.sqrt(a * a - halfFocal * halfFocal);\n if (!Number.isFinite(b) || b <= 0) {\n degenerate(element.id, `\u692D\u5706 \"${element.id}\" \u7684\u77ED\u534A\u8F74\u9000\u5316`);\n break;\n }\n const ellipse: Extract<ResolvedElement, { kind: 'ellipse' }> = {\n kind: 'ellipse',\n id: element.id,\n center: [(firstFocus[0] + secondFocus[0]) / 2, (firstFocus[1] + secondFocus[1]) / 2],\n a,\n b,\n rotation: metrics.axisAngle,\n startParam: 0,\n endParam: TWO_PI,\n };\n if (element.type === 'ellipseArc') {\n const span = element.endParam - element.startParam;\n if (!Number.isFinite(span)) {\n degenerate(element.id, `\u692D\u5706\u5F27 \"${element.id}\" \u7684\u8D77\u6B62\u53C2\u6570\u975E\u6CD5`);\n break;\n }\n if (Math.abs(span) >= TWO_PI - 1e-6) {\n // \u6574\u5468\u5F27\u7B49\u4EF7\u4E8E\u6574\u692D\u5706\uFF1B\u907F\u514D\u5F52\u4E00\u5316\u628A 2\u03C0 \u8BEF\u5224\u4E3A\u91CD\u5408\n ellipse.startParam = 0;\n ellipse.endParam = TWO_PI;\n } else {\n const range = normalizeEllipseRange(element.startParam, element.endParam);\n if (!range) {\n degenerate(element.id, `\u692D\u5706\u5F27 \"${element.id}\" \u7684\u8D77\u6B62\u53C2\u6570\u91CD\u5408`);\n break;\n }\n ellipse.startParam = range[0];\n ellipse.endParam = range[1];\n }\n }\n add(ellipse);\n break;\n }\n case 'polygon': {\n const vertices: Array<[number, number]> = [];\n let missingParent = false;\n for (const parentId of element.parents) {\n const point = pointOf(parentId);\n if (!point) {\n missingParent = true;\n break;\n }\n vertices.push([point[0], point[1]]);\n }\n if (missingParent) {\n degenerate(element.id, `\u591A\u8FB9\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n if (vertices.length < 3) {\n degenerate(element.id, `\u591A\u8FB9\u5F62 \"${element.id}\" \u7684\u9876\u70B9\u5C11\u4E8E 3 \u4E2A`);\n break;\n }\n add({ kind: 'polygon', id: element.id, vertices });\n break;\n }\n case 'parallelogram': {\n const a = pointOf(element.parents[0]);\n const b = pointOf(element.parents[1]);\n const c = pointOf(element.parents[2]);\n if (!a || !b || !c) {\n degenerate(element.id, `\u5E73\u884C\u56DB\u8FB9\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n add({\n kind: 'polygon',\n id: element.id,\n vertices: [a, b, c, [a[0] + c[0] - b[0], a[1] + c[1] - b[1]]],\n });\n break;\n }\n case 'rhombus': {\n const a = pointOf(element.parents[0]);\n const b = pointOf(element.parents[1]);\n const c = pointOf(element.parents[2]);\n if (!a || !b || !c) {\n degenerate(element.id, `\u83F1\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const direction = unitVector(c[0] - b[0], c[1] - b[1]);\n const edgeLength = Math.hypot(b[0] - a[0], b[1] - a[1]);\n if (!direction || edgeLength < ZERO_EPSILON) {\n degenerate(element.id, `\u83F1\u5F62 \"${element.id}\" \u7684\u8FB9\u9000\u5316\u4E3A\u96F6`);\n break;\n }\n const cPrime: [number, number] = [b[0] + direction[0] * edgeLength, b[1] + direction[1] * edgeLength];\n const d: [number, number] = [a[0] + direction[0] * edgeLength, a[1] + direction[1] * edgeLength];\n add({ kind: 'polygon', id: element.id, vertices: [a, b, cPrime, d] });\n break;\n }\n case 'intersection': {\n const parentA = resolvedById.get(element.parents[0]);\n const parentB = resolvedById.get(element.parents[1]);\n if (!parentA || !parentB) {\n degenerate(element.id, `\u4EA4\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u7F3A\u5931`);\n break;\n }\n const shapeA = elementWithParentEdge(parentA, element.parentEdges?.[0] ?? null);\n const shapeB = elementWithParentEdge(parentB, element.parentEdges?.[1] ?? null);\n if (!shapeA || !shapeB) {\n degenerate(element.id, `\u4EA4\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u8FB9/\u817F\u8D85\u51FA\u8303\u56F4`);\n break;\n }\n const points = intersectResolved(shapeA, shapeB);\n const index = element.index ?? 0;\n if (!Number.isInteger(index) || index < 0 || index >= points.length) {\n degenerate(element.id, `\u4EA4\u70B9 \"${element.id}\" \u7684\u7B2C ${index} \u4E2A\u4EA4\u70B9\u4E0D\u5B58\u5728`);\n break;\n }\n const point = points[index]!;\n const position: [number, number] = [point[0], point[1]];\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'perpendicular':\n case 'parallel': {\n const base = resolvedById.get(element.parents[0]);\n const through = pointOf(element.parents[1]);\n if (!base || !through) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u7684\u7236\u7EBF\u6216\u8FC7\u70B9\u7F3A\u5931`);\n break;\n }\n const baseLine = resolvedLineOf(base, element.parentEdges?.[0] ?? null);\n if (!baseLine) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u7684\u7236\u7EBF\u4E0D\u53EF\u7528`);\n break;\n }\n const direction: [number, number] = element.type === 'perpendicular'\n ? [-baseLine.direction[1], baseLine.direction[0]]\n : [baseLine.direction[0], baseLine.direction[1]];\n add({\n kind: 'line',\n id: element.id,\n origin: [through[0], through[1]],\n direction,\n extent: 'line',\n });\n break;\n }\n case 'angleBisector': {\n const first = pointOf(element.parents[0]);\n const vertex = pointOf(element.parents[1]);\n const second = pointOf(element.parents[2]);\n if (!first || !vertex || !second) {\n degenerate(element.id, `\u89D2\u5E73\u5206\u7EBF \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const firstDirection = unitVector(first[0] - vertex[0], first[1] - vertex[1]);\n const secondDirection = unitVector(second[0] - vertex[0], second[1] - vertex[1]);\n if (!firstDirection || !secondDirection) {\n degenerate(element.id, `\u89D2\u5E73\u5206\u7EBF \"${element.id}\" \u7684\u8FB9\u9000\u5316\u4E3A\u96F6`);\n break;\n }\n const direction = unitVector(\n firstDirection[0] + secondDirection[0],\n firstDirection[1] + secondDirection[1],\n );\n if (!direction) {\n degenerate(element.id, `\u89D2\u5E73\u5206\u7EBF \"${element.id}\" \u7684\u4E24\u8FB9\u53CD\u5411\u5171\u7EBF`);\n break;\n }\n add({\n kind: 'line',\n id: element.id,\n origin: [vertex[0], vertex[1]],\n direction,\n extent: 'line',\n });\n break;\n }\n case 'angle': {\n const first = pointOf(element.parents[0]);\n const vertex = pointOf(element.parents[1]);\n const second = pointOf(element.parents[2]);\n if (!first || !vertex || !second) {\n degenerate(element.id, `\u89D2 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const firstDirection = unitVector(first[0] - vertex[0], first[1] - vertex[1]);\n const secondDirection = unitVector(second[0] - vertex[0], second[1] - vertex[1]);\n if (!firstDirection || !secondDirection) {\n degenerate(element.id, `\u89D2 \"${element.id}\" \u7684\u8FB9\u9000\u5316\u4E3A\u96F6`);\n break;\n }\n const startAngle = Math.atan2(firstDirection[1], firstDirection[0]);\n const endAngle = Math.atan2(secondDirection[1], secondDirection[0]);\n const sweep = normalizeAngleDelta(endAngle - startAngle);\n if (Math.abs(sweep) < ZERO_EPSILON) {\n degenerate(element.id, `\u89D2 \"${element.id}\" \u7684\u5706\u5FC3\u89D2\u4E3A\u96F6`);\n break;\n }\n add({\n kind: 'angle',\n id: element.id,\n vertex: [vertex[0], vertex[1]],\n startAngle,\n sweep,\n valueRad: Math.abs(sweep),\n });\n break;\n }\n case 'circleTangent': {\n const circle = resolvedById.get(element.parents[0]);\n if (!circle || circle.kind !== 'circle') {\n degenerate(element.id, `\u5706\u5207\u7EBF \"${element.id}\" \u7684\u7236\u5706\u7F3A\u5931`);\n break;\n }\n const radial = unitVector(\n element.position[0] - circle.center[0],\n element.position[1] - circle.center[1],\n );\n if (!radial) {\n degenerate(element.id, `\u5706\u5207\u7EBF \"${element.id}\" \u7684\u5207\u70B9\u4E0E\u5706\u5FC3\u91CD\u5408`);\n break;\n }\n const tangentPoint: [number, number] = [\n circle.center[0] + radial[0] * circle.radius,\n circle.center[1] + radial[1] * circle.radius,\n ];\n add({\n kind: 'line',\n id: element.id,\n origin: tangentPoint,\n direction: [-radial[1], radial[0]],\n extent: 'line',\n });\n break;\n }\n case 'glider': {\n const parent = resolvedById.get(element.parent);\n if (!parent) {\n degenerate(element.id, `\u7EA6\u675F\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u7F3A\u5931`);\n break;\n }\n const position = gliderPosition({\n parent,\n param: overrides?.get(element.id) ?? element.param,\n parentEdge: element.parentEdge,\n });\n if (!position) {\n degenerate(element.id, `\u7EA6\u675F\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u7C7B\u578B\u4E0D\u652F\u6301\u6216\u53C2\u6570\u8D8A\u754C`);\n break;\n }\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'measure': {\n const target = resolvedById.get(element.target);\n if (!target) {\n degenerate(element.id, `\u6D4B\u91CF \"${element.id}\" \u7684\u76EE\u6807\u5143\u7D20 \"${element.target}\" \u7F3A\u5931`);\n break;\n }\n const value = measureResolved({ kind: element.kind, target, edge: element.edge });\n const labelPosition = measureLabelPosition(element.kind, target, element.edge);\n if (value === null || !labelPosition) {\n degenerate(element.id, `\u6D4B\u91CF \"${element.id}\" \u7684\u76EE\u6807\u5143\u7D20\u4E0D\u53EF\u6D4B`);\n break;\n }\n add({\n kind: 'measure',\n id: element.id,\n measurementKind: element.kind,\n target: element.target,\n value,\n formatted: formatMeasure(element.kind, value, element.precision ?? 2),\n labelPosition,\n });\n break;\n }\n case 'text': {\n add({\n kind: 'text',\n id: element.id,\n position: [element.position[0], element.position[1]],\n raw: element.raw,\n format: element.format ?? 'plain',\n });\n break;\n }\n case 'transform': {\n const source = resolvedById.get(element.source);\n if (!source) {\n degenerate(element.id, `\u53D8\u6362 \"${element.id}\" \u7684\u6E90\u5143\u7D20\u7F3A\u5931`);\n break;\n }\n const matrix = matrixForTransform(element, resolvedById);\n if (!matrix) {\n degenerate(element.id, `\u53D8\u6362 \"${element.id}\" \u7684\u53C2\u6570\u6216\u4E2D\u5FC3/\u8F74\u4E0D\u53EF\u7528`);\n break;\n }\n const transformed = transformResolved(source, matrix, element.id);\n if (!transformed) {\n diagnostics.push({\n code: 'UNSUPPORTED_TYPE',\n severity: 'warning',\n elementId: element.id,\n message: `\u53D8\u6362 \"${element.id}\" \u4E0D\u652F\u6301\u6E90\u7C7B\u578B ${source.kind}`,\n });\n break;\n }\n add(transformed);\n break;\n }\n case 'curvePoint':\n case 'curveIntersection':\n case 'curveTangent':\n diagnostics.push({\n code: 'UNSUPPORTED_CURVE_DEPENDENT',\n severity: 'warning',\n elementId: element.id,\n message: `\u66F2\u7EBF\u6D3E\u751F\u5143\u7D20 \"${element.id}\"\uFF08${element.type}\uFF09\u5728 MCP v1 \u4E2D\u6682\u4E0D\u652F\u6301`,\n });\n break;\n }\n }\n\n return { elements: resolved, diagnostics, resolvedById };\n}\n\n/**\n * \u89E3\u6790\u5143\u7D20\u5217\u8868\uFF08\u6784\u9020\u987A\u5E8F\u5373\u4F9D\u8D56\u987A\u5E8F\uFF09\u3002\n * \u5DF2\u89E3\u6790\u7684\u5750\u6807\u70B9\u5B58\u5165 positions\uFF0C\u4F9B\u540E\u7EED\u7C7B\u578B\u6309 id \u67E5\u627E\u7236\u70B9\u3002\n * \u8F68\u8FF9\u5355\u72EC\u91C7\u6837\uFF1A\u5176\u4F59\u5143\u7D20\u89E3\u6790\u4E00\u6B21\uFF0C\u8F68\u8FF9\u6309\u9A71\u52A8\u53C2\u6570\u91CD\u590D\u89E3\u6790\u3002\n */\nexport function resolveElements(\n elements: GeometryElementSpec[],\n options: { overrides?: ReadonlyMap<string, number> } = {},\n): ResolveElementsResult {\n const nonLocus = elements.filter((el) => el.type !== 'locus');\n const core = resolveCore(nonLocus, options.overrides);\n const diagnostics = [...core.diagnostics];\n const loci: ResolvedElement[] = [];\n for (const spec of elements) {\n if (spec.type !== 'locus') continue;\n const result = resolveLocus(spec, nonLocus, core);\n if (result.locus) loci.push(result.locus);\n diagnostics.push(...result.diagnostics);\n }\n return { elements: [...core.elements, ...loci], diagnostics };\n}\n\ninterface LocusResolution {\n locus: Extract<ResolvedElement, { kind: 'locus' }> | null;\n diagnostics: GeometryDiagnostic[];\n}\n\n/** \u8F68\u8FF9\u91C7\u6837\u6B21\u6570\u4E0A\u9650\uFF1B\u6B65\u8FDB\u5FAA\u73AF\u56FA\u5B9A\u6267\u884C samples \u6B21\uFF0C\u4E0D\u5B58\u5728\u53D1\u6563\u5FAA\u73AF */\nconst MAX_LOCUS_SAMPLES = 400;\nconst MAX_LOCUS_WORK = 100_000;\n\nfunction resolveLocus(\n spec: LocusSpec,\n nonLocus: GeometryElementSpec[],\n core: ResolveCoreResult,\n): LocusResolution {\n const invalid = (message: string): LocusResolution => ({\n locus: null,\n diagnostics: [{ code: 'DEGENERATE', severity: 'warning', elementId: spec.id, message }],\n });\n const driverSpec = nonLocus.find((el) => el.id === spec.driver);\n if (!driverSpec || driverSpec.type !== 'glider') return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u9A71\u52A8\u5143\u7D20\u4E0D\u662F\u7EA6\u675F\u70B9`);\n const driverParent = core.resolvedById.get(driverSpec.parent);\n if (!driverParent) return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u9A71\u52A8\u7236\u5143\u7D20\u7F3A\u5931`);\n const domain = driverDomainOf(driverParent, driverSpec.parentEdge);\n if (!domain) {\n return {\n locus: null,\n diagnostics: [\n {\n code: 'UNSUPPORTED_TYPE',\n severity: 'warning',\n elementId: spec.id,\n message: `\u8F68\u8FF9 \"${spec.id}\" \u7684\u9A71\u52A8\u5BF9\u8C61\u65E0\u754C\u6216\u7C7B\u578B\u4E0D\u652F\u6301`,\n },\n ],\n };\n }\n const requested = spec.samples ?? 200;\n if (!Number.isFinite(requested)) return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u91C7\u6837\u6570\u975E\u6CD5`);\n // \u91C7\u6837\u6570\u6309\u89C4\u683C\u94B3\u5236\u5230 16\u2013400\uFF0C\u7F3A\u7701 200\n const samples = Math.max(16, Math.min(MAX_LOCUS_SAMPLES, Math.round(requested)));\n if (samples * nonLocus.length > MAX_LOCUS_WORK) {\n return {\n locus: null,\n diagnostics: [\n {\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n elementId: spec.id,\n message: `\u8F68\u8FF9 \"${spec.id}\" \u91C7\u6837\u91CF\u8D85\u9650\uFF08samples=${samples}\uFF0C\u5143\u7D20=${nonLocus.length}\uFF09`,\n },\n ],\n };\n }\n\n const step = domain.closed\n ? (domain.to - domain.from) / samples\n : (domain.to - domain.from) / (samples - 1);\n const points: Array<[number, number]> = [];\n let failed = 0;\n for (let i = 0; i < samples; i += 1) {\n const param = domain.from + step * i;\n const resolved = resolveCore(nonLocus, new Map([[spec.driver, param]]));\n const trace = resolved.resolvedById.get(spec.trace);\n if (!trace || trace.kind !== 'point') {\n failed += 1;\n continue;\n }\n points.push([trace.position[0], trace.position[1]]);\n }\n if (points.length < 2) return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u6709\u6548\u91C7\u6837\u70B9\u5C11\u4E8E 2 \u4E2A`);\n const first = points[0]!;\n if (points.every((p) => Math.hypot(p[0] - first[0], p[1] - first[1]) < 1e-9)) {\n return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u9000\u5316\u4E3A\u4E00\u70B9`);\n }\n const diagnostics: GeometryDiagnostic[] = failed > 0\n ? [{\n code: 'DEGENERATE',\n severity: 'warning',\n elementId: spec.id,\n message: `\u8F68\u8FF9 \"${spec.id}\" \u6709 ${failed} \u4E2A\u91C7\u6837\u70B9\u89E3\u6790\u5931\u8D25`,\n }]\n : [];\n return { locus: { kind: 'locus', id: spec.id, points, closed: domain.closed }, diagnostics };\n}\n", "/**\n * \u89D2\u5EA6\u6807\u6CE8\u6587\u672C\u683C\u5F0F\u5316\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n * \u9ED8\u8BA4\u5355\u4F4D\u4E3A\u5EA6\uFF08deg\uFF09\uFF1A'90.0\u00B0'\uFF1B\u5F27\u5EA6\uFF08rad\uFF09\uFF1A'1.57 rad'\u3002\n */\n\nimport type { GeometryAngleUnit } from '../core/types-geometry';\n\nexport const DEFAULT_ANGLE_UNIT: GeometryAngleUnit = 'deg';\n\n/** \u5EA6\u6570 \u2192 \u6309\u5355\u4F4D\u683C\u5F0F\u5316\u7684\u663E\u793A\u6587\u672C */\nexport function formatAngle(degrees: number, unit: GeometryAngleUnit = DEFAULT_ANGLE_UNIT): string {\n if (!Number.isFinite(degrees)) return '';\n if (unit === 'rad') return `${((degrees * Math.PI) / 180).toFixed(2)} rad`;\n return `${degrees.toFixed(1)}\u00B0`;\n}\n", "/**\n * \u51E0\u4F55\u5185\u6838\u5165\u53E3\uFF1A\u6587\u6863\u6821\u9A8C + \u5143\u7D20\u89E3\u6790 + \u8BCA\u65AD\u6C47\u603B\u3002\n * \u4E0D\u629B\u5F02\u5E38\uFF0C\u6240\u6709\u9519\u8BEF\u8FDB\u5165 diagnostics\uFF1B\u4EC5\u5B58\u5728 error \u7EA7\u8BCA\u65AD\u65F6 ok \u4E3A false\u3002\n */\n\nimport type { GeometryDocument } from '../core/types-geometry';\nimport { validateConstruction } from './construction.js';\nimport { resolveElements, type ResolvedElement } from './resolve.js';\n\nexport type { ResolvedElement } from './resolve.js';\n\nexport { measureResolved, formatMeasure } from './measure.js';\nexport { formatAngle } from './angle.js';\nexport { driverDomainOf } from './locus.js';\nexport { dependsOn } from './construction.js';\nexport { transformResolved, matrixForTransform } from './transform.js';\n\nexport type GeometryDiagnosticCode =\n | 'INVALID_DOCUMENT' | 'DUPLICATE_ID' | 'UNKNOWN_PARENT' | 'CYCLE'\n | 'DEGENERATE' | 'UNSUPPORTED_TYPE' | 'UNSUPPORTED_CURVE_DEPENDENT'\n | 'LIMIT_EXCEEDED' | 'LATEX_RENDERER_MISSING';\n\nexport interface GeometryDiagnostic {\n code: GeometryDiagnosticCode;\n severity: 'error' | 'warning';\n elementId?: string;\n message: string;\n}\n\nexport interface ResolveResult {\n ok: boolean;\n document: GeometryDocument;\n elements: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n}\n\nexport function resolveGeometryDocument(\n document: GeometryDocument,\n options: { maxElements?: number } = {},\n): ResolveResult {\n const diagnostics: GeometryDiagnostic[] = [];\n if (document.protocolVersion !== 1 || !Array.isArray(document.elements)) {\n return { ok: false, document, elements: [], diagnostics: [{ code: 'INVALID_DOCUMENT', severity: 'error', message: '\u4EC5\u652F\u6301 protocolVersion=1 \u7684 GeometryDocument' }] };\n }\n if (document.elements.length > (options.maxElements ?? 500)) {\n diagnostics.push({ code: 'LIMIT_EXCEEDED', severity: 'error', message: '\u5143\u7D20\u6570\u8D85\u8FC7 500' });\n }\n for (const issue of validateConstruction(document.elements)) {\n diagnostics.push({\n code: issue.rule === 'C1' ? 'DUPLICATE_ID' : issue.rule === 'C2' ? 'UNKNOWN_PARENT' : 'INVALID_DOCUMENT',\n severity: 'error',\n message: issue.message,\n });\n }\n const { elements, diagnostics: resolveDiagnostics } = resolveElements(document.elements);\n diagnostics.push(...resolveDiagnostics);\n return {\n ok: diagnostics.every((d) => d.severity !== 'error'),\n document,\n elements,\n diagnostics,\n };\n}\n", "/**\n * geometry_measure \u5DE5\u5177\uFF1A\u8BFB\u53D6 protocolVersion=1 \u5E73\u9762\u51E0\u4F55\u6587\u6863\u5185\u7684\u6D4B\u91CF/\u89D2\u5EA6\uFF0C\n * \u5E76\u652F\u6301\u4E34\u65F6\u67E5\u8BE2\uFF08\u4E24\u70B9\u8DDD\u79BB\u3001\u4E09\u70B9\u89D2\u5EA6\u3001\u6307\u5B9A\u76EE\u6807\u5143\u7D20\u6D4B\u91CF\uFF09\u3002\n * \u65E0 DOM\u3001\u65E0\u72B6\u6001\uFF0C\u76F4\u8C03 @studylab/viz/geometry-kernel\uFF1B\u67E5\u8BE2\u7ED3\u679C\u4E0D\u5199\u56DE\u6587\u6863\u3002\n * \u6587\u6863/\u67E5\u8BE2\u8BED\u4E49\u5931\u8D25\u4EE5 diagnostics \u8FD4\u56DE\uFF1B\u4EC5\u53C2\u6570\u7ED3\u6784\u635F\u574F\u624D isError\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n resolveGeometryDocument,\n measureResolved,\n formatMeasure,\n formatAngle,\n type GeometryDiagnostic,\n type ResolvedElement,\n} from '@studylab/viz/geometry-kernel';\n\n/** \u6587\u6863 JSON \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const DOCUMENT_JSON_MAX_LENGTH = 512 * 1024;\n\n/** \u4E34\u65F6\u67E5\u8BE2\u6761\u6570\u4E0A\u9650 */\nexport const MEASURE_QUERIES_MAX = 50;\n\ntype GeometryDocumentInput = Parameters<typeof resolveGeometryDocument>[0];\ntype AngleUnit = 'deg' | 'rad';\ntype MeasurementKind = 'length' | 'perimeter' | 'area';\ntype QueryPoint = [number, number];\n\nexport interface GeometryMeasureArgs {\n document: unknown;\n queries?: unknown;\n}\n\ninterface GeometryMeasurement {\n kind: MeasurementKind | 'angle';\n target?: string;\n value: number;\n formatted: string;\n}\n\ninterface GeometryMeasurePayload {\n measurements: GeometryMeasurement[];\n diagnostics: GeometryDiagnostic[];\n}\n\ntype ParsedQuery =\n | { mode: 'points-length'; points: [QueryPoint, QueryPoint] }\n | { mode: 'points-angle'; points: [QueryPoint, QueryPoint, QueryPoint] }\n | { mode: 'target'; measurementKind: MeasurementKind; target: string; edge?: number };\n\nfunction textResult(payload: GeometryMeasurePayload): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalidDocument(message: string): GeometryDiagnostic {\n return { code: 'INVALID_DOCUMENT', severity: 'error', message };\n}\n\nfunction limitDiagnostic(): GeometryDiagnostic {\n return {\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n message: `\u6587\u6863 JSON \u8D85\u8FC7 ${DOCUMENT_JSON_MAX_LENGTH} \u5B57\u8282\uFF08512KB\uFF09`,\n };\n}\n\nfunction queryDiagnostic(index: number, message: string): GeometryDiagnostic {\n return { code: 'DEGENERATE', severity: 'warning', message: `\u7B2C ${index + 1} \u4E2A\u67E5\u8BE2\uFF1A${message}` };\n}\n\n/** \u6587\u6863\u6837\u5F0F\u4E2D\u7684\u89D2\u5EA6\u5355\u4F4D\uFF0C\u7F3A\u7701\u4E3A\u5EA6 */\nfunction angleUnitOf(document: Record<string, unknown>): AngleUnit {\n const style = document.style;\n return isRecord(style) && style.angleUnit === 'rad' ? 'rad' : 'deg';\n}\n\nfunction parsePoint(value: unknown): QueryPoint | null {\n if (!Array.isArray(value) || value.length !== 2) return null;\n const [x, y] = value;\n if (typeof x !== 'number' || typeof y !== 'number') return null;\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return [x, y];\n}\n\nfunction parsePoints2(value: unknown): [QueryPoint, QueryPoint] | null {\n if (!Array.isArray(value) || value.length !== 2) return null;\n const first = parsePoint(value[0]);\n const second = parsePoint(value[1]);\n return first && second ? [first, second] : null;\n}\n\nfunction parsePoints3(value: unknown): [QueryPoint, QueryPoint, QueryPoint] | null {\n if (!Array.isArray(value) || value.length !== 3) return null;\n const first = parsePoint(value[0]);\n const second = parsePoint(value[1]);\n const third = parsePoint(value[2]);\n return first && second && third ? [first, second, third] : null;\n}\n\n/** \u7CBE\u786E\u533A\u5206 points \u4E0E target \u4E24\u79CD\u67E5\u8BE2\u5F62\u72B6\uFF1B\u7ED3\u6784\u635F\u574F\u8FD4\u56DE null */\nfunction parseQuery(value: unknown): ParsedQuery | null {\n if (!isRecord(value)) return null;\n const { kind, points, target, edge } = value;\n\n if (points !== undefined) {\n if (kind === 'length') {\n const parsed = parsePoints2(points);\n return parsed ? { mode: 'points-length', points: parsed } : null;\n }\n if (kind === 'angle') {\n const parsed = parsePoints3(points);\n return parsed ? { mode: 'points-angle', points: parsed } : null;\n }\n return null;\n }\n\n if (kind !== 'length' && kind !== 'perimeter' && kind !== 'area') return null;\n if (typeof target !== 'string' || target.length === 0) return null;\n if (edge !== undefined && (typeof edge !== 'number' || !Number.isInteger(edge))) return null;\n return edge === undefined\n ? { mode: 'target', measurementKind: kind, target }\n : { mode: 'target', measurementKind: kind, target, edge };\n}\n\nfunction unitVector(from: QueryPoint, origin: QueryPoint): QueryPoint | null {\n const dx = from[0] - origin[0];\n const dy = from[1] - origin[1];\n const length = Math.hypot(dx, dy);\n if (length < 1e-9) return null;\n return [dx / length, dy / length];\n}\n\n/** \u51F8\u89D2\uFF080..\u03C0\uFF09\u7684\u5F27\u5EA6\u503C\uFF1B\u4EFB\u4E00\u8FB9\u9000\u5316\u65F6\u8FD4\u56DE null */\nfunction convexAngle(a: QueryPoint, vertex: QueryPoint, b: QueryPoint): number | null {\n const first = unitVector(a, vertex);\n const second = unitVector(b, vertex);\n if (!first || !second) return null;\n const dot = first[0] * second[0] + first[1] * second[1];\n return Math.acos(Math.min(1, Math.max(-1, dot)));\n}\n\nfunction documentMeasurements(\n elements: ResolvedElement[],\n angleUnit: AngleUnit,\n): GeometryMeasurement[] {\n const measurements: GeometryMeasurement[] = [];\n for (const element of elements) {\n if (element.kind === 'measure') {\n measurements.push({\n kind: element.measurementKind,\n target: element.target,\n value: element.value,\n formatted: element.formatted,\n });\n } else if (element.kind === 'angle') {\n measurements.push({\n kind: 'angle',\n target: element.id,\n value: element.valueRad,\n formatted: formatAngle((element.valueRad * 180) / Math.PI, angleUnit),\n });\n }\n }\n return measurements;\n}\n\nexport async function handleGeometryMeasure(args: GeometryMeasureArgs): Promise<CallToolResult> {\n try {\n if (!args || typeof args !== 'object' || !('document' in args)) {\n return errorResult('\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981 { document: object, queries?: Array }');\n }\n\n const { document, queries } = args;\n if (queries !== undefined && !Array.isArray(queries)) {\n return errorResult('\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1Aqueries \u5FC5\u987B\u662F\u6570\u7EC4');\n }\n if (Array.isArray(queries) && queries.length > MEASURE_QUERIES_MAX) {\n return errorResult(`\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1Aqueries \u6700\u591A ${MEASURE_QUERIES_MAX} \u6761`);\n }\n\n const parsedQueries: ParsedQuery[] = [];\n if (Array.isArray(queries)) {\n for (let index = 0; index < queries.length; index += 1) {\n const parsed = parseQuery(queries[index]);\n if (!parsed) {\n return errorResult(\n `\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1A\u7B2C ${index + 1} \u4E2A\u67E5\u8BE2\u9700\u4E3A { kind:'length', points:[p,q] }\u3001{ kind:'angle', points:[a,v,b] } \u6216 { kind, target, edge? }`,\n );\n }\n parsedQueries.push(parsed);\n }\n }\n\n const documentJson = JSON.stringify(document);\n if (documentJson !== undefined && documentJson.length > DOCUMENT_JSON_MAX_LENGTH) {\n return textResult({ measurements: [], diagnostics: [limitDiagnostic()] });\n }\n\n if (!isRecord(document)) {\n return textResult({\n measurements: [],\n diagnostics: [invalidDocument('\u6587\u6863\u5FC5\u987B\u662F protocolVersion=1 \u7684 GeometryDocument \u5BF9\u8C61')],\n });\n }\n\n if (document.protocolVersion !== 1 || 'equations' in document) {\n return textResult({\n measurements: [],\n diagnostics: [invalidDocument('\u4EC5\u652F\u6301 protocolVersion=1 \u4E14\u4E0D\u542B equations \u7684 GeometryDocument')],\n });\n }\n\n const result = resolveGeometryDocument(document as unknown as GeometryDocumentInput);\n const diagnostics: GeometryDiagnostic[] = [...result.diagnostics];\n const measurements = documentMeasurements(result.elements, angleUnitOf(document));\n\n for (let index = 0; index < parsedQueries.length; index += 1) {\n const query = parsedQueries[index];\n if (!query) continue;\n\n if (query.mode === 'points-length') {\n const [first, second] = query.points;\n const value = Math.hypot(second[0] - first[0], second[1] - first[1]);\n measurements.push({\n kind: 'length',\n value,\n formatted: formatMeasure('length', value),\n });\n continue;\n }\n\n if (query.mode === 'points-angle') {\n const [first, vertex, second] = query.points;\n const value = convexAngle(first, vertex, second);\n if (value === null) {\n diagnostics.push(queryDiagnostic(index, '\u89D2\u5EA6\u67E5\u8BE2\u7684\u8FB9\u9000\u5316\u4E3A\u96F6\uFF08\u70B9\u91CD\u5408\uFF09'));\n continue;\n }\n measurements.push({\n kind: 'angle',\n value,\n formatted: formatAngle((value * 180) / Math.PI),\n });\n continue;\n }\n\n const target = result.elements.find((element) => element.id === query.target);\n if (!target) {\n diagnostics.push(queryDiagnostic(index, `\u76EE\u6807\u5143\u7D20 \"${query.target}\" \u4E0D\u5B58\u5728`));\n continue;\n }\n const value = measureResolved({\n kind: query.measurementKind,\n target,\n edge: query.edge,\n });\n if (value === null) {\n diagnostics.push(queryDiagnostic(index, `\u76EE\u6807\u5143\u7D20 \"${query.target}\" \u4E0D\u53EF\u6D4B\uFF08${query.measurementKind}\uFF09`));\n continue;\n }\n measurements.push({\n kind: query.measurementKind,\n target: query.target,\n value,\n formatted: formatMeasure(query.measurementKind, value),\n });\n }\n\n return textResult({ measurements, diagnostics });\n } catch (error) {\n // \u515C\u5E95\uFF1A\u53C2\u6570\u5F02\u5E38\uFF08\u5982\u5FAA\u73AF\u5F15\u7528\uFF09\u6216\u5185\u6838\u610F\u5916\u5F02\u5E38\u90FD\u4E0D\u80FD\u8BA9 stdio \u8FDB\u7A0B\u5D29\u6E83\n return errorResult(error instanceof Error ? error.message : String(error));\n }\n}\n\nconst pointSchema = z.tuple([z.number(), z.number()]);\n\nconst querySchema = z.union([\n z.object({\n kind: z.literal('length'),\n points: z.tuple([pointSchema, pointSchema]),\n }),\n z.object({\n kind: z.literal('angle'),\n points: z.tuple([pointSchema, pointSchema, pointSchema]),\n }),\n z.object({\n kind: z.enum(['length', 'perimeter', 'area']),\n target: z.string(),\n edge: z.number().int().optional(),\n }),\n]);\n\n/** \u5411 MCP server \u6CE8\u518C geometry_measure \u5DE5\u5177 */\nexport function registerGeometryMeasureTool(server: McpServer): void {\n server.registerTool(\n 'geometry_measure',\n {\n title: '\u51E0\u4F55\u6D4B\u91CF',\n description:\n '\u6D4B\u91CF protocolVersion=1 \u5E73\u9762\u51E0\u4F55\u6587\u6863\u5185\u5DF2\u6807\u6CE8\u7684 measure \u4E0E angle \u5143\u7D20\uFF0C\u5E76\u652F\u6301\u4E34\u65F6\u67E5\u8BE2\u4E24\u70B9\u8DDD\u79BB\u3001\u4E09\u70B9\u89D2\u5EA6\u6216\u6307\u5B9A\u5143\u7D20\u7684\u957F\u5EA6/\u5468\u957F/\u9762\u79EF\uFF08\u7ED3\u679C\u4E0D\u5199\u56DE\u6587\u6863\uFF09\uFF1B\u8FD4\u56DE measurements \u4E0E diagnostics\u3002',\n inputSchema: {\n document: z.record(z.unknown()),\n queries: z.array(querySchema).max(MEASURE_QUERIES_MAX).optional(),\n },\n },\n (args) => handleGeometryMeasure(args),\n );\n}\n", "/**\n * geometry_render \u5DE5\u5177\uFF1A\u5C06 protocolVersion=1 \u7EAF\u51E0\u4F55\u6587\u6863\u6E32\u67D3\u4E3A SVG \u6216 PNG\u3002\n * \u65E0 DOM\u3001\u65E0\u72B6\u6001\uFF0C\u76F4\u8C03 @studylab/viz/geometry-svg\uFF1BLaTeX \u6807\u6CE8\u6309\u9700\u6CE8\u5165 MathJax \u6E32\u67D3\u5668\u3002\n * \u6587\u6863/\u53C2\u6570\u7C7B\u5931\u8D25\u4EE5 isError \u8FD4\u56DE\u9519\u8BEF\u7801\u6587\u672C\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport { Resvg } from '@resvg/resvg-js';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n renderGeometrySvg,\n type GeometryRenderResult,\n} from '@studylab/viz/geometry-svg';\nimport { resolveGeometryDocument } from '@studylab/viz/geometry-kernel';\n\n/** \u6587\u6863 JSON \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const DOCUMENT_JSON_MAX_LENGTH = 512 * 1024;\n\n/** \u753B\u5E03\u5C3A\u5BF8\u8303\u56F4\u4E0E\u9ED8\u8BA4\u503C\uFF08\u65B9\u6848 \u00A79.1\uFF09 */\nexport const SIZE_MIN = 200;\nexport const SIZE_MAX = 2000;\nexport const DEFAULT_WIDTH = 800;\nexport const DEFAULT_HEIGHT = 500;\n\n/** PNG base64 \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const PNG_BASE64_MAX_LENGTH = 8 * 1024 * 1024;\n\ntype GeometryDocumentInput = Parameters<typeof renderGeometrySvg>[0];\n\nexport interface GeometryRenderArgs {\n document: unknown;\n format?: 'svg' | 'png';\n width?: number;\n height?: number;\n theme?: 'light' | 'dark';\n grid?: boolean;\n axis?: boolean;\n showLabels?: boolean;\n}\n\ninterface RenderTiming {\n resolveMs: number;\n renderMs: number;\n encodeMs: number;\n}\n\nfunction textResult(payload: object): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction clampSize(value: number | undefined, fallback: number): number {\n if (value == null || !Number.isFinite(value)) return fallback;\n return Math.round(Math.min(SIZE_MAX, Math.max(SIZE_MIN, value)));\n}\n\n/** \u6587\u6863\u4E2D\u5B58\u5728 LaTeX \u6587\u672C\u65F6\u624D\u6CE8\u5165\u6E32\u67D3\u5668\uFF0C\u907F\u514D\u65E0\u8C13\u521D\u59CB\u5316 MathJax */\nfunction needsLatexRenderer(document: Record<string, unknown>): boolean {\n const elements = Array.isArray(document.elements) ? document.elements : [];\n return elements.some(\n (element) => isRecord(element) && element.type === 'text' && element.format === 'latex',\n );\n}\n\nfunction elapsedMs(start: number): number {\n return Math.round((performance.now() - start) * 1000) / 1000;\n}\n\n/** \u6309\u9700\u52A0\u8F7D MathJax \u6E32\u67D3\u5668\uFF1A\u65E0 LaTeX \u6587\u6863\u4E0D\u5F15\u5165 mathjax-full */\nasync function loadLatexToSvg(): Promise<(latex: string) => string> {\n const module = await import('@studylab/formula/latex-svg');\n return module.renderLatexToSvg;\n}\n\nexport async function handleGeometryRender(args: GeometryRenderArgs): Promise<CallToolResult> {\n try {\n if (!args || typeof args !== 'object' || !('document' in args)) {\n return errorResult(\n \"INVALID_DOCUMENT: \u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF0C\u9700\u8981 { document: object, format?: 'svg'|'png', width?: number, height?: number, theme?: 'light'|'dark', grid?: boolean, axis?: boolean, showLabels?: boolean }\",\n );\n }\n\n const { document } = args;\n\n let documentJson: string | undefined;\n try {\n documentJson = JSON.stringify(document);\n } catch {\n return errorResult('INVALID_DOCUMENT: document \u65E0\u6CD5\u5E8F\u5217\u5316\u4E3A JSON');\n }\n if (documentJson === undefined || documentJson.length > DOCUMENT_JSON_MAX_LENGTH) {\n return errorResult(`LIMIT_EXCEEDED: \u6587\u6863 JSON \u8D85\u8FC7 ${DOCUMENT_JSON_MAX_LENGTH} \u5B57\u8282\uFF08512KB\uFF09`);\n }\n\n if (!isRecord(document)) {\n return errorResult('INVALID_DOCUMENT: document \u5FC5\u987B\u662F protocolVersion=1 \u7684 GeometryDocument \u5BF9\u8C61');\n }\n if (document.protocolVersion !== 1 || 'equations' in document) {\n return errorResult('UNSUPPORTED_DOCUMENT: \u4EC5\u652F\u6301 protocolVersion=1 \u4E14\u4E0D\u542B equations \u7684\u7EAF\u51E0\u4F55\u6587\u6863');\n }\n\n const format = args.format === 'png' ? 'png' : 'svg';\n const width = clampSize(args.width, DEFAULT_WIDTH);\n const height = clampSize(args.height, DEFAULT_HEIGHT);\n\n const resolveStarted = performance.now();\n resolveGeometryDocument(document as unknown as GeometryDocumentInput);\n const latexToSvg = needsLatexRenderer(document) ? await loadLatexToSvg() : undefined;\n const resolveMs = elapsedMs(resolveStarted);\n\n const renderStarted = performance.now();\n const result: GeometryRenderResult = renderGeometrySvg(\n document as unknown as GeometryDocumentInput,\n {\n width,\n height,\n theme: args.theme ?? 'light',\n grid: args.grid,\n axis: args.axis,\n showLabels: args.showLabels,\n ...(latexToSvg ? { latexToSvg } : {}),\n },\n );\n const renderMs = elapsedMs(renderStarted);\n\n const elementCount = Array.isArray(document.elements) ? document.elements.length : 0;\n const summary = `${result.width}\u00D7${result.height}\uFF0C${elementCount} \u4E2A\u5143\u7D20`;\n\n if (format === 'png') {\n const encodeStarted = performance.now();\n const png = new Resvg(result.svg, {\n fitTo: { mode: 'width', value: result.width },\n })\n .render()\n .asPng();\n const dataBase64 = png.toString('base64');\n const encodeMs = elapsedMs(encodeStarted);\n\n if (dataBase64.length > PNG_BASE64_MAX_LENGTH) {\n return errorResult(\n `LIMIT_EXCEEDED: PNG base64 \u8D85\u8FC7 ${PNG_BASE64_MAX_LENGTH} \u5B57\u8282\uFF088MB\uFF09\uFF0C\u8BF7\u6539\u7528 format:'svg'`,\n );\n }\n\n const timing: RenderTiming = { resolveMs, renderMs, encodeMs };\n return textResult({\n format: 'png',\n mime: 'image/png',\n dataBase64,\n width: result.width,\n height: result.height,\n summary,\n timing,\n diagnostics: result.diagnostics,\n });\n }\n\n const timing: RenderTiming = { resolveMs, renderMs, encodeMs: 0 };\n return textResult({\n format: 'svg',\n mime: 'image/svg+xml',\n svg: result.svg,\n width: result.width,\n height: result.height,\n summary,\n timing,\n diagnostics: result.diagnostics,\n });\n } catch (error) {\n return errorResult(\n `RENDER_FAILED: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\n/** \u5411 MCP server \u6CE8\u518C geometry_render \u5DE5\u5177 */\nexport function registerGeometryRenderTool(server: McpServer): void {\n server.registerTool(\n 'geometry_render',\n {\n title: '\u51E0\u4F55\u6587\u6863\u6E32\u67D3',\n description:\n '\u5C06 protocolVersion=1 \u7684\u7EAF\u51E0\u4F55\u6587\u6863\u6E32\u67D3\u4E3A SVG \u6216 PNG\uFF08\u652F\u6301\u4E3B\u9898\u3001\u7F51\u683C\u3001\u5750\u6807\u8F74\u3001\u6807\u7B7E\u4E0E LaTeX \u6807\u6CE8\uFF09\uFF0C\u8FD4\u56DE\u56FE\u50CF\u5185\u5BB9\u3001\u5C3A\u5BF8\u3001\u6458\u8981\u4E0E\u8017\u65F6\uFF1BPNG \u4EE5 base64 \u8FD4\u56DE\u3002',\n inputSchema: {\n document: z.record(z.unknown()),\n format: z.enum(['svg', 'png']).default('svg'),\n width: z.number().min(SIZE_MIN).max(SIZE_MAX).optional(),\n height: z.number().min(SIZE_MIN).max(SIZE_MAX).optional(),\n theme: z.enum(['light', 'dark']).default('light'),\n grid: z.boolean().optional(),\n axis: z.boolean().optional(),\n showLabels: z.boolean().optional(),\n },\n },\n (args) => handleGeometryRender(args),\n );\n}\n", "/**\n * \u51E0\u4F55\u6837\u5F0F\u8BED\u4E49 \u2192 \u5F15\u64CE\u6570\u503C\u6620\u5C04\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u586B\u5145\u6837\u5F0F\u7EDF\u4E00\u4E3A\u4E09\u6863\uFF1A\u5B9E\u5FC3 / \u534A\u900F\u660E / \u65E0\u80CC\u666F\uFF1B\u7F3A\u7701\u4E3A\u534A\u900F\u660E\u3002\n * \u5386\u53F2\u6587\u6863\u672A\u58F0\u660E fill\uFF0C\u4E00\u5F8B\u6309\u7F3A\u7701\u6E32\u67D3\uFF0C\u4ECE\u800C\u7EDF\u4E00\u5706/\u591A\u8FB9\u5F62\u7684\u586B\u5145\u89C2\u611F\u3002\n */\n\nimport type {\n FillStyle,\n GeometryElementRole,\n GeometryElementSpec,\n GeometryFontSize,\n GeometryLabelPosition,\n GeometryLineWidth,\n} from '../core/types-geometry';\n\nexport const FILL_STYLES: readonly FillStyle[] = ['solid', 'translucent', 'none'];\n\n/** \u9ED8\u8BA4\u586B\u5145\uFF1A\u534A\u900F\u660E */\nexport const DEFAULT_FILL: FillStyle = 'translucent';\n\nconst FILL_OPACITY: Record<FillStyle, number> = {\n solid: 1,\n translucent: 0.3,\n none: 0,\n};\n\n/** \u586B\u5145\u6837\u5F0F \u2192 \u586B\u5145\u4E0D\u900F\u660E\u5EA6\uFF08\u7F3A\u7701\u56DE\u9000\u534A\u900F\u660E\uFF09 */\nexport function fillOpacityOf(fill?: FillStyle): number {\n return FILL_OPACITY[fill ?? DEFAULT_FILL];\n}\n\n/** \u9ED8\u8BA4\u89D2\u8272\uFF1A\u5173\u952E\u70B9\uFF08\u7528\u6237\u4E3B\u52A8\u653E\u7F6E\uFF09 */\nexport const DEFAULT_ROLE: GeometryElementRole = 'key';\n\n/** \u6240\u6709\u70B9\u7EDF\u4E00\u534A\u5F84\uFF08\u56FE\u5F62\u70B9 / \u4F5C\u56FE\u70B9 / \u4EA4\u4E92\u70B9\u4E00\u81F4\uFF09 */\nexport const DEFAULT_POINT_SIZE = 3;\n\n/** \u70B9\u586B\u5145\u4E0D\u900F\u660E\u5EA6\uFF1A\u9ED8\u8BA4\u5168\u90E8\u5B9E\u5FC3\uFF08\u53EF\u901A\u8FC7\u5355\u5143\u7D20 fill \u6837\u5F0F\u6539\u4E3A\u534A\u900F\u660E/\u7A7A\u5FC3\uFF09 */\nconst POINT_OPACITY: Record<GeometryElementRole, number> = { key: 1, auxiliary: 1 };\n\nexport function pointSizeOf(_role?: GeometryElementRole): number {\n return DEFAULT_POINT_SIZE;\n}\n\n/** \u70B9\u5927\u5C0F\u6863\u4F4D \u2192 \u50CF\u7D20\uFF08\u4F9B\u9009\u4E2D\u70B9\u6279\u91CF\u8C03\u6574\uFF1B\u4E09\u6863\u62C9\u5F00\u5DEE\u8DDD\uFF0C\u5C0F\u6863\u660E\u663E\u66F4\u5C0F\uFF09 */\nexport const POINT_SIZE_LEVELS: Readonly<Record<'small' | 'medium' | 'large', number>> = {\n small: 2,\n medium: DEFAULT_POINT_SIZE,\n large: 5,\n};\n\n/** \u70B9\u6807\u7B7E\u4E5D\u5BAB\u683C\u4F4D\u7F6E \u2192 JSXGraph \u6807\u7B7E offset / anchor */\nexport interface LabelPositionStyle {\n offset: [number, number];\n anchorX: 'left' | 'middle' | 'right';\n anchorY: 'top' | 'middle' | 'bottom';\n}\n\nexport const LABEL_POSITIONS: readonly GeometryLabelPosition[] = [\n 'tl', 't', 'tr', 'l', 'c', 'r', 'bl', 'b', 'br',\n];\n\nconst LABEL_POSITION_STYLE: Record<GeometryLabelPosition, LabelPositionStyle> = {\n tl: { offset: [-12, 10], anchorX: 'right', anchorY: 'bottom' },\n t: { offset: [0, 10], anchorX: 'middle', anchorY: 'bottom' },\n tr: { offset: [12, 10], anchorX: 'left', anchorY: 'bottom' },\n l: { offset: [-12, 0], anchorX: 'right', anchorY: 'middle' },\n c: { offset: [0, 0], anchorX: 'middle', anchorY: 'middle' },\n r: { offset: [12, 0], anchorX: 'left', anchorY: 'middle' },\n bl: { offset: [-12, -10], anchorX: 'right', anchorY: 'top' },\n b: { offset: [0, -10], anchorX: 'middle', anchorY: 'top' },\n br: { offset: [12, -10], anchorX: 'left', anchorY: 'top' },\n};\n\n/** \u70B9\u6807\u7B7E\u4F4D\u7F6E\uFF08\u7F3A\u7701\u53F3\u4E0A\uFF0C\u4FDD\u6301\u65E2\u6709\u89C2\u611F\uFF09 */\nexport function labelPositionStyleOf(position?: GeometryLabelPosition): LabelPositionStyle {\n return LABEL_POSITION_STYLE[position ?? 'tr'];\n}\n\nexport function pointOpacityOf(role?: GeometryElementRole): number {\n return POINT_OPACITY[role ?? DEFAULT_ROLE]!;\n}\n\n/** \u9009\u4E2D\u70B9\u7684\u989D\u5916\u534A\u5F84\uFF08\u5F3A\u8C03\u5E45\u5EA6\uFF0C\u4FDD\u6301\u514B\u5236\uFF09 */\nexport const POINT_SELECT_GROW = 0.75;\n\n/** \u7EBF\u5BBD\u6863\u4F4D \u2192 \u50CF\u7D20 */\nconst LINE_WIDTH_PX: Record<GeometryLineWidth, number> = {\n thin: 1.5,\n medium: 2,\n thick: 3,\n};\n\n/** \u5B57\u53F7\u6863\u4F4D \u2192 \u50CF\u7D20 */\nconst FONT_SIZE_PX: Record<GeometryFontSize, number> = {\n small: 12,\n medium: 15,\n large: 18,\n};\n\n/** \u56FE\u5F62\u5143\u7D20\u7EDF\u4E00\u7EBF\u5BBD\uFF08\u7F3A\u7701 medium\uFF09 */\nexport function lineWidthOf(level?: GeometryLineWidth): number {\n return LINE_WIDTH_PX[level ?? 'medium'] as number;\n}\n\n/** \u81EA\u52A8\u523B\u5EA6\uFF1A\u76F8\u90BB\u523B\u5EA6\u6570\u5B57\u7684\u6700\u5C0F\u5C4F\u5E55\u95F4\u8DDD\uFF08\u50CF\u7D20\uFF09 */\nexport const AUTO_TICK_MIN_PX = 44;\n\n/** 1/2/5\u00D710^n \u5411\u4E0A\u53D6\u6574\u5230\u201C\u597D\u770B\u201D\u7684\u6B65\u957F */\nexport function niceStep(raw: number): number {\n if (!Number.isFinite(raw) || raw <= 0) return 1;\n const exp = Math.floor(Math.log10(raw));\n const base = 10 ** exp;\n for (const m of [1, 2, 5]) {\n if (m * base >= raw - 1e-9) return m * base;\n }\n return 10 * base;\n}\n\n/**\n * \u5750\u6807\u8F74\u81EA\u52A8\u6B65\u957F\uFF1AX/Y \u5171\u7528\u540C\u4E00\u4E2A\u6570\u503C\u95F4\u9694\uFF0C\u907F\u514D\u4E24\u8F74\u523B\u5EA6\u5BC6\u5EA6\u4E0D\u4E00\u81F4\u3002\n * unitPx \u53D6\u4E24\u8F74\u4E2D\u8F83\u5C0F\u7684\u201C\u6BCF\u5355\u4F4D\u50CF\u7D20\u6570\u201D\uFF08\u66F4\u5BC6\u7684\u4E00\u8F74\uFF09\uFF0C\u4FDD\u8BC1\u4EFB\u4F55\u4E00\u8F74\u90FD\u4E0D\u62E5\u6324\u3002\n */\nexport function autoAxisStep(unitPx: number, minPx = AUTO_TICK_MIN_PX): number {\n if (!Number.isFinite(unitPx) || unitPx <= 0) return 1;\n return niceStep(minPx / unitPx);\n}\n\n/** \u6807\u7B7E/\u6570\u5B57\u5B57\u53F7\uFF08\u7F3A\u7701 medium\uFF09 */\nexport function fontSizeOf(level?: GeometryFontSize): number {\n return FONT_SIZE_PX[level ?? 'medium'] as number;\n}\n\n/**\n * \u6D3E\u751F\u6784\u9020\uFF08\u4E2D\u70B9/\u4EA4\u70B9/\u5782\u7EBF/\u5E73\u884C\u7EBF/\u89D2\u5E73\u5206\u7EBF/\u89D2\u5EA6\uFF09\u7EDF\u4E00\u8272\u7CFB\u7528\u7684\u54E8\u5175 colorIndex\u3002\n * \u4F7F\u7528\u8BE5\u503C\uFF08\u5728\u8C03\u8272\u677F\u4E0B\u6807\u4E4B\u5916\uFF09\u7684\u5143\u7D20\u6E32\u67D3\u4E3A\u300C\u4E13\u5C5E\u6784\u9020\u8272\u300D\uFF0C\u4F7F\u540C\u4E00\u6784\u9020\u7684\u8F85\u52A9\u70B9\u4E0E\u7ED3\u679C\u8272\u7CFB\u4E00\u81F4\u3001\n * \u4E14\u4E0E\u7528\u6237\u624B\u7ED8\u5143\u7D20\u533A\u5206\u5F00\u3002\n */\nexport const CONSTRUCT_COLOR_INDEX = -1;\n\n/** \u6D3E\u751F\u6784\u9020\u7C7B\u578B\uFF08\u7ED3\u679C\u5143\u7D20\uFF09\uFF1A\u4F7F\u7528\u4E13\u5C5E\u6784\u9020\u8272 */\nconst CONSTRUCT_TYPES = new Set<GeometryElementSpec['type']>([\n 'midpoint', 'intersection', 'perpendicular', 'parallel', 'angleBisector', 'angle',\n]);\n\n/** \u8BE5\u5143\u7D20\u7C7B\u578B\u662F\u5426\u4E3A\u6D3E\u751F\u6784\u9020\u7ED3\u679C */\nexport function isConstructResult(type: GeometryElementSpec['type']): boolean {\n return CONSTRUCT_TYPES.has(type);\n}\n", "/**\n * \u70B9\u7684\u81EA\u52A8\u6807\u7B7E\u4F4D\u7F6E\u9009\u62E9\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u5728\u4E5D\u5BAB\u683C\u5019\u9009\u4E2D\u6309\u300C\u4E0E\u5173\u8054\u7EBF\u6BB5\u65B9\u5411\u5206\u79BB\u5EA6\u300D\u8BC4\u5206\uFF0C\u5E76\u5BF9\u753B\u5E03\u6EA2\u51FA\u4E0E\u90BB\u8FD1\u70B9\u6807\u7B7E\u78B0\u649E\u6263\u5206\u3002\n * \u4E0E\u9002\u914D\u5668\u5171\u4EAB\u540C\u4E00\u5957 offset \u5B9A\u4E49\uFF08`labelPositionStyleOf`\uFF09\uFF0C\u907F\u514D\u504F\u79FB\u8868\u91CD\u590D\u3002\n */\n\nimport type { GeometryLabelPosition } from '../core/types-geometry';\nimport { labelPositionStyleOf } from './style.js';\n\nconst PRIORITY: readonly GeometryLabelPosition[] = ['tr', 'tl', 'br', 'bl', 't', 'b', 'r', 'l'];\n\nexport interface LabelPlacementInput {\n origin: [number, number];\n others: Array<[number, number]>;\n canvas: { width: number; height: number };\n directions: number[];\n}\n\nfunction separation(a: number, b: number): number {\n const d = Math.abs(a - b) % Math.PI;\n return Math.min(d, Math.PI - d);\n}\n\nexport function autoLabelPosition(input: LabelPlacementInput): GeometryLabelPosition {\n const { origin, others, canvas, directions } = input;\n let best: GeometryLabelPosition = 'tr';\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const position of PRIORITY) {\n const style = labelPositionStyleOf(position);\n const angle = Math.atan2(style.offset[1], style.offset[0]);\n let score = directions.length\n ? Math.min(...directions.map((d) => separation(angle, d)))\n : Math.PI / 4;\n const x = origin[0] + style.offset[0];\n const y = origin[1] - style.offset[1];\n // \u8D34\u8FB9\u8F6F\u60E9\u7F5A\uFF1A\u4FDD\u6301\u4E0E\u62BD\u53D6\u524D\u7684\u9002\u914D\u5668\u8BC4\u5206\u5B8C\u5168\u4E00\u81F4\n const edge = 18;\n if (x < edge || x > canvas.width - edge) score -= 2.5;\n if (y < edge || y > canvas.height - edge) score -= 3.5;\n const overflowX = Math.max(0, 10 - x, x + 10 - canvas.width);\n const overflowY = Math.max(0, 14 - y, y + 14 - canvas.height);\n if (overflowX > 0 || overflowY > 0) score -= 50 + overflowX + overflowY;\n for (const other of others) {\n const d = Math.hypot(x - other[0], y - other[1]);\n if (d < 24) score -= (24 - d) / 5;\n }\n if (score > bestScore + 1e-9) {\n bestScore = score;\n best = position;\n }\n }\n return best;\n}\n", "/**\n * \u51E0\u4F55 SVG \u65E0\u5934\u6E32\u67D3\u7684\u9759\u6001\u4E3B\u9898\u3002\n *\n * \u670D\u52A1\u7AEF\uFF08MCP / resvg\uFF09\u6CA1\u6709 CSS \u53D8\u91CF\uFF0C\u56E0\u6B64 light/dark \u5404\u7528\u4E00\u5957\u56FA\u5B9A hex\uFF1B\n * \u7CFB\u5217\u8272\u4E0E plot-svg \u7684 LIGHT_PALETTE / DARK_PALETTE \u4FDD\u6301\u4E00\u81F4\uFF0C\n * \u80CC\u666F/\u8F74/\u7F51\u683C/\u6587\u5B57\u8272\u6CBF\u7528\u540C\u4E00\u7EA6\u5B9A\uFF0C\u4FDD\u8BC1\u4E24\u7C7B\u5BFC\u51FA\u56FE\u89C2\u611F\u7EDF\u4E00\u3002\n */\n\nexport type GeometryRenderThemeName = 'light' | 'dark';\n\nexport interface GeometryRenderTheme {\n background: string;\n axis: string;\n grid: string;\n ink: string;\n /** \u6D3E\u751F\u6784\u9020\u5143\u7D20\u4E13\u7528\u8272\uFF08\u5BF9\u9F50 ui-kit --viz-construct\uFF09 */\n construct: string;\n palette: readonly string[];\n}\n\nconst LIGHT_PALETTE = [\n '#2563eb', '#dc2626', '#16a34a', '#d97706', '#7c3aed', '#0891b2', '#db2777', '#4b5563',\n] as const;\n\nconst DARK_PALETTE = [\n '#60a5fa', '#f87171', '#4ade80', '#fbbf24', '#a78bfa', '#22d3ee', '#f472b6', '#9ca3af',\n] as const;\n\nexport const GEOMETRY_RENDER_THEMES: Readonly<Record<GeometryRenderThemeName, GeometryRenderTheme>> = {\n light: {\n background: '#ffffff',\n axis: '#666666',\n grid: '#e5e5e5',\n ink: '#374151',\n construct: '#0f766e',\n palette: LIGHT_PALETTE,\n },\n dark: {\n background: '#1c1f25',\n axis: '#9ca3af',\n grid: '#2d333b',\n ink: '#9ca3af',\n construct: '#2dd4bf',\n palette: DARK_PALETTE,\n },\n};\n\nexport function geometryRenderThemeOf(name: GeometryRenderThemeName = 'light'): GeometryRenderTheme {\n return GEOMETRY_RENDER_THEMES[name];\n}\n\n/** \u7EBF\u5BBD / \u5B57\u53F7\u6620\u5C04\u590D\u7528 style.ts\uFF0C\u4FDD\u6301\u4EA4\u4E92\u753B\u5E03\u4E0E\u65E0\u5934\u6E32\u67D3\u4E00\u81F4\u3002 */\nexport { fontSizeOf, lineWidthOf } from './style.js';\n", "/**\n * \u51E0\u4F55\u6587\u6863 \u2192 \u5B57\u7B26\u4E32 SVG\uFF08\u65E0 DOM \u4F9D\u8D56\uFF0C\u4F9B MCP / resvg \u5BFC\u51FA\u590D\u7528\uFF09\u3002\n *\n * \u89C6\u53E3\u6620\u5C04\u4FDD\u6301\u5BBD\u9AD8\u6BD4\uFF1A\u7EDF\u4E00\u7F29\u653E scale = min(width/xRange, height/yRange)\uFF0C\n * \u5185\u5BB9\u5728\u753B\u5E03\u5185\u5C45\u4E2D\uFF08letterbox\uFF09\uFF0C\u56E0\u6B64\u5706/\u692D\u5706\u4E0D\u4F1A\u88AB\u62C9\u4F38\u3002\n * \u5143\u7D20\u4E00\u5F8B\u5148\u7ECF resolveGeometryDocument \u89E3\u6790\uFF1B\u7F3A\u5931/\u9000\u5316\u5143\u7D20\u8FDB\u5165 diagnostics\u3002\n */\n\nimport type {\n GeometryCanvasStyle,\n GeometryDocument,\n GeometryElementSpec,\n GeometryElementStyle,\n} from '../core/types-geometry';\nimport type { GeometryDiagnostic } from './kernel';\nimport { resolveGeometryDocument, type ResolvedElement } from './kernel.js';\nimport { formatAngle } from './angle.js';\nimport { ellipseFrameOf, ellipsePointAt } from './conic.js';\nimport { autoLabelPosition } from './label-position.js';\nimport { xmlEscape } from '../plot-svg/svg-builder.js';\nimport {\n CONSTRUCT_COLOR_INDEX,\n autoAxisStep,\n fillOpacityOf,\n fontSizeOf,\n labelPositionStyleOf,\n lineWidthOf,\n pointSizeOf,\n} from './style.js';\nimport {\n geometryRenderThemeOf,\n type GeometryRenderTheme,\n type GeometryRenderThemeName,\n} from './render-theme.js';\n\nexport interface GeometryRenderOptions {\n /** \u753B\u5E03\u5BBD\u5EA6\uFF08px\uFF09\uFF0C\u94B3\u5236 200..2000\uFF0C\u9ED8\u8BA4 800 */\n width?: number;\n /** \u753B\u5E03\u9AD8\u5EA6\uFF08px\uFF09\uFF0C\u94B3\u5236 200..2000\uFF0C\u9ED8\u8BA4 500 */\n height?: number;\n theme?: GeometryRenderThemeName;\n grid?: boolean;\n axis?: boolean;\n showLabels?: boolean;\n /** LaTeX \u2192 SVG \u7247\u6BB5\uFF1B\u8FD4\u56DE null \u65F6\u56DE\u9000\u4E3A\u7EAF\u6587\u672C\u5E76\u8BB0\u5F55\u8BCA\u65AD */\n latexToSvg?: (latex: string) => string | null;\n}\n\nexport interface GeometryRenderResult {\n svg: string;\n width: number;\n height: number;\n diagnostics: GeometryDiagnostic[];\n}\n\ntype Vec = [number, number];\ntype TextAnchor = 'start' | 'middle' | 'end';\ntype TextBaseline = 'auto' | 'hanging' | 'middle';\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 500;\nconst SIZE_MIN = 200;\nconst SIZE_MAX = 2000;\nconst AXIS_FONT_SIZE = 12;\nconst TICK_HALF = 3;\nconst ARROW_SIZE = 8;\nconst MAX_TICKS = 200;\nconst AUXILIARY_POINT_OPACITY = 0.55;\nconst TWO_PI = Math.PI * 2;\n\ninterface ViewTransform {\n scale: number;\n left: number;\n top: number;\n right: number;\n bottom: number;\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n toX: (x: number) => number;\n toY: (y: number) => number;\n}\n\ninterface RenderContext {\n t: ViewTransform;\n theme: GeometryRenderTheme;\n canvasStyle: GeometryCanvasStyle;\n width: number;\n height: number;\n showLabels: boolean;\n specs: Map<string, GeometryElementSpec>;\n resolvedById: Map<string, ResolvedElement>;\n /** \u5168\u90E8\u5DF2\u89E3\u6790\u70B9\u7684\u5C4F\u5E55\u5750\u6807\uFF0C\u4F9B\u81EA\u52A8\u6807\u7B7E\u907F\u8BA9 */\n pointScreens: Array<{ id: string; screen: Vec }>;\n latexToSvg?: (latex: string) => string | null;\n diagnostics: GeometryDiagnostic[];\n}\n\nexport function renderGeometrySvg(\n document: GeometryDocument,\n options: GeometryRenderOptions = {},\n): GeometryRenderResult {\n const width = clampSize(options.width, DEFAULT_WIDTH);\n const height = clampSize(options.height, DEFAULT_HEIGHT);\n const canvasStyle: GeometryCanvasStyle = document.style ?? {};\n const theme = geometryRenderThemeOf(options.theme ?? (canvasStyle.invert ? 'dark' : 'light'));\n const resolved = resolveGeometryDocument(document);\n const diagnostics = [...resolved.diagnostics];\n\n const specs = new Map<string, GeometryElementSpec>();\n for (const spec of Array.isArray(document.elements) ? document.elements : []) {\n specs.set(spec.id, spec);\n }\n\n const transform = createViewTransform(document.viewport, width, height);\n const context: RenderContext = {\n t: transform,\n theme,\n canvasStyle,\n width,\n height,\n showLabels: options.showLabels !== false && canvasStyle.showLabels !== false,\n specs,\n resolvedById: new Map(resolved.elements.map((element) => [element.id, element])),\n pointScreens: resolved.elements\n .filter((element): element is Extract<ResolvedElement, { kind: 'point' }> => element.kind === 'point')\n .map((element) => ({\n id: element.id,\n screen: [transform.toX(element.position[0]), transform.toY(element.position[1])] as Vec,\n })),\n latexToSvg: options.latexToSvg,\n diagnostics,\n };\n\n const content: string[] = [];\n if (options.grid !== false && canvasStyle.gridVisible !== false) {\n content.push(...gridMarkup(transform, theme, canvasStyle));\n }\n if (options.axis !== false && canvasStyle.showAxes !== false) {\n content.push(...axisMarkup(transform, theme, canvasStyle));\n }\n for (const element of resolved.elements) {\n if (specs.get(element.id)?.style?.visible === false) continue;\n content.push(...renderElement(element, context));\n }\n\n const finite = finiteCanvasRect(canvasStyle, transform);\n const parts: string[] = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n ];\n if (finite) {\n parts.push(\n `<defs><clipPath id=\"geometry-canvas-clip\"><rect x=\"${fmt(finite.x)}\" y=\"${fmt(finite.y)}\" width=\"${fmt(finite.width)}\" height=\"${fmt(finite.height)}\" /></clipPath></defs>`,\n );\n }\n parts.push(`<rect x=\"0\" y=\"0\" width=\"${width}\" height=\"${height}\" fill=\"${canvasStyle.background ?? theme.background}\" />`);\n if (finite) {\n parts.push(\n `<rect x=\"${fmt(finite.x)}\" y=\"${fmt(finite.y)}\" width=\"${fmt(finite.width)}\" height=\"${fmt(finite.height)}\" fill=\"${canvasStyle.background ?? theme.background}\" stroke=\"${theme.axis}\" stroke-width=\"1\" stroke-dasharray=\"4 4\" />`,\n );\n parts.push(`<g clip-path=\"url(#geometry-canvas-clip)\">${content.join('')}</g>`);\n } else {\n parts.push(content.join(''));\n }\n parts.push('</svg>');\n return { svg: parts.join(''), width, height, diagnostics };\n}\n\n/** \u6709\u9650\u753B\u5E03\u5728\u5C4F\u5E55\u4E0A\u7684\u77E9\u5F62\uFF1BcanvasMode \u975E finite \u6216 bounds \u975E\u6CD5\u65F6\u8FD4\u56DE null */\nfunction finiteCanvasRect(\n style: GeometryCanvasStyle,\n t: ViewTransform,\n): { x: number; y: number; width: number; height: number } | null {\n if (style.canvasMode !== 'finite') return null;\n const bounds = style.canvasBounds;\n if (!bounds) return null;\n const valid = Number.isFinite(bounds.xMin) && Number.isFinite(bounds.xMax)\n && Number.isFinite(bounds.yMin) && Number.isFinite(bounds.yMax)\n && bounds.xMax > bounds.xMin && bounds.yMax > bounds.yMin;\n if (!valid) return null;\n const x = t.toX(bounds.xMin);\n const y = t.toY(bounds.yMax);\n return { x, y, width: (bounds.xMax - bounds.xMin) * t.scale, height: (bounds.yMax - bounds.yMin) * t.scale };\n}\n\nfunction renderElement(element: ResolvedElement, context: RenderContext): string[] {\n const { t, theme, canvasStyle, showLabels } = context;\n const spec = context.specs.get(element.id);\n const style = effectiveStyleOf(spec, context.specs, element.kind);\n const color = colorOf(style, theme);\n const strokeWidth = style?.width ?? lineWidthOf(canvasStyle.lineWidth);\n\n switch (element.kind) {\n case 'point': {\n const cx = t.toX(element.position[0]);\n const cy = t.toY(element.position[1]);\n const radius = style?.pointSize ?? pointSizeOf(style?.role);\n const opacity = style?.role === 'auxiliary'\n ? ` opacity=\"${fmt(AUXILIARY_POINT_OPACITY)}\"`\n : '';\n const out = [\n `<circle cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" r=\"${fmt(radius)}\" fill=\"${color}\"${opacity} />`,\n ];\n if (showLabels && style?.label && style.labelVisible !== false) {\n out.push(pointLabelMarkup(cx, cy, element.id, style, context));\n }\n return out;\n }\n case 'line': {\n const clipped = clipLine(element, t);\n if (!clipped) return [];\n const [[x1, y1], [x2, y2]] = clipped;\n return [\n `<line x1=\"${fmt(x1)}\" y1=\"${fmt(y1)}\" x2=\"${fmt(x2)}\" y2=\"${fmt(y2)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n ];\n }\n case 'circle': {\n const cx = t.toX(element.center[0]);\n const cy = t.toY(element.center[1]);\n return [\n `<circle cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" r=\"${fmt(element.radius * t.scale)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n ];\n }\n case 'arc': {\n if (element.arcKind === 'sector') {\n return [\n `<path d=\"${sectorPathD(element.center, element.radius, element.startAngle, element.endAngle, t)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n ];\n }\n const parts = [\n `<path d=\"${arcPathD(element.center, element.radius, element.startAngle, element.endAngle, t)}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n ];\n if (element.arcKind === 'semicircle') {\n const arc = arcScreenGeometry(element.center, element.radius, element.startAngle, element.endAngle, t);\n parts.push(\n `<line x1=\"${fmt(arc.start[0])}\" y1=\"${fmt(arc.start[1])}\" x2=\"${fmt(arc.end[0])}\" y2=\"${fmt(arc.end[1])}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n );\n }\n return parts;\n }\n case 'ellipse': {\n if (ellipseArcSpan(element) < TWO_PI - 1e-6) {\n return [\n `<path d=\"${ellipseArcPathD(element, t)}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n ];\n }\n const cx = t.toX(element.center[0]);\n const cy = t.toY(element.center[1]);\n const rotation = fmt((element.rotation * 180) / -Math.PI);\n return [\n `<ellipse cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" rx=\"${fmt(element.a * t.scale)}\" ry=\"${fmt(element.b * t.scale)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} transform=\"rotate(${rotation} ${fmt(cx)} ${fmt(cy)})\" />`,\n ];\n }\n case 'polygon': {\n const d = element.vertices\n .map((vertex, index) => `${index === 0 ? 'M' : 'L'}${fmt(t.toX(vertex[0]))} ${fmt(t.toY(vertex[1]))}`)\n .join(' ');\n return [\n `<path d=\"${d} Z\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n ];\n }\n case 'angle': {\n const radius = Math.min(t.xMax - t.xMin, t.yMax - t.yMin) / 8;\n const d = sectorPathD(element.vertex, radius, element.startAngle, element.startAngle + element.sweep, t);\n const mid = element.startAngle + element.sweep / 2;\n const labelX = t.toX(element.vertex[0] + Math.cos(mid) * radius * 1.3);\n const labelY = t.toY(element.vertex[1] + Math.sin(mid) * radius * 1.3);\n const text = formatAngle((element.valueRad * 180) / Math.PI, canvasStyle.angleUnit);\n return [\n `<path d=\"${d}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n textMarkup(labelX, labelY, text, color, fontSizeOf(canvasStyle.fontSize), 'middle', 'middle', true),\n ];\n }\n case 'text': {\n const x = t.toX(element.position[0]);\n const y = t.toY(element.position[1]);\n // \u4E0E\u753B\u5E03\u4E00\u81F4\uFF1A\u6587\u5B57\u6807\u6CE8\u4F7F\u7528\u5143\u7D20\u914D\u8272\uFF08\u663E\u5F0F color \u4F18\u5148\uFF0C\u5426\u5219\u6309 colorIndex\uFF09\n const fill = color;\n if (element.format === 'latex') {\n const fragment = context.latexToSvg ? context.latexToSvg(element.raw) : null;\n if (fragment) {\n // MathJax \u7247\u6BB5\u7528 fill=\"currentColor\" \u53D6\u8272\uFF1BSVG \u65E0 CSS \u4E0A\u4E0B\u6587\uFF0C\u5FC5\u987B\u6CE8\u5165\u5143\u7D20\u989C\u8272\uFF0C\n // \u5426\u5219\u9ED8\u8BA4\u89E3\u6790\u4E3A\u9ED1\u8272\uFF0C\u6DF1\u8272\u4E3B\u9898\u5BFC\u51FA\u540E\u4E0D\u53EF\u89C1\uFF0C\u4E14\u4E0E\u753B\u5E03\u5185\u7684\u516C\u5F0F\u914D\u8272\u4E0D\u4E00\u81F4\u3002\n const colored = fragment.replace(/currentColor/g, fill);\n return [`<g transform=\"translate(${fmt(x)} ${fmt(y)})\">${colored}</g>`];\n }\n context.diagnostics.push({\n code: 'LATEX_RENDERER_MISSING',\n severity: 'warning',\n elementId: element.id,\n message: context.latexToSvg\n ? `LaTeX \u6E32\u67D3\u5668\u672A\u8FD4\u56DE SVG \u7247\u6BB5\uFF08\"${element.id}\"\uFF09\uFF0C\u5DF2\u56DE\u9000\u4E3A\u7EAF\u6587\u672C`\n : `\u672A\u63D0\u4F9B LaTeX \u6E32\u67D3\u5668\uFF08\"${element.id}\"\uFF09\uFF0C\u5DF2\u56DE\u9000\u4E3A\u7EAF\u6587\u672C`,\n });\n }\n return [\n textMarkup(x, y, element.raw, fill, fontSizeOf(canvasStyle.fontSize), 'middle', 'middle', true),\n ];\n }\n case 'measure': {\n const baseX = t.toX(element.labelPosition[0]);\n const baseY = t.toY(element.labelPosition[1]);\n const offset = measureOffsetPx(element, spec, context);\n return [\n textMarkup(\n baseX + offset[0],\n baseY + offset[1],\n element.formatted,\n style?.color ?? theme.ink,\n fontSizeOf(canvasStyle.fontSize),\n 'middle',\n 'middle',\n true,\n ),\n ];\n }\n case 'locus': {\n if (element.points.length < 2) return [];\n const d = element.points\n .map((p, i) => `${i === 0 ? 'M' : 'L'}${fmt(t.toX(p[0]))} ${fmt(t.toY(p[1]))}`)\n .join(' ') + (element.closed ? ' Z' : '');\n return [`<path d=\"${d}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`];\n }\n default:\n return [];\n }\n}\n\n/** \u53D8\u6362\u5143\u7D20\u7EE7\u627F\u6E90\u6837\u5F0F\uFF08\u53EF\u8986\u76D6\uFF09\uFF1B\u70B9\u6807\u7B7E\u5728\u539F\u6807\u7B7E\u540E\u8FFD\u52A0 prime\uFF0C\u907F\u514D\u4E0E\u6E90\u70B9\u540C\u540D */\nfunction effectiveStyleOf(\n spec: GeometryElementSpec | undefined,\n specs: ReadonlyMap<string, GeometryElementSpec>,\n resultKind: ResolvedElement['kind'],\n): GeometryElementStyle | undefined {\n if (!spec) return undefined;\n if (spec.type !== 'transform') return spec.style;\n const sourceStyle = effectiveStyleOf(specs.get(spec.source), specs, resultKind);\n const own = spec.style ?? {};\n const label = own.label\n ?? (resultKind === 'point' && sourceStyle?.label ? `${sourceStyle.label}\u2032` : undefined);\n return { ...sourceStyle, ...own, ...(label !== undefined ? { label } : {}) };\n}\n\n/** \u6D4B\u91CF\u6587\u672C\u7684\u50CF\u7D20\u504F\u79FB\uFF1A\u957F\u5EA6\u6CBF\u76EE\u6807\u7EBF\u6BB5\u6CD5\u5411\u5916\u504F 14px\uFF0C\u5176\u4F59\u7EDF\u4E00\u4E0A\u79FB 14px */\nfunction measureOffsetPx(\n element: Extract<ResolvedElement, { kind: 'measure' }>,\n spec: GeometryElementSpec | undefined,\n context: RenderContext,\n): Vec {\n const UP: Vec = [0, -14];\n if (element.measurementKind !== 'length') return UP;\n const target = context.resolvedById.get(element.target);\n if (!target) return UP;\n let from: Vec | null = null;\n let to: Vec | null = null;\n if (target.kind === 'line' && target.extent === 'segment' && target.from && target.to) {\n from = target.from;\n to = target.to;\n } else if (target.kind === 'polygon' && spec?.type === 'measure') {\n const edge = spec.edge ?? 0;\n const vertices = target.vertices;\n if (Number.isInteger(edge) && edge >= 0 && edge < vertices.length) {\n from = vertices[edge]!;\n to = vertices[(edge + 1) % vertices.length]!;\n }\n }\n if (!from || !to) return UP;\n const { t } = context;\n const dx = t.toX(to[0]) - t.toX(from[0]);\n const dy = t.toY(to[1]) - t.toY(from[1]);\n const length = Math.hypot(dx, dy);\n if (length < 1e-9) return UP;\n return [(dy / length) * 14, (-dx / length) * 14];\n}\n\nfunction clampSize(value: number | undefined, fallback: number): number {\n if (value == null || !Number.isFinite(value)) return fallback;\n return Math.round(Math.min(SIZE_MAX, Math.max(SIZE_MIN, value)));\n}\n\nfunction createViewTransform(\n viewport: { xMin: number; xMax: number; yMin: number; yMax: number } | undefined,\n width: number,\n height: number,\n): ViewTransform {\n const raw = viewport ?? { xMin: -6, xMax: 6, yMin: -6, yMax: 6 };\n const valid = Number.isFinite(raw.xMin) && Number.isFinite(raw.xMax)\n && Number.isFinite(raw.yMin) && Number.isFinite(raw.yMax)\n && raw.xMax > raw.xMin && raw.yMax > raw.yMin;\n const xMin = valid ? raw.xMin : -6;\n const xMax = valid ? raw.xMax : 6;\n const yMin = valid ? raw.yMin : -6;\n const yMax = valid ? raw.yMax : 6;\n const scale = Math.min(width / (xMax - xMin), height / (yMax - yMin));\n const contentWidth = (xMax - xMin) * scale;\n const contentHeight = (yMax - yMin) * scale;\n const left = (width - contentWidth) / 2;\n const top = (height - contentHeight) / 2;\n return {\n scale,\n left,\n top,\n right: left + contentWidth,\n bottom: top + contentHeight,\n xMin,\n xMax,\n yMin,\n yMax,\n toX: (x) => left + (x - xMin) * scale,\n toY: (y) => top + (yMax - y) * scale,\n };\n}\n\nfunction colorOf(style: GeometryElementStyle | undefined, theme: GeometryRenderTheme): string {\n if (style?.color) return style.color;\n if (style?.colorIndex === CONSTRUCT_COLOR_INDEX) return theme.construct;\n const index = style?.colorIndex == null ? 0 : Math.trunc(style.colorIndex);\n const count = theme.palette.length;\n return theme.palette[((index % count) + count) % count]!;\n}\n\nfunction fillAttrs(color: string, fill: GeometryElementStyle['fill']): string {\n const opacity = fillOpacityOf(fill);\n if (opacity <= 0) return ' fill=\"none\"';\n return ` fill=\"${color}\" fill-opacity=\"${fmt(opacity)}\"`;\n}\n\nfunction pointLabelMarkup(\n cx: number,\n cy: number,\n id: string,\n style: GeometryElementStyle,\n context: RenderContext,\n): string {\n const position = style.labelPosition ?? autoLabelPosition({\n origin: [cx, cy],\n others: context.pointScreens.filter((point) => point.id !== id).map((point) => point.screen),\n canvas: { width: context.width, height: context.height },\n directions: [],\n });\n const placement = labelPositionStyleOf(position);\n const anchor: TextAnchor = placement.anchorX === 'left'\n ? 'start'\n : placement.anchorX === 'right' ? 'end' : 'middle';\n const baseline: TextBaseline = placement.anchorY === 'top'\n ? 'hanging'\n : placement.anchorY === 'middle' ? 'middle' : 'auto';\n return textMarkup(\n cx + placement.offset[0],\n cy - placement.offset[1],\n style.label ?? '',\n context.theme.ink,\n fontSizeOf(context.canvasStyle.fontSize),\n anchor,\n baseline,\n );\n}\n\nfunction textMarkup(\n x: number,\n y: number,\n text: string,\n fill: string,\n fontSize: number,\n anchor: TextAnchor,\n baseline: TextBaseline,\n halo = false,\n): string {\n const stroke = halo ? ' stroke=\"#ffffff\" stroke-width=\"2\" paint-order=\"stroke\"' : '';\n return `<text x=\"${fmt(x)}\" y=\"${fmt(y)}\" fill=\"${fill}\" font-size=\"${fmt(fontSize)}\" text-anchor=\"${anchor}\" dominant-baseline=\"${baseline}\"${stroke}>${xmlEscape(text)}</text>`;\n}\n\nfunction clipLine(\n element: Extract<ResolvedElement, { kind: 'line' }>,\n t: ViewTransform,\n): [Vec, Vec] | null {\n let base: Vec;\n let direction: Vec;\n let tMin: number;\n let tMax: number;\n if (element.extent === 'segment') {\n if (!element.from || !element.to) return null;\n base = element.from;\n direction = [element.to[0] - element.from[0], element.to[1] - element.from[1]];\n tMin = 0;\n tMax = 1;\n } else {\n base = element.origin;\n direction = element.direction;\n tMin = element.extent === 'ray' ? 0 : Number.NEGATIVE_INFINITY;\n tMax = Number.POSITIVE_INFINITY;\n }\n const clipped = clipToBox(base, direction, tMin, tMax, t);\n if (!clipped) return null;\n return [\n [t.toX(clipped[0][0]), t.toY(clipped[0][1])],\n [t.toX(clipped[1][0]), t.toY(clipped[1][1])],\n ];\n}\n\n/** Liang-Barsky\uFF1A\u628A p + t\u00B7d \u88C1\u526A\u5230\u89C6\u53E3\u4E16\u754C\u77E9\u5F62\uFF0C\u8FD4\u56DE\u4E24\u7AEF\u70B9 */\nfunction clipToBox(\n p: Vec,\n d: Vec,\n tMin: number,\n tMax: number,\n t: ViewTransform,\n): [Vec, Vec] | null {\n let t0 = tMin;\n let t1 = tMax;\n const edges: Array<[number, number]> = [\n [-d[0], p[0] - t.xMin],\n [d[0], t.xMax - p[0]],\n [-d[1], p[1] - t.yMin],\n [d[1], t.yMax - p[1]],\n ];\n for (const [denominator, numerator] of edges) {\n if (denominator === 0) {\n if (numerator < 0) return null;\n continue;\n }\n const ratio = numerator / denominator;\n if (denominator < 0) {\n if (ratio > t1) return null;\n if (ratio > t0) t0 = ratio;\n } else {\n if (ratio < t0) return null;\n if (ratio < t1) t1 = ratio;\n }\n }\n if (t0 > t1 || !Number.isFinite(t0) || !Number.isFinite(t1)) return null;\n return [\n [p[0] + t0 * d[0], p[1] + t0 * d[1]],\n [p[0] + t1 * d[0], p[1] + t1 * d[1]],\n ];\n}\n\nfunction arcScreenGeometry(\n center: Vec,\n radius: number,\n startAngle: number,\n endAngle: number,\n t: ViewTransform,\n): { start: Vec; end: Vec; radiusPx: number; largeArc: number; sweepFlag: number } {\n const sweep = endAngle - startAngle;\n return {\n start: [\n t.toX(center[0] + radius * Math.cos(startAngle)),\n t.toY(center[1] + radius * Math.sin(startAngle)),\n ],\n end: [\n t.toX(center[0] + radius * Math.cos(endAngle)),\n t.toY(center[1] + radius * Math.sin(endAngle)),\n ],\n radiusPx: radius * t.scale,\n largeArc: Math.abs(sweep) > Math.PI ? 1 : 0,\n // \u6570\u5B66\u5750\u6807\u9006\u65F6\u9488\uFF08sweep>0\uFF09\u5728\u5C4F\u5E55 y \u5411\u4E0B\u65F6\u5BF9\u5E94 SVG sweep-flag 0\n sweepFlag: sweep >= 0 ? 0 : 1,\n };\n}\n\nfunction arcPathD(\n center: Vec,\n radius: number,\n startAngle: number,\n endAngle: number,\n t: ViewTransform,\n): string {\n const arc = arcScreenGeometry(center, radius, startAngle, endAngle, t);\n const r = fmt(arc.radiusPx);\n return `M ${fmt(arc.start[0])} ${fmt(arc.start[1])} A ${r} ${r} 0 ${arc.largeArc} ${arc.sweepFlag} ${fmt(arc.end[0])} ${fmt(arc.end[1])}`;\n}\n\nfunction sectorPathD(\n center: Vec,\n radius: number,\n startAngle: number,\n endAngle: number,\n t: ViewTransform,\n): string {\n const arc = arcScreenGeometry(center, radius, startAngle, endAngle, t);\n const r = fmt(arc.radiusPx);\n return `M ${fmt(t.toX(center[0]))} ${fmt(t.toY(center[1]))} L ${fmt(arc.start[0])} ${fmt(arc.start[1])} A ${r} ${r} 0 ${arc.largeArc} ${arc.sweepFlag} ${fmt(arc.end[0])} ${fmt(arc.end[1])} Z`;\n}\n\nfunction ellipseArcSpan(element: Extract<ResolvedElement, { kind: 'ellipse' }>): number {\n return (element.endParam ?? TWO_PI) - (element.startParam ?? 0);\n}\n\n/**\n * \u692D\u5706\u53C2\u6570 \u03C6\uFF08\u7ED5\u7126\u70B9 F1\u3001\u76F8\u5BF9 F1\u2192F2 \u65B9\u5411\u7684\u6781\u89D2\uFF09\u2192 \u4E16\u754C\u70B9\u3002\n * \u4E0E `ellipsePointAt` / glider / intersections \u7684\u53C2\u6570\u8BED\u4E49\u4E00\u81F4\uFF1B\u4E0D\u5F97\u7528\u4E2D\u5FC3\u53C2\u6570\u5316\u3002\n */\nfunction ellipseWorldPointAt(\n element: Extract<ResolvedElement, { kind: 'ellipse' }>,\n param: number,\n): Vec {\n const frame = ellipseFrameOf(element.center, element.a, element.b, element.rotation);\n return ellipsePointAt(frame.metrics, frame.f1, param);\n}\n\nfunction ellipseArcPathD(\n element: Extract<ResolvedElement, { kind: 'ellipse' }>,\n t: ViewTransform,\n): string {\n const startParam = element.startParam ?? 0;\n const endParam = element.endParam ?? TWO_PI;\n const start = ellipseWorldPointAt(element, startParam);\n const end = ellipseWorldPointAt(element, endParam);\n const sweep = endParam - startParam;\n const largeArc = Math.abs(sweep) > Math.PI ? 1 : 0;\n const rotation = fmt((element.rotation * 180) / -Math.PI);\n return `M ${fmt(t.toX(start[0]))} ${fmt(t.toY(start[1]))} A ${fmt(element.a * t.scale)} ${fmt(element.b * t.scale)} ${rotation} ${largeArc} ${sweep >= 0 ? 0 : 1} ${fmt(t.toX(end[0]))} ${fmt(t.toY(end[1]))}`;\n}\n\nfunction axisStepOf(t: ViewTransform, style: GeometryCanvasStyle): number {\n const configured = style.axisStep;\n if (configured != null && Number.isFinite(configured) && configured > 0) return configured;\n return autoAxisStep(t.scale);\n}\n\nfunction tickValues(min: number, max: number, step: number): number[] {\n const values: number[] = [];\n if (!(step > 0) || !Number.isFinite(step)) return values;\n const first = Math.ceil(min / step - 1e-9);\n const last = Math.floor(max / step + 1e-9);\n for (let i = first; i <= last && values.length < MAX_TICKS; i += 1) {\n const value = i * step;\n values.push(Object.is(value, -0) ? 0 : value);\n }\n return values;\n}\n\nfunction tickLabel(value: number, step: number): string {\n if (value === 0) return '0';\n const decimals = step >= 1 ? 0 : Math.min(6, Math.ceil(-Math.log10(step)));\n return String(Number(value.toFixed(decimals)));\n}\n\nfunction gridMarkup(\n t: ViewTransform,\n theme: GeometryRenderTheme,\n style: GeometryCanvasStyle,\n): string[] {\n const color = style.grid ?? theme.grid;\n const step = axisStepOf(t, style);\n const out: string[] = [];\n for (const value of tickValues(t.xMin, t.xMax, step)) {\n const x = fmt(t.toX(value));\n out.push(`<line x1=\"${x}\" y1=\"${fmt(t.top)}\" x2=\"${x}\" y2=\"${fmt(t.bottom)}\" stroke=\"${color}\" stroke-width=\"0.5\" />`);\n }\n for (const value of tickValues(t.yMin, t.yMax, step)) {\n const y = fmt(t.toY(value));\n out.push(`<line x1=\"${fmt(t.left)}\" y1=\"${y}\" x2=\"${fmt(t.right)}\" y2=\"${y}\" stroke=\"${color}\" stroke-width=\"0.5\" />`);\n }\n return out;\n}\n\nfunction axisMarkup(\n t: ViewTransform,\n theme: GeometryRenderTheme,\n style: GeometryCanvasStyle,\n): string[] {\n const out: string[] = [];\n const axisXColor = style.axisX ?? theme.axis;\n const axisYColor = style.axisY ?? theme.axis;\n const dash = (style.axisStyle ?? 'solid') === 'dashed' ? ' stroke-dasharray=\"4 4\"' : '';\n const arrows = style.axisArrows ?? 'single';\n const step = axisStepOf(t, style);\n const showTicks = (style.tickStyle ?? 'integer') !== 'none';\n const hasXAxis = t.yMin <= 0 && t.yMax >= 0;\n const hasYAxis = t.xMin <= 0 && t.xMax >= 0;\n\n if (hasXAxis) {\n const y = t.toY(0);\n out.push(`<line x1=\"${fmt(t.left)}\" y1=\"${fmt(y)}\" x2=\"${fmt(t.right)}\" y2=\"${fmt(y)}\" stroke=\"${axisXColor}\" stroke-width=\"1\"${dash} />`);\n if (arrows !== 'none') out.push(arrowHead(t.right, y, 1, 0, axisXColor));\n if (arrows === 'double') out.push(arrowHead(t.left, y, -1, 0, axisXColor));\n for (const value of tickValues(t.xMin, t.xMax, step)) {\n const x = t.toX(value);\n if (showTicks) {\n out.push(`<line x1=\"${fmt(x)}\" y1=\"${fmt(y - TICK_HALF)}\" x2=\"${fmt(x)}\" y2=\"${fmt(y + TICK_HALF)}\" stroke=\"${axisXColor}\" stroke-width=\"1\" />`);\n }\n if (value !== 0) {\n out.push(textMarkup(x, y + 16, tickLabel(value, step), axisXColor, AXIS_FONT_SIZE, 'middle', 'hanging'));\n }\n }\n }\n\n if (hasYAxis) {\n const x = t.toX(0);\n out.push(`<line x1=\"${fmt(x)}\" y1=\"${fmt(t.top)}\" x2=\"${fmt(x)}\" y2=\"${fmt(t.bottom)}\" stroke=\"${axisYColor}\" stroke-width=\"1\"${dash} />`);\n if (arrows !== 'none') out.push(arrowHead(x, t.top, 0, -1, axisYColor));\n if (arrows === 'double') out.push(arrowHead(x, t.bottom, 0, 1, axisYColor));\n for (const value of tickValues(t.yMin, t.yMax, step)) {\n const y = t.toY(value);\n if (showTicks) {\n out.push(`<line x1=\"${fmt(x - TICK_HALF)}\" y1=\"${fmt(y)}\" x2=\"${fmt(x + TICK_HALF)}\" y2=\"${fmt(y)}\" stroke=\"${axisYColor}\" stroke-width=\"1\" />`);\n }\n if (value !== 0) {\n out.push(textMarkup(x - 8, y, tickLabel(value, step), axisYColor, AXIS_FONT_SIZE, 'end', 'middle'));\n }\n }\n }\n\n if (hasXAxis && hasYAxis) {\n out.push(textMarkup(t.toX(0) - 6, t.toY(0) + 14, '0', axisXColor, AXIS_FONT_SIZE, 'end', 'hanging'));\n }\n return out;\n}\n\nfunction arrowHead(x: number, y: number, dx: number, dy: number, color: string): string {\n const tipX = x;\n const tipY = y;\n const baseX = x - dx * ARROW_SIZE;\n const baseY = y - dy * ARROW_SIZE;\n const wingX = -dy * ARROW_SIZE * 0.5;\n const wingY = dx * ARROW_SIZE * 0.5;\n return `<path d=\"M ${fmt(tipX)} ${fmt(tipY)} L ${fmt(baseX + wingX)} ${fmt(baseY + wingY)} L ${fmt(baseX - wingX)} ${fmt(baseY - wingY)} Z\" fill=\"${color}\" />`;\n}\n\nfunction fmt(value: number): string {\n return Number(value.toFixed(3)).toString();\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * Node \u7AEF TeX\u2192SVG\uFF08\u65E0 DOM\u3001\u540C\u6B65\uFF09\uFF1A\u57FA\u4E8E mathjax-full liteAdaptor\u3002\n *\n * \u4F9B geometry SVG \u6E32\u67D3\u5668\u6CE8\u5165 `latexToSvg` \u56DE\u8C03\u4F7F\u7528\uFF08MCP \u56DE\u8C03\u4E3A\u540C\u6B65\u7B7E\u540D\uFF09\uFF0C\n * \u56E0\u6B64\u4E0D\u80FD\u4F7F\u7528\u52A8\u6001 import\uFF1B\u6539\u4E3A\u9996\u6B21\u8C03\u7528\u65F6\u540C\u6B65\u521D\u59CB\u5316\u5355\u4F8B\uFF0CMathJax \u53EA\u521D\u59CB\u5316\u4E00\u6B21\u3002\n * \u8F93\u51FA\u4EC5\u542B svg/defs/g/path/text \u7B49 resvg \u53EF\u6805\u683C\u5316\u7684\u8282\u70B9\uFF0C\u4E0D\u542B foreignObject\u3002\n */\nimport { mathjax } from 'mathjax-full/js/mathjax.js';\nimport { TeX } from 'mathjax-full/js/input/tex.js';\nimport { SVG } from 'mathjax-full/js/output/svg.js';\nimport { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js';\nimport { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html.js';\nimport { AllPackages } from 'mathjax-full/js/input/tex/AllPackages.js';\n\ninterface MathJaxSvgRenderer {\n convert(latex: string): string;\n}\n\nlet renderer: MathJaxSvgRenderer | null = null;\n\nfunction getRenderer(): MathJaxSvgRenderer {\n if (renderer) return renderer;\n const adaptor = liteAdaptor();\n RegisterHTMLHandler(adaptor);\n const tex = new TeX({ packages: AllPackages });\n const svg = new SVG({ fontCache: 'local' });\n const document = mathjax.document('', { InputJax: tex, OutputJax: svg });\n renderer = {\n convert: (latex: string) => {\n const node = document.convert(latex, { display: false });\n return adaptor.innerHTML(node);\n },\n };\n return renderer;\n}\n\n/** TeX \u6E90\u7801\u8F6C\u5185\u8054 `<svg>` \u5B57\u7B26\u4E32\uFF08\u884C\u5185\u6A21\u5F0F\uFF0C\u5B57\u4F53\u7F13\u5B58\u5185\u5D4C\u5728 defs \u4E2D\uFF09 */\nexport function renderLatexToSvg(latex: string): string {\n return getRenderer().convert(latex);\n}\n", "import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createMcpServer } from './server.js';\n\n/**\n * MCP stdio \u5165\u53E3\uFF1Astdout \u4EC5\u4F9B MCP \u534F\u8BAE\u5E27\u4F7F\u7528\uFF0C\u4EFB\u4F55\u65E5\u5FD7\u5FC5\u987B\u8D70 stderr\u3002\n */\nasync function main(): Promise<void> {\n const server = createMcpServer();\n await server.connect(new StdioServerTransport());\n console.error('[gramfig-mcp] \u5DE5\u5177\u5DF2\u6CE8\u518C\uFF0C\u7B49\u5F85 stdio \u8BF7\u6C42\u2026');\n}\n\nmain().catch((error) => {\n console.error('[gramfig-mcp] \u542F\u52A8\u5931\u8D25\uFF1A', error);\n process.exit(1);\n});\n", "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerLatexRenderTool } from './tools/latex-render.js';\nimport { registerGraphPlotTool } from './tools/graph-plot.js';\nimport { registerGeometrySolveTool } from './tools/geometry-solve.js';\nimport { registerGeometryMeasureTool } from './tools/geometry-measure.js';\nimport { registerGeometryRenderTool } from './tools/geometry-render.js';\n\n/** \u521B\u5EFA MCP server\uFF08stdio \u65E0\u72B6\u6001\uFF09\u5E76\u6CE8\u518C\u5168\u90E8\u5DE5\u5177 */\nexport function createMcpServer(): McpServer {\n const server = new McpServer(\n { name: 'gramfig-mcp', version: '0.1.1' },\n { capabilities: { tools: {} } },\n );\n registerLatexRenderTool(server);\n registerGraphPlotTool(server);\n registerGeometrySolveTool(server);\n registerGeometryMeasureTool(server);\n registerGeometryRenderTool(server);\n return server;\n}\n", "/**\n * latex_render \u5DE5\u5177\uFF1A\u76F4\u8C03 @studylab/formula \u672C\u5730\u51FD\u6570\u94FE\uFF08\u4E0D\u7ECF\u7F51\u7EDC\uFF0C\u5EF6\u8FDF <50ms\uFF09\u3002\n * \u8F93\u5165 schema \u7531 registerTool \u7684 zod inputSchema \u6821\u9A8C\uFF08\u542B 4KB \u957F\u5EA6\u4E0A\u9650\uFF09\uFF1B\n * \u672C handler \u53EA\u8D1F\u8D23\u6E32\u67D3\u7F16\u6392\uFF0C\u6240\u6709\u5931\u8D25\u5C31\u5730\u8F6C\u4E3A isError \u7ED3\u679C\uFF0C\u7EDD\u4E0D throw\n * \u2014\u2014 \u9632\u6B62\u672A\u6355\u83B7\u5F02\u5E38\u5BFC\u81F4 stdio \u8FDB\u7A0B\u5D29\u6E83\u3002\n */\nimport { JSDOM } from 'jsdom';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { renderFormula, sanitizeFormulaHtml, type RenderEngine } from '@studylab/formula';\nimport { MpRenderError, renderToMpNodes, type MpTreeNode } from '@studylab/formula/mp';\n\n/** \u5355\u6761 latex \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A72.2\uFF0C\u4E0E apps/api \u7684 LatexService \u5BF9\u9F50\uFF09 */\nexport const LATEX_MAX_LENGTH = 4096;\n\n/** \u767D\u540D\u5355\u6E05\u6D17\u7528 DOM \u5355\u4F8B\uFF1Ajsdom \u5B9E\u4F8B\u6A21\u5757\u7EA7\u590D\u7528\uFF0C\u907F\u514D\u6BCF\u6B21\u8BF7\u6C42\u91CD\u5EFA\u5F00\u9500 */\nconst sanitizeDocument = new JSDOM('').window.document;\n\n/** \u5DE5\u5177\u5165\u53C2\uFF08displayMode / outputs \u7684\u9ED8\u8BA4\u503C\u7531 inputSchema \u7684 zod default \u515C\u5E95\uFF09 */\nexport interface LatexRenderArgs {\n latex: string;\n displayMode: boolean;\n outputs: ('html' | 'mp-nodes')[];\n}\n\n/** \u6210\u529F\u8F7D\u8377\uFF1A\u4EC5\u5305\u542B\u5B9E\u9645\u8BF7\u6C42\u7684\u8F93\u51FA\u5B57\u6BB5 */\ninterface LatexRenderPayload {\n html?: string;\n mpNodes?: MpTreeNode[];\n engine?: RenderEngine;\n}\n\nfunction textResult(payload: LatexRenderPayload): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\n/**\n * latex_render \u7EAF handler\uFF08\u53EF\u8131\u79BB stdio \u76F4\u63A5\u5355\u6D4B\uFF09\u3002\n * html \u8F93\u51FA\u5FC5\u8FC7 sanitizeFormulaHtml \u767D\u540D\u5355\u6E05\u6D17\uFF1Bmp-nodes \u5931\u8D25\u6355\u83B7 MpRenderError\u3002\n */\nexport async function handleLatexRender(args: LatexRenderArgs): Promise<CallToolResult> {\n const { latex, displayMode, outputs } = args;\n const payload: LatexRenderPayload = {};\n\n try {\n if (outputs.includes('html')) {\n const rendered = await renderFormula(latex, displayMode);\n if (!rendered.ok) {\n return errorResult(`LaTeX \u6E32\u67D3\u5931\u8D25\uFF1A${rendered.message}`);\n }\n payload.html = sanitizeFormulaHtml(rendered.html, { documentRef: sanitizeDocument });\n payload.engine = rendered.engine;\n }\n\n if (outputs.includes('mp-nodes')) {\n try {\n payload.mpNodes = renderToMpNodes(latex, displayMode);\n payload.engine = payload.engine ?? 'katex';\n } catch (error) {\n const detail = error instanceof MpRenderError ? error.message : String(error);\n return errorResult(`LaTeX \u6E32\u67D3\u5931\u8D25\uFF08\u5C0F\u7A0B\u5E8F\u8282\u70B9\uFF09\uFF1A${detail}`);\n }\n }\n\n return textResult(payload);\n } catch (error) {\n // \u515C\u5E95\uFF1A\u4EFB\u4F55\u672A\u9884\u671F\u5F02\u5E38\u90FD\u4E0D\u80FD\u8BA9 stdio \u8FDB\u7A0B\u9000\u51FA\n return errorResult(error instanceof Error ? error.message : String(error));\n }\n}\n\n/** \u5411 MCP server \u6CE8\u518C latex_render \u5DE5\u5177 */\nexport function registerLatexRenderTool(server: McpServer): void {\n server.registerTool(\n 'latex_render',\n {\n title: 'LaTeX \u6E32\u67D3',\n description: '\u5C06 LaTeX \u6570\u5B66\u6216\u5316\u5B66\u516C\u5F0F\u6E32\u67D3\u4E3A\u5B89\u5168 HTML \u6216\u5C0F\u7A0B\u5E8F rich-text nodes\u3002',\n inputSchema: {\n latex: z.string().max(LATEX_MAX_LENGTH, `LaTeX \u957F\u5EA6\u8D85\u9650\uFF08\u4E0A\u9650 ${LATEX_MAX_LENGTH} \u5B57\u7B26\uFF09`),\n displayMode: z.boolean().default(false),\n outputs: z.array(z.enum(['html', 'mp-nodes'])).default(['html']),\n },\n },\n (args) => handleLatexRender(args),\n );\n}\n", "/**\n * \u516C\u5F0F\u6E32\u67D3\u5206\u6D41\uFF08PRD \u00A76.1\uFF09\uFF1A\n * - \u6570\u5B66\uFF08\u9ED8\u8BA4\uFF09\uFF1AKaTeX \u540C\u6B65\u6E32\u67D3 HTML\uFF1B\u89E3\u6790\u5931\u8D25\u663E\u5F0F\u629B\u9519\uFF0C\u7531\u7F16\u8F91\u6001\u6807\u7EA2\u3001\u4E0D\u4E2D\u65AD\u8F93\u5165\u3002\n * - \u5316\u5B66\uFF08`\\ce` / mhchem\uFF09\uFF1A\u52A8\u6001\u52A0\u8F7D MathJax 3\uFF08liteAdaptor\uFF0C\u7EAF JS \u65E0\u9700 DOM\uFF09\uFF0C\n * \u8F93\u51FA\u5185\u8054 SVG \u5B57\u7B26\u4E32\u3002\u52A8\u6001 import \u4F7F mathjax-full \u4E0D\u8FDB\u4E3B\u5305\u5173\u952E\u8DEF\u5F84\u3002\n * \u6240\u6709\u4EA7\u7269\u8FD4\u56DE\u524D\u8C03\u7528\u65B9\u5E94\u8FC7 sanitizeFormulaHtml \u767D\u540D\u5355\u3002\n */\nimport katex from 'katex';\n\nexport type RenderEngine = 'katex' | 'mathjax';\n\nexport interface RenderSuccess {\n ok: true;\n engine: RenderEngine;\n /** \u5DF2\u6E05\u6D17\u7684\u5B89\u5168 HTML/SVG \u7247\u6BB5 */\n html: string;\n}\n\nexport interface RenderFailure {\n ok: false;\n engine: RenderEngine;\n /** \u9762\u5411\u6392\u67E5\u7684\u9519\u8BEF\u4FE1\u606F\uFF1B\u7981\u6B62\u76F4\u63A5\u900F\u4F20\u7ED9\u6700\u7EC8\u7528\u6237 */\n message: string;\n}\n\nexport type RenderResult = RenderSuccess | RenderFailure;\n\n/** \u5316\u5B66\u65B9\u7A0B\u5F0F\u5224\u5B9A\uFF1A\\ce{...}\u3001\\cee\u3001\\pu \u6216\u663E\u5F0F require mhchem */\nconst CHEMISTRY_PATTERN = /\\\\(?:ce|cee|pu)\\b|\\\\require\\s*\\{[^}]*mhchem/;\n\nexport function isChemistry(latex: string): boolean {\n return CHEMISTRY_PATTERN.test(latex);\n}\n\nfunction renderWithKatex(latex: string, displayMode: boolean): RenderResult {\n try {\n return {\n ok: true,\n engine: 'katex',\n html: katex.renderToString(latex, {\n displayMode,\n throwOnError: true,\n strict: 'ignore',\n trust: false,\n }),\n };\n } catch (error) {\n return {\n ok: false,\n engine: 'katex',\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n// \u2500\u2500 MathJax \u5355\u4F8B\uFF08\u9996\u6B21\u6E32\u67D3\u5316\u5B66\u516C\u5F0F\u65F6\u5F02\u6B65\u521D\u59CB\u5316\uFF09\u2500\u2500\n\ninterface MathJaxBundle {\n convert: (latex: string, displayMode: boolean) => string;\n}\n\nlet mathjaxBundlePromise: Promise<MathJaxBundle> | null = null;\n\nasync function getMathJax(): Promise<MathJaxBundle> {\n if (!mathjaxBundlePromise) {\n mathjaxBundlePromise = (async () => {\n const [{ mathjax }, { TeX }, { SVG }, { liteAdaptor }, { RegisterHTMLHandler }, { AllPackages }] =\n await Promise.all([\n import('mathjax-full/js/mathjax.js'),\n import('mathjax-full/js/input/tex.js'),\n import('mathjax-full/js/output/svg.js'),\n import('mathjax-full/js/adaptors/liteAdaptor.js'),\n import('mathjax-full/js/handlers/html.js'),\n import('mathjax-full/js/input/tex/AllPackages.js'),\n ]);\n const adaptor = liteAdaptor();\n RegisterHTMLHandler(adaptor);\n const tex = new TeX({ packages: AllPackages });\n const svg = new SVG({ fontCache: 'local' });\n const document = mathjax.document('', { InputJax: tex, OutputJax: svg });\n return {\n convert: (latex: string, displayMode: boolean) => {\n const node = document.convert(latex, { display: displayMode });\n return adaptor.outerHTML(node);\n },\n };\n })();\n }\n return mathjaxBundlePromise;\n}\n\nasync function renderWithMathJax(latex: string, displayMode: boolean): Promise<RenderResult> {\n try {\n const bundle = await getMathJax();\n return { ok: true, engine: 'mathjax', html: bundle.convert(latex, displayMode) };\n } catch (error) {\n return {\n ok: false,\n engine: 'mathjax',\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * \u7EDF\u4E00\u6E32\u67D3\u5165\u53E3\uFF1A\u81EA\u52A8\u6309\u5185\u5BB9\u5206\u6D41\u3002\n * @param displayMode \u72EC\u7ACB\u516C\u5F0F\u5757 true\uFF1B\u884C\u5185 false\n */\nexport async function renderFormula(latex: string, displayMode: boolean): Promise<RenderResult> {\n if (isChemistry(latex)) return renderWithMathJax(latex, displayMode);\n return renderWithKatex(latex, displayMode);\n}\n\n/** \u4F9B\u6027\u80FD\u57FA\u51C6\u4E0E\u540C\u6B65\u573A\u666F\u4F7F\u7528\uFF1A\u76F4\u63A5\u8D70 KaTeX\uFF08\u4E0D\u542B\u5316\u5B66\u5206\u6D41\uFF09 */\nexport function renderMathSync(latex: string, displayMode: boolean): RenderResult {\n return renderWithKatex(latex, displayMode);\n}\n", "/**\n * \u6E32\u67D3\u4EA7\u7269\u767D\u540D\u5355\u6E05\u6D17\uFF08PRD \u00A76.1 \u5B89\u5168\u6761\u6B3E\uFF09\u3002\n *\n * \u6E32\u67D3\u6765\u6E90\u867D\u53D7\u63A7\uFF08KaTeX/MathJax \u8F93\u51FA\uFF09\uFF0C\u4F46\u516C\u5F0F\u5185\u5BB9\u542B\u7528\u6237\u8F93\u5165\uFF0C\n * \u5165\u9898/\u5165\u5E93\u524D\u7EDF\u4E00\u8FC7\u767D\u540D\u5355\uFF1A\u5265\u79BB\u811A\u672C\u3001\u4E8B\u4EF6\u5C5E\u6027\u4E0E\u5371\u9669\u6807\u7B7E\uFF0C\u4EC5\u4FDD\u7559\n * KaTeX HTML \u4E0E MathJax SVG \u6E32\u67D3\u6240\u9700\u7684\u6807\u7B7E/\u5C5E\u6027/\u6837\u5F0F\u3002\n *\n * M0 POC \u57FA\u4E8E\u6D4F\u89C8\u5668 DOMParser\uFF1B\u9636\u6BB5 I \u670D\u52A1\u7AEF\u590D\u7528\u65F6\u6CE8\u5165\u7B49\u4EF7 document \u5B9E\u73B0\u3002\n */\n\nconst ALLOWED_TAGS = new Set([\n // KaTeX HTML\n 'span',\n 'math',\n 'semantics',\n 'annotation',\n 'mrow',\n 'mi',\n 'mo',\n 'mn',\n 'ms',\n 'mtext',\n 'mspace',\n 'msup',\n 'msub',\n 'msubsup',\n 'munder',\n 'mover',\n 'munderover',\n 'mfrac',\n 'msqrt',\n 'mroot',\n 'mtable',\n 'mlabeledtr',\n 'mtr',\n 'mtd',\n 'mstyle',\n // MathJax SVG\n 'svg',\n 'g',\n 'path',\n 'line',\n 'rect',\n 'polygon',\n 'ellipse',\n 'circle',\n 'use',\n 'text',\n 'tspan',\n 'defs',\n 'b',\n 'i',\n 'br',\n]);\n\n/** \u5141\u8BB8\u900F\u4F20\u7684\u5C5E\u6027\uFF08\u767D\u540D\u5355\u5236\uFF1Bdata \u4E0E aria \u524D\u7F00\u5C5E\u6027\u516C\u5F0F\u4EA7\u7269\u4E0D\u9700\u8981\uFF0C\u4E00\u5F8B\u62D2\u7EDD\uFF09 */\nconst ALLOWED_ATTRS = new Set([\n 'class',\n 'style',\n 'aria-hidden',\n // MathML\n 'mathvariant',\n 'mathsize',\n 'fence',\n 'separator',\n 'stretchy',\n 'lspace',\n 'rspace',\n 'largeop',\n 'movablelimits',\n 'accent',\n 'accentunder',\n 'columnalign',\n 'rowalign',\n 'columnspan',\n 'rowspan',\n // SVG\n 'viewbox',\n 'preserveaspectratio',\n 'width',\n 'height',\n 'x',\n 'y',\n 'x1',\n 'y1',\n 'x2',\n 'y2',\n 'cx',\n 'cy',\n 'rx',\n 'ry',\n 'd',\n 'points',\n 'transform',\n 'fill',\n 'stroke',\n 'stroke-width',\n 'stroke-linecap',\n 'xmlns',\n 'href',\n 'xlink:href',\n 'id',\n 'overflow',\n 'vertical-align',\n]);\n\n/** \u5141\u8BB8\u4FDD\u7559\u7684 CSS \u5C5E\u6027\uFF08\u6E32\u67D3\u516C\u5F0F\u6240\u9700\uFF1B\u7981\u6B62 url()/expression \u7B49\u5916\u8054\u80FD\u529B\uFF09 */\nconst ALLOWED_STYLE_PROPS = new Set([\n 'margin-left',\n 'margin-right',\n 'margin-top',\n 'margin-bottom',\n 'top',\n 'left',\n 'position',\n 'width',\n 'height',\n 'min-width',\n 'vertical-align',\n 'transform',\n 'transform-origin',\n 'color',\n 'font-size',\n 'font-family',\n 'font-style',\n 'font-weight',\n 'line-height',\n 'border-top-width',\n 'border-bottom-width',\n 'border-left-width',\n 'border-right-width',\n 'border-style',\n 'padding',\n 'padding-left',\n 'padding-right',\n 'padding-top',\n 'padding-bottom',\n 'display',\n]);\n\n/** \u4EFB\u4F55\u503C\u4E2D\u51FA\u73B0\u8FD9\u4E9B\u7247\u6BB5\u7684\u6837\u5F0F\u9879\u76F4\u63A5\u4E22\u5F03 */\nconst FORBIDDEN_STYLE_PATTERN = /url\\s*\\(|expression\\s*\\(|javascript:|@import|<\\/?script/i;\n\nfunction sanitizeStyle(cssText: string): string {\n const kept: string[] = [];\n for (const declaration of cssText.split(';')) {\n const separatorIndex = declaration.indexOf(':');\n if (separatorIndex === -1) continue;\n const prop = declaration.slice(0, separatorIndex).trim().toLowerCase();\n const value = declaration.slice(separatorIndex + 1).trim();\n if (!ALLOWED_STYLE_PROPS.has(prop)) continue;\n if (FORBIDDEN_STYLE_PATTERN.test(value)) continue;\n kept.push(`${prop}:${value}`);\n }\n return kept.join(';');\n}\n\nfunction walk(node: Element | DocumentFragment): void {\n for (const child of Array.from(node.children)) {\n walk(child);\n if (!ALLOWED_TAGS.has(child.tagName.toLowerCase())) {\n // \u975E\u767D\u540D\u5355\u6807\u7B7E\uFF1A\u4FDD\u7559\u6587\u672C\u5185\u5BB9\uFF0C\u79FB\u9664\u5143\u7D20\u672C\u8EAB\n child.replaceWith(...Array.from(child.childNodes));\n continue;\n }\n for (const attr of Array.from(child.attributes)) {\n const name = attr.name.toLowerCase();\n const value = attr.value;\n if (!ALLOWED_ATTRS.has(name)) {\n child.removeAttribute(attr.name);\n } else if (name === 'style') {\n const cleaned = sanitizeStyle(value);\n if (cleaned.length === 0) child.removeAttribute('style');\n else child.setAttribute('style', cleaned);\n }\n }\n }\n}\n\nexport interface SanitizeOptions {\n /** \u670D\u52A1\u7AEF\u590D\u7528\u65F6\u6CE8\u5165\uFF08jsdom document\uFF09\uFF1B\u9ED8\u8BA4\u53D6\u6D4F\u89C8\u5668\u5168\u5C40 */\n documentRef?: Document;\n}\n\n/**\n * \u767D\u540D\u5355\u6E05\u6D17\u6E32\u67D3 HTML/SVG\u3002\u8FD4\u56DE\u5B89\u5168\u7247\u6BB5\u5B57\u7B26\u4E32\u3002\n * \u6CE8\u610F\uFF1A\u8F93\u5165\u5E94\u4E3A\u6E32\u67D3\u5F15\u64CE\u4EA7\u7269\uFF0C\u672C\u51FD\u6570\u4E0D\u627F\u62C5\u901A\u7528\u5BCC\u6587\u672C\u51C0\u5316\u804C\u8D23\u3002\n */\nexport function sanitizeFormulaHtml(html: string, options: SanitizeOptions = {}): string {\n const docRef = options.documentRef ?? globalThis.document;\n const template = docRef.createElement('template');\n template.innerHTML = html;\n // <template> \u7684\u5B50\u8282\u70B9\u4F4D\u4E8E .content \u6587\u6863\u7247\u6BB5\u4E2D\uFF0Ctemplate.children \u6052\u4E3A\u7A7A\n walk(template.content);\n return template.innerHTML;\n}\n", "/**\n * \u6B63\u6587\u88F8 LaTeX \u68C0\u6D4B / \u5207\u5206 + \u5185\u5BB9\u6587\u6863\u5BA1\u6838\uFF08\u5355\u4E00\u771F\u76F8\u6E90\uFF09\u3002\n *\n * \u80CC\u666F\uFF1A\u9898\u76EE\u5185\u5BB9\u6309 block JSON v1 \u5B58\u50A8\uFF0CLaTeX \u53EA\u80FD\u5B58\u5728\u4E8E formula \u8282\u70B9\uFF08\u884C\u5185/\u72EC\u7ACB\u5757\uFF09\u3002\n * \u82E5 LaTeX \u547D\u4EE4\u6DF7\u8FDB\u4E86 paragraph \u7684 text \u8282\u70B9\uFF0C\u524D\u7AEF\u53EA\u80FD\u6309\u5B57\u9762\u6587\u672C\u663E\u793A\uFF08\u4E0D\u6E32\u67D3\uFF09\u3002\n *\n * \u672C\u6A21\u5757\u4F9B\u4E09\u5904\u590D\u7528\uFF1A\n * - seed \u6784\u5EFA\u5C42\uFF1A\u628A\u6587\u672C\u6BB5\u91CC\u7684\u88F8\u547D\u4EE4\u5F52\u4E00\u5316\u4E3A\u884C\u5185\u516C\u5F0F\u8282\u70B9\uFF08splitTextIntoLatex\uFF09\n * - \u7F16\u8F91\u5668\uFF1A\u6B63\u6587\u5B9E\u65F6\u7EA2\u7EBF\u6807\u6CE8\uFF08scanNakedLatex\uFF09+ \u4FDD\u5B58\u524D\u5BA1\u6838\uFF08auditQuestionContent\uFF09\n * - API\uFF1A\u4FDD\u5B58\u65F6\u5BA1\u6838\u5E76\u6301\u4E45\u5316\u9898\u76EE\u6807\u8BB0\uFF08auditQuestionContent\uFF09\n *\n * \u4FDD\u5B88\u7B56\u7565\uFF1A\u547D\u4EE4\u8D70**\u767D\u540D\u5355**\uFF0C\u5B81\u53EF\u6F0F\u68C0\u4E0D\u53EF\u8BEF\u4F24\uFF1B\u767D\u540D\u5355\u5916\u7684\u53CD\u659C\u6760\u4E00\u5F8B\u4E0D\u7BA1\u3002\n * \u8BEF\u62A5\u8FB9\u754C\uFF1A\n * - \u666E\u901A\u53CD\u659C\u6760\u4E0E\u8F6C\u4E49\u5B57\u9762\u91CF\uFF08\\$ \\% \\& \\# \\_\uFF09\u4E0D\u5728\u767D\u540D\u5355\uFF0C\u4E0D\u547D\u4E2D\uFF1B\n * - \\text{...} / \\ce{...} \u8FDE\u540C\u82B1\u62EC\u53F7\u6574\u4F53\u4F5C\u4E3A\u4E00\u4E2A\u7247\u6BB5\u6D88\u8D39\uFF0C\u4E0D\u9012\u5F52\u5207\u5185\u90E8\uFF1B\n * - \\left|\\right:\\big \u7B49\u5FC5\u987B\u540E\u968F\u5B9A\u754C\u7B26\u624D\u7B97\u547D\u4E2D\uFF1B\n * - formula \u8282\u70B9\u53EA\u8DD1 lintLatex\uFF08R1\u2013R4\uFF09\uFF0C\u4E0D\u8DD1\u88F8\u547D\u4EE4\u68C0\u67E5 \u2014\u2014 \u5408\u6CD5\u7684 `\\ldots` \u4E0D\u4F1A\u8BEF\u5224\u3002\n */\n\nimport { lintLatex } from './latex-lint.js';\n\nexport interface NakedLatexSpan {\n /** \u6587\u672C\u5185\u8D77\u59CB\u504F\u79FB */\n from: number;\n /** \u6587\u672C\u5185\u7ED3\u675F\u504F\u79FB\uFF08\u4E0D\u542B\uFF09 */\n to: number;\n snippet: string;\n kind: 'command' | 'delimiter';\n}\n\nexport type InlineSegment = { type: 'text'; text: string } | { type: 'formula'; latex: string };\n\nexport interface ContentIssue {\n /** \u4F4D\u7F6E\u8BED\u4E49\uFF1A\u9898\u5E72 / \u89E3\u6790 / \u9009\u9879A / \u53C2\u8003\u7B54\u6848 / \u586B\u7A7A1 */\n scope: string;\n /** \u7ED3\u6784\u5B9A\u4F4D\uFF1A\u5982 blocks[2].children[3] */\n path: string;\n kind: 'naked-latex-in-text' | 'delimiter-in-text' | 'rule-violation';\n /** \u4EC5 rule-violation \u643A\u5E26\uFF08R1\u2013R4\uFF09 */\n rule?: string;\n message: string;\n snippet: string;\n}\n\n/** \u6570\u5B66\u547D\u4EE4\u767D\u540D\u5355\uFF08\u786E\u5B9A\u5C5E\u4E8E LaTeX \u7684\u624D\u6536\u5F55\uFF09 */\nconst COMMANDS = [\n // \u7701\u7565\u53F7\u4E0E\u70B9\n 'ldots', 'cdots', 'dots', 'vdots', 'ddots',\n // \u5E0C\u814A\u5B57\u6BCD\n 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'varepsilon', 'zeta', 'eta', 'theta', 'vartheta',\n 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'varphi',\n 'chi', 'psi', 'omega', 'Gamma', 'Delta', 'Theta', 'Lambda', 'Xi', 'Pi', 'Sigma', 'Upsilon',\n 'Phi', 'Psi', 'Omega',\n // \u8FD0\u7B97\u7B26\u4E0E\u5173\u7CFB\n 'times', 'div', 'pm', 'mp', 'cdot', 'circ', 'bullet', 'le', 'leq', 'ge', 'geq', 'ne', 'neq',\n 'equiv', 'approx', 'sim', 'cong', 'propto', 'in', 'notin', 'subset', 'subseteq', 'supset',\n 'supseteq', 'cup', 'cap', 'emptyset', 'varnothing', 'forall', 'exists', 'nexists', 'neg',\n 'Rightarrow', 'Leftrightarrow', 'rightarrow', 'leftarrow', 'to', 'mapsto', 'angle', 'parallel',\n 'perp', 'triangle', 'because', 'therefore', 'setminus', 'mid', 'infty', 'partial', 'nabla',\n 'prime', 'ell', 'hbar',\n // \u7ED3\u6784\u4E0E\u51FD\u6570\n 'frac', 'dfrac', 'tfrac', 'sqrt', 'overline', 'underline', 'vec', 'widehat', 'widetilde',\n 'overrightarrow', 'overleftarrow', 'underbrace', 'overbrace', 'binom', 'begin', 'end',\n 'sin', 'cos', 'tan', 'cot', 'sec', 'csc', 'arcsin', 'arccos', 'arctan', 'sinh', 'cosh', 'tanh',\n 'log', 'lg', 'ln', 'exp', 'lim', 'sum', 'prod', 'int', 'oint', 'iint', 'iiint', 'max', 'min',\n 'gcd', 'det', 'dim', 'arg', 'deg', 'bmod', 'pmod', 'text', 'mathrm', 'mathbb', 'mathcal',\n 'mathbf', 'mathit', 'mathsf', 'mathtt', 'operatorname', 'ce', 'cee', 'pu', 'hspace', 'space',\n 'quad', 'qquad', 'underbrace',\n // \u89C4\u8303\u7981\u7528\u7684\u547D\u4EE4\uFF08\u51FA\u73B0\u5373\u9700\u63D0\u793A\uFF09\n 'displaystyle', 'limits', 'nolimits', 'def', 'newcommand', 'renewcommand', 'require',\n 'includegraphics', 'href', 'url',\n];\n\nconst CMD_ALT = COMMANDS.join('|');\n/** \u82B1\u62EC\u53F7\u7EC4\uFF1A\u652F\u6301\u4E00\u5C42\u5D4C\u5957\uFF08\\frac{1}{2} / \\ce{H2O}\uFF09 */\nconst GROUP = String.raw`\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}`;\n/** \\left| \\right) \\bigg[ \u7B49\uFF1A\u5FC5\u987B\u540E\u968F\u5B9A\u754C\u7B26 */\nconst DELIM_COMMAND = String.raw`\\\\(?:left|right|big|Big|bigg|Bigg)\\s*[()\\[\\]{}|./]`;\n\nconst COMMAND_SOURCE =\n String.raw`\\\\(?:` + CMD_ALT + String.raw`)\\b(?:` + GROUP + String.raw`){0,2}(?:[_^](?:` + GROUP + String.raw`|[A-Za-z0-9]))*` +\n '|' + DELIM_COMMAND;\n\n/** \u6210\u5BF9\u5B9A\u754C\u7B26\uFF1A$\u2026$ \u4E0E \\(\u2026\\)\uFF08\u524D\u7F6E\u53CD\u659C\u6760\u7684 \\$ \u89C6\u4E3A\u8F6C\u4E49\u5B57\u9762\u91CF\uFF0C\u4E0D\u547D\u4E2D\uFF09 */\nconst DELIMITER_SOURCE = String.raw`(?<!\\\\)\\$([^$\\n]{1,200})(?<!\\\\)\\$|(?<!\\\\)\\\\\\(([^()\\n]{1,200})\\\\\\)`;\n\n/**\n * \u626B\u63CF\u6587\u672C\u91CC\u6DF7\u5165\u7684\u88F8 LaTeX \u7247\u6BB5\uFF08\u547D\u4EE4\u4E0E\u5B9A\u754C\u7B26\uFF09\uFF0C\u6309\u4F4D\u7F6E\u5347\u5E8F\u3001\u53BB\u91CD\u53E0\u3002\n */\nexport function scanNakedLatex(text: string): NakedLatexSpan[] {\n if (!text) return [];\n const found: NakedLatexSpan[] = [];\n\n const commandRe = new RegExp(COMMAND_SOURCE, 'g');\n for (const match of text.matchAll(commandRe)) {\n if (match.index === undefined) continue;\n found.push({ from: match.index, to: match.index + match[0].length, snippet: match[0], kind: 'command' });\n }\n\n const delimiterRe = new RegExp(DELIMITER_SOURCE, 'g');\n for (const match of text.matchAll(delimiterRe)) {\n if (match.index === undefined) continue;\n // \u5B9A\u754C\u7B26\u5185\u5BB9\u542B\u4E2D\u65E5\u97E9\u5B57\u7B26\uFF08\u5982\u300C\u4EF7\u683C $5 \u4E0E $6\u300D\uFF09\u89C6\u4E3A\u666E\u901A\u6587\u672C\uFF0C\u4E0D\u4F5C\u4E3A\u516C\u5F0F\u5B9A\u754C\n if (/[\\u4e00-\\u9fff\\u3040-\\u30ff]/.test(match[0])) continue;\n found.push({ from: match.index, to: match.index + match[0].length, snippet: match[0], kind: 'delimiter' });\n }\n\n found.sort((a, b) => a.from - b.from || b.to - a.to);\n const spans: NakedLatexSpan[] = [];\n let cursor = -1;\n for (const span of found) {\n if (span.from < cursor) continue; // \u4E22\u5F03\u4E0E\u524D\u4E00\u4E2A\u91CD\u53E0\u7684\u547D\u4E2D\n spans.push(span);\n cursor = span.to;\n }\n return spans;\n}\n\n/** \u6587\u672C\u91CC\u662F\u5426\u6DF7\u5165\u4E86\u88F8 LaTeX\uFF08\u4F9B\u6279\u91CF\u5DE1\u68C0\u811A\u672C\u4E0E\u65AD\u8A00\u4F7F\u7528\uFF09 */\nexport function hasNakedLatex(text: string): boolean {\n return scanNakedLatex(text).length > 0;\n}\n\n/** \u5B9A\u754C\u7B26\u7247\u6BB5\u5265\u79BB\u5916\u5C42 `$` / `\\(`\uFF0C\u53D6\u51FA\u7EAF LaTeX */\nfunction unwrapDelimiter(snippet: string): string {\n const dollar = /^\\$([^$\\n]*)\\$$/.exec(snippet);\n if (dollar) return dollar[1]!;\n const paren = /^\\\\\\(([^()\\n]*)\\\\\\)$/.exec(snippet);\n if (paren) return paren[1]!;\n return snippet;\n}\n\n/**\n * \u628A\u7EAF\u6587\u672C\u5207\u6210 text / \u884C\u5185 formula \u7247\u6BB5\uFF08seed \u6784\u5EFA\u5C42\u5F52\u4E00\u5316\u7528\uFF09\u3002\n * \u8FDE\u7EED\u65E0\u95F4\u9694\u7684\u547D\u4E2D\u5408\u5E76\u4E3A\u540C\u4E00\u4E2A\u516C\u5F0F\u7247\u6BB5\u3002\n */\nexport function splitTextIntoLatex(text: string): InlineSegment[] {\n const spans = scanNakedLatex(text);\n if (!spans.length) return text ? [{ type: 'text', text }] : [];\n\n const segments: InlineSegment[] = [];\n const pushText = (value: string) => {\n if (value) segments.push({ type: 'text', text: value });\n };\n\n let cursor = 0;\n for (const span of spans) {\n pushText(text.slice(cursor, span.from));\n const latex = span.kind === 'delimiter' ? unwrapDelimiter(span.snippet) : span.snippet;\n const last = segments[segments.length - 1];\n // \u7D27\u90BB\u4E0A\u4E00\u4E2A\u516C\u5F0F\u7247\u6BB5\uFF08\u4E2D\u95F4\u6CA1\u6709\u6587\u672C\uFF09\u65F6\u5408\u5E76\uFF0C\u907F\u514D\u5207\u51FA\u788E\u7247\u516C\u5F0F\n if (last && last.type === 'formula' && cursor === span.from) {\n last.latex += latex;\n } else {\n segments.push({ type: 'formula', latex });\n }\n cursor = span.to;\n }\n pushText(text.slice(cursor));\n return segments;\n}\n\n// \u2500\u2500 \u5185\u5BB9\u6587\u6863\u5BA1\u6838 \u2500\u2500\n\nfunction lintFormula(latex: string, display: boolean, scope: string, path: string, out: ContentIssue[]): void {\n for (const issue of lintLatex(latex, { display })) {\n out.push({\n scope,\n path,\n kind: 'rule-violation',\n rule: issue.rule,\n message: issue.message,\n snippet: latex.slice(0, 60),\n });\n }\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;\n}\n\nfunction walkInline(node: unknown, scope: string, path: string, out: ContentIssue[]): void {\n const record = asRecord(node);\n if (!record) return;\n if (record['type'] === 'text') {\n const text = typeof record['text'] === 'string' ? record['text'] : '';\n for (const span of scanNakedLatex(text)) {\n out.push({\n scope,\n path,\n kind: span.kind === 'delimiter' ? 'delimiter-in-text' : 'naked-latex-in-text',\n message:\n span.kind === 'delimiter'\n ? `\u6B63\u6587\u91CC\u4E0D\u5E94\u5199\u5B9A\u754C\u7B26\u300C${span.snippet}\u300D\uFF1A\u516C\u5F0F\u8BF7\u7528\u300C\u63D2\u5165\u884C\u5185\u516C\u5F0F\u300D\u63D2\u5165`\n : `\u6B63\u6587\u91CC\u6DF7\u5165\u4E86\u672A\u6E32\u67D3\u7684 LaTeX \u547D\u4EE4\u300C${span.snippet}\u300D\uFF1A\u8BF7\u6539\u7528\u300C\u63D2\u5165\u884C\u5185\u516C\u5F0F\u300D`,\n snippet: span.snippet,\n });\n }\n return;\n }\n if (record['type'] === 'formula') {\n const latex = typeof record['latex'] === 'string' ? record['latex'] : '';\n lintFormula(latex, false, scope, path, out);\n }\n}\n\nfunction walkBlocks(blocks: unknown, scope: string, path: string, out: ContentIssue[]): void {\n if (!Array.isArray(blocks)) return;\n blocks.forEach((block, index) => {\n const record = asRecord(block);\n if (!record) return;\n const blockPath = `${path}[${index}]`;\n const type = record['type'];\n if (type === 'paragraph') {\n const children = record['children'];\n if (Array.isArray(children)) {\n children.forEach((child, i) => walkInline(child, scope, `${blockPath}.children[${i}]`, out));\n }\n return;\n }\n if (type === 'formula') {\n const latex = typeof record['latex'] === 'string' ? record['latex'] : '';\n lintFormula(latex, record['display'] === true, scope, blockPath, out);\n return;\n }\n if (type === 'table' && Array.isArray(record['rows'])) {\n record['rows'].forEach((row, r) => {\n const rowRecord = asRecord(row);\n if (!rowRecord || !Array.isArray(rowRecord['cells'])) return;\n rowRecord['cells'].forEach((cell, c) => {\n const cellRecord = asRecord(cell);\n if (cellRecord) walkBlocks(cellRecord['blocks'], scope, `${blockPath}.rows[${r}].cells[${c}].blocks`, out);\n });\n });\n return;\n }\n if (type === 'list' && Array.isArray(record['items'])) {\n record['items'].forEach((item, i) => {\n const itemRecord = asRecord(item);\n if (itemRecord) walkBlocks(itemRecord['blocks'], scope, `${blockPath}.items[${i}].blocks`, out);\n });\n }\n });\n}\n\n/** \u5BA1\u6838\u4E00\u4E2A\u5185\u5BB9\u6587\u6863\uFF08\u9898\u5E72/\u89E3\u6790/\u53C2\u8003\u7B54\u6848\u7B49\u540C\u4E00\u5F62\u72B6\uFF09 */\nexport function auditContentDocument(doc: unknown, scope = '\u5185\u5BB9'): ContentIssue[] {\n const out: ContentIssue[] = [];\n walkBlocks(asRecord(doc)?.['blocks'], scope, 'blocks', out);\n return out;\n}\n\n/**\n * \u5BA1\u6838\u4E00\u9053\u9898\u7684\u5B8C\u6574\u5185\u5BB9\uFF1A\u9898\u5E72 + \u89E3\u6790 + \u7B54\u6848\uFF08\u6309 kind \u5206\u652F\u8986\u76D6\u9009\u9879/\u586B\u7A7A/\u53C2\u8003\u89E3\u7B54\uFF09\u3002\n * \u975E\u963B\u65AD\u7528\u9014 \u2014\u2014 \u8C03\u7528\u65B9\u51B3\u5B9A\u63D0\u793A\u3001\u6807\u8BB0\u6216\u62E6\u622A\u3002\n */\nexport function auditQuestionContent(input: {\n content?: unknown;\n analysis?: unknown;\n answer?: unknown;\n}): ContentIssue[] {\n const out: ContentIssue[] = [];\n if (input.content) out.push(...auditContentDocument(input.content, '\u9898\u5E72'));\n if (input.analysis) out.push(...auditContentDocument(input.analysis, '\u89E3\u6790'));\n\n const answer = asRecord(input.answer);\n if (!answer) return out;\n const kind = answer['kind'];\n\n if (kind === 'choice' && Array.isArray(answer['options'])) {\n answer['options'].forEach((option, i) => {\n const optionRecord = asRecord(option);\n if (!optionRecord) return;\n const key = typeof optionRecord['key'] === 'string' ? optionRecord['key'] : String(i + 1);\n const scope = `\u9009\u9879${key}`;\n const content = asRecord(optionRecord['content']);\n if (!content) return;\n if (content['kind'] === 'latex' && typeof content['latex'] === 'string') {\n lintFormula(content['latex'], false, scope, `answer.options[${i}].content`, out);\n } else if (content['kind'] === 'blocks') {\n walkBlocks(content['blocks'], scope, `answer.options[${i}].content.blocks`, out);\n }\n });\n return out;\n }\n\n if (kind === 'fill' && Array.isArray(answer['blanks'])) {\n answer['blanks'].forEach((blank, i) => {\n const accept = asRecord(blank)?.['accept'];\n if (!Array.isArray(accept)) return;\n accept.forEach((value, j) => {\n if (typeof value !== 'string') return;\n lintFormula(value, false, `\u586B\u7A7A${i + 1}`, `answer.blanks[${i}].accept[${j}]`, out);\n });\n });\n return out;\n }\n\n if (kind === 'solution' && answer['reference']) {\n out.push(...auditContentDocument(answer['reference'], '\u53C2\u8003\u7B54\u6848'));\n }\n return out;\n}\n", "/**\n * \u5C0F\u7A0B\u5E8F\u9884\u6E32\u67D3\u5165\u53E3\uFF08plan-M0 \u9636\u6BB5 I \u7B2C 27 \u6761\uFF09\uFF1A\n * renderToMpNodes(latex) \u2192 \u5FAE\u4FE1 rich-text nodes\u3002\n *\n * \u4E3A\u4EC0\u4E48\u5316\u5B66\u516C\u5F0F\u4E0D\u8D70 MathJax\uFF1A\u5B98\u65B9 rich-text \u53D7\u4FE1\u4EFB\u8282\u70B9\u5217\u8868\u53EA\u542B HTML \u6807\u7B7E\uFF0C\n * \u4E0D\u542B svg/path\uFF0CMathJax \u7684 SVG \u8F93\u51FA\u4F1A\u88AB\u6574\u68F5\u79FB\u9664\u3002\u56E0\u6B64\u7EDF\u4E00\u8D70\n * KaTeX + \u5B98\u65B9 mhchem \u6269\u5C55\uFF08\u8F93\u51FA HTML\uFF09\uFF0C\u6570\u5B66/\u5316\u5B66\u540C\u4E00\u6761\u94FE\u8DEF\u3002\n *\n * \u65E0\u72B6\u6001\u3001\u53EF\u88AB API \u7AEF\uFF08BullMQ \u9884\u6E32\u67D3\u4EFB\u52A1\uFF09\u76F4\u63A5\u8C03\u7528\uFF1B\n * \u4EFB\u4F55\u5931\u8D25\u663E\u5F0F\u629B MpRenderError\uFF0C\u7531\u8C03\u7528\u65B9\u963B\u65AD rendered_mp \u5165\u5E93\u3002\n */\nimport katex from 'katex';\nimport 'katex/contrib/mhchem';\nimport { katexHtmlToNodes, MpRenderError } from './mp-html.js';\nimport type { MpElementNode, MpTextNode, MpTreeNode } from './mp-html.js';\n\nexport { MpRenderError, katexHtmlToNodes };\nexport type { MpElementNode, MpTextNode, MpTreeNode };\n\nconst KATEX_OPTIONS = {\n throwOnError: true,\n strict: 'ignore',\n trust: false,\n output: 'html',\n} as const;\n\n/**\n * KaTeX \u57FA\u51C6 HTML \u6E32\u67D3\uFF08\u4E0E renderToMpNodes \u540C\u53C2\u540C\u94FE\u8DEF\uFF09\uFF0C\n * \u4F9B Web \u5FEB\u7167\u9875\u4F5C\u4E3A\u53CC\u7AEF\u4E00\u81F4\u6027\u5BF9\u6BD4\u7684\u57FA\u51C6\u6E32\u67D3\u3002\n * @throws \u89E3\u6790\u5931\u8D25\u76F4\u63A5\u629B\u51FA\uFF08KaTeX ParseError\uFF09\n */\nexport function renderKatexHtml(latex: string, displayMode = false): string {\n return katex.renderToString(latex, { ...KATEX_OPTIONS, displayMode });\n}\n\n/**\n * \u6E32\u67D3 LaTeX\uFF08\u6570\u5B66/\u5316\u5B66\uFF09\u5E76\u8F6C\u6362\u4E3A rich-text nodes\u3002\n * @param displayMode \u72EC\u7ACB\u516C\u5F0F\u5757 true\uFF1B\u884C\u5185 false\n * @throws MpRenderError KaTeX \u6E32\u67D3\u5931\u8D25\u6216 HTML\u2192nodes \u8F6C\u6362\u5931\u8D25\n */\nexport function renderToMpNodes(latex: string, displayMode = false): MpTreeNode[] {\n let html: string;\n try {\n html = katex.renderToString(latex, { ...KATEX_OPTIONS, displayMode });\n } catch (error) {\n throw new MpRenderError(latex, 'KaTeX \u6E32\u67D3\u5931\u8D25', error);\n }\n try {\n return katexHtmlToNodes(html);\n } catch (error) {\n throw new MpRenderError(latex, 'HTML\u2192nodes \u8F6C\u6362\u5931\u8D25', error);\n }\n}\n", "/**\n * KaTeX HTML \u2192 \u5FAE\u4FE1 rich-text nodes \u8F6C\u6362\uFF08plan-M0 \u9636\u6BB5 I \u7B2C 27 \u6761\uFF09\u3002\n *\n * \u4E3A\u4EC0\u4E48\u624B\u5199\u8F7B\u91CF\u89E3\u6790\u5668\u800C\u4E0D\u5F15\u5165 DOM \u89E3\u6790\u5E93\uFF1A\n * - KaTeX renderToString(output: 'html') \u7684\u4EA7\u7269\u662F\u826F\u6784 HTML\uFF1A\u6807\u7B7E\u5FC5\u7136\u914D\u5BF9\u3001\n * \u5C5E\u6027\u6052\u4E3A\u53CC\u5F15\u53F7\u3001\u65E0\u6CE8\u91CA\u3001\u65E0\u81EA\u95ED\u5408\u3001\u65E0 CDATA\uFF0Ctokenizer \u5373\u53EF\u5B89\u5168\u89E3\u6790\uFF0C\n * Node\uFF08\u670D\u52A1\u7AEF\u9884\u6E32\u67D3\uFF09\u4E0E\u6D4F\u89C8\u5668\uFF08POC \u5DE5\u5177\u9875\uFF09\u96F6\u4F9D\u8D56\u8FD0\u884C\u3002\n * - \u5FAE\u4FE1 rich-text \u4EC5\u63A5\u53D7\"\u53D7\u4FE1\u4EFB\u7684\u8282\u70B9\u4E0E\u5C5E\u6027\"\uFF08\u5B98\u65B9\u6587\u6863\uFF09\uFF0C\u6B64\u5C42\u540C\u65F6\u5B8C\u6210\u5C5E\u6027\u6536\u655B\uFF1A\n * \u53EA\u4FDD\u7559\u5168\u5C40\u53D7\u4FE1\u4EFB\u7684 class \u4E0E style\uFF0C\u5176\u4F59\uFF08aria-hidden \u7B49\uFF09\u4E00\u5F8B\u4E22\u5F03\u3002\n *\n * \u5931\u8D25\u8BED\u4E49\uFF1A\u6807\u7B7E\u4E0D\u914D\u5BF9\u7B49\u5F02\u5E38\u663E\u5F0F\u629B MpRenderError\uFF0C\u7EDD\u4E0D\u9759\u9ED8\u964D\u7EA7\u2014\u2014\n * nodes \u4EA7\u7269\u5C06\u4F5C\u4E3A rendered_mp \u7F13\u5B58\u5165\u5E93\uFF08PRD \u00A79.2\uFF09\uFF0C\u9519\u8BEF\u5FC5\u987B\u5728\u751F\u6210\u671F\u66B4\u9732\u3002\n */\n\nexport interface MpElementNode {\n name: string;\n /** class/style \u5168\u5C40\u53D7\u4FE1\u4EFB\uFF1Bimg \u8282\u70B9\u989D\u5916\u900F\u4F20 src\uFF08rich-text \u53D7\u4FE1\u4EFB\u5C5E\u6027\uFF09 */\n attrs?: { class?: string; style?: string; src?: string };\n children?: MpTreeNode[];\n}\n\nexport interface MpTextNode {\n type: 'text';\n text: string;\n}\n\nexport type MpTreeNode = MpElementNode | MpTextNode;\n\n/** \u8F6C\u6362/\u6E32\u67D3\u5931\u8D25\uFF1A\u663E\u5F0F\u62A5\u9519\uFF0C\u8C03\u7528\u65B9\u5FC5\u987B\u6355\u83B7\u5E76\u963B\u65AD\u5165\u5E93 */\nexport class MpRenderError extends Error {\n readonly latex: string;\n readonly stage: string;\n\n constructor(latex: string, stage: string, cause?: unknown) {\n const detail = cause instanceof Error ? cause.message : String(cause ?? '\u672A\u77E5\u9519\u8BEF');\n super(`[POC-B:${stage}] ${latex.slice(0, 80)} \u2192 ${detail}`);\n this.name = 'MpRenderError';\n this.latex = latex;\n this.stage = stage;\n }\n}\n\n/** rich-text \u5168\u5C40\u53D7\u4FE1\u4EFB\u5C5E\u6027\uFF08\u5B98\u65B9\u6587\u6863\uFF09\uFF1BKaTeX HTML \u4E2D\u5176\u4F59\u5C5E\u6027\u4E00\u5F8B\u4E22\u5F03 */\nconst MP_ALLOWED_ATTRS = new Set(['class', 'style']);\n\n/** \u81EA\u95ED\u5408/\u7A7A\u5143\u7D20\uFF1A\u4E0D\u5165\u6808\uFF08KaTeX \u8F93\u51FA\u4E0D\u542B\uFF0C\u4FDD\u9669\u5904\u7406\uFF09 */\nconst VOID_TAGS = new Set(['br', 'hr', 'img']);\n\n/** \u8DE8\u73AF\u5883 base64 \u7F16\u7801\uFF08Node \u6784\u5EFA\u671F + \u6D4F\u89C8\u5668 POC \u5DE5\u5177\u9875\u53CC\u7AEF\u590D\u7528\uFF09 */\nfunction svgToBase64(svg: string): string {\n const g = globalThis as { Buffer?: { from(s: string, enc: string): { toString(enc: string): string } }; btoa?: (s: string) => string };\n if (g.Buffer) return g.Buffer.from(svg, 'utf8').toString('base64');\n const bytes = new TextEncoder().encode(svg);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n return (g.btoa ?? btoa)(binary);\n}\n\nconst TAG_RE = /<(\\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:\\s+[-\\w]+(?:\\s*=\\s*\"[^\"]*\")?)*\\s*)>/g;\n\nconst ATTR_RE = /([-\\w]+)(?:\\s*=\\s*\"([^\"]*)\")?/g;\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: '\u00A0',\n};\n\nfunction decodeEntities(text: string): string {\n if (!text.includes('&')) return text;\n return text.replace(/&(#[xX]?[0-9a-fA-F]+|[a-zA-Z]+);/g, (raw, body: string) => {\n if (body.startsWith('#')) {\n const isHex = body[1] === 'x' || body[1] === 'X';\n const code = Number.parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);\n return Number.isFinite(code) && code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : raw;\n }\n return NAMED_ENTITIES[body] ?? raw;\n });\n}\n\nfunction parseAttrs(attrText: string): { class?: string; style?: string } | undefined {\n let classValue: string | undefined;\n let styleValue: string | undefined;\n ATTR_RE.lastIndex = 0;\n for (let m = ATTR_RE.exec(attrText); m !== null; m = ATTR_RE.exec(attrText)) {\n const name = (m[1] ?? '').toLowerCase();\n if (!MP_ALLOWED_ATTRS.has(name)) continue;\n const value = decodeEntities(m[2] ?? '');\n if (name === 'class') classValue = value;\n else styleValue = value;\n }\n if (classValue === undefined && styleValue === undefined) return undefined;\n return { ...(classValue !== undefined && { class: classValue }), ...(styleValue !== undefined && { style: styleValue }) };\n}\n\nfunction appendText(stack: MpElementNode[], text: string): void {\n // \u7EAF\u7A7A\u767D\u8282\u70B9\u4E22\u5F03\uFF1AKaTeX \u7684\u95F4\u8DDD\u5168\u90E8\u7531 mspace/strut \u7B49\u53D7\u63A7 span \u5B9E\u73B0\uFF0C\n // \u7F29\u8FDB/\u6362\u884C\u7A7A\u767D\u8FDB\u5165 rich-text \u53CD\u800C\u5F15\u5165\u4E0D\u53EF\u63A7\u5360\u4F4D\u3002\n if (text.trim().length === 0) return;\n const top = stack[stack.length - 1];\n if (!top) throw new MpRenderError('', '\u7ED3\u6784\u5F02\u5E38\uFF1A\u7A7A\u6808');\n (top.children ??= []).push({ type: 'text', text: decodeEntities(text) });\n}\n\n/**\n * \u771F\u673A rich-text \u9009\u62E9\u5668\u517C\u5BB9\u6CE8\u5165\uFF08POC-B \u771F\u673A\u5B9E\u6D4B\u7ED3\u8BBA\uFF09\uFF1A\n * rich-text \u5185\u90E8\u4EC5\"\u9009\u62E9\u5668\u6700\u540E\u4E00\u6BB5\u4EE5\u76EE\u6807\u81EA\u8EAB\u7C7B\u540D\u7ED3\u5C3E\"\u7684\u89C4\u5219\u53EF\u9760\u547D\u4E2D\uFF1B\n * \u6807\u7B7E\u540D\u7ED3\u5C3E\u7684\u540E\u4EE3/\u5B50\u9009\u62E9\u5668\uFF08\u5982 `.vlist>span`\uFF09\u4E00\u5F8B\u5931\u6548\uFF0C\u4E14 `>` \u5B50\u7EC4\u5408\u5668\n * \u4E0D\u88AB\u652F\u6301\u3002KaTeX \u7684 vlist \u884C\u5BB9\u5668\u7B49\u4E2D\u95F4\u5C42 span \u6CA1\u6709\u7C7B\u540D\uFF0C\u5FC5\u987B\u5728\u8F6C\u6362\u671F\u6309\n * \u56FA\u5B9A DOM \u6A21\u5F0F\u6CE8\u5165\u7ED3\u6784\u7C7B\uFF0C\u914D\u5957\u7684\u6539\u5199\u540E\u89C4\u5219\u89C1 generate-mp-poc.mjs \u7684\n * MP_RULE_REWRITES\u3002\u4EC5\u4F9D\u8D56\u76F4\u63A5\u7236/\u7956\u7236\u7C7B\uFF0CKaTeX DOM \u7ED3\u6784\u5728 0.16 \u7248\u672C\u7A33\u5B9A\u3002\n */\nfunction classListOf(node: MpElementNode): string[] {\n return node.attrs?.class?.split(/\\s+/).filter(Boolean) ?? [];\n}\n\nfunction addClass(node: MpElementNode, name: string): void {\n const list = classListOf(node);\n if (list.includes(name)) return;\n list.push(name);\n node.attrs = {\n ...node.attrs,\n class: list.join(' '),\n ...(node.attrs?.style !== undefined && { style: node.attrs.style }),\n };\n}\n\ninterface MpInheritCtx {\n /** \u7956\u5148 .katex-display \u5E26 leqno\uFF08\u516C\u5F0F\u7F16\u53F7\u5728\u5DE6\uFF09 */\n leqno: boolean;\n /** \u7956\u5148 .katex-display \u5E26 fleqn\uFF08\u516C\u5F0F\u5DE6\u5BF9\u9F50\uFF09 */\n fleqn: boolean;\n}\n\nfunction injectStructureClasses(\n node: MpElementNode,\n parent: MpElementNode | null,\n grand: MpElementNode | null,\n ctx: MpInheritCtx,\n): void {\n const self = classListOf(node);\n const pCls = parent && parent.name !== '#root' ? classListOf(parent) : [];\n const gCls = grand && grand.name !== '#root' ? classListOf(grand) : [];\n const has = (c: string): boolean => self.includes(c);\n const pHas = (c: string): boolean => pCls.includes(c);\n const gHas = (c: string): boolean => gCls.includes(c);\n\n // .vlist>span\uFF08\u7CBE\u786E\u5339\u914D vlist\uFF0C\u6392\u9664 vlist-t/vlist-r/vlist-s\uFF09\n if (pHas('vlist')) addClass(node, 'mp-vrow');\n // .vlist>span>span\n if (pHas('mp-vrow')) addClass(node, 'mp-vcell');\n // .mfrac>span>span\uFF1Amfrac\u2192vlist-t\u2192vlist-r\uFF08\u542B vlist-t2 \u955C\u50CF\u884C\uFF09\n if (gHas('mfrac')) addClass(node, 'mp-mfrac-r');\n // .llap/.clap/.rlap > .inner \u7684\u65B9\u4F4D\u533A\u5206\uFF08position:absolute \u7EDF\u4E00\u7531 .inner \u89C4\u5219\u627F\u8F7D\uFF09\n if (has('inner') && pHas('llap')) addClass(node, 'mp-inner-r');\n if (has('inner') && (pHas('clap') || pHas('rlap'))) addClass(node, 'mp-inner-l');\n // .clap>.inner>span\n if (pHas('inner') && gHas('clap')) addClass(node, 'mp-clap-s');\n // .delimsizing.mult .delim-size1/4 > span\n if (pHas('delim-size1')) addClass(node, 'mp-ds1');\n if (pHas('delim-size4')) addClass(node, 'mp-ds4');\n // .accent .accent-body\uFF1A\u4E09\u6BB5\u540E\u4EE3\u9009\u62E9\u5668\u5728 rich-text \u4E0D\u547D\u4E2D\uFF08\u94C1\u5F8B\u4EC5\u9A8C\u8BC1\u4E24\u6BB5\uFF09\uFF0C\n // \u6CE8\u5165\u7ED3\u6784\u7C7B\u8D70\u5355\u7C7B\u89C4\u5219\uFF1Baccent-full\uFF08\\widehat/\\widetilde\uFF09\u53E6\u8D70 mp-accent-full \u4FDD\u7559\u539F\u5BBD\n if (has('accent-body')) {\n addClass(node, has('accent-full') ? 'mp-accent-full' : 'mp-accent-body');\n }\n // .accent/.op-limits/.col-align-* > .vlist-t\n if (has('vlist-t') && (pHas('accent') || pHas('op-limits'))) addClass(node, 'mp-vac');\n if (has('vlist-t') && pHas('col-align-c')) addClass(node, 'mp-cac');\n if (has('vlist-t') && pHas('col-align-l')) addClass(node, 'mp-cal');\n if (has('vlist-t') && pHas('col-align-r')) addClass(node, 'mp-car');\n // \u5757\u7EA7\u516C\u5F0F\uFF1A.katex-display>.katex \u53CA\u5176 .katex-html\n if (has('katex') && pHas('katex-display')) addClass(node, 'mp-kd');\n if (has('katex-html') && pHas('mp-kd')) addClass(node, 'mp-kdh');\n if (ctx.leqno && has('tag')) addClass(node, 'mp-tagl');\n if (has('katex') && pHas('katex-display') && ctx.fleqn) addClass(node, 'mp-kfl');\n\n const nextCtx: MpInheritCtx = {\n leqno: ctx.leqno || (has('katex-display') && has('leqno')),\n fleqn: ctx.fleqn || (has('katex-display') && has('fleqn')),\n };\n for (const child of node.children ?? []) {\n if ('type' in child) continue;\n injectStructureClasses(child, node, parent, nextCtx);\n }\n}\n\n/**\n * \u5C06 KaTeX HTML \u7247\u6BB5\u8F6C\u6362\u4E3A\u5FAE\u4FE1 rich-text nodes \u6811\u3002\n * \u8F93\u5165\u5E94\u4E3A renderToMpNodes \u7684 KaTeX \u4EA7\u7269\uFF08\u826F\u6784 HTML\uFF09\u3002\n *\n * svg \u5904\u7406\uFF08POC-B \u771F\u673A\u5B9E\u6D4B\u7ED3\u8BBA\uFF09\uFF1Arich-text \u53D7\u4FE1\u4EFB\u8282\u70B9\u4E0D\u542B svg\uFF0CKaTeX \u7684\n * sqrt/\u62C9\u4F38\u7BAD\u5934/vec/\u8D85\u9AD8\u5B9A\u754C\u7B26\u5168\u90E8\u7528 svg path \u6E32\u67D3\uFF0C\u771F\u673A\u88AB\u6574\u68F5\u79FB\u9664\u3002\u6B64\u5904\n * \u5728\u89E3\u6790\u4E3B\u6D41\u6C34\u7EBF\u5185\u8054\u66FF\u6362\u4E3A\u7B49\u4E49\u5B57\u7B26\uFF08SVG_PATH_SPECS\uFF09\uFF0Csvg \u8282\u70B9\u4E0D\u5165\u6811\u3002\n * \u81EA\u95ED\u5408 <path .../> \u4E0D\u5339\u914D TAG_RE\uFF0C\u6574\u6BB5\u843D\u5728 svg \u5F00\u95ED\u6807\u7B7E\u4E4B\u95F4\u7684\u6587\u672C\u91CC\u5904\u7406\u3002\n * @throws MpRenderError \u6807\u7B7E\u4E0D\u914D\u5BF9\u6216\u8868\u5916 svg path \u65F6\n */\nexport function katexHtmlToNodes(html: string): MpTreeNode[] {\n const root: MpElementNode = { name: '#root', children: [] };\n const stack: MpElementNode[] = [root];\n /** \u975E null \u8868\u793A\u5F53\u524D\u5904\u4E8E <svg> \u5185\uFF08\u8BB0\u5F55 svg \u76D2\u5C3A\u5BF8+viewBox \u4F9B\u5B57\u7B26\u66FF\u6362/img \u91CD\u5EFA\u8BA1\u7B97\uFF09 */\n let svgBox: { width: number; height: number; viewBox: string; preserveAspectRatio: string } | null = null;\n let last = 0;\n TAG_RE.lastIndex = 0;\n for (let m = TAG_RE.exec(html); m !== null; m = TAG_RE.exec(html)) {\n const textChunk = html.slice(last, m.index);\n if (svgBox) emitSvgPaths(stack, textChunk, svgBox, html);\n else appendText(stack, textChunk);\n const [, close, rawName, attrText] = m;\n const name = (rawName ?? '').toLowerCase();\n if (svgBox) {\n // svg \u5185\u90E8\uFF1A\u4EC5\u6D88\u8D39 </svg> \u95ED\u5408\uFF1B\u5176\u4F59\u6807\u7B7E\uFF08path \u7B49\uFF09\u5DF2\u88AB emitSvgPaths \u5904\u7406\n if (close && name === 'svg') svgBox = null;\n } else if (!close && name === 'svg') {\n svgBox = parseSvgBox(attrText ?? '', html);\n } else if (close) {\n const top = stack[stack.length - 1];\n if (!top || top.name !== name) {\n throw new MpRenderError(html, `\u6807\u7B7E\u4E0D\u914D\u5BF9\uFF1A\u671F\u671B </${top?.name ?? '#root'}> \u5B9E\u9645 </${name}>`);\n }\n stack.pop();\n } else {\n const node: MpElementNode = { name };\n const attrs = parseAttrs(attrText ?? '');\n if (attrs) node.attrs = attrs;\n const top = stack[stack.length - 1];\n if (!top) throw new MpRenderError(html, '\u7ED3\u6784\u5F02\u5E38\uFF1A\u7A7A\u6808');\n (top.children ??= []).push(node);\n if (!VOID_TAGS.has(name)) stack.push(node);\n }\n last = TAG_RE.lastIndex;\n }\n const tail = html.slice(last);\n if (svgBox) emitSvgPaths(stack, tail, svgBox, html);\n else appendText(stack, tail);\n if (svgBox) throw new MpRenderError(html, '\u5B58\u5728\u672A\u95ED\u5408\u6807\u7B7E <svg>');\n if (stack.length > 1) {\n const unclosed = stack[stack.length - 1]?.name ?? '#root';\n throw new MpRenderError(html, `\u5B58\u5728\u672A\u95ED\u5408\u6807\u7B7E <${unclosed}>`);\n }\n for (const child of root.children ?? []) {\n if (!('type' in child)) injectStructureClasses(child, root, null, { leqno: false, fleqn: false });\n }\n return root.children ?? [];\n}\n\n/**\n * svg path \u2192 \u5B57\u7B26\u66FF\u6362\u89C4\u683C\u8868\u3002\n *\n * \u6570\u636E\u6765\u6E90\uFF08\u6784\u5EFA\u671F\u811A\u672C\u5B9E\u8BC1\uFF0C\u52FF\u51ED\u8BB0\u5FC6\u4FEE\u6539\uFF09\uFF1A\n * - path d \u503C\uFF1Aanalyze \u811A\u672C\u5BF9 105 \u6837\u672C\u5168\u91CF\u63D0\u53D6\uFF08\u4E0E KaTeX src/svgGeometry.ts \u6838\u5BF9\u547D\u540D\uFF09\uFF1B\n * - \u5B57\u5F62\u5EA6\u91CF height/depth/width\uFF1AKaTeX src/fontMetricsData.js\uFF08font-size:1em \u57FA\u51C6\uFF09\u3002\n *\n * \u5339\u914D\u7528\"\u53BB\u7A7A\u767D\u524D\u7F00 + \u53EF\u9009\u6807\u8BB0\u5B50\u4E32\"\uFF1A\u8D85\u9AD8\u5B9A\u754C\u7B26 path \u4E2D\u6BB5\u542B\u9AD8\u5EA6\u63D2\u503C\n * \uFF08vert \u7684 v${midHeight}\u3001paren \u7684 l0,${midHeight}\uFF09\uFF0C\u524D\u7F00\u5339\u914D\u4F7F\u4EFB\u610F\u9AD8\u5EA6\u901A\u7528\u3002\n * \u8868\u5916 path \u663E\u5F0F\u629B MpRenderError\uFF08\u963B\u65AD rendered_mp \u5165\u5E93\uFF09\uFF0C\u7EDD\u4E0D\u9759\u9ED8\u4E22\u56FE\u5F62\u3002\n */\ninterface SvgPathSpec {\n /** path d \u53BB\u7A7A\u767D\u540E\u7684\u524D\u7F00 */\n prefix: string;\n /** \u989D\u5916\u5FC5\u987B\u5305\u542B\u7684\u5B50\u4E32\uFF08\u533A\u5206\u540C\u524D\u7F00\u7684 vert/doublevert \u7B49\uFF09 */\n marker?: string;\n kind: 'drop' | 'char';\n /** \u66FF\u6362\u5B57\u7B26\uFF08kind='char' \u65F6\u5FC5\u586B\uFF09 */\n char?: string;\n /** \u5B57\u4F53\u7C7B\uFF08rich-text \u5185\u8054 font-family \u4E0D\u53EF\u9760\uFF0C\u5B57\u4F53\u9009\u62E9\u8D70 WXSS \u7C7B\uFF09 */\n fontClass?: 'mp-svg-main' | 'mp-svg-size1' | 'mp-svg-size2' | 'mp-svg-size3' | 'mp-svg-size4';\n /**\n * fill\uFF1A\u5B57\u7B26 ink \u586B\u6EE1 svg \u76D2\u2014\u2014F=H/(glyphHeight+glyphDepth)\u3001va=glyphDepth\u00B7F\uFF1B\n * \u5E26 rule \u7684\u6839\u53F7\u6539\u7528 F=(H-0.08)/(gh+gd)\uFF08\u76D2\u9876\u542B hLinePad\uFF0Cink \u533A\u95F4 [0.08,H]\uFF09\u3002\n * \uFF08sqrt/\u5B9A\u754C\u7B26/vec\uFF1Asvg ink \u5145\u6EE1\u76D2\u5B50\uFF09\u3002\n * natural\uFF1A\u5B57\u5F62\u539F\u5C3A\u5BF8\uFF08\u62C9\u4F38\u7BAD\u5934\uFF1Asvg \u76D2 0.522em \u5185 ink \u4EC5 0.234em \u4E14\u81EA\u7136\n * \u5B57\u5F62\u7684\u8F74\u7EBF\u4F4D\u7F6E\u4E0E\u539F\u59CB\u6E32\u67D3\u4EC5\u5DEE 0.01em\uFF0C\u586B\u76D2\u516C\u5F0F\u53CD\u800C\u5931\u771F\uFF09\u3002\n */\n mode?: 'fill' | 'natural';\n glyphHeight?: number;\n glyphDepth?: number;\n glyphWidth?: number;\n /** \u5B9A\u5BBD svg\uFF08\u5B9A\u754C\u7B26/vec\uFF09\uFF1A\u8D1F margin \u628A\u5B57\u5F62 advance \u538B\u56DE svg \u5BBD\u5EA6\uFF0C\u4FDD\u6301 vlist \u603B\u5BBD\u4E0D\u53D8 */\n fixedWidth?: boolean;\n /** \u7ED9 svg \u7236\u5BB9\u5668\u8FFD\u52A0\u7684 class\uFF08\u7BAD\u5934\u5C45\u4E2D\u3001\u534A\u5E45\u7BAD\u5934\u6539\u5168\u5E45\u7B49\uFF09 */\n parentClass?: string;\n /**\n * \u6839\u53F7\u4E0A\u6A2A\u7EBF\uFF1Ahide-tail\uFF08position:relative;overflow:hidden\uFF09\u5185\u7684\u7EDD\u5BF9\u5B9A\u4F4D\u8272\u6761\u3002\n * leftEm \u4E3A\u5B57\u5F62\u8F6E\u5ED3\u5185\u6A2A\u7EBF\u8D77\u70B9\u7684 em \u503C\uFF08\u5B57\u4F53\u4E0E svg \u540C\u4E3A 1000upm\uFF0C\u6E32\u67D3\u65F6\u4E58 F\uFF09\uFF1B\n * top/thickness \u56FA\u5B9A 0.08em/0.04em\uFF08hLinePad=80\u3001sqrtRuleThickness=40\uFF0C\n * \u5404\u6863 svg scale\u22480.00095-0.001\uFF0C\u8BEF\u5DEE\u22640.002em \u4E0D\u53EF\u89C1\uFF09\u3002\n */\n rule?: { leftEm: number };\n /**\n * 'char'\uFF08\u9ED8\u8BA4\uFF0C\u8D70 fontSize/strut/rule \u66FF\u6362\u5B57\u7B26\uFF09| 'img'\uFF08\u91CD\u5EFA SVG \u4E3A data URL\n * `<img>`\uFF0C\u4FDD\u7559 KaTeX \u539F\u59CB\u51E0\u4F55\uFF09\u3002\n * \u5B57\u7B26\u66FF\u6362\u65B9\u6848\u5728 sqrt/vec/\u21CC \u4E0A\u53CD\u590D\u51FA\u73B0 ink \u504F\u79FB\uFF08vertical-align \u516C\u5F0F\u5931\u771F\u3001\n * accent-body left \u504F\u79FB\uFF09\uFF0C\u6539\u7528 img \u76F4\u63A5\u6E32\u67D3 KaTeX SVG path \u4E00\u52B3\u6C38\u9038\uFF1B\n * \u5176\u4F59 spec\uFF08\u2192/drop/vert/paren\uFF09\u4FDD\u7559\u5B57\u7B26\u66FF\u6362\u4E0D\u53D8\u3002\n * img \u5206\u652F\u4E0D\u8BFB mode/glyphHeight/Depth/Width/fixedWidth/rule \u5B57\u6BB5\uFF0C\n * \u4F46 prefix/marker/parentClass \u4ECD\u751F\u6548\u3002\n */\n output?: 'char' | 'img';\n}\n\nconst SVG_PATH_SPECS: SvgPathSpec[] = [\n // \u5806\u53E0\u82B1\u62EC\u53F7\u7684\u63A5\u7F1D\u906E\u76D6\u53D1\u4E1D\u7EBF\uFF080.8889\u00D70.016em\uFF09\uFF1A\u5B57\u5F62\u672C\u8EAB\u8FDE\u7EED\uFF0C\u906E\u76D6\u6761\u7EAF\u6297\u63A5\u7F1D\uFF0C\u4E22\u5F03\n { prefix: 'M3840H504V16H384z', kind: 'drop' },\n // \u21CC \u53F3\u534A\uFF08righthalfarrow\uFF09\uFF1AxMaxYMin slice \u951A\u53F3\uFF0Cimg width:100% \u586B\u6EE1 .halfarrow-right\n // \uFF08position:absolute;right:0;width:50.2%;overflow:hidden\uFF09\u2192 SVG \u88C1\u526A\u663E\u793A\u53F3\u7BAD\u5934\n { prefix: 'M0241v40h399993', kind: 'char', output: 'img' },\n // \u2192 \u62C9\u4F38\u7BAD\u5934\uFF08mhchem -> / \\xrightarrow\uFF09\uFF1A\u539F svg \u968F\u6807\u7B7E\u5BBD\u5EA6\u62C9\u4F38\uFF0C\u5B57\u7B26\u7248\u5B9A\u5BBD\u5C45\u4E2D\n // \uFF08\u504F\u5DEE\u8BB0\u5F55\u8FDB POC \u62A5\u544A\uFF09\uFF1B\u7236\u5BB9\u5668 hide-tail \u5DF2\u6709 position:relative\uFF0C\u8865 text-align:center\n { prefix: 'M0241v40h399891', kind: 'char', char: '\u2192', fontClass: 'mp-svg-main', mode: 'natural', parentClass: 'mp-xarrow' },\n // \u21CC\uFF08mhchem <=> / \\rightleftharpoons\uFF09\uFF1Ahalfarrow \u5DE6\u53F3\u4E24\u534A\u5404 50.2% \u5BBD\u62FC\u63A5\uFF0C\n // \u5404\u81EA img \u6E32\u67D3\u539F SVG path\u3002\u5DE6\u534A xMinYMin slice \u951A\u5DE6\uFF08.halfarrow-left \u88C1\u526A\u663E\u793A\u5DE6\u7BAD\u5934\uFF09\uFF0C\n // \u53F3\u534A xMaxYMin slice \u951A\u53F3\uFF08.halfarrow-right \u88C1\u526A\u663E\u793A\u53F3\u7BAD\u5934\uFF09\u3002\u4E24 img \u91CD\u53E0 0.4% \u62FC\u5408 \u21CC\u3002\n // \u5B57\u7B26\u66FF\u6362\u65B9\u6848\u4E2D \u21CC \u53CD\u590D\u504F\u4E0B\u6C89\uFF08\u5B57\u5F62 baseline \u4E0E SVG \u8F74\u7EBF\u5DEE 0.322em\uFF09\uFF1B\u53CC img \u76F4\u63A5\n // \u6E32\u67D3 KaTeX SVG path\uFF0C\u51E0\u4F55\u4E0E\u6D4F\u89C8\u5668\u9010\u5355\u4F4D\u91CD\u5408\u3002\n {\n prefix: 'M7435c-44',\n kind: 'char',\n output: 'img',\n char: '\u21CC',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.671,\n glyphDepth: 0.011,\n glyphWidth: 1,\n },\n // \u221A \u6839\u53F7\u4E94\u6863\uFF08KaTeX 0.16 \u5168\u90E8\u8D70 svg\uFF0C\u89C1 delimiter.ts makeSqrtImage\uFF09\u3002\n // \u6863\u4F4D\u2192\u5B57\u5F62\u4E25\u683C\u5BF9\u5E94\uFF08svgGeometry.ts\uFF1A\u5404\u6863 path \u53D6\u81EA\u5BF9\u5E94\u5B57\u4F53 U+221A \u8F6E\u5ED3\uFF0C\n // \u9876\u90E8\u6A2A\u7EBF\u7531 path \u81EA\u8EAB\u5EF6\u4F38 H400000 \u5B9E\u73B0\uFF1B\u5B57\u4F53\u7684\u540C\u4E00\u8F6E\u5ED3\u5728\u6A2A\u7EBF\u8D77\u70B9\u5904\u6298\u8FD4\uFF09\uFF1A\n // - M95,702 sqrtMain \u2192 KaTeX_Main \u221A\uFF08h0.8/d0.2\uFF09\n // - M263,681 sqrtSize1 \u2192 KaTeX_Size1 \u221A\uFF08h0.85/d0.35001\uFF09\n // - M424, sqrtSize3 \u2192 KaTeX_Size3 \u221A\uFF08h1.45/d0.95003\uFF1BVB=2592=1080\u00D72.4\uFF09\n // - M983 90 sqrtSize2 \u2192 KaTeX_Size2 \u221A\uFF08h1.15/d0.65002\uFF1BVB=1944=1080\u00D71.8\uFF09\n // - M702\u2423 sqrtTall \u2192 KaTeX_Size4 \u23B7 U+23B7\uFF08h0.915/d0.88502/w1.05556\uFF09\uFF0C\n // VB \u968F\u5185\u5BB9\u9AD8\u53EF\u53D8\uFF08path \u53BB\u7A7A\u767D\u540E\u524D\u7F00 M702 \u6703\u4E0E\u5750\u6807\u7C98\u8FDE\uFF0Cmarker \u7528 H742v \u5224\u522B\uFF09\n // svg \u76D2\u9AD8 H = \u5B57\u5F62\u5168\u9AD8 + 0.08em \u9876\u90E8 pad\uFF08hLinePad=80\uFF0CsizeMultiplier=1 \u65F6\n // \u6052\u7B49\uFF09\uFF0C\u6545 fill \u516C\u5F0F\u53D6 F=(H-0.08)/(gh+gd)\u3001va=gd\u00B7F\uFF1A\u5B57\u5F62 ink \u6070\u843D\u5728\n // [0.08em, H]\uFF0C\u4E0E svg \u539F\u59CB surd \u9010\u5355\u4F4D\u91CD\u5408\uFF08\u5B57\u4F53\u4E0E svg \u540C\u4E3A 1000upm \u8F6E\u5ED3\uFF09\u3002\n // \u6A2A\u7EBF\u7531 rule \u8272\u6761\u8865\u9F50\uFF1Ay\u2208[0.08,0.12]em\uFF08hLinePad/sqrtRuleThickness=80/40\uFF09\uFF0C\n // leftEm \u4E3A\u5B57\u5F62\u8F6E\u5ED3\u5185\u6A2A\u7EBF\u8D77\u70B9\uFF08\u6298\u8FD4 x\uFF1AMain 834\u3001Size1-4 1001\u3001Tall 702\uFF09\uFF0C\n // \u6E32\u67D3\u65F6\u4E58 F\u2014\u2014F\u22481 \u65F6\u4E0E\u5B57\u5F62\u9876\u90E8\u77ED\u6A2A\u5C16\u7AEF\u7CBE\u786E\u76F8\u63A5\u3002\n {\n prefix: 'M95,702c-2.7,0',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.8,\n glyphDepth: 0.2,\n glyphWidth: 0.83334,\n rule: { leftEm: 0.834 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M263,681c0.7,0',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-size1',\n mode: 'fill',\n glyphHeight: 0.85,\n glyphDepth: 0.35001,\n glyphWidth: 1.0,\n rule: { leftEm: 1.001 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M424,',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-size3',\n mode: 'fill',\n glyphHeight: 1.45,\n glyphDepth: 0.95003,\n glyphWidth: 1.0,\n rule: { leftEm: 1.001 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M98390l0-0c4',\n kind: 'char',\n output: 'img',\n char: '\u221A',\n fontClass: 'mp-svg-size2',\n mode: 'fill',\n glyphHeight: 1.15,\n glyphDepth: 0.65002,\n glyphWidth: 1.0,\n rule: { leftEm: 1.001 },\n parentClass: 'mp-sqrt-tail',\n },\n {\n prefix: 'M702',\n marker: 'H742v',\n kind: 'char',\n output: 'img',\n char: '\u23B7',\n fontClass: 'mp-svg-size4',\n mode: 'fill',\n glyphHeight: 0.915,\n glyphDepth: 0.88502,\n glyphWidth: 1.05556,\n rule: { leftEm: 0.702 },\n parentClass: 'mp-sqrt-tail',\n },\n // \\vec \u5C0F\u7BAD\u5934\uFF08U+20D7 \u7EC4\u5408\u7BAD\u5934\uFF0CKaTeX_Main \u4E2D\u4E3A\u6709\u5BBD\u5EA6\u7684\u72EC\u7ACB\u5B57\u5F62\uFF1Baccent overlay \u5B9A\u5BBD\u5BB9\u5668\uFF09\u3002\n // \u5B57\u7B26\u66FF\u6362\u65B9\u6848\u4E2D \u20D7 \u53CD\u590D\u504F\u5DE6\uFF1Aaccent-body \u7684 left \u504F\u79FB\u516C\u5F0F\u96BE\u4EE5\u8FD8\u539F KaTeX SVG\n // \u5728\u5B9A\u5BBD\u5BB9\u5668\u5185\u7684\u5C45\u4E2D\u4F4D\u7F6E\uFF08advance 0.5em \u4E0E ink \u533A\u95F4 [0.033, 0.4]em \u7684\u8F74\u7EBF\u5DEE\uFF09\uFF1B\n // \u6539\u7528 img \u76F4\u63A5\u6E32\u67D3 SVG path\uFF0C\u51E0\u4F55\u4E0E\u6D4F\u89C8\u5668\u9010\u5355\u4F4D\u91CD\u5408\u3002\n {\n prefix: 'M37720c0-5.3331.833',\n kind: 'char',\n output: 'img',\n char: '\\u20D7',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.71444,\n glyphDepth: 0,\n glyphWidth: 0.5,\n fixedWidth: true,\n },\n // \u8D85\u9AD8\u7AD6\u7EBF\uFF08svgGeometry vertTall\uFF0C\u5982 vmatrix \u7684 |\uFF1Bmarker \u533A\u5206 doublevert\uFF09\n {\n prefix: 'M14515v585v',\n marker: 'zM18815H145',\n kind: 'char',\n char: '|',\n fontClass: 'mp-svg-main',\n mode: 'fill',\n glyphHeight: 0.75,\n glyphDepth: 0.25,\n glyphWidth: 0.27778,\n fixedWidth: true,\n },\n // \u8D85\u9AD8\u5DE6\u53F3\u62EC\u53F7\uFF08makeCustomSizedDelim lparen/rparen\uFF0C\u4E2D\u6BB5 l0,${midHeight} \u968F\u9AD8\u5EA6\u63D2\u503C\uFF09\n {\n prefix: 'M863,9c0,-2,-2,-5,-6,-9',\n kind: 'char',\n char: '(',\n fontClass: 'mp-svg-size4',\n mode: 'fill',\n glyphHeight: 1.75,\n glyphDepth: 1.25003,\n glyphWidth: 0.79167,\n fixedWidth: true,\n },\n {\n prefix: 'M76,0c-16.7,0,-25,3,-25,9',\n kind: 'char',\n char: ')',\n fontClass: 'mp-svg-size4',\n mode: 'fill',\n glyphHeight: 1.75,\n glyphDepth: 1.25003,\n glyphWidth: 0.79167,\n fixedWidth: true,\n },\n];\n\nconst SVG_PATH_TAG_RE = /<path\\b[^>]*?\\bd=\"([^\"]*)\"[^>]*?\\/>/g;\n\nfunction round4(n: number): string {\n return String(Number(n.toFixed(4)));\n}\n\n/** \u89E3\u6790 <svg> \u5F00\u6807\u7B7E\u7684 width/height\uFF08\u5FC5\u586B\uFF0Cem \u5355\u4F4D\uFF09+ viewBox/preserveAspectRatio\uFF08\u53EF\u9009\uFF09\u3002\n * KaTeX \u7684 sqrt/vec/\u21CC/\u62C9\u4F38\u5B9A\u754C\u7B26 svg \u5747\u542B width/height\uFF1B\u8D85\u9AD8\u5B9A\u754C\u7B26\uFF08vert/paren\uFF09\n * \u7684 svg \u4E0D\u5E26 preserveAspectRatio\uFF08\u7528\u6D4F\u89C8\u5668\u9ED8\u8BA4 xMidYMid meet\uFF09\u2014\u2014\u6545 vb/par \u4E3A\u53EF\u9009\u3002\n * output:'img' \u5206\u652F\u91CD\u5EFA SVG \u65F6\u539F\u6837\u900F\u4F20\uFF0C\u7F3A\u5931\u7684\u5C5E\u6027\u4E0D\u5199\u5165\uFF08SVG \u9ED8\u8BA4\u503C\u751F\u6548\uFF09\u3002 */\nfunction parseSvgBox(\n attrText: string,\n html: string,\n): { width: number; height: number; viewBox: string; preserveAspectRatio: string } {\n const w = /\\bwidth=\"([0-9.]+)em\"/.exec(attrText);\n const h = /\\bheight=\"([0-9.]+)em\"/.exec(attrText);\n if (!w || !h) throw new MpRenderError(html, `svg \u7F3A\u5C11 em \u5355\u4F4D width/height \u5C5E\u6027\uFF1A${attrText.trim().slice(0, 80)}`);\n const vb = /\\bviewBox=\"([^\"]*)\"/.exec(attrText);\n const par = /\\bpreserveAspectRatio=\"([^\"]*)\"/.exec(attrText);\n return {\n width: Number.parseFloat(w[1] ?? ''),\n height: Number.parseFloat(h[1] ?? ''),\n viewBox: vb?.[1] ?? '',\n preserveAspectRatio: par?.[1] ?? '',\n };\n}\n\n/**\n * \u5904\u7406 <svg> \u4E0E </svg> \u4E4B\u95F4\u7684\u6587\u672C\u6BB5\uFF1A\u63D0\u53D6\u5168\u90E8 <path/> \u5E76\u9010\u4E2A\u66FF\u6362\u3002\n * \u6BB5\u5185\u9664 path \u6807\u7B7E\u4E0E\u7A7A\u767D\u5916\u4E0D\u5141\u8BB8\u5176\u4ED6\u5185\u5BB9\uFF08KaTeX svg \u4EC5\u542B path\uFF09\u3002\n */\nfunction emitSvgPaths(\n stack: MpElementNode[],\n chunk: string,\n box: { width: number; height: number; viewBox: string; preserveAspectRatio: string },\n html: string,\n): void {\n let residue = chunk;\n SVG_PATH_TAG_RE.lastIndex = 0;\n for (let m = SVG_PATH_TAG_RE.exec(chunk); m !== null; m = SVG_PATH_TAG_RE.exec(chunk)) {\n applySvgPath(stack, m[1] ?? '', box, html);\n residue = residue.replace(m[0], '');\n }\n if (residue.trim().length > 0) {\n throw new MpRenderError(html, `svg \u5185\u542B\u975E path \u5185\u5BB9\uFF1A${residue.trim().slice(0, 60)}`);\n }\n}\n\nfunction applySvgPath(\n stack: MpElementNode[],\n d: string,\n box: { width: number; height: number; viewBox: string; preserveAspectRatio: string },\n html: string,\n): void {\n // \u89C4\u683C\u5339\u914D\u7528\u5265\u7A7A\u767D\u526F\u672C\uFF08SVG_PATH_SPECS \u7684 prefix/marker \u5747\u4E3A\u65E0\u7A7A\u767D\u7D27\u51D1\u683C\u5F0F\uFF09\u3002\n // \u8F93\u51FA\u5FC5\u987B\u7528\u539F\u59CB d\uFF1AKaTeX \u90E8\u5206 path\uFF08vec/\u21CC/\u62C9\u4F38\u7BAD\u5934\uFF09\u4EE5\u7A7A\u683C\u5206\u9694\u5750\u6807\uFF08\u5982\n // \"M377 20c0-5.333 1.833-10\"\uFF09\uFF0C\u5265\u7A7A\u683C\u4F1A\u628A\u76F8\u90BB\u5750\u6807\u7C98\u8FDE\u6210\u4E00\u4E2A\u6570\uFF08\"M37720\"\uFF09\uFF0C\n // \u4EFB\u4F55 SVG \u89E3\u6790\u5668\u90FD\u65E0\u6CD5\u8FD8\u539F \u2192 \u771F\u673A\u6E32\u67D3\u7A7A\u767D\uFF08#79/#105 \u7B2C\u4E09\u8F6E\u6839\u56E0\uFF09\u3002\u221A \u7684 path\n // \u6070\u4E3A\u9017\u53F7\u5206\u9694\u683C\u5F0F\uFF08\u65E0\u7A7A\u683C\uFF09\u6545\u6B64\u524D\u5E78\u514D\u3002d \u5C5E\u6027\u5185\u7A7A\u683C/\u6362\u884C\u5747\u4E3A SVG \u5408\u6CD5\u5206\u9694\u7B26\u3002\n const dNorm = d.replace(/\\s+/g, '');\n const spec = SVG_PATH_SPECS.find((s) => dNorm.startsWith(s.prefix) && (!s.marker || dNorm.includes(s.marker)));\n if (!spec) {\n throw new MpRenderError(html, `\u8868\u5916 svg path\uFF08d \u524D\u7F00\uFF1A${d.slice(0, 80)}\uFF09`);\n }\n if (spec.kind === 'drop') return;\n const parent = stack[stack.length - 1];\n if (!parent || parent.name === '#root') throw new MpRenderError(html, 'svg \u7236\u5BB9\u5668\u5F02\u5E38');\n if (spec.parentClass) addClass(parent, spec.parentClass);\n\n // output:'img' \u5206\u652F\uFF1A\u91CD\u5EFA SVG \u4E3A data URL\uFF0Cimg \u590D\u523B KaTeX svg \u7684 CSS \u884C\u4E3A\n // \uFF08display:block;position:absolute;width:100%;height:inherit\u2014\u2014\u89C1 .katex svg \u89C4\u5219\uFF09\u3002\n // \u5173\u952E\uFF1Aimg \u5FC5\u987B position:absolute \u8131\u79BB\u6587\u6863\u6D41\uFF0C\u5426\u5219\u5176 intrinsic \u5BBD\u5EA6\uFF08\u221A \u7684 400em\uFF09\n // \u4F1A\u6491\u5F00\u7236\u5BB9\u5668\uFF0Cvinculum \u6A2A\u7EBF\u6EA2\u51FA\u6574\u4E2A\u516C\u5F0F\uFF08#32 \u6839\u53F7\u6A2A\u7EBF\u8FC7\u957F\u7684\u6839\u56E0\uFF09\u3002\n // \u5BBD\u5EA6\u7B56\u7565\uFF1Asvg \u5BBD \u22655em\uFF08\u221A \u7684 400em\uFF09\u7528 width:100% \u586B\u6EE1\u7236\u5BB9\u5668\uFF08\u7236 overflow:hidden \u88C1\u526A\uFF0C\n // \u7236\u5BBD\u5EA6\u7531 vlist \u5E03\u5C40\u51B3\u5B9A\uFF0C\u8986\u76D6\u88AB\u5F00\u65B9\u6570\uFF09\uFF1Bsvg \u5BBD <5em\uFF08\u20D7 \u7684 0.471em\uFF09\u7528\u663E\u5F0F\u5BBD\u5EA6\uFF0C\n // \u56E0 .overlay \u65E0 position\uFF0Cwidth:100% \u4F1A\u5411\u4E0A\u627E\u5230 .accent-body\uFF08width:0\uFF09\u5BFC\u81F4 img \u5BBD 0\u3002\n // height \u7528 box.height\uFF08= SVG height \u5C5E\u6027 = \u7236 inline height\uFF09\uFF0C\u4E0D\u7528 inherit \u907F\u514D\u6B67\u4E49\u3002\n // \u7F16\u7801\uFF1A\u63A2\u9488\u5B9E\u8BC1\uFF08I9-I14\uFF09rich-text img \u5BF9 base64 \u4E0E charset \u4E24\u79CD data URL \u7F16\u7801\u3001\n // SVG \u4F4D\u56FE\u5747\u652F\u6301\uFF08\u6781\u7B80 SVG/\u221A SVG \u4E24\u79CD\u7F16\u7801\u90FD\u80FD\u6E32\u67D3\uFF09\uFF1Bbase64 \u65E0\u4FDD\u7559\u5B57\u7B26\u6B67\u4E49\u66F4\u7A33\u3002\n // left:0;top:0 \u663E\u5F0F\u5B9A\u4F4D\u907F\u514D rich-text \u5BF9 position:absolute \u5143\u7D20\"\u9759\u6001\u4F4D\u7F6E\"\u6B67\u4E49\u3002\n if (spec.output === 'img') {\n let svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${box.width}em\" height=\"${box.height}em\"`;\n if (box.viewBox) svg += ` viewBox=\"${box.viewBox}\"`;\n if (box.preserveAspectRatio) svg += ` preserveAspectRatio=\"${box.preserveAspectRatio}\"`;\n svg += `><path d=\"${d}\"/></svg>`;\n const b64 = svgToBase64(svg);\n const src = `data:image/svg+xml;base64,${b64}`;\n const imgWidth = box.width > 5 ? '100%' : `${box.width}em`;\n (parent.children ??= []).push({\n name: 'img',\n attrs: {\n src,\n style: `display:block;position:absolute;left:0;top:0;width:${imgWidth};height:${box.height}em`,\n },\n });\n return;\n }\n\n const styles: string[] = [];\n let fontSize = 0;\n if (spec.mode === 'fill') {\n const gh = spec.glyphHeight ?? 0;\n const gd = spec.glyphDepth ?? 0;\n // \u6839\u53F7\uFF08\u5E26 rule\uFF09\uFF1Asvg \u76D2\u9876\u90E8\u542B 0.08em hLinePad\uFF0Cink \u53EA\u5360 [0.08, H]\uFF0C\n // \u6545 F=(H-0.08)/(gh+gd)\uFF1B\u5176\u4F59 fill \u89C4\u683C svg \u76D2\u5373 ink \u76D2\uFF0CF=H/(gh+gd)\u3002\n const inkHeight = spec.rule ? box.height - 0.08 : box.height;\n fontSize = inkHeight / (gh + gd);\n styles.push(`font-size:${round4(fontSize)}em`);\n // va=gd\uFF08char \u5355\u4F4D\uFF0C\u975E gd\u00B7F\uFF09\uFF1Astrut\uFF08\u89C1\u4E0B\uFF09\u628A\u884C\u76D2\u57FA\u7EBF\u9489\u5728\u76D2\u5E95\u540E\uFF0C\u5B57\u5F62 ink\n // \u7CBE\u786E\u843D\u5728 [-(gh+gd)\u00B7F, 0] = [-inkHeight, 0]\uFF0C\u4E0E svg ink \u9010\u5355\u4F4D\u91CD\u5408\u3002\n // \u5B9E\u6D4B\uFF08canvas actualBoundingBox\uFF09\uFF1Ava=gd\u00B7F \u5728 F\u22601 \u7684\u62EC\u53F7\u7C7B\u4F7F ink \u504F\u9AD8\n // gd\u00B7(F-1)\u00B7F em\uFF08pmatrix \u5927\u62EC\u53F7\u504F\u9AD8 20.65px\uFF0C\u5185\u5BB9\u76F8\u5BF9\"\u4E0B\u6C89\"\uFF09\u3002\n if (Math.abs(gd) > 1e-4) styles.push(`vertical-align:${round4(gd)}em`);\n if (spec.fixedWidth) {\n const margin = (box.width - (spec.glyphWidth ?? 0) * fontSize) / 2;\n styles.push(`margin-left:${round4(margin)}em`, `margin-right:${round4(margin)}em`);\n }\n }\n // \u884C\u76D2\u57FA\u7EBF\u9489\u5E95\uFF08\u5168\u90E8 fill \u89C4\u683C\uFF0C\u975E\u4EC5\u6839\u53F7\uFF09\uFF1Asvg \u5BB9\u5668\uFF08hide-tail / \u5B9A\u5C3A\u5BF8\n // mp-vcell\uFF09\u5185 normal line-box \u57FA\u7EBF\u4F4D\u7F6E\u4E0D\u786E\u5B9A\uFF0Cva \u516C\u5F0F\u6574\u4F53\u6F02\u79FB\uFF08\u5B9E\u6D4B\u65E0 strut\n // \u7684\u62EC\u53F7\u7C7B ink \u504F\u9AD8 20.65px\uFF09\u3002\u2460 \u5B57\u7B26\u524D\u63D2\u5185\u90E8 strut\uFF08inline-block \u9AD8 H\u3001\n // \u57FA\u7EBF=\u5E95\u8FB9\uFF09\u628A\u884C\u76D2\u6491\u5230\u76D2\u9AD8\u4E14\u884C\u57FA\u7EBF\u9489\u5728\u76D2\u5E95\uFF1B\u2461 \u5B57\u7B26\u8BBE line-height:1\n // \u6D88\u9664\u5B57\u4F53 leading \u4E0D\u786E\u5B9A\u6027\u3002\n if (spec.mode === 'fill') {\n (parent.children ??= []).push({\n name: 'span',\n attrs: {\n style: `display:inline-block;width:0;height:${round4(box.height)}em;overflow:hidden`,\n },\n });\n styles.push('line-height:1');\n }\n (parent.children ??= []).push({\n name: 'span',\n attrs: {\n ...(spec.fontClass !== undefined && { class: spec.fontClass }),\n ...(styles.length > 0 && { style: styles.join(';') }),\n },\n children: [{ type: 'text', text: spec.char ?? '' }],\n });\n if (spec.rule) {\n // \u6A2A\u7EBF\u8D77\u70B9 = \u5B57\u5F62\u8F6E\u5ED3\u6298\u8FD4 x \u00D7 F\uFF1A\u5B57\u4F53\u4E0E svg \u540C\u4E3A 1000upm \u8F6E\u5ED3\uFF0CF\u22481 \u65F6\n // \u8272\u6761\u4E0E\u5B57\u5F62\u9876\u90E8\u77ED\u6A2A\u5C16\u7AEF\u7CBE\u786E\u76F8\u63A5\uFF08KaTeX \u539F\u59CB svg \u5373\u6B64\u51E0\u4F55\uFF09\u3002\n (parent.children ??= []).push({\n name: 'span',\n attrs: {\n class: 'mp-svg-rule',\n // hLinePad=80\u3001sqrtRuleThickness=40 \u2192 top:0.08em height:0.04em\n style: [\n `left:${round4(spec.rule.leftEm * fontSize)}em`,\n 'top:0.08em',\n 'right:-1em',\n 'height:0.04em',\n ].join(';'),\n },\n });\n }\n}\n", "/**\n * POC-B \u6837\u672C\u96C6\uFF08plan-M0 \u9636\u6BB5 I \u7B2C 28 \u6761\uFF09\uFF1A\u2265100 \u4E2A\u590D\u6742\u516C\u5F0F\u3002\n *\n * 7 \u7C7B\u8986\u76D6\uFF1A\u77E9\u9635\u3001cases\u3001\u5206\u5F0F\u6839\u5F0F\u5D4C\u5957\u3001\u79EF\u5206\u6C42\u548C\u3001\u4E0A\u4E0B\u6807\u3001\u5E0C\u814A\u5B57\u6BCD\u3001\\ce \u5316\u5B66\u65B9\u7A0B\u5F0F\u3002\n * \u7EA6\u675F\uFF1A\n * - \u5168\u90E8\u7ECF KaTeX 0.16 + mhchem \u6E32\u67D3\u9A8C\u8BC1\uFF08\u751F\u6210\u811A\u672C\u9010\u6761\u6267\u884C\uFF0C\u5931\u8D25\u5373\u62A5\u9519\uFF09\uFF1B\n * - \u4E0D\u4F7F\u7528 KaTeX_Math-BoldItalic \u4E4B\u5916\u7684\u4E0D\u53EF\u63A7\u5B57\u4F53\u7279\u6027\uFF08\\mathfrak/\\boldsymbol \u5DF2\u6392\u9664\uFF0C\n * \u5B57\u4F53\u6E05\u5355\u89C1 mp-fonts.ts\uFF09\uFF1B\n * - LaTeX \u662F\u552F\u4E00\u771F\u76F8\u6E90\uFF1A\u672C\u6E05\u5355\u540C\u65F6\u9A71\u52A8 Web \u5FEB\u7167\u9875\u4E0E\u5C0F\u7A0B\u5E8F nodes \u751F\u6210\uFF0C\u4FDD\u8BC1\u53CC\u7AEF\u540C\u6E90\u3002\n */\n\nexport type MpSampleCategory =\n | 'matrix'\n | 'cases'\n | 'frac-sqrt'\n | 'integral'\n | 'script'\n | 'greek'\n | 'chem';\n\nexport interface MpSample {\n /** \u7A33\u5B9A\u7F16\u53F7\uFF0C\u53CC\u7AEF\u622A\u56FE\u5BF9\u6BD4\u7684\u951A\u70B9\uFF08#001 \u8D77\uFF09 */\n id: number;\n category: MpSampleCategory;\n latex: string;\n /** \u72EC\u7ACB\u516C\u5F0F\u5757 true\uFF1B\u884C\u5185 false */\n display: boolean;\n}\n\nconst MATRIX = [\n '\\\\begin{pmatrix}a&b\\\\\\\\c&d\\\\end{pmatrix}',\n '\\\\begin{bmatrix}1&0\\\\\\\\0&1\\\\end{bmatrix}',\n '\\\\begin{vmatrix}a&b\\\\\\\\c&d\\\\end{vmatrix}',\n '\\\\begin{Bmatrix}x\\\\\\\\y\\\\\\\\z\\\\end{Bmatrix}',\n '\\\\begin{pmatrix}\\\\frac{1}{2}&\\\\sqrt{2}\\\\\\\\\\\\sin\\\\theta&\\\\cos\\\\theta\\\\end{pmatrix}',\n '\\\\begin{pmatrix}a_{11}&a_{12}&a_{13}\\\\\\\\a_{21}&a_{22}&a_{23}\\\\\\\\a_{31}&a_{32}&a_{33}\\\\end{pmatrix}',\n '\\\\begin{pmatrix}1&\\\\cdots&n\\\\\\\\\\\\vdots&\\\\ddots&\\\\vdots\\\\\\\\m&\\\\cdots&p\\\\end{pmatrix}',\n '\\\\left(\\\\begin{array}{cc|c}1&2&3\\\\\\\\4&5&6\\\\end{array}\\\\right)',\n 'A=\\\\begin{pmatrix}\\\\lambda_1&\\\\\\\\&\\\\lambda_2\\\\end{pmatrix}',\n '\\\\begin{pmatrix}\\\\mathbf{e}_1&\\\\mathbf{e}_2&\\\\mathbf{e}_3\\\\end{pmatrix}',\n '\\\\det\\\\begin{pmatrix}1&1\\\\\\\\1&1\\\\end{pmatrix}=0',\n '\\\\begin{pmatrix}0&-i\\\\\\\\i&0\\\\end{pmatrix}',\n '\\\\operatorname{rank}\\\\begin{pmatrix}1&2\\\\\\\\2&4\\\\end{pmatrix}=1',\n '\\\\begin{pmatrix}\\\\dfrac{a}{b}&\\\\dfrac{c}{d}\\\\\\\\\\\\dfrac{e}{f}&\\\\dfrac{g}{h}\\\\end{pmatrix}',\n '\\\\begin{pmatrix}x_1&x_2\\\\end{pmatrix}^{\\\\mathsf{T}}',\n];\n\nconst CASES = [\n '|x|=\\\\begin{cases}x,&x\\\\ge 0\\\\\\\\-x,&x<0\\\\end{cases}',\n 'f(n)=\\\\begin{cases}1,&n=0\\\\\\\\n\\\\cdot f(n-1),&n>0\\\\end{cases}',\n '\\\\begin{cases}x+y=2\\\\\\\\2x-y=1\\\\end{cases}',\n 'D(x)=\\\\begin{cases}1,&x\\\\in\\\\mathbb{Q}\\\\\\\\0,&x\\\\notin\\\\mathbb{Q}\\\\end{cases}',\n '\\\\begin{cases}a_1x_1+b_1x_2=c_1\\\\\\\\a_2x_1+b_2x_2=c_2\\\\end{cases}',\n 'g(x)=\\\\begin{cases}\\\\sin x,&x>0\\\\\\\\\\\\cos x,&x\\\\le 0\\\\end{cases}',\n '\\\\begin{cases}\\\\dfrac{\\\\partial u}{\\\\partial t}=k\\\\dfrac{\\\\partial^2 u}{\\\\partial x^2}\\\\\\\\u(0,t)=0\\\\end{cases}',\n '\\\\max(0,x)=\\\\begin{cases}0,&x<0\\\\\\\\x,&x\\\\ge 0\\\\end{cases}',\n '\\\\begin{cases}v=v_0+at\\\\\\\\s=v_0t+\\\\frac{1}{2}at^2\\\\end{cases}',\n '\\\\begin{cases}\\\\text{\u53CA\u683C},&s\\\\ge 60\\\\\\\\\\\\text{\u4E0D\u53CA\u683C},&s<60\\\\end{cases}',\n '\\\\begin{cases}1,&\\\\text{\u82E5 }x=y\\\\\\\\0,&\\\\text{\u5426\u5219}\\\\end{cases}',\n '\\\\begin{cases}x\\\\equiv 1\\\\pmod{3}\\\\\\\\x\\\\equiv 2\\\\pmod{5}\\\\end{cases}',\n '\\\\begin{cases}\\\\dot{x}=y\\\\\\\\\\\\dot{y}=-x\\\\end{cases}',\n 'F(n)=\\\\begin{cases}0,&n=1\\\\\\\\F(n-1)+F(n-2),&n>1\\\\end{cases}',\n '\\\\left\\\\{\\\\begin{aligned}x&=2t\\\\\\\\y&=t^2\\\\end{aligned}\\\\right.',\n];\n\nconst FRAC_SQRT = [\n '\\\\frac{1}{2}',\n '\\\\frac{\\\\sqrt{2}}{2}',\n '\\\\frac{1}{1+\\\\frac{1}{1+\\\\frac{1}{x}}}',\n '\\\\sqrt{a^2+b^2}',\n '\\\\sqrt[3]{27}=3',\n '\\\\sqrt[3]{\\\\frac{a}{b}}',\n '\\\\frac{\\\\partial^2 u}{\\\\partial x\\\\,\\\\partial y}',\n '\\\\sqrt{\\\\frac{\\\\sigma^2}{n}}=\\\\frac{\\\\sigma}{\\\\sqrt{n}}',\n '\\\\cfrac{1}{2+\\\\cfrac{1}{3+\\\\cfrac{1}{4}}}',\n '\\\\binom{n}{k}=\\\\frac{n!}{k!(n-k)!}',\n '\\\\frac{dy}{dx}=\\\\frac{dy}{du}\\\\cdot\\\\frac{du}{dx}',\n '\\\\sqrt{2+\\\\sqrt{3}}\\\\approx\\\\frac{1+\\\\sqrt{3}}{2}',\n '\\\\frac{-b\\\\pm\\\\sqrt{b^2-4ac}}{2a}',\n '\\\\sqrt[n]{x^n}=|x|',\n '\\\\frac{1}{\\\\sqrt{2}}\\\\cdot\\\\frac{\\\\sqrt{2}}{\\\\sqrt{2}}=\\\\frac{\\\\sqrt{2}}{2}',\n];\n\nconst INTEGRAL = [\n '\\\\int_0^1 x^2\\\\,dx',\n '\\\\int_{-\\\\infty}^{+\\\\infty}e^{-x^2}dx=\\\\sqrt{\\\\pi}',\n '\\\\sum_{i=1}^{n}i=\\\\frac{n(n+1)}{2}',\n '\\\\lim_{n\\\\to\\\\infty}\\\\left(1+\\\\frac{1}{n}\\\\right)^n=e',\n '\\\\prod_{k=1}^{n}k=n!',\n '\\\\iint_D f(x,y)\\\\,dA',\n '\\\\oint_C \\\\vec{F}\\\\cdot d\\\\vec{r}',\n '\\\\sum_{k=0}^{\\\\infty}\\\\frac{x^k}{k!}=e^x',\n '\\\\int_a^b f\\'(x)dx=f(b)-f(a)',\n '\\\\lim_{x\\\\to 0}\\\\frac{\\\\sin x}{x}=1',\n '\\\\sum_{n=1}^{\\\\infty}\\\\frac{1}{n^2}=\\\\frac{\\\\pi^2}{6}',\n '\\\\int\\\\frac{dx}{1+x^2}=\\\\arctan x+C',\n '\\\\limsup_{n\\\\to\\\\infty}a_n,\\\\ \\\\bigcup_{n=1}^{\\\\infty}A_n',\n '\\\\int_0^{\\\\pi}\\\\sin^2 x\\\\,dx=\\\\frac{\\\\pi}{2}',\n '\\\\frac{d}{dx}\\\\int_0^x f(t)\\\\,dt=f(x)',\n];\n\nconst SCRIPT = [\n 'a^2+b^2=c^2',\n 'x_1+x_2+\\\\cdots+x_n',\n 'e^{i\\\\pi}+1=0',\n 'a_{ij}^{(k)}',\n 'x^{y^{z}}',\n '\\\\dot{x}+\\\\ddot{y}',\n '\\\\hat{y}=\\\\bar{x}+\\\\tilde{\\\\theta}',\n '\\\\vec{v}\\\\times\\\\vec{w}',\n \"f'(x)=nx^{n-1}\",\n '\\\\max_{x\\\\in S}f(x)',\n '\\\\arg\\\\min_{\\\\theta}\\\\|X\\\\theta-y\\\\|^2',\n '\\\\text{Si}_2(x)=\\\\int_0^x\\\\frac{\\\\sin t}{t}dt',\n '\\\\aleph_0+\\\\aleph_1',\n 'x_i\\'=\\\\lambda_i x_i',\n '2^{10}=1024',\n];\n\nconst GREEK = [\n '\\\\alpha+\\\\beta=\\\\gamma',\n '\\\\Gamma(n+1)=n!',\n '\\\\Delta=\\\\sqrt{b^2-4ac}',\n '\\\\nabla\\\\cdot\\\\vec{E}=\\\\frac{\\\\rho}{\\\\varepsilon_0}',\n '\\\\varphi+\\\\phi=2\\\\phi_0',\n '\\\\sigma=\\\\sqrt{\\\\frac{1}{N}\\\\sum(x_i-\\\\mu)^2}',\n '\\\\forall\\\\epsilon>0,\\\\ \\\\exists\\\\delta>0',\n '\\\\lambda,\\\\mu,\\\\nu,\\\\xi,\\\\pi,\\\\rho',\n '\\\\Omega=\\\\{\\\\omega_1,\\\\omega_2,\\\\ldots\\\\}',\n '\\\\hbar=\\\\frac{h}{2\\\\pi}',\n '\\\\theta\\\\in[0,2\\\\pi)',\n '\\\\zeta(s)=\\\\sum_{n=1}^{\\\\infty}\\\\frac{1}{n^s}',\n '\\\\partial\\\\Omega=\\\\emptyset',\n '\\\\top,\\\\ \\\\bot,\\\\ \\\\vdash,\\\\ \\\\models',\n '\\\\ell^2=\\\\left\\\\{\\\\sum|x_k|^2<\\\\infty\\\\right\\\\}',\n];\n\nconst CHEM = [\n '\\\\ce{CO2 + C -> 2 CO}',\n '\\\\ce{2H2 + O2 -> 2H2O}',\n '\\\\ce{N2 + 3H2 <=> 2NH3}',\n '\\\\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O}',\n '\\\\ce{Fe^3+ + 3OH- -> Fe(OH)3 v}',\n '\\\\ce{CaCO3 ->[\u9AD8\u6E29] CaO + CO2 ^}',\n '\\\\ce{Zn + Cu^2+ -> Zn^2+ + Cu}',\n '\\\\ce{CH3COOH <=> CH3COO- + H+}',\n '\\\\ce{^{227}_{90}Th+}',\n '\\\\ce{SO4^2- + Ba^2+ -> BaSO4 v}',\n '\\\\ce{CH3CH2OH ->[\u6D53H2SO4][170^\\\\circ C] CH2=CH2 + H2O}',\n '\\\\ce{2Na + 2H2O -> 2NaOH + H2 ^}',\n '\\\\ce{CH4 + 2O2 ->[\u70B9\u71C3] CO2 + 2H2O}',\n '\\\\ce{Zn | Zn^{2+}(aq) || Cu^{2+}(aq) | Cu}',\n '\\\\ce{AgCl(s) <=> Ag+(aq) + Cl-(aq)}',\n];\n\nconst GROUPS: Array<[MpSampleCategory, boolean, string[]]> = [\n ['matrix', true, MATRIX],\n ['cases', true, CASES],\n ['frac-sqrt', false, FRAC_SQRT],\n ['integral', true, INTEGRAL],\n ['script', false, SCRIPT],\n ['greek', false, GREEK],\n ['chem', true, CHEM],\n];\n\n/** \u5168\u91CF\u6837\u672C\uFF1A7 \u7C7B \u00D7 15 = 105 \u6761\uFF0C\u6309\u7EC4\u987A\u5E8F\u7EDF\u4E00\u7F16\u53F7\uFF08#001\u2013#105\uFF09 */\nexport const MP_SAMPLES: MpSample[] = GROUPS.flatMap(([category, display, list]) =>\n list.map((latex) => ({ category, latex, display })),\n).map((sample, index) => ({ ...sample, id: index + 1 }));\n", "/**\n * graph_plot \u5DE5\u5177\uFF1A\u76F4\u8C03 @studylab/viz/plot-svg \u672C\u5730\u6E32\u67D3\uFF08\u4E0D\u7ECF\u7F51\u7EDC\uFF0C\u5EF6\u8FDF <50ms SVG / <100ms PNG\uFF09\u3002\n * \u8F93\u5165 schema \u7531 registerTool \u7684 zod inputSchema \u6821\u9A8C\uFF1B\n * handler \u5185 SVG/PNG \u751F\u6210\u540C\u6B65\u5B8C\u6210\uFF0C\u6240\u6709\u5931\u8D25\u5C31\u5730 isError\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport { Resvg } from '@resvg/resvg-js';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n renderPlotSvg,\n EXPRESSION_RAW_MAX_LENGTH,\n EXPRESSIONS_MAX,\n SIZE_MAX,\n} from '@studylab/viz/plot-svg';\nimport type { PlotTheme } from '@studylab/viz/plot-svg';\n\nexport interface GraphPlotArgs {\n expressions: { raw: string; label?: string; color?: string }[];\n viewport?: { xMin: number; xMax: number; yMin: number; yMax: number };\n size?: { width: number; height: number };\n format?: 'svg' | 'png';\n theme?: PlotTheme;\n grid?: boolean;\n axis?: boolean;\n}\n\nfunction textResult(text: string): CallToolResult {\n return { content: [{ type: 'text', text }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nexport async function handleGraphPlot(args: GraphPlotArgs): Promise<CallToolResult> {\n try {\n const result = renderPlotSvg({\n expressions: args.expressions,\n viewport: args.viewport,\n size: args.size,\n theme: args.theme ?? 'light',\n grid: args.grid,\n axis: args.axis,\n });\n\n const okList = result.expressions.filter((e) => e.ok);\n const failList = result.expressions.filter((e) => !e.ok);\n const summary = `\u5171 ${result.expressions.length} \u6761\u66F2\u7EBF\uFF1A${okList.length} \u6210\u529F\uFF08${okList.map((e) => `${e.raw} ${e.segments ?? 1} \u6BB5`).join('\uFF1B')}\uFF09\uFF1B${failList.length} \u5931\u8D25\uFF08${failList.map((e) => `${e.raw}: ${e.error}`).join('\uFF1B')}\uFF09`;\n\n if (args.format === 'png') {\n const png = new Resvg(result.svg).render().asPng();\n const payload = {\n format: 'png',\n mime: 'image/png',\n dataBase64: png.toString('base64'),\n width: args.size?.width ?? 800,\n height: args.size?.height ?? 500,\n summary,\n timing: result.timing,\n };\n return textResult(JSON.stringify(payload));\n }\n\n const payload = {\n format: 'svg',\n mime: 'image/svg+xml',\n svg: result.svg,\n width: args.size?.width ?? 800,\n height: args.size?.height ?? 500,\n summary,\n timing: result.timing,\n };\n return textResult(JSON.stringify(payload));\n } catch (err) {\n if (err instanceof Error && /\u8868\u8FBE\u5F0F|compile|\u6240\u6709\u8868\u8FBE\u5F0F\u5747\u65E0\u6CD5\u7F16\u8BD1/.test(err.message)) {\n return errorResult(`\u8868\u8FBE\u5F0F\u7F16\u8BD1\u5931\u8D25\uFF1A${err.message}\uFF08\u8BF7\u68C0\u67E5 mathjs \u8BED\u6CD5\uFF0C\u5982 sin(x)\u3001x^2\u3001sqrt(x)\uFF09`);\n }\n return errorResult(err instanceof Error ? err.message : String(err));\n }\n}\n\nexport function registerGraphPlotTool(server: McpServer): void {\n server.registerTool(\n 'graph_plot',\n {\n title: '\u51FD\u6570\u56FE\u50CF\u7ED8\u5236',\n description: '\u5C06 mathjs \u6570\u5B66\u8868\u8FBE\u5F0F\u6E32\u67D3\u4E3A SVG \u6216 PNG \u56FE\u50CF\uFF08\u53EF\u9009\u4E3B\u9898\u3001\u89C6\u53E3\u3001\u7F51\u683C\u3001\u8F74\uFF09',\n inputSchema: {\n expressions: z.array(\n z.object({\n raw: z.string().max(EXPRESSION_RAW_MAX_LENGTH),\n label: z.string().optional(),\n color: z.string().optional(),\n }),\n ).min(1).max(EXPRESSIONS_MAX),\n viewport: z.object({\n xMin: z.number(),\n xMax: z.number(),\n yMin: z.number(),\n yMax: z.number(),\n }).optional(),\n size: z.object({\n width: z.number().min(1).max(SIZE_MAX),\n height: z.number().min(1).max(SIZE_MAX),\n }).optional(),\n format: z.enum(['svg', 'png']).default('svg'),\n theme: z.enum(['light', 'dark']).default('light'),\n grid: z.boolean().default(true),\n axis: z.boolean().default(true),\n },\n },\n (args) => handleGraphPlot(args),\n );\n}\n", "/**\n * \u4E00\u5143\u6570\u503C\u5206\u6790\u5185\u6838\uFF08FR-VIZ P2\uFF09\uFF1A\u53D6\u503C\u3001\u5BFC\u6570\u3001\u5B9A\u79EF\u5206\u3001\u6C42\u6839\u3001\u4EA4\u70B9\u3002\n * \u7EAF mathjs + \u6570\u503C\u65B9\u6CD5\uFF0C\u4E0D\u4F9D\u8D56 React/\u5F15\u64CE\uFF0C\u53EF\u5355\u6D4B\u3001\u53EF\u88AB\u670D\u52A1\u7AEF\u590D\u7528\u3002\n * - \u5BFC\u6570\u4F18\u5148 mathjs \u7B26\u53F7\u6C42\u5BFC\uFF0C\u4E0D\u53EF\u5FAE\u9000\u56DE\u4E2D\u5FC3\u5DEE\u5206\u3002\n * - \u79EF\u5206\u7528\u81EA\u9002\u5E94 Simpson\uFF1B\u65E0\u7A77\u9650\u51E0\u4F55\u6269\u5C55\u622A\u65AD\u5E76\u663E\u5F0F\u6807\u8BB0\u3002\n * - \u6C42\u6839\u7528\u7B26\u53F7\u626B\u63CF + \u4E8C\u5206\uFF1B\u6781\u70B9\uFF08\u4E2D\u70B9\u975E\u6709\u9650\u503C\uFF09\u533A\u95F4\u62D2\u7EDD\uFF0C1/x\u3001tan \u4E0D\u4EA7\u751F\u5047\u6839\u3002\n */\nimport { derivative, parse } from 'mathjs';\nimport { buildScope, compileExpression, makePointEvaluator } from './compile.js';\nimport type { ParameterSpec } from './types.js';\n\nexport type UnivariateFn = (x: number) => number;\n\nconst DEFAULT_TOL = 1e-7;\nconst ROOT_SCAN_SEGMENTS = 1000;\nconst ROOT_BISECT_ITERS = 60;\nconst SIMPSON_MAX_DEPTH = 20;\n/** \u65E0\u7A77\u9650\u622A\u65AD\u4E0A\u9650\uFF08\u51E0\u4F55\u6269\u5C55 1,2,4\u2026 \u5230\u7EA6 16384\uFF09 */\nconst INFINITE_CUTOFF = 2e4;\n\n/** \u7F16\u8BD1\u663E\u5F0F\u65B9\u7A0B y=f(x) \u4E3A NaN \u5B89\u5168\u7684\u4E00\u5143\u51FD\u6570\uFF1B\u8BED\u6CD5\u9519\u629B ExpressionError */\nexport function makeUnivariate(raw: string, parameters: ParameterSpec[] = []): UnivariateFn {\n const point = makePointEvaluator(compileExpression(raw), buildScope(parameters));\n return (x: number) => point({ x });\n}\n\n/** \u70B9\u6C42\u503C\uFF1B\u5B9A\u4E49\u57DF\u5916\u8FD4\u56DE NaN\uFF08\u4E0D\u629B\u5F02\u5E38\uFF09 */\nexport function evaluateAt(raw: string, x: number, parameters: ParameterSpec[] = []): number {\n return makeUnivariate(raw, parameters)(x);\n}\n\n/**\n * \u5BFC\u6570\uFF1A\u5148\u5C1D\u8BD5 mathjs \u7B26\u53F7\u5BFC\u6570\uFF08\u7CBE\u786E\uFF09\uFF0C\u5931\u8D25\u9000\u56DE\u81EA\u9002\u5E94\u4E2D\u5FC3\u5DEE\u5206\u3002\n * h = \u221Aeps\u00B7max(1,|x|) \u5E73\u8861\u622A\u65AD\u8BEF\u5DEE\u4E0E\u820D\u5165\u8BEF\u5DEE\u3002\n */\nexport function derivativeAt(raw: string, x: number, parameters: ParameterSpec[] = []): number {\n try {\n const derivativeNode = derivative(parse(raw), 'x');\n const point = makePointEvaluator(\n { evaluate: (scope) => derivativeNode.compile().evaluate(scope) },\n buildScope(parameters),\n );\n const value = point({ x });\n if (Number.isFinite(value)) return value;\n } catch {\n // \u7B26\u53F7\u5BFC\u6570\u4E0D\u652F\u6301\u8BE5\u8868\u8FBE\u5F0F\uFF0C\u8D70\u6570\u503C\u5DEE\u5206\n }\n const f = makeUnivariate(raw, parameters);\n const h = Math.sqrt(Number.EPSILON) * Math.max(1, Math.abs(x));\n const y1 = f(x + h);\n const y0 = f(x - h);\n if (![y1, y0].every(Number.isFinite)) return NaN;\n return (y1 - y0) / (2 * h);\n}\n\nfunction adaptiveSimpsonRecursive(\n f: UnivariateFn,\n a: number,\n b: number,\n fa: number,\n fm: number,\n fb: number,\n whole: number,\n tol: number,\n depth: number,\n): number {\n const m = (a + b) / 2;\n const lm = (a + m) / 2;\n const rm = (m + b) / 2;\n const flm = f(lm);\n const frm = f(rm);\n if (![flm, frm].every(Number.isFinite)) return NaN;\n const left = ((m - a) / 6) * (fa + 4 * flm + fm);\n const right = ((b - m) / 6) * (fm + 4 * frm + fb);\n if (depth <= 0 || Math.abs(left + right - whole) <= 15 * tol) {\n return left + right + (left + right - whole) / 15;\n }\n return (\n adaptiveSimpsonRecursive(f, a, m, fa, flm, fm, left, tol / 2, depth - 1)\n + adaptiveSimpsonRecursive(f, m, b, fm, frm, fb, right, tol / 2, depth - 1)\n );\n}\n\n/** \u81EA\u9002\u5E94 Simpson \u6C42\u79EF\uFF1B\u533A\u95F4\u5185\u542B\u5947\u70B9\uFF08\u5982 1/x \u8DE8 0\uFF09\u8FD4\u56DE NaN */\nexport function integrate(f: UnivariateFn, a: number, b: number, tol: number = DEFAULT_TOL): number {\n if (!(Number.isFinite(a) && Number.isFinite(b))) return NaN;\n if (a === b) return 0;\n if (a > b) return -integrate(f, b, a, tol);\n const fa = f(a);\n const fm = f((a + b) / 2);\n const fb = f(b);\n if (![fa, fm, fb].every(Number.isFinite)) return NaN;\n return adaptiveSimpsonRecursive(\n f, a, b, fa, fm, fb,\n ((b - a) / 6) * (fa + 4 * fm + fb),\n tol,\n SIMPSON_MAX_DEPTH,\n );\n}\n\nexport interface InfiniteIntegralResult {\n value: number;\n /** \u662F\u5426\u4F7F\u7528\u4E86\u65E0\u7A77\u9650\u622A\u65AD\uFF08UI \u9700\u63D0\u793A\uFF09 */\n truncated: boolean;\n /** \u622A\u65AD\u8FD1\u4F3C\u65F6\u5B9E\u9645\u79EF\u5206\u5230\u7684\u6709\u9650\u533A\u95F4\uFF08\u753B\u677F\u9AD8\u4EAE\u7528\uFF09\uFF1B\u6709\u9650\u8FB9\u754C\u4E3A null */\n renderInterval: [number, number] | null;\n}\n\n/** \u5355\u4FA7\u5E7F\u4E49\u79EF\u5206\uFF1A\u4ECE\u6709\u9650\u7AEF\u5411\u65E0\u7A77\u65B9\u5411\u51E0\u4F55\u6269\u5C55\uFF0C\u8FD4\u56DE\u79EF\u5206\u503C\u4E0E\u622A\u65AD\u7AEF\u70B9 */\nfunction integrateInfiniteSide(\n f: UnivariateFn,\n finiteEnd: number,\n direction: 1 | -1,\n tol: number,\n): { value: number; end: number } {\n let sum = 0;\n let inner = finiteEnd;\n let width = 1;\n for (let i = 0; i < 60; i += 1) {\n const outer = inner + direction * width;\n const piece = integrate(f, Math.min(inner, outer), Math.max(inner, outer), tol);\n if (!Number.isFinite(piece)) return { value: NaN, end: NaN };\n sum += piece;\n if (Math.abs(piece) < tol * Math.max(1, Math.abs(sum))) return { value: sum, end: outer };\n if (Math.abs(outer - finiteEnd) > INFINITE_CUTOFF) return { value: NaN, end: NaN }; // \u4E0D\u6536\u655B\n inner = outer;\n width *= 2;\n }\n return { value: NaN, end: NaN };\n}\n\n/**\n * \u5B9A\u79EF\u5206\uFF0C\u652F\u6301 \u00B1\u221E \u8FB9\u754C\uFF08\u6309\u622A\u65AD\u8FD1\u4F3C\uFF0Ctruncated=true\uFF09\u3002\n * \u53CC\u4FA7\u65E0\u7A77\u5728 0 \u5904\u62C6\u5206\uFF1B\u53D1\u6563\u6216\u542B\u5947\u70B9 value \u4E3A NaN\u3002\n */\nexport function integrateExpression(\n raw: string,\n a: number,\n b: number,\n parameters: ParameterSpec[] = [],\n): InfiniteIntegralResult {\n const f = makeUnivariate(raw, parameters);\n if (Number.isFinite(a) && Number.isFinite(b)) {\n return { value: integrate(f, a, b), truncated: false, renderInterval: null };\n }\n let value = NaN;\n let renderInterval: [number, number] | null = null;\n if (a === Number.NEGATIVE_INFINITY && b === Number.POSITIVE_INFINITY) {\n // \u5728 0 \u5904\u62C6\u6210\u4E24\u4E2A\u5355\u4FA7\u5E7F\u4E49\u79EF\u5206\n const left = integrateInfiniteSide(f, 0, -1, DEFAULT_TOL);\n const right = integrateInfiniteSide(f, 0, 1, DEFAULT_TOL);\n if (Number.isFinite(left.value) && Number.isFinite(right.value)) {\n value = left.value + right.value;\n renderInterval = [left.end, right.end];\n }\n } else if (a === Number.NEGATIVE_INFINITY && Number.isFinite(b)) {\n const side = integrateInfiniteSide(f, b, -1, DEFAULT_TOL);\n if (Number.isFinite(side.value)) {\n value = side.value;\n renderInterval = [side.end, b];\n }\n } else if (Number.isFinite(a) && b === Number.POSITIVE_INFINITY) {\n const side = integrateInfiniteSide(f, a, 1, DEFAULT_TOL);\n if (Number.isFinite(side.value)) {\n value = side.value;\n renderInterval = [a, side.end];\n }\n }\n return { value, truncated: true, renderInterval };\n}\n\n/** \u533A\u95F4\u5185\u4E8C\u5206\u6C42\u6839\uFF1B\u4E2D\u70B9\u843D\u5230\u975E\u6709\u9650\u503C\uFF08\u6781\u70B9\uFF09\u5219\u653E\u5F03\u8BE5\u533A\u95F4 */\nfunction bisect(f: UnivariateFn, a: number, b: number): number | null {\n let lo = a;\n let hi = b;\n let flo = f(lo);\n let fhi = f(hi);\n if (![flo, fhi].every(Number.isFinite)) return null;\n if (flo === 0) return lo;\n if (fhi === 0) return hi;\n for (let i = 0; i < ROOT_BISECT_ITERS; i += 1) {\n const mid = (lo + hi) / 2;\n const fm = f(mid);\n if (!Number.isFinite(fm)) return null; // \u8DE8\u8FC7\u6781\u70B9\uFF081/x\u3001tan\uFF09\uFF0C\u4E0D\u662F\u6839\n if (fm === 0) return mid;\n if (hi - lo < 1e-12) {\n // \u6D6E\u70B9\u6781\u70B9\uFF08tan(3\u03C0/2)\u22481.6e16\uFF09\u4E5F\u4F1A\u8BA9\u533A\u95F4\u6536\u7F29\uFF0C\u5FC5\u987B\u786E\u8BA4\u51FD\u6570\u503C\u786E\u5B9E\u63A5\u8FD1\u96F6\n return Math.abs(fm) < 1e-6 ? mid : null;\n }\n if (flo * fm <= 0) { hi = mid; fhi = fm; }\n else { lo = mid; flo = fm; }\n }\n const root = (lo + hi) / 2;\n return Math.abs(f(root)) < 1e-6 ? root : null;\n}\n\n/**\n * \u5728 [xMin,xMax] \u5185\u6C42\u5168\u90E8\u5B9E\u6839\uFF1A\u7B49\u8DDD\u7B26\u53F7\u626B\u63CF + \u4E8C\u5206\uFF0C\u7ED3\u679C\u53BB\u91CD\u6392\u5E8F\u3002\n * \u91C7\u6837\u70B9\u6070\u597D\u843D\u5728\u6839\u4E0A\u65F6\u4E5F\u4F1A\u6355\u83B7\u3002\n */\nexport function findRoots(f: UnivariateFn, xMin: number, xMax: number): number[] {\n if (!(xMax > xMin)) return [];\n const step = (xMax - xMin) / ROOT_SCAN_SEGMENTS;\n const roots: number[] = [];\n // \u8FB9\u754C\u6839\uFF1A\u6D6E\u70B9\u4E0B sin(\u03C0)\u22481.2e-16 \u975E\u96F6\uFF0C\u7B26\u53F7\u626B\u63CF\u770B\u4E0D\u5230\u8D34\u8FB9\u7684\u6839\n if (Math.abs(f(xMin)) < 1e-8) roots.push(xMin);\n let prevX = xMin;\n let prevY = f(prevX);\n for (let i = 1; i <= ROOT_SCAN_SEGMENTS; i += 1) {\n const x = xMin + i * step;\n const y = f(x);\n if (Number.isFinite(prevY) && Number.isFinite(y)) {\n if (prevY === 0) roots.push(prevX);\n else if (prevY * y < 0) {\n const root = bisect(f, prevX, x);\n if (root !== null) roots.push(root);\n }\n }\n prevX = x;\n prevY = y;\n }\n if (prevY === 0 || Math.abs(prevY) < 1e-8) roots.push(prevX);\n\n roots.sort((p, q) => p - q);\n const deduped: number[] = [];\n for (const root of roots) {\n const last = deduped[deduped.length - 1];\n if (last === undefined || Math.abs(root - last) > 1e-6 * Math.max(1, Math.abs(last))) {\n deduped.push(root);\n }\n }\n return deduped;\n}\n\n/** \u6C42\u4E24\u663E\u5F0F\u66F2\u7EBF\u4EA4\u70B9\uFF1A\u5BF9 f1\u2212f2 \u6C42\u6839\uFF0C\u8FD4\u56DE\u66F2\u7EBF\u4E0A\u7684 (x,y) */\nexport function findIntersections(\n raw1: string,\n raw2: string,\n xMin: number,\n xMax: number,\n parameters: ParameterSpec[] = [],\n): Array<{ x: number; y: number }> {\n const f1 = makeUnivariate(raw1, parameters);\n const f2 = makeUnivariate(raw2, parameters);\n const roots = findRoots((x) => f1(x) - f2(x), xMin, xMax);\n return roots\n .map((x) => ({ x, y: f1(x) }))\n .filter((point) => Number.isFinite(point.y));\n}\n", "/**\n * mathjs \u7F16\u8BD1\u5C42\uFF1A\u552F\u4E00\u8868\u8FBE\u5F0F\u5185\u6838\uFF08FR-VIZ-01\uFF09\u3002\n * - parse/compile \u751F\u6210\u7EAF\u51FD\u6570\uFF0C\u7981\u6B62 eval/Function\u3002\n * - \u9519\u8BEF\u4E00\u5F8B\u5F52\u4E00\u5316\u4E3A ExpressionError\uFF08\u8BED\u6CD5\u9519 / \u672A\u5B9A\u4E49\u7B26\u53F7 / \u5B9A\u4E49\u57DF\uFF09\uFF0CUI \u4E0D\u4E2D\u65AD\u8F93\u5165\u3002\n * - \u9010\u70B9\u91C7\u6837\u9519\u8BEF\uFF08\u5947\u70B9/\u8D8A\u754C/\u590D\u6570\uFF09\u4E00\u5F8B\u8FD4\u56DE NaN\uFF0C\u7531\u5F15\u64CE\u65AD\u7F1D\uFF0C\u7EDD\u4E0D\u753B\u7A7F\u9876\u7EBF\u3002\n */\nimport { parse, gamma } from 'mathjs';\nimport type { ParameterSpec } from './types.js';\n\nexport type ExpressionErrorKind = 'syntax' | 'undefined-symbol' | 'domain';\n\nexport class ExpressionError extends Error {\n readonly kind: ExpressionErrorKind;\n constructor(kind: ExpressionErrorKind, message: string) {\n super(message);\n this.name = 'ExpressionError';\n this.kind = kind;\n }\n}\n\n/**\n * \u9519\u8BEF\u8868\u5185\u5BB9\u7B7E\u540D\uFF08id:kind:message \u6309 id \u6392\u5E8F\u62FC\u63A5\uFF09\u3002\n * \u53C2\u6570\u52A8\u753B\u6BCF\u5E27\u91CD\u5EFA errors \u5BF9\u8C61\uFF0C\u5185\u5BB9\u672A\u53D8\u65F6\u56DE\u8C03\u4F1A\u8BA9\u9875\u9762 setState \u51FA\u65B0\u5BF9\u8C61\uFF0C\n * \u5728 passive effect\uFF08\u63D0\u4EA4\u9636\u6BB5\uFF09\u5185\u7D2F\u8BA1\u5D4C\u5957\u66F4\u65B0\u89E6\u53D1 React\u300CMaximum update depth exceeded\u300D\uFF1B\n * \u9002\u914D\u5668\u636E\u6B64\u53BB\u91CD\uFF0C\u4EC5\u7B7E\u540D\u53D8\u5316\u65F6\u56DE\u8C03\u3002\n */\nexport function expressionErrorsSignature(errors: Record<string, ExpressionError>): string {\n return Object.keys(errors)\n .sort()\n .map((id) => `${id}:${errors[id]!.kind}:${errors[id]!.message}`)\n .join('|');\n}\n\nexport interface CompiledExpression {\n evaluate(scope: Record<string, unknown>): unknown;\n}\n\nexport function compileExpression(raw: string): CompiledExpression {\n let node;\n try {\n node = parse(raw);\n } catch (err) {\n throw new ExpressionError('syntax', normalizeMessage(err));\n }\n const compiled = node.compile();\n return {\n evaluate(scope: Record<string, unknown>): unknown {\n try {\n return compiled.evaluate(scope);\n } catch (err) {\n throw normalizeThrown(err);\n }\n },\n };\n}\n\nexport type ValidationResult = { ok: true } | { ok: false; error: ExpressionError };\n\n/** mathjs \u5185\u7F6E\u51FD\u6570\u540D\uFF1A\u88AB\u5F53\u7B26\u53F7\u4F7F\u7528\uFF08sin*x\u3001\u88F8 sin\uFF09\u65F6 mathjs \u4F1A\u9759\u9ED8\u56DE\u9000\u89E3\u6790\uFF0C\u9700\u663E\u5F0F\u62E6\u622A */\nconst FUNCTION_LIKE_SYMBOLS = new Set([\n 'sin', 'cos', 'tan', 'cot', 'sec', 'csc',\n 'asin', 'acos', 'atan', 'acot', 'acsc', 'asec',\n 'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch',\n 'asinh', 'acosh', 'atanh', 'acoth', 'acsch', 'asech',\n 'log', 'log2', 'log10', 'exp', 'sqrt', 'abs',\n 'max', 'min', 'floor', 'ceil', 'round', 'sign',\n]);\n\n/**\n * \u8F93\u5165\u6846\u5B9E\u65F6\u6821\u9A8C\uFF1A\u8BED\u6CD5\u5FC5\u987B\u901A\u8FC7\uFF0C\u5E76\u7528\u5B89\u5168\u63A2\u9488\u6C42\u503C\u53D1\u73B0\u672A\u5B9A\u4E49\u7B26\u53F7\u3002\n * \u5B9A\u4E49\u57DF\u9519\u8BEF\uFF08\u5982\u67D0\u70B9\u9664\u96F6\u3001\u8D1F\u6839\u5F0F\uFF09\u4E0D\u5728\u8FD9\u91CC\u62E6\u622A\u2014\u2014\u4EA4\u7ED9\u753B\u5E03\u9010\u70B9\u6C42\u503C\u5904\u7406\u3002\n */\nexport function validateExpression(raw: string): ValidationResult {\n let node;\n try {\n node = parse(raw);\n } catch (err) {\n return { ok: false, error: new ExpressionError('syntax', normalizeMessage(err)) };\n }\n // \u51FD\u6570\u540D\u88AB\u5F53\u7B26\u53F7\uFF08\\sin x \u6F0F\u7F51\u8F6C\u6362\u6210 sin*x\uFF09\uFF1A\u63A2\u9488\u6C42\u503C\u4E0D\u62A5\u9519\u4F46\u9010\u70B9\u7ED3\u679C\u5168 NaN\uFF0C\n // \u66F2\u7EBF\u9759\u9ED8\u6D88\u5931\u3002\u8FD9\u91CC\u5728 AST \u5C42\u62E6\u622A\uFF08\u51FD\u6570\u8C03\u7528\u4F4D\u7F6E fn \u7684 SymbolNode \u9664\u5916\uFF09\u3002\n let functionLike: string | null = null;\n node.traverse((n: unknown, _path?: string, parent?: unknown) => {\n if (functionLike) return;\n const node = n as { isSymbolNode?: boolean; name?: string };\n const parentNode = parent as { isFunctionNode?: boolean; fn?: unknown } | undefined;\n if (!node.isSymbolNode) return;\n if (parentNode?.isFunctionNode && parentNode.fn === n) return;\n if (node.name && FUNCTION_LIKE_SYMBOLS.has(node.name)) functionLike = node.name;\n });\n if (functionLike) {\n const name = functionLike as string;\n return {\n ok: false,\n error: new ExpressionError('undefined-symbol', `\u51FD\u6570 ${name} \u9700\u5199\u6210 ${name}(x) \u5F62\u5F0F\u8C03\u7528`),\n };\n }\n try {\n const probe: Record<string, unknown> = {\n ...buildScope(),\n x: 1, y: 1, t: 1, theta: 1, r: 2,\n a: 1, b: 1, c: 1, n: 2, k: 1, x1: 1, x2: 2,\n };\n node.compile().evaluate(probe);\n } catch (err) {\n const normalized = normalizeThrown(err);\n if (normalized.kind === 'undefined-symbol') return { ok: false, error: normalized };\n }\n return { ok: true };\n}\n\n/** mathjs AST \u2192 LaTeX\uFF08\u539F\u6587\u6A21\u5F0F\u5B9E\u65F6\u9884\u89C8\u7528\uFF09\uFF1B\u89E3\u6790\u5931\u8D25\u8FD4\u56DE null */\nexport function tryToTex(raw: string): string | null {\n try {\n return parse(raw).toTex();\n } catch {\n return null;\n }\n}\n\n/** \u5750\u6807\u53D8\u91CF\u4FDD\u7559\u5B57\uFF1A\u7528\u6237\u53C2\u6570\u4E0D\u5141\u8BB8\u4EE5\u6B64\u547D\u540D\uFF08\u5426\u5219\u8986\u76D6\u91C7\u6837\u5750\u6807\uFF0C\u56FE\u5F62\u9519\u8BEF\uFF09 */\nexport const RESERVED_SCOPE_NAMES = new Set(['x', 'y', 'z', 't', 'theta', 'r']);\n\n/** \u4F5C\u56FE scope\uFF1A\u5750\u6807\u53D8\u91CF\u3001\u6ED1\u6746\u53C2\u6570\u3001\u5E0C\u814A\u5B57\u6BCD\u53D8\u91CF\u4E0E\u4F3D\u9A6C\u51FD\u6570\uFF08\\Gamma\uFF09 */\nexport function buildScope(parameters: ParameterSpec[] = []): Record<string, unknown> {\n const scope: Record<string, unknown> = {\n x: 0, y: 0, z: 0, t: 0, theta: 0, r: 0,\n alpha: 1, beta: 1, gamma: 1, delta: 1, epsilon: 1,\n lambda: 1, mu: 1, sigma: 1, omega: 1,\n // MathLive \u5E0C\u814A\u952E\u76D8\u5C42\u5168\u91CF\u5B57\u6BCD\uFF08\u4E0E latex-to-mathjs CONST_WORDS \u5BF9\u9F50\uFF09\n phi: 1, eta: 1, xi: 1, kappa: 1, nu: 1, rho: 1, tau: 1, upsilon: 1,\n chi: 1, psi: 1, zeta: 1, iota: 1, omicron: 1,\n vartheta: 1, varepsilon: 1, varsigma: 1, varpi: 1, varrho: 1, varkappa: 1, varphi: 1,\n gammaFn: (x: number) => gamma(x),\n };\n // \u4FDD\u7559\u5B57\u4E0E\u975E\u6CD5\u540D\u9759\u9ED8\u5FFD\u7565\uFF08UI \u5C42\u6709\u884C\u5185\u6821\u9A8C\u63D0\u793A\uFF09\uFF0C\u4FDD\u8BC1\u91C7\u6837\u5750\u6807\u4E0D\u88AB\u8986\u76D6\n for (const p of parameters) {\n if (!RESERVED_SCOPE_NAMES.has(p.name)) scope[p.name] = p.value;\n }\n return scope;\n}\n\n/**\n * \u9010\u70B9\u5B89\u5168\u6C42\u503C\uFF1A\u4EFB\u4F55\u5F02\u5E38\u3001\u65E0\u7A77\u3001\u975E\u5B9E\u6570\u7ED3\u679C \u2192 NaN\uFF08\u5F15\u64CE\u9047 NaN \u65AD\u7F1D\uFF09\u3002\n * \u6CE8\u610F\uFF1A\u6BCF\u70B9\u521B\u5EFA scope \u5BF9\u8C61\uFF0C\u57FA\u51C6\u96C6 18 \u677F\u4ECD\u5728 30ms \u5185\uFF1B\u5982 P2 \u6ED1\u6746\u5E27\u7387\u5403\u7D27\u518D\u4F18\u5316\u4E3A\u9884\u5206\u914D\u3002\n */\nexport function makePointEvaluator(expr: CompiledExpression, base: Record<string, unknown>) {\n return (vars: Record<string, number>): number => {\n try {\n const v = expr.evaluate({ ...base, ...vars }) as unknown;\n if (typeof v === 'number') return Number.isFinite(v) ? v : NaN;\n // mathjs \u9047\u8D1F\u6839\u5F0F\u7B49\u8FD4\u56DE Complex / \u5206\u6570\u5BF9\u8C61\uFF1A\u4F5C\u56FE\u53EA\u753B\u5B9E\u6570\u90E8\u5206\n if (v && typeof v === 'object' && 'im' in v && (v as { im: number }).im !== 0) return NaN;\n if (v && typeof v === 'object' && 'valueOf' in v) {\n const n = Number(v.valueOf());\n return Number.isFinite(n) ? n : NaN;\n }\n const n = Number(v);\n return Number.isFinite(n) ? n : NaN;\n } catch {\n return NaN;\n }\n };\n}\n\nfunction normalizeThrown(err: unknown): ExpressionError {\n const message = normalizeMessage(err);\n if (/Undefined symbol|\u672A\u5B9A\u4E49/.test(message)) return new ExpressionError('undefined-symbol', message);\n if (/Complex|domain|\u8303\u56F4|division by zero|\u9664\u6570/i.test(message)) {\n return new ExpressionError('domain', message);\n }\n return new ExpressionError('syntax', message);\n}\n\nfunction normalizeMessage(err: unknown): string {\n const raw = err instanceof Error ? err.message : String(err);\n return (raw.split('\\n')[0] ?? raw).slice(0, 120);\n}\n", "/**\n * \u6570\u636E\u5750\u6807 \u2194 \u50CF\u7D20\u5750\u6807\u7EBF\u6027\u6620\u5C04\uFF08\u65E0 DOM\uFF0C\u7EAF\u51FD\u6570\uFF09\u3002\n * SVG \u7684 y \u8F74\u5411\u4E0B\u589E\u957F\uFF0C\u6545 y2px \u9700\u7FFB\u8F6C\u3002\n */\nexport interface Scale {\n x2px(x: number): number;\n y2px(y: number): number;\n}\n\nexport function createScales(\n xMin: number,\n xMax: number,\n yMin: number,\n yMax: number,\n width: number,\n height: number,\n padding: number,\n): Scale {\n const innerW = Math.max(1, width - padding * 2);\n const innerH = Math.max(1, height - padding * 2);\n const xScale = innerW / (xMax - xMin);\n const yScale = innerH / (yMax - yMin);\n return {\n x2px(x: number): number {\n return padding + (x - xMin) * xScale;\n },\n y2px(y: number): number {\n return height - padding - (y - yMin) * yScale;\n },\n };\n}\n", "/**\n * \u81EA\u9002\u5E94\u91C7\u6837\u5668\uFF1A\u5747\u5300\u7C97\u91C7 \u2192 \u66F2\u7387\u52A0\u5BC6 \u2192 \u6E10\u8FD1\u7EBF\u65AD\u70B9\u3002\n * \u6E10\u8FD1\u7EBF\u7B56\u7565\u6CBF\u7528 jsxgraphAdapter \u5DF2\u9A8C\u8BC1\u7ECF\u9A8C\uFF1A\n * |f(x)| \u8D85\u53EF\u89C1 Y \u57DF 10 \u500D\u7F6E NaN\uFF0C\u8FDE\u7EED \u22652 \u4E2A NaN \u65AD\u5206\u6BB5\uFF08\u907F\u514D\u591A\u9879\u5F0F\u62DF\u5408\u8BEF\u8FDE\uFF09\u3002\n */\nimport type { UnivariateFn } from '../core/numeric.js';\n\nexport type Point2 = readonly [number, number];\n/** \u8FDE\u7EED\u6BB5\uFF1A\u6BCF\u6BB5\u662F Point2 \u6570\u7EC4\uFF0C\u6BB5\u95F4\u65AD\u5F00\u5904\u4E0D\u8FDE\u7EBF */\nexport type Segment = Point2[];\nexport type Segments = Segment[];\n\n/** \u7C97\u91C7\u70B9\u6570 */\nconst COARSE_POINTS = 200;\n/** \u9012\u5F52\u52A0\u5BC6\u6700\u5927\u6DF1\u5EA6\uFF08200 * 2^10 \u5728\u7CBE\u5EA6/\u6027\u80FD\u95F4\u8DB3\u591F\uFF09 */\nconst MAX_REFINE_DEPTH = 10;\n/** \u6E10\u8FD1\u7EBF\u9608\u503C\u500D\u6570 */\nconst ASYMPTOTE_FACTOR = 10;\n/** \u66F2\u7387\u9608\u503C\uFF08\u5F27\u5EA6\uFF09\uFF1A\u76F8\u90BB\u4E24\u7EBF\u6BB5\u7684\u8F6C\u89D2\u8D85\u8FC7\u8BE5\u503C\u65F6\u9012\u5F52\u52A0\u5BC6 */\nconst CURVATURE_RAD_THRESHOLD = 0.05;\n\n/**\n * \u91C7\u6837\u5E76\u8FD4\u56DE\u8FDE\u7EED\u6BB5\u6570\u7EC4\u3002\n * @param fn NaN \u5B89\u5168\u7684\u4E00\u5143\u51FD\u6570\uFF08x \u8D85\u51FA\u5B9A\u4E49\u57DF\u8FD4\u56DE NaN\uFF09\n * @param xMin \u91C7\u6837\u5DE6\u7AEF\u70B9\n * @param xMax \u91C7\u6837\u53F3\u7AEF\u70B9\n * @param yMin \u53EF\u89C1 Y \u6700\u5C0F\u503C\uFF08\u7528\u4E8E\u6E10\u8FD1\u7EBF\u5224\u5B9A\uFF09\n * @param yMax \u53EF\u89C1 Y \u6700\u5927\u503C\uFF08\u7528\u4E8E\u6E10\u8FD1\u7EBF\u5224\u5B9A\uFF09\n */\nexport function adaptiveSample(\n fn: UnivariateFn,\n xMin: number,\n xMax: number,\n yMin: number,\n yMax: number,\n): Segments {\n const yAbsMax = Math.max(Math.abs(yMin), Math.abs(yMax));\n const asymptoteLimit = yAbsMax * ASYMPTOTE_FACTOR;\n const points: Point2[] = [];\n let total = 0;\n\n // \u7C97\u91C7\n for (let i = 0; i <= COARSE_POINTS; i += 1) {\n const x = xMin + ((xMax - xMin) * i) / COARSE_POINTS;\n const y = fn(x);\n points.push([x, sanitizeY(y, asymptoteLimit)]);\n total += 1;\n }\n\n // \u66F2\u7387\u52A0\u5BC6\uFF08\u9012\u5F52\uFF09\uFF1BNaN \u76F8\u90BB\u533A\u95F4\u540C\u6837\u4E8C\u5206\u2014\u2014\u7528\u4E8E\u903C\u8FD1\u6E10\u8FD1\u7EBF/\u5B9A\u4E49\u57DF\u8FB9\u754C\uFF0C\u5236\u9020\u8FDE\u7EED NaN \u89E6\u53D1\u5206\u6BB5\n const refined: Point2[] = [];\n for (let i = 0; i < points.length - 1; i += 1) {\n const p0 = points[i]!;\n const p1 = points[i + 1]!;\n refined.push(p0);\n refineRecursive(p0, p1, fn, asymptoteLimit, 0, refined);\n }\n refined.push(points[points.length - 1]!);\n total = refined.length; // eslint-disable-line prefer-const\n\n // \u5206\u6BB5\uFF1A\u8FDE\u7EED NaN \u22652 \u65AD\u6BB5\uFF1B\u5355\u70B9 NaN \u89C6\u4E3A\u7A7F\u8D8A\u5B9A\u4E49\u57DF\u8FB9\u754C\uFF0C\u4E0D\u5355\u72EC\u65AD\u5F00\n return splitSegments(refined, total);\n}\n\nfunction refineRecursive(\n p0: Point2,\n p1: Point2,\n fn: UnivariateFn,\n asymptoteLimit: number,\n depth: number,\n out: Point2[],\n): void {\n if (depth >= MAX_REFINE_DEPTH) {\n out.push(p1);\n return;\n }\n const mx = (p0[0] + p1[0]) / 2;\n const my = sanitizeY(fn(mx), asymptoteLimit);\n const mid: Point2 = [mx, my];\n\n const p0Ok = Number.isFinite(p0[1]);\n const p1Ok = Number.isFinite(p1[1]);\n const midOk = Number.isFinite(my);\n\n if (!p0Ok || !p1Ok || !midOk) {\n // \u4EFB\u4E00\u7AEF\u70B9/\u4E2D\u70B9\u975E\u6709\u9650\uFF1A\u4E8C\u5206\u903C\u8FD1 NaN \u533A\u57DF\u8FB9\u754C\uFF0C\u76F4\u81F3\u6DF1\u5EA6\u4E0A\u9650\n refineRecursive(p0, mid, fn, asymptoteLimit, depth + 1, out);\n refineRecursive(mid, p1, fn, asymptoteLimit, depth + 1, out);\n return;\n }\n\n // \u66F2\u7387 = \u5411\u91CF (p0\u2192m) \u4E0E (m\u2192p1) \u7684\u8F6C\u89D2\n const dx1 = mx - p0[0];\n const dy1 = my - p0[1];\n const dx2 = p1[0] - mx;\n const dy2 = p1[1] - my;\n const angle = Math.atan2(dy2, dx2) - Math.atan2(dy1, dx1);\n const curvature = Math.abs(angle);\n\n if (curvature > CURVATURE_RAD_THRESHOLD) {\n refineRecursive(p0, mid, fn, asymptoteLimit, depth + 1, out);\n refineRecursive(mid, p1, fn, asymptoteLimit, depth + 1, out);\n } else {\n out.push(mid, p1);\n }\n}\n\nfunction sanitizeY(y: number, asymptoteLimit: number): number {\n if (!Number.isFinite(y)) return NaN;\n if (Math.abs(y) > asymptoteLimit) return NaN;\n return y;\n}\n\n/**\n * \u5C06\u70B9\u5E8F\u5217\u6309\u8FDE\u7EED NaN \u5206\u6BB5\u3002\n * \u5355\u70B9 NaN\uFF1A\u8DF3\u8FC7\uFF08\u89C6\u4E3A\u77ED\u6682\u5B9A\u4E49\u57DF\u7F3A\u53E3\uFF09\uFF1B\n * \u8FDE\u7EED \u22652 NaN\uFF1A\u65AD\u5F00\u5206\u6BB5\uFF08\u6E10\u8FD1\u7EBF\u884C\u4E3A\uFF09\u3002\n */\nfunction splitSegments(points: Point2[], _total: number): Segments {\n const segments: Segments = [];\n let current: Point2[] = [];\n let nanRun = 0;\n\n const flush = (): void => {\n if (current.length >= 2) segments.push(current);\n current = [];\n };\n\n for (const p of points) {\n if (Number.isFinite(p[1])) {\n if (nanRun === 1 && current.length > 0) {\n // \u5355\u70B9 NaN \u7A7F\u8D8A\uFF1A\u4FDD\u7559\u5F53\u524D\u6BB5\uFF0C\u7EE7\u7EED\u8854\u63A5\n } else if (nanRun >= 2) {\n flush();\n }\n nanRun = 0;\n current.push(p);\n } else {\n nanRun += 1;\n }\n }\n flush();\n return segments;\n}\n", "/**\n * \u8F74/\u7F51\u683C/\u523B\u5EA6\u5E03\u5C40\uFF1Anice-number \u6B65\u957F\uFF081/2/5\u00D710^n\uFF09\uFF0C\n * \u8F93\u51FA\u50CF\u7D20\u5750\u6807\u7531\u8C03\u7528\u65B9\u7ED3\u5408 Scale \u6362\u7B97\uFF08\u4FDD\u6301\u672C\u6A21\u5757\u7EAF\u6570\u636E\u3001\u65E0\u4F9D\u8D56\uFF09\u3002\n */\n\nexport interface Tick {\n value: number;\n label: string;\n}\n\n/** nice-number \u6B65\u957F\uFF1A1/2/5\u00D710^n\uFF0C\u53D6\u4F7F\u523B\u5EA6\u6570 \u2264 maxTicks \u7684\u6700\u7EC6\u6863 */\nexport function niceStep(range: number, maxTicks: number): number {\n if (range <= 0 || !Number.isFinite(range)) return 1;\n const rough = range / Math.max(1, maxTicks);\n const mag = 10 ** Math.floor(Math.log10(rough));\n for (const mul of [1, 2, 5, 10]) {\n if (mag * mul >= rough) return mag * mul;\n }\n return mag * 10;\n}\n\n/**\n * \u751F\u6210 [min, max] \u533A\u95F4\u5185\u7684\u523B\u5EA6\uFF08\u542B 0 \u523B\u5EA6\u82E5\u53EF\u89C1\uFF09\u3002\n * \u6807\u7B7E\u7528 Number.toPrecision \u622A\u65AD\uFF0C\u9632\u6D6E\u70B9\u566A\u58F0\u3002\n */\nexport function generateTicks(min: number, max: number, maxTicks: number): Tick[] {\n const step = niceStep(max - min, maxTicks);\n const ticks: Tick[] = [];\n const start = Math.ceil(min / step) * step;\n for (let v = start; v <= max + step * 1e-9; v += step) {\n const snapped = Math.abs(v) < step * 1e-9 ? 0 : v;\n ticks.push({ value: snapped, label: formatTickLabel(snapped) });\n }\n return ticks;\n}\n\nfunction formatTickLabel(v: number): string {\n if (v === 0) return '0';\n if (Number.isInteger(v)) return String(v);\n const abs = Math.abs(v);\n if (abs >= 1e6 || abs < 1e-4) return v.toExponential(2);\n return Number(v.toPrecision(6)).toString();\n}\n", "/**\n * SVG \u5B57\u7B26\u4E32\u62FC\u88C5\u5668\uFF1A\u4E0D\u4F9D\u8D56 XML/DOM \u5E93\uFF0C\u5168\u90E8\u5B57\u7B26\u4E32\u62FC\u63A5\u3002\n * \u9632\u5FA1\uFF1A\u6240\u6709\u7528\u6237\u8F93\u5165/\u52A8\u6001\u6587\u672C\u7ECF xmlEscape \u8F6C\u4E49\uFF1B\n * \u53EA\u751F\u6210 path/line/rect/text/circle/g \u767D\u540D\u5355\u5143\u7D20\uFF0C\u7981\u6B62 script/\u4E8B\u4EF6\u5C5E\u6027\u3002\n */\n\nexport interface SvgPath {\n type: 'path';\n d: string;\n stroke: string;\n strokeWidth: number;\n fill?: string;\n}\n\nexport interface SvgLine {\n type: 'line';\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n stroke: string;\n strokeWidth: number;\n}\n\nexport interface SvgText {\n type: 'text';\n x: number;\n y: number;\n text: string;\n fill: string;\n fontSize?: number;\n textAnchor?: 'start' | 'middle' | 'end';\n dominantBaseline?: 'hanging' | 'middle' | 'alphabetic';\n}\n\nexport interface SvgRect {\n type: 'rect';\n x: number;\n y: number;\n width: number;\n height: number;\n fill: string;\n}\n\nexport type SvgElement = SvgPath | SvgLine | SvgText | SvgRect;\n\nexport function xmlEscape(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function buildSvg(\n width: number,\n height: number,\n elements: SvgElement[],\n): string {\n const parts: string[] = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n ];\n for (const el of elements) {\n switch (el.type) {\n case 'path':\n parts.push(\n `<path d=\"${el.d}\" fill=\"none\" stroke=\"${el.stroke}\" stroke-width=\"${el.strokeWidth}\" />`,\n );\n break;\n case 'line':\n parts.push(\n `<line x1=\"${fmt(el.x1)}\" y1=\"${fmt(el.y1)}\" x2=\"${fmt(el.x2)}\" y2=\"${fmt(el.y2)}\" stroke=\"${el.stroke}\" stroke-width=\"${el.strokeWidth}\" />`,\n );\n break;\n case 'rect':\n parts.push(\n `<rect x=\"${fmt(el.x)}\" y=\"${fmt(el.y)}\" width=\"${fmt(el.width)}\" height=\"${fmt(el.height)}\" fill=\"${el.fill}\" />`,\n );\n break;\n case 'text': {\n const ta = el.textAnchor ? ` text-anchor=\"${el.textAnchor}\"` : '';\n const db = el.dominantBaseline\n ? ` dominant-baseline=\"${el.dominantBaseline}\"`\n : '';\n const fs = el.fontSize ? ` font-size=\"${el.fontSize}px\"` : '';\n parts.push(\n `<text x=\"${fmt(el.x)}\" y=\"${fmt(el.y)}\" fill=\"${el.fill}\"${fs}${ta}${db}>${xmlEscape(el.text)}</text>`,\n );\n break;\n }\n }\n }\n parts.push('</svg>');\n return parts.join('');\n}\n\nfunction fmt(n: number): string {\n return Number(n.toPrecision(6)).toString();\n}\n", "/**\n * \u670D\u52A1\u7AEF\u51FD\u6570\u56FE\u50CF\u6E32\u67D3\u4E3B\u5165\u53E3\uFF08Q4 \u4EFB\u52A1 2.3 / 4.2\uFF09\u3002\n * \u65E0 DOM \u4F9D\u8D56\uFF1Amathjs \u7F16\u8BD1 + \u81EA\u9002\u5E94\u91C7\u6837 + \u5B57\u7B26\u4E32 SVG \u751F\u6210\u3002\n * \u914D\u5957 W5\uFF1Aresvg-js \u8F6C PNG\uFF1Bdark \u4E3B\u9898\u4E00\u671F\u5DF2\u5185\u7F6E\u8C03\u8272\u677F\u3002\n */\nimport { makeUnivariate } from '../core/numeric.js';\nimport { validateExpression, ExpressionError } from '../core/compile.js';\n\nexport { ExpressionError } from '../core/compile.js';\nimport { createScales } from './scales.js';\nimport { adaptiveSample, type Segments } from './sampler.js';\nimport { generateTicks } from './axes.js';\nimport { buildSvg, type SvgElement } from './svg-builder.js';\n\nexport type PlotTheme = 'light' | 'dark';\n\nexport interface PlotExpressionInput {\n /** mathjs \u8868\u8FBE\u5F0F\uFF08\u663E\u5F0F y=f(x)\uFF09 */\n raw: string;\n /** \u53EF\u9009\u56FE\u4F8B\u6587\u672C\uFF08\u7EAF\u6587\u672C Unicode\uFF0C\u4E00\u671F\u4E0D\u652F\u6301\u5B8C\u6574 LaTeX\uFF09 */\n label?: string;\n /** \u53EF\u9009 hex \u8986\u76D6\u8C03\u8272\u677F\u989C\u8272 */\n color?: string;\n}\n\nexport interface PlotSpec {\n expressions: PlotExpressionInput[];\n viewport?: { xMin: number; xMax: number; yMin: number; yMax: number };\n size?: { width: number; height: number };\n theme?: PlotTheme;\n grid?: boolean;\n axis?: boolean;\n}\n\nexport interface PlotExpressionResult {\n raw: string;\n ok: boolean;\n sampleCount?: number;\n segments?: number;\n error?: string;\n}\n\nexport interface PlotTiming {\n compileMs: number;\n sampleMs: number;\n renderMs: number;\n}\n\nexport interface PlotResult {\n svg: string;\n expressions: PlotExpressionResult[];\n timing: PlotTiming;\n}\n\n/** \u5355\u6761\u8868\u8FBE\u5F0F mathjs \u539F\u6587\u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A7\u4E94\uFF09 */\nexport const EXPRESSION_RAW_MAX_LENGTH = 200;\n/** \u5355\u6B21\u8BF7\u6C42\u8868\u8FBE\u5F0F\u6761\u6570\u4E0A\u9650\uFF08\u5BF9\u9F50 SERIES_PALETTE 8 \u8272\uFF09 */\nexport const EXPRESSIONS_MAX = 8;\n/** \u753B\u5E03\u8FB9\u957F\u4E0A\u9650\uFF08\u65B9\u6848 \u00A72.2\uFF09 */\nexport const SIZE_MAX = 2000;\n\nconst LIGHT_PALETTE = [\n '#2563eb', '#dc2626', '#16a34a', '#d97706', '#7c3aed', '#0891b2', '#db2777', '#4b5563',\n];\nconst DARK_PALETTE = [\n '#60a5fa', '#f87171', '#4ade80', '#fbbf24', '#a78bfa', '#22d3ee', '#f472b6', '#9ca3af',\n];\n\nconst PADDING = 48;\nconst TICKS_X = 8;\nconst TICKS_Y = 6;\n\nexport function renderPlotSvg(spec: PlotSpec): PlotResult {\n const width = Math.min(spec.size?.width ?? 800, SIZE_MAX);\n const height = Math.min(spec.size?.height ?? 500, SIZE_MAX);\n const theme = spec.theme ?? 'light';\n const palette = theme === 'dark' ? DARK_PALETTE : LIGHT_PALETTE;\n\n const vp = spec.viewport ?? { xMin: -6, xMax: 6, yMin: -4, yMax: 4 };\n const scale = createScales(vp.xMin, vp.xMax, vp.yMin, vp.yMax, width, height, PADDING);\n\n const elements: SvgElement[] = [];\n const exprResults: PlotExpressionResult[] = [];\n let compileMs = 0;\n let sampleMs = 0;\n\n // \u80CC\u666F\uFF08\u542B\u5E95\u8272 rect\uFF0CPNG \u8F6C\u6362\u65F6\u81EA\u5E26\u80CC\u666F\uFF09\n elements.push({\n type: 'rect',\n x: 0,\n y: 0,\n width,\n height,\n fill: theme === 'dark' ? '#1c1f25' : '#ffffff',\n });\n\n // \u7F51\u683C\uFF08\u5148\u4E8E\u8F74\u7ED8\u5236\uFF0C\u8F74\u8986\u76D6\u7F51\u683C\uFF09\n if (spec.grid ?? true) {\n const gridColor = theme === 'dark' ? '#2d333b' : '#e5e5e5';\n const xTicks = generateTicks(vp.xMin, vp.xMax, TICKS_X);\n const yTicks = generateTicks(vp.yMin, vp.yMax, TICKS_Y);\n for (const t of xTicks) {\n const px = scale.x2px(t.value);\n elements.push({ type: 'line', x1: px, y1: PADDING, x2: px, y2: height - PADDING, stroke: gridColor, strokeWidth: 0.5 });\n }\n for (const t of yTicks) {\n const py = scale.y2px(t.value);\n elements.push({ type: 'line', x1: PADDING, y1: py, x2: width - PADDING, y2: py, stroke: gridColor, strokeWidth: 0.5 });\n }\n }\n\n // \u8F74 + \u523B\u5EA6\u6807\u7B7E\n if (spec.axis ?? true) {\n const axisColor = theme === 'dark' ? '#9ca3af' : '#666666';\n const axisY0 = scale.y2px(0);\n const axisX0 = scale.x2px(0);\n if (vp.yMin <= 0 && vp.yMax >= 0) {\n elements.push({ type: 'line', x1: PADDING, y1: axisY0, x2: width - PADDING, y2: axisY0, stroke: axisColor, strokeWidth: 1 });\n }\n if (vp.xMin <= 0 && vp.xMax >= 0) {\n elements.push({ type: 'line', x1: axisX0, y1: PADDING, x2: axisX0, y2: height - PADDING, stroke: axisColor, strokeWidth: 1 });\n }\n const tickTextFill = theme === 'dark' ? '#9ca3af' : '#374151';\n for (const t of generateTicks(vp.xMin, vp.xMax, TICKS_X)) {\n if (t.value === 0) continue;\n elements.push({\n type: 'text', x: scale.x2px(t.value), y: axisY0 + 18, text: t.label,\n fill: tickTextFill, fontSize: 12, textAnchor: 'middle', dominantBaseline: 'hanging',\n });\n }\n for (const t of generateTicks(vp.yMin, vp.yMax, TICKS_Y)) {\n if (t.value === 0) continue;\n elements.push({\n type: 'text', x: axisX0 - 10, y: scale.y2px(t.value), text: t.label,\n fill: tickTextFill, fontSize: 12, textAnchor: 'end', dominantBaseline: 'middle',\n });\n }\n if (vp.xMin <= 0 && vp.xMax >= 0 && vp.yMin <= 0 && vp.yMax >= 0) {\n elements.push({\n type: 'text', x: axisX0 - 8, y: axisY0 + 14, text: '0',\n fill: tickTextFill, fontSize: 12, textAnchor: 'end', dominantBaseline: 'hanging',\n });\n }\n }\n\n // \u66F2\u7EBF\n for (const [i, expr] of spec.expressions.entries()) {\n const raw = expr.raw;\n const color = expr.color ?? palette[i % palette.length]!;\n\n const tC = Date.now();\n const validation = validateExpression(raw);\n compileMs += Date.now() - tC;\n\n if (!validation.ok) {\n exprResults.push({\n raw,\n ok: false,\n error: `${validation.error.kind}: ${validation.error.message}`,\n });\n continue;\n }\n\n const tS = Date.now();\n const fn = makeUnivariate(raw);\n const segments = adaptiveSample(fn, vp.xMin, vp.xMax, vp.yMin, vp.yMax);\n sampleMs += Date.now() - tS;\n\n let sampleCount = 0;\n const pathParts: string[] = [];\n for (const seg of segments) {\n sampleCount += seg.length;\n if (seg.length < 2) continue;\n pathParts.push(segmentToPathD(seg, scale));\n }\n\n if (pathParts.length > 0) {\n elements.push({\n type: 'path',\n d: pathParts.join(' '),\n stroke: color,\n strokeWidth: 2,\n });\n }\n\n exprResults.push({ raw, ok: true, sampleCount, segments: segments.length });\n\n // \u56FE\u4F8B\uFF08\u7EAF\u6587\u672C Unicode\uFF0C\u4E00\u671F\uFF09\n if (expr.label) {\n elements.push({\n type: 'text',\n x: width - PADDING - 8,\n y: PADDING + 18 * (exprResults.filter((r) => r.ok).length - 1) + 12,\n text: expr.label,\n fill: color,\n fontSize: 13,\n textAnchor: 'end',\n dominantBaseline: 'hanging',\n });\n }\n }\n\n const tR = Date.now();\n const svg = buildSvg(width, height, elements);\n const renderMs = Date.now() - tR;\n\n const allFailed = exprResults.every((r) => !r.ok);\n if (allFailed && exprResults.length > 0) {\n throw new ExpressionError('syntax', '\u6240\u6709\u8868\u8FBE\u5F0F\u5747\u65E0\u6CD5\u7F16\u8BD1');\n }\n\n return {\n svg,\n expressions: exprResults,\n timing: { compileMs, sampleMs, renderMs },\n };\n}\n\nfunction segmentToPathD(seg: Segments[number], scale: ReturnType<typeof createScales>): string {\n const parts: string[] = [];\n for (const [i, [x, y]] of seg.entries()) {\n const px = scale.x2px(x);\n const py = scale.y2px(y);\n parts.push(`${i === 0 ? 'M' : 'L'}${Number(px.toPrecision(6))},${Number(py.toPrecision(6))}`);\n }\n return parts.join(' ');\n}\n", "/**\n * geometry_solve \u5DE5\u5177\uFF1A\u6821\u9A8C\u5E76\u5BF9 protocolVersion=1 \u7684\u5E73\u9762\u51E0\u4F55\u6587\u6863\u505A\u5143\u7D20\u6C42\u89E3\u3002\n * \u65E0 DOM\u3001\u65E0\u72B6\u6001\uFF0C\u76F4\u8C03 @studylab/viz/geometry-kernel\u3002\n * \u6587\u6863/\u8BCA\u65AD\u7C7B\u5931\u8D25\u4EE5 ok:false \u8FD4\u56DE\uFF08\u4FBF\u4E8E AI \u8BFB\u53D6\u8BCA\u65AD\uFF09\uFF1B\u4EC5\u53C2\u6570\u7ED3\u6784\u635F\u574F\u624D isError\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n resolveGeometryDocument,\n type GeometryDiagnostic,\n type ResolvedElement,\n} from '@studylab/viz/geometry-kernel';\n\n/** \u6587\u6863 JSON \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const DOCUMENT_JSON_MAX_LENGTH = 512 * 1024;\n\n/** \u6587\u672C\u6807\u6CE8 raw \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const TEXT_RAW_MAX_LENGTH = 2000;\n\ntype GeometryDocumentInput = Parameters<typeof resolveGeometryDocument>[0];\n\nexport interface GeometrySolveArgs {\n document: unknown;\n options?: { includeResolved?: boolean };\n}\n\n/** \u89C4\u8303\u5316\u526F\u672C\uFF1A\u53EA\u4FDD\u7559 GeometryDocument \u7684\u9876\u5C42\u5B57\u6BB5 */\ninterface NormalizedGeometryDocument {\n protocolVersion: unknown;\n elements: unknown;\n viewport: unknown;\n style?: unknown;\n}\n\ninterface GeometrySolvePayload {\n ok: boolean;\n document?: NormalizedGeometryDocument | null;\n resolved?: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n}\n\nfunction textResult(payload: GeometrySolvePayload): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalidDocument(message: string): GeometryDiagnostic {\n return { code: 'INVALID_DOCUMENT', severity: 'error', message };\n}\n\nfunction normalizeDocument(document: Record<string, unknown>): NormalizedGeometryDocument {\n const normalized: NormalizedGeometryDocument = {\n protocolVersion: document.protocolVersion,\n elements: Array.isArray(document.elements) ? document.elements : [],\n viewport: isRecord(document.viewport) ? document.viewport : {},\n };\n if (document.style !== undefined) normalized.style = document.style;\n return normalized;\n}\n\n/** \u6587\u672C raw \u8D85\u9650\u8BCA\u65AD\uFF08\u5143\u7D20\u6570\u8D85\u9650\u7531\u5185\u6838\u4EA7\u51FA\uFF09 */\nfunction textLimitDiagnostics(elements: unknown[]): GeometryDiagnostic[] {\n const diagnostics: GeometryDiagnostic[] = [];\n for (const element of elements) {\n if (!isRecord(element) || element.type !== 'text') continue;\n if (typeof element.raw !== 'string' || element.raw.length <= TEXT_RAW_MAX_LENGTH) continue;\n const elementId = typeof element.id === 'string' ? element.id : undefined;\n diagnostics.push({\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n elementId,\n message: `\u6587\u672C\u5143\u7D20 \"${elementId ?? '?'}\" \u7684 raw \u8D85\u8FC7 ${TEXT_RAW_MAX_LENGTH} \u5B57\u7B26`,\n });\n }\n return diagnostics;\n}\n\nexport async function handleGeometrySolve(args: GeometrySolveArgs): Promise<CallToolResult> {\n try {\n if (!args || typeof args !== 'object' || !('document' in args)) {\n return errorResult('\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981 { document: object, options?: { includeResolved?: boolean } }');\n }\n\n const { document, options } = args;\n const includeResolved = options?.includeResolved !== false;\n\n if (JSON.stringify(document).length > DOCUMENT_JSON_MAX_LENGTH) {\n return textResult({\n ok: false,\n diagnostics: [\n {\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n message: `\u6587\u6863 JSON \u8D85\u8FC7 ${DOCUMENT_JSON_MAX_LENGTH} \u5B57\u8282\uFF08512KB\uFF09`,\n },\n ],\n });\n }\n\n if (!isRecord(document)) {\n return textResult({\n ok: false,\n document: null,\n diagnostics: [invalidDocument('\u6587\u6863\u5FC5\u987B\u662F protocolVersion=1 \u7684 GeometryDocument \u5BF9\u8C61')],\n });\n }\n\n const normalized = normalizeDocument(document);\n if (document.protocolVersion !== 1 || 'equations' in document) {\n return textResult({\n ok: false,\n document: normalized,\n diagnostics: [invalidDocument('\u4EC5\u652F\u6301 protocolVersion=1 \u4E14\u4E0D\u542B equations \u7684 GeometryDocument')],\n });\n }\n\n const result = resolveGeometryDocument(document as unknown as GeometryDocumentInput);\n const diagnostics: GeometryDiagnostic[] = [\n ...result.diagnostics,\n ...textLimitDiagnostics(Array.isArray(document.elements) ? document.elements : []),\n ];\n\n return textResult({\n ok: diagnostics.every((d) => d.severity !== 'error'),\n document: normalized,\n ...(includeResolved ? { resolved: result.elements } : {}),\n diagnostics,\n });\n } catch (error) {\n // \u515C\u5E95\uFF1A\u53C2\u6570\u5F02\u5E38\uFF08\u5982\u5FAA\u73AF\u5F15\u7528\uFF09\u6216\u5185\u6838\u610F\u5916\u5F02\u5E38\u90FD\u4E0D\u80FD\u8BA9 stdio \u8FDB\u7A0B\u5D29\u6E83\n return errorResult(error instanceof Error ? error.message : String(error));\n }\n}\n\n/** \u5411 MCP server \u6CE8\u518C geometry_solve \u5DE5\u5177 */\nexport function registerGeometrySolveTool(server: McpServer): void {\n server.registerTool(\n 'geometry_solve',\n {\n title: '\u51E0\u4F55\u6587\u6863\u6C42\u89E3\u4E0E\u6821\u9A8C',\n description:\n '\u6821\u9A8C protocolVersion=1 \u7684\u5E73\u9762\u51E0\u4F55\u6587\u6863\u5E76\u6C42\u89E3\u5143\u7D20\uFF08\u5750\u6807\u3001\u4EA4\u70B9\u3001\u6D4B\u91CF\uFF09\uFF0C\u8FD4\u56DE\u89C4\u8303\u5316\u6587\u6863\u4E0E\u8BCA\u65AD\uFF1B\u53EF\u4F9B AI \u6784\u9020\u6587\u6863\u540E\u81EA\u68C0\uFF0C\u8FD4\u56DE\u7684 document \u53EF\u76F4\u63A5\u4F20\u7ED9\u540E\u7EED\u5DE5\u5177\u3002',\n inputSchema: {\n document: z.record(z.unknown()),\n options: z.object({ includeResolved: z.boolean().default(true) }).optional(),\n },\n },\n (args) => handleGeometrySolve(args),\n );\n}\n", "/**\n * \u51E0\u4F55\u6784\u9020\u56FE\uFF1A\u4F9D\u8D56\u62D3\u6251\u4E0E\u7EA7\u8054\u5220\u9664\u3002\n *\n * \u6784\u9020\u987A\u5E8F\u5373 elements \u6570\u7EC4\u987A\u5E8F\uFF1Bparents \u5FC5\u987B\u5F15\u7528\u5DF2\u5B58\u5728\u5143\u7D20\u3002\n * \u5220\u9664\u7236\u5143\u7D20\u65F6\uFF0C\u6240\u6709\u540E\u4EE3\uFF08\u76F4\u63A5/\u95F4\u63A5\u4F9D\u8D56\u8005\uFF09\u5FC5\u987B\u7EA7\u8054\u5220\u9664\u3002\n */\n\nimport type { GeometryElementSpec } from '../core/types-geometry';\n\n/** \u5143\u7D20\u4F9D\u8D56\u7684\u51E0\u4F55\u7236 id\uFF08\u542B\u66F2\u7EBF\u4EA4\u70B9\u7684 shapeId\uFF09 */\nfunction parentIdsOf(el: GeometryElementSpec): readonly string[] {\n if (el.type === 'transform') {\n return el.kind === 'translate'\n ? [el.source]\n : el.kind === 'reflectLine'\n ? [el.source, el.axis]\n : [el.source, el.center];\n }\n if (el.type === 'locus') return [el.driver, el.trace];\n if (el.type === 'measure') return [el.target];\n if (el.type === 'glider') return [el.parent];\n const base = 'parents' in el ? el.parents : undefined;\n if (el.type === 'curveIntersection' && el.shapeId) {\n return [...(base ?? []), el.shapeId];\n }\n return base ?? [];\n}\n\n/** \u65E0\u9700 geometry parents \u7684\u7C7B\u578B\uFF08\u81EA\u7531\u70B9/\u6587\u672C/\u66F2\u7EBF\u70B9/\u5207\u7EBF/\u7EAF\u66F2\u7EBF\u4EA4\u70B9\uFF09 */\nconst PARENTLESS_TYPES = new Set<GeometryElementSpec['type']>([\n 'point',\n 'text',\n 'curvePoint',\n 'curveTangent',\n 'curveIntersection',\n]);\n\nexport interface ConstructionIssue {\n rule: 'C1' | 'C2' | 'C3';\n message: string;\n}\n\n/**\n * \u6821\u9A8C\u6784\u9020\u56FE\u5408\u6CD5\u6027\uFF1A\n * - C1\uFF1Aid \u552F\u4E00\n * - C2\uFF1Aparents \u5F15\u7528\u5FC5\u987B\u5B58\u5728\u4E8E\u6570\u7EC4\u5F53\u524D\u5143\u7D20\u4E4B\u524D\uFF08\u62D3\u6251\u6709\u5E8F\uFF09\n * - C3\uFF1A\u9664\u81EA\u7531\u70B9/\u66F2\u7EBF\u6D3E\u751F\u5143\u7D20\u5916\u5FC5\u987B\u58F0\u660E parents\n */\nexport function validateConstruction(elements: GeometryElementSpec[]): ConstructionIssue[] {\n const issues: ConstructionIssue[] = [];\n const seen = new Set<string>();\n for (const el of elements) {\n if (seen.has(el.id)) {\n issues.push({ rule: 'C1', message: `\u5143\u7D20 id \u91CD\u590D\uFF1A\"${el.id}\"` });\n }\n const parents = parentIdsOf(el);\n const needsParents = !PARENTLESS_TYPES.has(el.type);\n if (needsParents && parents.length === 0) {\n issues.push({ rule: 'C3', message: `\u5143\u7D20 \"${el.id}\"\uFF08${el.type}\uFF09\u7F3A\u5C11 parents` });\n }\n if (el.type === 'curveIntersection' && !el.curveIdB && !el.shapeId) {\n issues.push({ rule: 'C3', message: `\u66F2\u7EBF\u4EA4\u70B9 \"${el.id}\" \u7F3A\u5C11\u7B2C\u4E8C\u6761\u66F2\u7EBF\u6216\u51E0\u4F55\u7236\u5143\u7D20` });\n }\n for (const p of parents) {\n if (!seen.has(p)) {\n issues.push({ rule: 'C2', message: `\u5143\u7D20 \"${el.id}\" \u7684 parent \"${p}\" \u5C1A\u672A\u6784\u9020` });\n }\n }\n seen.add(el.id);\n }\n return issues;\n}\n\n/**\n * \u8BA1\u7B97\u5220\u9664 ids \u7684\u7EA7\u8054\u95ED\u5305\uFF1A\u8FD4\u56DE\u6240\u6709\u5FC5\u987B\u4E00\u5E76\u5220\u9664\u7684\u5143\u7D20 id\uFF08\u542B\u5165\u53C2\uFF09\u3002\n */\nexport function cascadeDelete(elements: GeometryElementSpec[], ids: string[]): Set<string> {\n const toDelete = new Set(ids);\n let changed = true;\n while (changed) {\n changed = false;\n for (const el of elements) {\n if (toDelete.has(el.id)) continue;\n if (parentIdsOf(el).some((p) => toDelete.has(p))) {\n toDelete.add(el.id);\n changed = true;\n }\n }\n }\n return toDelete;\n}\n\n/**\n * id \u662F\u5426\uFF08\u76F4\u63A5\u6216\u95F4\u63A5\uFF09\u4F9D\u8D56 ancestorId\u3002\n * \u6CBF parentIdsOf \u9012\u5F52\uFF0C\u4F9B\u8F68\u8FF9\u6821\u9A8C\u300Ctrace \u4F9D\u8D56 driver\u300D\u7B49\u573A\u666F\u4F7F\u7528\u3002\n */\nexport function dependsOn(\n elements: GeometryElementSpec[],\n id: string,\n ancestorId: string,\n): boolean {\n const byId = new Map(elements.map((el) => [el.id, el]));\n const seen = new Set<string>();\n const stack = [id];\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (seen.has(current)) continue;\n seen.add(current);\n const spec = byId.get(current);\n if (!spec) continue;\n for (const parent of parentIdsOf(spec)) {\n if (parent === ancestorId) return true;\n stack.push(parent);\n }\n }\n return false;\n}\n\nexport interface DeletePlan {\n /** \u5B9E\u9645\u5220\u9664\u96C6\u5408\uFF08\u542B\u7EA7\u8054\u540E\u4EE3\u4E0E\u5B64\u513F\u9876\u70B9\uFF09 */\n ids: Set<string>;\n /** \u56E0\u672C\u6B21\u5220\u9664\u800C\u5931\u53BB\u5168\u90E8\u5F15\u7528\u3001\u88AB\u4E00\u5E76\u5220\u9664\u7684\u9876\u70B9 */\n orphanVertices: Set<string>;\n}\n\n/** \u5F62\u72B6\u7C7B\u5143\u7D20\uFF1A\u5220\u9664\u5B83\u65F6\uFF0C\u5176\u76F4\u63A5\u7236\u70B9\u82E5\u4E0D\u518D\u88AB\u5F15\u7528\u5219\u4E00\u5E76\u5220\u9664 */\nconst VERTEX_OWNER_TYPES = new Set<GeometryElementSpec['type']>([\n 'polygon',\n 'parallelogram',\n 'rhombus',\n 'segment',\n 'line',\n 'ray',\n 'circle',\n 'circumcircle',\n 'semicircle',\n 'sector',\n 'ellipse',\n 'ellipseArc',\n 'arc',\n]);\n\n/** \u70B9\u7C7B\u5143\u7D20\uFF08\u81EA\u7531\u70B9\u3001\u7EA6\u675F\u70B9\u4E0E\u66F2\u7EBF\u4E0A\u7684\u70B9\u90FD\u53EF\u4F5C\u4E3A\u5F62\u72B6\u9876\u70B9\uFF09 */\nfunction isPointElement(el: GeometryElementSpec): boolean {\n return el.type === 'point' || el.type === 'glider' || el.type === 'curvePoint';\n}\n\n/**\n * \u5220\u9664\u8BA1\u5212\uFF1A\u7EA7\u8054\u5220\u9664 + \u8FDE\u5E26\u5220\u9664\u300C\u53EA\u5C5E\u4E8E\u88AB\u5220\u5F62\u72B6\u300D\u7684\u9876\u70B9\u3002\n *\n * \u53EA\u5904\u7406 polygon/segment/line/ray/circle \u7684\u76F4\u63A5\u7236\u70B9\uFF0C\u4E14\u8981\u6C42\u8BE5\u70B9\u5220\u9664\u540E\u4E0D\u518D\u88AB\u4EFB\u4F55\u5E78\u5B58\u5143\u7D20\u5F15\u7528\uFF1B\n * \u6D3E\u751F\u6784\u9020\uFF08\u5782\u7EBF/\u5E73\u884C\u7EBF/\u89D2\u5E73\u5206\u7EBF/\u4EA4\u70B9/\u4E2D\u70B9/\u89D2\u5EA6\uFF09\u7684\u8F93\u5165\u70B9\u4E0D\u5728\u6B64\u5217\uFF0C\u907F\u514D\u8BEF\u5220\u7528\u6237\u5173\u952E\u70B9\u3002\n */\nexport function planDelete(elements: GeometryElementSpec[], ids: string[]): DeletePlan {\n const removed = cascadeDelete(elements, ids);\n const byId = new Map(elements.map((el) => [el.id, el]));\n const survivors = elements.filter((el) => !removed.has(el.id));\n\n const referenced = new Set<string>();\n for (const el of survivors) {\n for (const p of parentIdsOf(el)) referenced.add(p);\n }\n\n const orphanVertices = new Set<string>();\n for (const el of elements) {\n if (!removed.has(el.id) || !VERTEX_OWNER_TYPES.has(el.type)) continue;\n for (const p of parentIdsOf(el)) {\n const parent = byId.get(p);\n if (parent && isPointElement(parent) && !referenced.has(p)) orphanVertices.add(p);\n }\n }\n\n for (const id of orphanVertices) removed.add(id);\n return { ids: removed, orphanVertices };\n}\n\n/**\n * \u4F9D\u8D56\u62D3\u6251\u6392\u5E8F\uFF08Kahn \u7B97\u6CD5\uFF09\u3002\n * \u5047\u5B9A\u6784\u9020\u56FE\u5DF2\u901A\u8FC7 validateConstruction \u6821\u9A8C\uFF08C2 \u65E0\u73AF\u4FDD\u8BC1\uFF09\u3002\n */\nexport function topoSort(elements: GeometryElementSpec[]): GeometryElementSpec[] {\n const map = new Map(elements.map((el) => [el.id, el]));\n const inDegree = new Map<string, number>();\n const dependents = new Map<string, string[]>();\n\n for (const el of elements) {\n const deps = parentIdsOf(el).filter((p) => map.has(p));\n inDegree.set(el.id, deps.length);\n for (const p of deps) {\n const children = dependents.get(p) ?? [];\n children.push(el.id);\n dependents.set(p, children);\n }\n }\n\n const queue = elements.filter((el) => inDegree.get(el.id) === 0).map((el) => el.id);\n const sorted: GeometryElementSpec[] = [];\n while (queue.length > 0) {\n const id = queue.shift()!;\n const el = map.get(id);\n if (el) sorted.push(el);\n for (const childId of dependents.get(id) ?? []) {\n const d = (inDegree.get(childId) ?? 0) - 1;\n inDegree.set(childId, d);\n if (d === 0) queue.push(childId);\n }\n }\n return sorted;\n}\n", "/**\n * \u4E09\u70B9\u5706\u5F27\u7684\u7EAF\u51E0\u4F55\u8BA1\u7B97\uFF08\u5F15\u64CE\u65E0\u5173\uFF09\u3002\n * \u7528\u4E8E\u7ED8\u5236\u9884\u89C8\uFF1A\u8D77\u70B9 \u2192 \u7ECF\u8FC7\u70B9 \u2192 \u7EC8\u70B9\uFF0C\u4E09\u70B9\u4E0D\u5171\u7EBF\u65F6\u552F\u4E00\u786E\u5B9A\u4E00\u6BB5\u5706\u5F27\u3002\n */\n\nexport type ArcPoint = [number, number];\n\ninterface Circumcircle {\n center: ArcPoint;\n radius: number;\n startAngle: number;\n /** \u4ECE\u8D77\u70B9\u626B\u5230\u7EC8\u70B9\u7684\u6709\u5411\u5F27\u5EA6\uFF08\u7ECF\u8FC7\u70B9\u5728\u5F27\u4E0A\uFF09 */\n sweep: number;\n}\n\nconst TWO_PI = Math.PI * 2;\nconst EPS = 1e-9;\n\nfunction normalizeAngle(angle: number): number {\n let next = angle % TWO_PI;\n if (next < 0) next += TWO_PI;\n return next;\n}\n\n/** \u4E09\u70B9\u5916\u63A5\u5706\uFF1B\u4E09\u70B9\u5171\u7EBF\u6216\u91CD\u5408\u65F6\u8FD4\u56DE null */\nexport function circumcircleThrough(\n start: ArcPoint,\n through: ArcPoint,\n end: ArcPoint,\n): Circumcircle | null {\n const [ax, ay] = start;\n const [bx, by] = through;\n const [cx, cy] = end;\n if (\n Math.hypot(ax - bx, ay - by) < EPS ||\n Math.hypot(bx - cx, by - cy) < EPS ||\n Math.hypot(ax - cx, ay - cy) < EPS\n ) {\n return null;\n }\n const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));\n if (Math.abs(d) < EPS) return null;\n const a2 = ax * ax + ay * ay;\n const b2 = bx * bx + by * by;\n const c2 = cx * cx + cy * cy;\n const ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / d;\n const uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / d;\n const radius = Math.hypot(ax - ux, ay - uy);\n if (!Number.isFinite(radius) || radius < EPS) return null;\n const startAngle = Math.atan2(ay - uy, ax - ux);\n const throughAngle = Math.atan2(by - uy, bx - ux);\n const endAngle = Math.atan2(cy - uy, cx - ux);\n const ccwSweep = normalizeAngle(endAngle - startAngle);\n if (ccwSweep < EPS || TWO_PI - ccwSweep < EPS) return null;\n // \u7ECF\u8FC7\u70B9\u843D\u5728\u9006\u65F6\u9488\u8DEF\u5F84\u4E0A \u2192 \u9006\u65F6\u9488\uFF1B\u5426\u5219\u8D70\u987A\u65F6\u9488\n const ccwThrough = normalizeAngle(throughAngle - startAngle);\n const sweep = ccwThrough <= ccwSweep ? ccwSweep : -(TWO_PI - ccwSweep);\n return { center: [ux, uy], radius, startAngle, sweep };\n}\n\n/** \u91C7\u6837\u5706\u5F27\u4E3A\u6298\u7EBF\uFF08\u542B\u9996\u5C3E\u70B9\uFF09\uFF1B\u9000\u5316\u65F6\u8FD4\u56DE null */\nexport function sampleArcThroughPoints(\n start: ArcPoint,\n through: ArcPoint,\n end: ArcPoint,\n count = 96,\n): { dataX: number[]; dataY: number[] } | null {\n const circle = circumcircleThrough(start, through, end);\n if (!circle) return null;\n const steps = Math.max(2, Math.round(count));\n const dataX: number[] = [];\n const dataY: number[] = [];\n for (let i = 0; i <= steps; i += 1) {\n const angle = circle.startAngle + (circle.sweep * i) / steps;\n dataX.push(circle.center[0] + Math.cos(angle) * circle.radius);\n dataY.push(circle.center[1] + Math.sin(angle) * circle.radius);\n }\n return { dataX, dataY };\n}\n", "/**\n * \u5706\u9525\u66F2\u7EBF\u76F8\u5173\u7EAF\u51FD\u6570\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u692D\u5706\u53C2\u6570 \u03C6 \u4E0E JSXGraph `ellipse` \u4E00\u81F4\uFF1A\u7ED5\u7126\u70B9 F1\u3001\u76F8\u5BF9 F1\u2192F2 \u65B9\u5411\u7684\u6781\u89D2\u3002\n */\n\nexport type Point2D = [number, number];\n\nconst TAU = Math.PI * 2;\n\n/** \u692D\u5706\u5EA6\u91CF\uFF1A\u957F\u8F74\u957F\uFF08\u5230\u4E24\u7126\u70B9\u8DDD\u79BB\u4E4B\u548C\uFF09\u3001\u7126\u8DDD\u3001\u7126\u70B9\u8F74\u65B9\u5411 */\nexport interface EllipseMetrics {\n /** \u957F\u8F74\u957F r = |F1P| + |F2P| */\n major: number;\n /** \u7126\u8DDD c = |F1F2| */\n focal: number;\n /** F1\u2192F2 \u65B9\u5411\u89D2 */\n axisAngle: number;\n}\n\n/**\n * \u7531\u4E24\u7126\u70B9\u4E0E\u692D\u5706\u4E0A\u4E00\u70B9\u8BA1\u7B97\u5EA6\u91CF\uFF1B\u7126\u70B9\u91CD\u5408\u3001\u4E09\u70B9\u5171\u7EBF\uFF08\u70B9\u843D\u5728\u4E24\u7126\u70B9\u8FDE\u7EBF\u4E0A\u4E14\u7B49\u4E8E\u7126\u8DDD\uFF09\u65F6\u8FD4\u56DE null\u3002\n */\nexport function ellipseMetricsOf(f1: Point2D, f2: Point2D, p: Point2D): EllipseMetrics | null {\n const focal = Math.hypot(f2[0] - f1[0], f2[1] - f1[1]);\n const major = Math.hypot(p[0] - f1[0], p[1] - f1[1]) + Math.hypot(p[0] - f2[0], p[1] - f2[1]);\n if (!Number.isFinite(focal) || !Number.isFinite(major)) return null;\n const scale = Math.max(1, Math.abs(f1[0]), Math.abs(f1[1]), Math.abs(f2[0]), Math.abs(f2[1]), Math.abs(p[0]), Math.abs(p[1]));\n const eps = scale * 1e-9;\n if (focal <= eps) return null;\n if (major <= focal + eps) return null;\n return {\n major,\n focal,\n axisAngle: Math.atan2(f2[1] - f1[1], f2[0] - f1[0]),\n };\n}\n\n/** \u692D\u5706\u51E0\u4F55\u6846\u67B6\uFF1A\u7126\u70B9\u3001\u5EA6\u91CF\u4E0E\u4E2D\u5FC3/\u65CB\u8F6C\uFF0C\u4F9B glider\u3001\u4EA4\u70B9\u3001SVG \u5171\u7528 */\nexport interface EllipseFrame {\n f1: Point2D;\n f2: Point2D;\n metrics: EllipseMetrics;\n}\n\n/**\n * \u7531\u4E2D\u5FC3\u3001\u534A\u8F74\u4E0E\u65CB\u8F6C\u8FD8\u539F\u7126\u70B9\u4E0E\u5EA6\u91CF\u3002\n *\n * \u5185\u6838\u5B58\u50A8\u692D\u5706\u65F6\u53EA\u4FDD\u7559 center/a/b/rotation\uFF1B\u4EFB\u4F55\u9700\u8981\u7126\u70B9\u6781\u89D2\u53C2\u6570\u7684\u5730\u65B9\n * \u90FD\u5FC5\u987B\u7ECF\u6B64\u51FD\u6570\u8FD8\u539F\uFF0C\u907F\u514D\u5404\u81EA\u590D\u5236\u516C\u5F0F\u9020\u6210\u53C2\u6570\u6F02\u79FB\u3002\n */\nexport function ellipseFrameOf(\n center: Point2D,\n a: number,\n b: number,\n rotation: number,\n): EllipseFrame {\n const halfFocal = Math.sqrt(Math.max(0, a * a - b * b));\n const cos = Math.cos(rotation);\n const sin = Math.sin(rotation);\n return {\n f1: [center[0] - cos * halfFocal, center[1] - sin * halfFocal],\n f2: [center[0] + cos * halfFocal, center[1] + sin * halfFocal],\n metrics: { major: a * 2, focal: halfFocal * 2, axisAngle: rotation },\n };\n}\n\n/** \u692D\u5706\u4E0A\u53C2\u6570 \u03C6 \u5BF9\u5E94\u7684\u70B9\uFF08\u4E0E JSXGraph \u6781\u5750\u6807\u5F62\u5F0F\u4E00\u81F4\uFF09 */\nexport function ellipsePointAt(metrics: EllipseMetrics, f1: Point2D, phi: number): Point2D {\n const { major: r, focal: c, axisAngle } = metrics;\n const radius = (0.5 * (c * c - r * r)) / (c * Math.cos(phi) - r);\n const angle = axisAngle + phi;\n return [f1[0] + Math.cos(angle) * radius, f1[1] + Math.sin(angle) * radius];\n}\n\n/** \u70B9\u51FB\u65B9\u5411 \u2192 \u692D\u5706\u53C2\u6570 \u03C6\uFF080..2\u03C0\uFF09\uFF0C\u4E0D\u8981\u6C42\u70B9\u51FB\u70B9\u843D\u5728\u692D\u5706\u4E0A */\nexport function ellipseParamFromDirection(f1: Point2D, f2: Point2D, point: Point2D): number {\n const axisAngle = Math.atan2(f2[1] - f1[1], f2[0] - f1[0]);\n const direction = Math.atan2(point[1] - f1[1], point[0] - f1[0]);\n const phi = direction - axisAngle;\n return ((phi % TAU) + TAU) % TAU;\n}\n\n/**\n * \u5F52\u4E00\u5316\u692D\u5706\u5F27\u8D77\u6B62\u53C2\u6570\uFF1Astart \u843D\u56DE 0..2\u03C0\uFF0Cend \u9006\u65F6\u9488\u5927\u4E8E start\u3002\n * \u8D77\u6B62\u65B9\u5411\u91CD\u5408\uFF08\u5DEE < 1e-6\uFF09\u65F6\u8FD4\u56DE null\uFF0C\u907F\u514D\u9000\u5316\u4E3A\u6574\u692D\u5706\u6216\u7A7A\u5F27\u3002\n */\nexport function normalizeEllipseRange(start: number, end: number): [number, number] | null {\n const s = ((start % TAU) + TAU) % TAU;\n let e = ((end % TAU) + TAU) % TAU;\n if (Math.abs(e - s) < 1e-6) return null;\n if (e <= s) e += TAU;\n return [s, e];\n}\n\n/** \u91C7\u6837\u6574\u692D\u5706\uFF08\u7528\u4E8E\u692D\u5706\u5F27\u9884\u89C8\uFF09 */\nexport function sampleEllipse(\n metrics: EllipseMetrics,\n f1: Point2D,\n start: number,\n end: number,\n count = 96,\n): Point2D[] {\n const n = Math.max(2, Math.floor(count));\n const out: Point2D[] = [];\n for (let i = 0; i <= n; i += 1) {\n out.push(ellipsePointAt(metrics, f1, start + ((end - start) * i) / n));\n }\n return out;\n}\n", "/**\n * \u7EA6\u675F\u70B9\uFF08glider\uFF09\u53C2\u6570 \u2192 \u5750\u6807\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u53C2\u6570\u8BED\u4E49\u89C1\u8BBE\u8BA1\u89C4\u683C \u00A76\uFF1A\n * - line / ray\uFF1A\u4ECE\u8D77\u70B9\u6CBF\u5355\u4F4D\u65B9\u5411\u7684\u6709\u7B26\u53F7\u8DDD\u79BB\uFF1B\n * - segment / polygon \u8FB9\uFF1At\u2208[0,1]\uFF1B\n * - circle\uFF1A\u5706\u5FC3\u89D2 \u03B8\uFF08\u4E0D\u505A\u94B3\u5236\uFF09\uFF1B\n * - arc / semicircle / sector\uFF1A\u89D2\u5EA6\u94B3\u5236\u5728\u5F27\u7684\u626B\u89D2\u8303\u56F4\u5185\uFF1B\n * - ellipse / ellipseArc\uFF1A\u53C2\u6570 \u03C6\uFF0C\u6309 `ellipsePointAt` \u7684\u7126\u70B9\u6781\u89D2\u5B9A\u4E49\uFF0C\n * \u6709 startParam/endParam \u65F6\u94B3\u5236\u5230\u8BE5\u533A\u95F4\uFF0C\u6574\u692D\u5706\u6309 2\u03C0 \u5468\u671F\u5F52\u4E00\u3002\n */\n\nimport type { ParentEdge } from '../core/types-geometry';\nimport { ellipseFrameOf, ellipseParamFromDirection, ellipsePointAt } from './conic.js';\nimport type { ResolvedElement } from './resolve';\n\nconst TWO_PI = Math.PI * 2;\nconst EPS = 1e-9;\n\nexport interface GliderInput {\n parent: ResolvedElement;\n param: number;\n /** \u591A\u8FB9\u5F62\u8FB9\u53F7\uFF08parents[k] \u2192 parents[k+1]\uFF09\uFF0C\u7F3A\u7701 0 */\n parentEdge?: ParentEdge;\n}\n\nexport interface GliderParamInput {\n parent: ResolvedElement;\n /** \u70B9\u51FB/\u62D6\u52A8\u4F4D\u7F6E\uFF08\u4E16\u754C\u5750\u6807\uFF09\uFF0C\u7528\u4E8E\u53CD\u7B97\u53C2\u6570 */\n point: [number, number];\n /** \u591A\u8FB9\u5F62\u8FB9\u53F7\uFF08parents[k] \u2192 parents[k+1]\uFF09\uFF0C\u7F3A\u7701 0 */\n parentEdge?: ParentEdge;\n}\n\n/** \u7EA6\u675F\u70B9\u5750\u6807\uFF1B\u7236\u7C7B\u578B\u4E0D\u652F\u6301\u3001\u8FB9\u53F7\u8D8A\u754C\u6216\u53C2\u6570\u975E\u6CD5\u65F6\u8FD4\u56DE null */\nexport function gliderPosition(input: GliderInput): [number, number] | null {\n const { parent, param } = input;\n if (!Number.isFinite(param)) return null;\n\n switch (parent.kind) {\n case 'line': {\n if (parent.extent === 'segment') {\n const from = parent.from ?? parent.origin;\n const to = parent.to;\n if (!to) return null;\n const t = clamp01(param);\n return [\n from[0] + (to[0] - from[0]) * t,\n from[1] + (to[1] - from[1]) * t,\n ];\n }\n const distance = parent.extent === 'ray' ? Math.max(0, param) : param;\n return [\n parent.origin[0] + parent.direction[0] * distance,\n parent.origin[1] + parent.direction[1] * distance,\n ];\n }\n case 'circle':\n return [\n parent.center[0] + parent.radius * Math.cos(param),\n parent.center[1] + parent.radius * Math.sin(param),\n ];\n case 'arc': {\n const angle = clampAngleToArc(param, parent);\n return [\n parent.center[0] + parent.radius * Math.cos(angle),\n parent.center[1] + parent.radius * Math.sin(angle),\n ];\n }\n case 'ellipse': {\n const phi = clampEllipseParam(param, parent);\n return ellipsePointOn(parent, phi);\n }\n case 'polygon': {\n const edge = input.parentEdge ?? 0;\n if (!Number.isInteger(edge) || edge < 0 || edge >= parent.vertices.length) return null;\n const a = parent.vertices[edge]!;\n const b = parent.vertices[(edge + 1) % parent.vertices.length]!;\n const t = clamp01(param);\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\n }\n default:\n return null;\n }\n}\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\n/**\n * \u70B9\u51FB/\u62D6\u52A8\u4F4D\u7F6E \u2192 \u7236\u5143\u7D20\u53C2\u6570\uFF08`gliderPosition` \u7684\u53CD\u7B97\uFF09\u3002\n * \u7236\u7C7B\u578B\u4E0D\u652F\u6301\u3001\u7236\u5143\u7D20\u9000\u5316\u6216\u8FB9\u53F7\u8D8A\u754C\u65F6\u8FD4\u56DE null\uFF1B\u53C2\u6570\u59CB\u7EC8\u94B3\u5236\u5230\u5408\u6CD5\u8303\u56F4\u3002\n */\nexport function gliderParamFromPoint(input: GliderParamInput): number | null {\n const { parent, point } = input;\n if (!Number.isFinite(point[0]) || !Number.isFinite(point[1])) return null;\n\n switch (parent.kind) {\n case 'line': {\n if (parent.extent === 'segment') {\n const from = parent.from ?? parent.origin;\n const to = parent.to;\n if (!to) return null;\n return segmentParam(from, to, point);\n }\n const length = Math.hypot(parent.direction[0], parent.direction[1]);\n if (!(length > EPS)) return null;\n const distance =\n ((point[0] - parent.origin[0]) * parent.direction[0]\n + (point[1] - parent.origin[1]) * parent.direction[1])\n / (length * length);\n return parent.extent === 'ray' ? Math.max(0, distance) : distance;\n }\n case 'circle': {\n if (!(parent.radius > EPS)) return null;\n return modTwoPi(Math.atan2(point[1] - parent.center[1], point[0] - parent.center[0]));\n }\n case 'arc': {\n if (!(parent.radius > EPS)) return null;\n if (Math.abs(arcSweep(parent)) < EPS) return null;\n const angle = Math.atan2(point[1] - parent.center[1], point[0] - parent.center[0]);\n return clampAngleToArc(angle, parent);\n }\n case 'ellipse': {\n if (!(parent.a > EPS) || !(parent.b > EPS)) return null;\n const cos = Math.cos(parent.rotation);\n const sin = Math.sin(parent.rotation);\n const halfFocal = Math.sqrt(Math.max(0, parent.a * parent.a - parent.b * parent.b));\n const f1: [number, number] = [\n parent.center[0] - cos * halfFocal,\n parent.center[1] - sin * halfFocal,\n ];\n const f2: [number, number] = [\n parent.center[0] + cos * halfFocal,\n parent.center[1] + sin * halfFocal,\n ];\n return clampEllipseParam(ellipseParamFromDirection(f1, f2, point), parent);\n }\n case 'polygon': {\n const edge = input.parentEdge ?? 0;\n if (!Number.isInteger(edge) || edge < 0 || edge >= parent.vertices.length) return null;\n const a = parent.vertices[edge]!;\n const b = parent.vertices[(edge + 1) % parent.vertices.length]!;\n return segmentParam(a, b, point);\n }\n default:\n return null;\n }\n}\n\n/** \u7EBF\u6BB5 t\u2208[0,1]\uFF1B\u4E24\u7AEF\u91CD\u5408\u65F6\u8FD4\u56DE null */\nfunction segmentParam(\n from: [number, number],\n to: [number, number],\n point: [number, number],\n): number | null {\n const dx = to[0] - from[0];\n const dy = to[1] - from[1];\n const length2 = dx * dx + dy * dy;\n if (!(length2 > EPS * EPS)) return null;\n return clamp01(((point[0] - from[0]) * dx + (point[1] - from[1]) * dy) / length2);\n}\n\nfunction modTwoPi(angle: number): number {\n const next = angle % TWO_PI;\n return next < 0 ? next + TWO_PI : next;\n}\n\n/** \u5F27\u7684\u6709\u7B26\u53F7\u626B\u89D2\uFF1Accw \u4E3A\u6B63\u5411\uFF0C\u5426\u5219\u4E3A\u53CD\u5411\uFF08\u5141\u8BB8 endAngle < startAngle\uFF09 */\nfunction arcSweep(arc: Extract<ResolvedElement, { kind: 'arc' }>): number {\n return arc.ccw\n ? modTwoPi(arc.endAngle - arc.startAngle)\n : -modTwoPi(arc.startAngle - arc.endAngle);\n}\n\n/** \u89D2\u5EA6\u94B3\u5236\u5230\u5F27\u7684\u626B\u89D2\u8303\u56F4\u5185\uFF08\u542B ccw=false \u4E0E\u8DE8 2\u03C0 \u60C5\u5F62\uFF09 */\nfunction clampAngleToArc(\n angle: number,\n arc: Extract<ResolvedElement, { kind: 'arc' }>,\n): number {\n const sweep = arcSweep(arc);\n if (Math.abs(sweep) < EPS) return arc.startAngle;\n const magnitude = sweep > 0\n ? modTwoPi(angle - arc.startAngle)\n : modTwoPi(arc.startAngle - angle);\n const bounded = Math.min(Math.abs(sweep), magnitude);\n return arc.startAngle + (sweep < 0 ? -bounded : bounded);\n}\n\n/** \u692D\u5706\u53C2\u6570\u94B3\u5236\uFF1A\u6574\u692D\u5706\u6309\u5468\u671F\u5F52\u4E00\uFF0C\u692D\u5706\u5F27\u94B3\u5236\u5230 [startParam, endParam] */\nfunction clampEllipseParam(\n param: number,\n ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>,\n): number {\n const start = ellipse.startParam ?? 0;\n const end = ellipse.endParam ?? TWO_PI;\n const sweep = end - start;\n if (!Number.isFinite(sweep) || sweep <= EPS) return start;\n if (sweep >= TWO_PI - EPS) return start + modTwoPi(param - start);\n return Math.min(end, Math.max(start, param));\n}\n\n/** \u692D\u5706\u53C2\u6570 \u03C6 \u2192 \u4E16\u754C\u5750\u6807\uFF08\u7126\u70B9\u6781\u89D2\uFF0C\u4E0E JSXGraph / intersections / SVG \u4E00\u81F4\uFF09 */\nfunction ellipsePointOn(\n ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>,\n phi: number,\n): [number, number] {\n const frame = ellipseFrameOf(ellipse.center, ellipse.a, ellipse.b, ellipse.rotation);\n return ellipsePointAt(frame.metrics, frame.f1, phi);\n}\n", "/**\n * \u8F68\u8FF9\uFF08locus\uFF09\u9A71\u52A8\u57DF\uFF1A\u7EA6\u675F\u70B9\u7236\u5143\u7D20 \u2192 \u9A71\u52A8\u53C2\u6570\u533A\u95F4\u3002\n *\n * \u53EA\u6709\u6709\u754C\u7236\u5143\u7D20\uFF08\u7EBF\u6BB5/\u5706/\u5706\u5F27/\u534A\u5706/\u692D\u5706/\u692D\u5706\u5F27\uFF09\u53EF\u4F5C\u4E3A\u8F68\u8FF9\u9A71\u52A8\uFF1B\n * \u76F4\u7EBF/\u5C04\u7EBF/\u6247\u5F62\u7B49\u65E0\u754C\u6216\u975E\u626B\u63A0\u5BF9\u8C61\u8FD4\u56DE null\uFF0C\u7531\u8C03\u7528\u65B9\u8F6C\u4E3A\u8BCA\u65AD\u3002\n */\n\nimport type { ParentEdge } from '../core/types-geometry';\nimport type { ResolvedElement } from './resolve';\n\nexport interface DriverDomain {\n from: number;\n to: number;\n closed: boolean;\n}\n\nconst TWO_PI = Math.PI * 2;\n\nexport function driverDomainOf(\n parent: ResolvedElement,\n _parentEdge?: ParentEdge,\n): DriverDomain | null {\n if (parent.kind === 'line') {\n if (parent.extent !== 'segment' || !parent.from || !parent.to) return null;\n return { from: 0, to: 1, closed: false };\n }\n if (parent.kind === 'circle') {\n return { from: 0, to: TWO_PI, closed: true };\n }\n if (parent.kind === 'ellipse') {\n const start = parent.startParam ?? 0;\n const end = parent.endParam ?? TWO_PI;\n if (Math.abs(end - start - TWO_PI) < 1e-9 && Math.abs(start) < 1e-9) {\n return { from: 0, to: TWO_PI, closed: true };\n }\n return { from: start, to: end, closed: false };\n }\n if (parent.kind === 'arc') {\n if (parent.arcKind === 'sector') return null;\n if (parent.arcKind === 'semicircle') {\n return { from: parent.startAngle, to: parent.startAngle + Math.PI, closed: false };\n }\n return { from: parent.startAngle, to: parent.endAngle, closed: false };\n }\n return null;\n}\n", "/**\n * \u6298\u7EBF\u4E0E\u53C2\u6570\u66F2\u7EBF\u91C7\u6837\u7684\u7EAF\u51FD\u6570\u5DE5\u5177\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u7528\u9014\uFF1A\n * - \u6781\u5750\u6807 / \u53C2\u6570\u65B9\u7A0B\u6309\u53C2\u6570\u91C7\u6837\u4E3A\u6298\u7EBF\uFF1B\n * - JXG implicitcurve \u7684 dataX/dataY \u8F6C\u6298\u7EBF\uFF08NaN \u65AD\u6BB5\uFF09\uFF1B\n * - \u66F2\u7EBF\u5438\u9644\uFF08\u6700\u8FD1\u70B9\u6295\u5F71\uFF09\u3001\u66F2\u7EBF\u00D7\u66F2\u7EBF\u3001\u66F2\u7EBF\u00D7\u51E0\u4F55\u6C42\u4EA4\u3002\n */\n\nexport interface PolylinePoint {\n x: number;\n y: number;\n /** \u66F2\u7EBF\u53C2\u6570\uFF1Aexplicit=x\uFF0Cpolar=\u03B8\uFF0Cparametric=t\uFF0Cimplicit=\u5168\u5C40\u7D2F\u8BA1\u5F27\u957F\u5F52\u4E00\u5316 0..1 */\n t: number;\n}\n\nexport type PolylineBranch = PolylinePoint[];\n\nexport interface PolylineProjection {\n x: number;\n y: number;\n /** \u6295\u5F71\u70B9\u5BF9\u5E94\u7684\u53C2\u6570\uFF08\u6309\u6BB5\u7EBF\u6027\u63D2\u503C\uFF09 */\n param: number;\n /** \u5F52\u4E00\u5316\u692D\u5706\u8DDD\u79BB\uFF08tol \u4E3A Infinity \u65F6\u8FD1\u4F3C 0\uFF09 */\n distance: number;\n}\n\nexport type Segment = [[number, number], [number, number]];\n\nconst DEFAULT_COUNT = 480;\nconst EPS = 1e-9;\n\n/** \u53C2\u6570\u533A\u95F4\u9006\u5E8F\u65F6\u7EDF\u4E00\u4E3A\u5347\u5E8F\uFF08JXG \u5BF9\u9006\u5E8F\u533A\u95F4\u4F1A\u7A7A\u7ED8\uFF09 */\nexport function normalizeParamRange(t0: number, t1: number): [number, number] {\n if (!Number.isFinite(t0) || !Number.isFinite(t1)) return [0, 0];\n return t0 <= t1 ? [t0, t1] : [t1, t0];\n}\n\n/** \u53C2\u6570\u91C7\u6837\uFF1ApointAt \u8FD4\u56DE null / \u975E\u6709\u9650\u503C\u5904\u65AD\u6BB5 */\nexport function sampleParametricPoint(\n pointAt: (t: number) => [number, number] | null,\n t0: number,\n t1: number,\n count = DEFAULT_COUNT,\n): PolylineBranch[] {\n const [lo, hi] = normalizeParamRange(t0, t1);\n const n = Math.max(2, Math.floor(count));\n const branches: PolylineBranch[] = [];\n let current: PolylineBranch = [];\n for (let i = 0; i <= n; i += 1) {\n const t = lo + ((hi - lo) * i) / n;\n let p: [number, number] | null = null;\n try {\n p = pointAt(t);\n } catch {\n p = null;\n }\n if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) {\n if (current.length > 0) branches.push(current);\n current = [];\n continue;\n }\n current.push({ x: p[0], y: p[1], t });\n }\n if (current.length > 0) branches.push(current);\n return branches;\n}\n\n/** dataX/dataY\uFF08NaN \u5206\u9694\uFF09\u2192 \u5206\u652F\uFF1Bt \u7528\u5168\u5C40\u7D2F\u8BA1\u5F27\u957F\u5F52\u4E00\u5316 0..1 */\nexport function polylineFromData(\n dataX: readonly number[],\n dataY: readonly number[],\n): PolylineBranch[] {\n const n = Math.min(dataX.length, dataY.length);\n const raw: Array<Array<{ x: number; y: number }>> = [];\n let current: Array<{ x: number; y: number }> = [];\n for (let i = 0; i < n; i += 1) {\n const x = dataX[i]!;\n const y = dataY[i]!;\n if (!Number.isFinite(x) || !Number.isFinite(y)) {\n if (current.length > 0) raw.push(current);\n current = [];\n continue;\n }\n current.push({ x, y });\n }\n if (current.length > 0) raw.push(current);\n if (raw.length === 0) return [];\n\n // \u5148\u7B97\u5404\u5206\u652F\u7D2F\u8BA1\u5F27\u957F\uFF0C\u518D\u7EDF\u4E00\u5F52\u4E00\u5316\n let total = 0;\n const accs: number[][] = raw.map((branch) => {\n // \u6BCF\u4E2A\u5206\u652F\u7684\u7D2F\u8BA1\u5F27\u957F\u4ECE\u4E0A\u4E00\u5206\u652F\u672B\u5C3E\u7EE7\u7EED\uFF0C\u4FDD\u8BC1\u8DE8\u5206\u652F t \u5355\u8C03\n const acc: number[] = [total];\n for (let i = 1; i < branch.length; i += 1) {\n total += Math.hypot(branch[i]!.x - branch[i - 1]!.x, branch[i]!.y - branch[i - 1]!.y);\n acc.push(total);\n }\n return acc;\n });\n return raw.map((branch, bi) =>\n branch.map((p, i) => ({\n x: p.x,\n y: p.y,\n t: total > EPS ? accs[bi]![i]! / total : 0,\n })),\n );\n}\n\n/**\n * \u6298\u7EBF\u6700\u8FD1\u70B9\u6295\u5F71\u3002tolX/tolY \u7528\u4E8E\u5F52\u4E00\u5316\u692D\u5706\u8DDD\u79BB\uFF1B\u7F3A\u7701 Infinity \u8868\u793A\u4E0D\u8BBE\u5BB9\u5DEE\u3002\n * \u8FD4\u56DE null \u8868\u793A\u6CA1\u6709\u70B9\u843D\u5728\u5BB9\u5DEE\u5185\u3002\n */\nexport function projectToPolyline(\n branches: PolylineBranch[],\n x: number,\n y: number,\n tolX = Number.POSITIVE_INFINITY,\n tolY = Number.POSITIVE_INFINITY,\n): PolylineProjection | null {\n if (!(tolX > 0) || !(tolY > 0)) return null;\n const useNorm = Number.isFinite(tolX) && Number.isFinite(tolY);\n // \u65E0\u5BB9\u5DEE\u65F6\u6309\u6B27\u6C0F\u8DDD\u79BB\u6BD4\u8F83\uFF0C\u5426\u5219\u6309\u5F52\u4E00\u5316\u692D\u5706\u8DDD\u79BB\n const metric = (px: number, py: number): number =>\n useNorm\n ? ((px - x) / tolX) ** 2 + ((py - y) / tolY) ** 2\n : (px - x) ** 2 + (py - y) ** 2;\n let bestMetric = Number.POSITIVE_INFINITY;\n let best: PolylineProjection | null = null;\n for (const branch of branches) {\n if (branch.length === 1) {\n const p = branch[0]!;\n const m = metric(p.x, p.y);\n if (m < bestMetric) {\n bestMetric = m;\n best = { x: p.x, y: p.y, param: p.t, distance: 0 };\n }\n continue;\n }\n for (let i = 1; i < branch.length; i += 1) {\n const a = branch[i - 1]!;\n const b = branch[i]!;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len2 = dx * dx + dy * dy;\n const u =\n len2 <= EPS\n ? 0\n : Math.min(1, Math.max(0, ((x - a.x) * dx + (y - a.y) * dy) / len2));\n const px = a.x + dx * u;\n const py = a.y + dy * u;\n const m = metric(px, py);\n if (m < bestMetric) {\n bestMetric = m;\n best = {\n x: px,\n y: py,\n param: a.t + (b.t - a.t) * u,\n distance: 0,\n };\n }\n }\n }\n if (!best) return null;\n if (useNorm && bestMetric > 1) return null;\n return { ...best, distance: Math.sqrt(Math.max(0, bestMetric)) };\n}\n\n/** \u53D6\u6298\u7EBF\u4E0A\u53C2\u6570 t\uFF080..1\uFF09\u5BF9\u5E94\u7684\u70B9\u4E0E\u5C40\u90E8\u5355\u4F4D\u65B9\u5411 */\nexport function pointAtPolylineParam(\n branches: PolylineBranch[],\n t: number,\n): { point: [number, number]; direction: [number, number] | null } | null {\n if (branches.length === 0) return null;\n const tt = Math.min(1, Math.max(0, t));\n for (const branch of branches) {\n if (branch.length === 1) continue;\n for (let i = 1; i < branch.length; i += 1) {\n const a = branch[i - 1]!;\n const b = branch[i]!;\n if (tt < a.t - 1e-9 || tt > b.t + 1e-9) continue;\n const span = b.t - a.t;\n const u = span > EPS ? Math.min(1, Math.max(0, (tt - a.t) / span)) : 0;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n return {\n point: [a.x + dx * u, a.y + dy * u],\n direction: len > EPS ? [dx / len, dy / len] : null,\n };\n }\n }\n // \u8D85\u51FA\u8303\u56F4\uFF1A\u5939\u5230\u9996/\u5C3E\u70B9\n const firstBranch = branches.find((b) => b.length > 0);\n const lastBranch = [...branches].reverse().find((b) => b.length > 0);\n if (!firstBranch || !lastBranch) return null;\n const p = tt <= 0 ? firstBranch[0]! : lastBranch[lastBranch.length - 1]!;\n return { point: [p.x, p.y], direction: null };\n}\n\nfunction pushUnique(out: Array<[number, number]>, p: [number, number], eps: number): void {\n if (out.some((q) => Math.hypot(q[0] - p[0], q[1] - p[1]) <= eps)) return;\n out.push(p);\n}\n\nfunction projectPointToSegment(\n p: [number, number],\n a: [number, number],\n b: [number, number],\n): [number, number] {\n const dx = b[0] - a[0];\n const dy = b[1] - a[1];\n const len2 = dx * dx + dy * dy;\n const u =\n len2 <= EPS ? 0 : Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));\n return [a[0] + dx * u, a[1] + dy * u];\n}\n\nfunction segmentIntersection(\n a0: [number, number],\n a1: [number, number],\n b0: [number, number],\n b1: [number, number],\n eps: number,\n): [number, number] | null {\n // bbox \u7C97\u7B5B\n if (\n Math.max(a0[0], a1[0]) < Math.min(b0[0], b1[0]) - eps ||\n Math.max(b0[0], b1[0]) < Math.min(a0[0], a1[0]) - eps ||\n Math.max(a0[1], a1[1]) < Math.min(b0[1], b1[1]) - eps ||\n Math.max(b0[1], b1[1]) < Math.min(a0[1], a1[1]) - eps\n ) {\n return null;\n }\n const rx = a1[0] - a0[0];\n const ry = a1[1] - a0[1];\n const sx = b1[0] - b0[0];\n const sy = b1[1] - b0[1];\n const den = rx * sy - ry * sx;\n // \u5E73\u884C\u5224\u5B9A\u6309\u7EBF\u6BB5\u957F\u5EA6\u5F52\u4E00\uFF1A\u56FA\u5B9A eps \u4F1A\u628A\u5C0F\u534A\u5F84/\u5BC6\u96C6\u91C7\u6837\u7684\u771F\u5B9E\u4EA4\u53C9\u8BEF\u5224\u4E3A\u5E73\u884C\n const parallelEps = Math.max(1e-12, Math.hypot(rx, ry) * Math.hypot(sx, sy) * 1e-9);\n if (Math.abs(den) > parallelEps) {\n const qx = b0[0] - a0[0];\n const qy = b0[1] - a0[1];\n const t = (qx * sy - qy * sx) / den;\n const u = (qx * ry - qy * rx) / den;\n if (t >= -eps && t <= 1 + eps && u >= -eps && u <= 1 + eps) {\n return [a0[0] + t * rx, a0[1] + t * ry];\n }\n }\n // \u8FD1\u4F3C\u76F8\u5207\uFF1A\u4E24\u6761\u7EBF\u6BB5\u6CA1\u6709\u771F\u4EA4\u53C9\uFF0C\u4F46\u6700\u8FD1\u8DDD\u79BB\u5728 eps \u5185 \u2192 \u53D6\u6700\u8FD1\u70B9\u4E2D\u70B9\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestPoint: [number, number] | null = null;\n const pairs: Array<[[number, number], [number, number]]> = [\n [a0, projectPointToSegment(a0, b0, b1)],\n [a1, projectPointToSegment(a1, b0, b1)],\n [projectPointToSegment(b0, a0, a1), b0],\n [projectPointToSegment(b1, a0, a1), b1],\n ];\n for (const [pa, pb] of pairs) {\n const d = Math.hypot(pa[0] - pb[0], pa[1] - pb[1]);\n if (d < bestDistance) {\n bestDistance = d;\n bestPoint = [(pa[0] + pb[0]) / 2, (pa[1] + pb[1]) / 2];\n }\n }\n return bestDistance <= eps ? bestPoint : null;\n}\n\nfunction branchSegments(branch: PolylineBranch): Segment[] {\n const out: Segment[] = [];\n for (let i = 1; i < branch.length; i += 1) {\n const a = branch[i - 1]!;\n const b = branch[i]!;\n out.push([\n [a.x, a.y],\n [b.x, b.y],\n ]);\n }\n return out;\n}\n\n/** \u6298\u7EBF \u00D7 \u6298\u7EBF\u6C42\u4EA4\uFF08\u7ED3\u679C\u6309 eps \u53BB\u91CD\uFF1B\u5171\u7EBF\u91CD\u53E0\u4E0D\u5355\u72EC\u5904\u7406\uFF09 */\nexport function intersectPolylines(\n a: PolylineBranch[],\n b: PolylineBranch[],\n eps = 1e-6,\n): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n const segsA = a.flatMap(branchSegments);\n const segsB = b.flatMap(branchSegments);\n for (const sa of segsA) {\n for (const sb of segsB) {\n const hit = segmentIntersection(sa[0], sa[1], sb[0], sb[1], eps);\n if (hit) pushUnique(out, hit, eps);\n }\n }\n return out;\n}\n\n/** \u6298\u7EBF \u00D7 \u7EBF\u6BB5\u96C6\u5408\u6C42\u4EA4\uFF08\u4F9B\u66F2\u7EBF \u00D7 \u51E0\u4F55\u5F62\u72B6\uFF09 */\nexport function intersectPolylineSegments(\n a: PolylineBranch[],\n segments: Segment[],\n eps = 1e-6,\n): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n const segsA = a.flatMap(branchSegments);\n for (const sa of segsA) {\n for (const sb of segments) {\n const hit = segmentIntersection(sa[0], sa[1], sb[0], sb[1], eps);\n if (hit) pushUnique(out, hit, eps);\n }\n }\n return out;\n}\n", "/**\n * \u5143\u7D20\u6C42\u4EA4\u4E0E\u7236\u5143\u7D20\u69FD\u4F4D\u89E3\u6790\uFF08\u5F15\u64CE\u65E0\u5173\uFF09\u3002\n *\n * \u6C42\u4EA4\u4F18\u5148\u7EA7\uFF1A\n * - \u7EBF\u00D7\u7EBF / \u7EBF\u00D7\u5706 / \u7EBF\u00D7\u692D\u5706 / \u5706\u00D7\u5706\uFF1A\u89E3\u6790\u89E3\uFF1B\n * - \u7EBF\u00D7\u591A\u8FB9\u5F62\uFF1A\u9010\u8FB9\u7EBF\u6BB5\u89E3\u6790\u6C42\u4EA4\uFF1B\u7EBF\u00D7\u5706\u5F27\uFF1A\u6309 64 \u6BB5\u6298\u7EBF\u9010\u6BB5\u89E3\u6790\uFF1B\n * - \u5176\u4F59\u7EC4\u5408\uFF08\u591A\u8FB9\u5F62 / \u5706\u5F27 / \u6247\u5F62 / \u692D\u5706\u5F27 \u4E4B\u95F4\u7684\u7EC4\u5408\uFF09\uFF1A\u91C7\u6837\u4E3A\u6298\u7EBF\u540E\n * `intersectPolylines` \u6C42\u4EA4\uFF0C\u5706\u4E0E\u692D\u5706 128 \u6BB5\u3001\u5706\u5F27 64 \u6BB5\u3002\n *\n * \u91C7\u6837\u7EC4\u5408\u7CBE\u5EA6\u53D7\u5F26\u957F\u9650\u5236\uFF08\u22641e-3\uFF09\uFF1B\u89E3\u6790\u7EC4\u5408\u8BEF\u5DEE \u22641e-9\u3002\n * \u7ED3\u679C\u7EDF\u4E00\u6309 x \u5347\u5E8F\u3001\u518D\u6309 y \u5347\u5E8F\uFF0C\u5E76\u53BB\u9664\u8DDD\u79BB <1e-9 \u7684\u91CD\u590D\u70B9\u3002\n */\n\nimport { intersectPolylines, type PolylineBranch } from '../core/polyline.js';\nimport type { ParentEdge } from '../core/types-geometry';\nimport { ellipseFrameOf, ellipsePointAt } from './conic.js';\nimport type { ResolvedElement } from './resolve';\n\ntype Point = [number, number];\n\nconst EPS = 1e-9;\nconst TWO_PI = Math.PI * 2;\nconst CIRCLE_SEGMENTS = 128;\nconst ELLIPSE_SEGMENTS = 128;\nconst ARC_SEGMENTS = 64;\n\n/** \u89E3\u6790\u540E\u7684\u7EBF\u7C7B\u51E0\u4F55\uFF08\u65B9\u5411\u4E3A\u5355\u4F4D\u5411\u91CF\uFF1Bsegment \u8BB0 from/to\uFF09 */\nexport interface ResolvedLine {\n origin: Point;\n direction: Point;\n extent: 'segment' | 'line' | 'ray';\n from?: Point;\n to?: Point;\n}\n\nfunction unitVector(dx: number, dy: number): Point | null {\n const length = Math.hypot(dx, dy);\n if (length < EPS) return null;\n return [dx / length, dy / length];\n}\n\n/**\n * \u7236\u5143\u7D20 + \u69FD\u4F4D \u2192 \u5B9E\u9645\u53C2\u4E0E\u8BA1\u7B97\u7684\u5143\u7D20\u3002\n * - \u69FD\u4F4D\u4E3A\u7A7A\uFF1A\u4F7F\u7528\u7236\u5143\u7D20\u672C\u8EAB\uFF1B\n * - \u591A\u8FB9\u5F62\u7B2C k \u6761\u8FB9\uFF1AVk \u2192 V(k+1)\uFF0C\u7EBF\u6BB5\uFF1B\n * - \u89D2\u5EA6\u7B2C k \u6761\u817F\uFF080 \u2192 parents[0]\uFF0C1 \u2192 parents[2]\uFF09\uFF1A\u4ECE\u9876\u70B9\u51FA\u53D1\u7684\u5C04\u7EBF\u3002\n */\nexport function elementWithParentEdge(\n element: ResolvedElement,\n edge: ParentEdge | null | undefined,\n): ResolvedElement | null {\n if (edge == null) return element;\n\n if (element.kind === 'polygon') {\n if (!Number.isInteger(edge) || edge < 0 || edge >= element.vertices.length) return null;\n const start = element.vertices[edge]!;\n const end = element.vertices[(edge + 1) % element.vertices.length]!;\n const direction = unitVector(end[0] - start[0], end[1] - start[1]);\n if (!direction) return null;\n return {\n kind: 'line',\n id: element.id,\n origin: [start[0], start[1]],\n direction,\n extent: 'segment',\n from: [start[0], start[1]],\n to: [end[0], end[1]],\n };\n }\n\n if (element.kind === 'angle') {\n if (edge !== 0 && edge !== 1) return null;\n const angle = element.startAngle + (edge === 1 ? element.sweep : 0);\n return {\n kind: 'line',\n id: element.id,\n origin: [element.vertex[0], element.vertex[1]],\n direction: [Math.cos(angle), Math.sin(angle)],\n extent: 'ray',\n from: [element.vertex[0], element.vertex[1]],\n };\n }\n\n return element;\n}\n\n/** \u7EBF\u7C7B\u7236\u5143\u7D20\uFF08\u53EF\u5E26\u8FB9/\u817F\u69FD\u4F4D\uFF09\u2192 \u7EBF\u51E0\u4F55\uFF1B\u975E\u7EBF\u7C7B\u8FD4\u56DE null */\nexport function resolvedLineOf(\n element: ResolvedElement,\n edge: ParentEdge | null | undefined,\n): ResolvedLine | null {\n const withEdge = elementWithParentEdge(element, edge);\n if (!withEdge || withEdge.kind !== 'line') return null;\n const line: ResolvedLine = {\n origin: [withEdge.origin[0], withEdge.origin[1]],\n direction: [withEdge.direction[0], withEdge.direction[1]],\n extent: withEdge.extent,\n };\n if (withEdge.from) line.from = [withEdge.from[0], withEdge.from[1]];\n if (withEdge.to) line.to = [withEdge.to[0], withEdge.to[1]];\n return line;\n}\n\n/** \u7EBF\u53C2\u6570\u8303\u56F4\uFF1Asegment \u4E3A\u957F\u5EA6\u5F52\u4E00\u540E\u7684 [0, |to\u2212from|]\uFF0Cray \u4E3A t\u22650\uFF0Cline \u4E0D\u9650\u5236 */\nfunction parameterWithin(t: number, line: ResolvedLine): boolean {\n if (line.extent === 'line') return true;\n if (line.extent === 'ray') return t >= -EPS;\n const from = line.from ?? line.origin;\n const to = line.to ?? line.origin;\n const length = Math.hypot(to[0] - from[0], to[1] - from[1]);\n return t >= -EPS && t <= length + EPS;\n}\n\nfunction intersectLines(a: ResolvedLine, b: ResolvedLine): Point[] {\n const denominator = a.direction[0] * b.direction[1] - a.direction[1] * b.direction[0];\n // \u5E73\u884C/\u5171\u7EBF\uFF1A\u65E0\u79BB\u6563\u4EA4\u70B9\n if (Math.abs(denominator) <= 1e-12) return [];\n const qx = b.origin[0] - a.origin[0];\n const qy = b.origin[1] - a.origin[1];\n const t = (qx * b.direction[1] - qy * b.direction[0]) / denominator;\n const u = (qx * a.direction[1] - qy * a.direction[0]) / denominator;\n if (!parameterWithin(t, a) || !parameterWithin(u, b)) return [];\n return [[a.origin[0] + t * a.direction[0], a.origin[1] + t * a.direction[1]]];\n}\n\nfunction intersectLineCircle(\n line: ResolvedLine,\n circle: Extract<ResolvedElement, { kind: 'circle' }>,\n): Point[] {\n const ox = line.origin[0] - circle.center[0];\n const oy = line.origin[1] - circle.center[1];\n // \u65B9\u5411\u4E3A\u5355\u4F4D\u5411\u91CF\uFF0C\u4E8C\u6B21\u9879\u7CFB\u6570\u6052\u4E3A 1\n const b = 2 * (line.direction[0] * ox + line.direction[1] * oy);\n const c = ox * ox + oy * oy - circle.radius * circle.radius;\n const discriminant = b * b - 4 * c;\n const tolerance = 1e-12 * Math.max(1, b * b);\n if (discriminant < -tolerance) return [];\n const root = Math.sqrt(Math.max(0, discriminant));\n const offsets = root <= Math.sqrt(tolerance) ? [-b / 2] : [(-b - root) / 2, (-b + root) / 2];\n const points: Point[] = [];\n for (const t of offsets) {\n if (!parameterWithin(t, line)) continue;\n points.push([line.origin[0] + t * line.direction[0], line.origin[1] + t * line.direction[1]]);\n }\n return points;\n}\n\n/** \u692D\u5706 \u2192 \u7126\u70B9\u6846\u67B6\uFF08\u4E0E ellipsePointAt \u7684\u7126\u70B9\u6781\u89D2\u53C2\u6570\u4E00\u81F4\uFF09 */\nfunction ellipseGeometry(ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>) {\n const frame = ellipseFrameOf(ellipse.center, ellipse.a, ellipse.b, ellipse.rotation);\n return {\n f1: frame.f1,\n metrics: frame.metrics,\n cos: Math.cos(ellipse.rotation),\n sin: Math.sin(ellipse.rotation),\n };\n}\n\n/** \u4E16\u754C\u70B9 \u2192 \u7ED5 F1\u3001\u76F8\u5BF9 F1\u2192F2 \u65B9\u5411\u7684\u6781\u89D2\uFF080..2\u03C0\uFF09 */\nfunction ellipseParamOf(point: Point, f1: Point, rotation: number): number {\n const dx = point[0] - f1[0];\n const dy = point[1] - f1[1];\n let phi = Math.atan2(dy, dx) - rotation;\n phi %= TWO_PI;\n if (phi < 0) phi += TWO_PI;\n return phi;\n}\n\nfunction intersectLineEllipse(\n line: ResolvedLine,\n ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>,\n): Point[] {\n const { f1, cos, sin } = ellipseGeometry(ellipse);\n const dx = line.origin[0] - ellipse.center[0];\n const dy = line.origin[1] - ellipse.center[1];\n const localX = dx * cos + dy * sin;\n const localY = -dx * sin + dy * cos;\n const localDx = line.direction[0] * cos + line.direction[1] * sin;\n const localDy = -line.direction[0] * sin + line.direction[1] * cos;\n const invA2 = 1 / (ellipse.a * ellipse.a);\n const invB2 = 1 / (ellipse.b * ellipse.b);\n const a = localDx * localDx * invA2 + localDy * localDy * invB2;\n const b = 2 * (localX * localDx * invA2 + localY * localDy * invB2);\n const c = localX * localX * invA2 + localY * localY * invB2 - 1;\n const discriminant = b * b - 4 * a * c;\n const tolerance = 1e-12 * Math.max(1, b * b);\n if (discriminant < -tolerance) return [];\n const root = Math.sqrt(Math.max(0, discriminant));\n const offsets = root <= Math.sqrt(tolerance) ? [-b / (2 * a)] : [(-b - root) / (2 * a), (-b + root) / (2 * a)];\n const start = ellipse.startParam ?? 0;\n const end = ellipse.endParam ?? TWO_PI;\n const points: Point[] = [];\n for (const t of offsets) {\n if (!parameterWithin(t, line)) continue;\n const px = localX + t * localDx;\n const py = localY + t * localDy;\n const world: Point = [\n ellipse.center[0] + px * cos - py * sin,\n ellipse.center[1] + px * sin + py * cos,\n ];\n let param = ellipseParamOf(world, f1, ellipse.rotation);\n if (param < start - EPS) param += TWO_PI;\n if (param > end + EPS) continue;\n points.push(world);\n }\n return points;\n}\n\nfunction intersectCircles(\n a: Extract<ResolvedElement, { kind: 'circle' }>,\n b: Extract<ResolvedElement, { kind: 'circle' }>,\n): Point[] {\n const dx = b.center[0] - a.center[0];\n const dy = b.center[1] - a.center[1];\n const distance = Math.hypot(dx, dy);\n // \u540C\u5FC3\uFF08\u542B\u91CD\u5408\uFF09\uFF1A\u65E0\u79BB\u6563\u4EA4\u70B9\n if (distance < EPS) return [];\n if (distance > a.radius + b.radius + EPS) return [];\n if (distance < Math.abs(a.radius - b.radius) - EPS) return [];\n const along = (distance * distance + a.radius * a.radius - b.radius * b.radius) / (2 * distance);\n const heightSquared = a.radius * a.radius - along * along;\n const tolerance = 1e-12 * Math.max(1, a.radius * a.radius);\n if (heightSquared < -tolerance) return [];\n const height = Math.sqrt(Math.max(0, heightSquared));\n const ex = dx / distance;\n const ey = dy / distance;\n const base: Point = [a.center[0] + ex * along, a.center[1] + ey * along];\n if (height <= Math.sqrt(tolerance)) return [base];\n return [\n [base[0] - ey * height, base[1] + ex * height],\n [base[0] + ey * height, base[1] - ex * height],\n ];\n}\n\nfunction circleBranch(circle: Extract<ResolvedElement, { kind: 'circle' }>): PolylineBranch {\n const points: PolylineBranch = [];\n for (let i = 0; i <= CIRCLE_SEGMENTS; i += 1) {\n const angle = (TWO_PI * i) / CIRCLE_SEGMENTS;\n points.push({\n x: circle.center[0] + Math.cos(angle) * circle.radius,\n y: circle.center[1] + Math.sin(angle) * circle.radius,\n t: i / CIRCLE_SEGMENTS,\n });\n }\n return points;\n}\n\nfunction ellipseBranch(ellipse: Extract<ResolvedElement, { kind: 'ellipse' }>): PolylineBranch {\n const { f1, metrics } = ellipseGeometry(ellipse);\n const start = ellipse.startParam ?? 0;\n const end = ellipse.endParam ?? TWO_PI;\n const points: PolylineBranch = [];\n for (let i = 0; i <= ELLIPSE_SEGMENTS; i += 1) {\n const param = start + ((end - start) * i) / ELLIPSE_SEGMENTS;\n const [x, y] = ellipsePointAt(metrics, f1, param);\n points.push({ x, y, t: i / ELLIPSE_SEGMENTS });\n }\n return points;\n}\n\nfunction arcBranch(arc: Extract<ResolvedElement, { kind: 'arc' }>): PolylineBranch {\n const points: PolylineBranch = [];\n for (let i = 0; i <= ARC_SEGMENTS; i += 1) {\n const angle = arc.startAngle + ((arc.endAngle - arc.startAngle) * i) / ARC_SEGMENTS;\n points.push({\n x: arc.center[0] + Math.cos(angle) * arc.radius,\n y: arc.center[1] + Math.sin(angle) * arc.radius,\n t: i / ARC_SEGMENTS,\n });\n }\n return points;\n}\n\nfunction polygonBranch(polygon: Extract<ResolvedElement, { kind: 'polygon' }>): PolylineBranch {\n const points: PolylineBranch = polygon.vertices.map(([x, y], index) => ({\n x,\n y,\n t: index / polygon.vertices.length,\n }));\n const first = polygon.vertices[0]!;\n points.push({ x: first[0], y: first[1], t: 1 });\n return points;\n}\n\nfunction shapeBranches(element: ResolvedElement): PolylineBranch[] | null {\n switch (element.kind) {\n case 'circle':\n return [circleBranch(element)];\n case 'ellipse':\n return [ellipseBranch(element)];\n case 'arc':\n return [arcBranch(element)];\n case 'polygon':\n return [polygonBranch(element)];\n default:\n return null;\n }\n}\n\nfunction polylineSegments(branches: PolylineBranch[]): ResolvedLine[] {\n const out: ResolvedLine[] = [];\n for (const branch of branches) {\n for (let i = 1; i < branch.length; i += 1) {\n const start = branch[i - 1]!;\n const end = branch[i]!;\n const direction = unitVector(end.x - start.x, end.y - start.y);\n if (!direction) continue;\n out.push({\n origin: [start.x, start.y],\n direction,\n extent: 'segment',\n from: [start.x, start.y],\n to: [end.x, end.y],\n });\n }\n }\n return out;\n}\n\nfunction intersectLineWithBranches(line: ResolvedLine, branches: PolylineBranch[]): Point[] {\n const points: Point[] = [];\n for (const segment of polylineSegments(branches)) {\n points.push(...intersectLines(line, segment));\n }\n return points;\n}\n\nfunction intersectLineWithShape(\n line: ResolvedLine,\n shape: ResolvedElement,\n): Point[] {\n switch (shape.kind) {\n case 'circle':\n return intersectLineCircle(line, shape);\n case 'ellipse':\n return intersectLineEllipse(line, shape);\n case 'polygon':\n return intersectLineWithBranches(line, [polygonBranch(shape)]);\n case 'arc':\n return intersectLineWithBranches(line, [arcBranch(shape)]);\n default:\n return [];\n }\n}\n\nfunction intersectPair(a: ResolvedElement, b: ResolvedElement): Point[] {\n if (a.kind === 'line' && b.kind === 'line') return intersectLines(a, b);\n if (a.kind === 'line') return intersectLineWithShape(a, b);\n if (b.kind === 'line') return intersectLineWithShape(b, a);\n if (a.kind === 'circle' && b.kind === 'circle') return intersectCircles(a, b);\n const branchesA = shapeBranches(a);\n const branchesB = shapeBranches(b);\n if (!branchesA || !branchesB) return [];\n return intersectPolylines(branchesA, branchesB, EPS);\n}\n\nfunction sortAndDedupe(points: Point[]): Point[] {\n const sorted = [...points].sort((p, q) => p[0] - q[0] || p[1] - q[1]);\n const out: Point[] = [];\n for (const point of sorted) {\n const last = out[out.length - 1];\n if (last && Math.hypot(point[0] - last[0], point[1] - last[1]) < EPS) continue;\n out.push([point[0], point[1]]);\n }\n return out;\n}\n\n/** \u6C42\u4E24\u89E3\u6790\u5143\u7D20\uFF08\u5DF2\u6309\u69FD\u4F4D\u5904\u7406\uFF09\u7684\u5168\u90E8\u4EA4\u70B9\uFF0C\u6309 x\u3001y \u5347\u5E8F\u5E76\u53BB\u91CD */\nexport function intersectResolved(a: ResolvedElement, b: ResolvedElement): Point[] {\n return sortAndDedupe(intersectPair(a, b));\n}\n", "/**\n * \u6D4B\u91CF\u8BA1\u7B97\uFF08\u5F15\u64CE\u65E0\u5173\uFF09\u3002\n *\n * `measureResolved` \u53EA\u8FD4\u56DE\u7EAF\u6570\u503C\uFF1B`measureLabelPosition` \u8FD4\u56DE\u4E16\u754C\u5750\u6807\u951A\u70B9\u3002\n * \u89C4\u683C\u4E2D\u7684\u50CF\u7D20\u504F\u79FB\uFF08\u5982\u7EBF\u6BB5\u4E2D\u70B9\u6CD5\u5411\u5916\u504F 14px\uFF09\u7531 UI/SVG \u6E32\u67D3\u5C42\u65BD\u52A0\uFF0C\u6B64\u5904\u4E0D\u53E0\u52A0\u5C4F\u5E55\u5355\u4F4D\u3002\n */\n\nimport type { MeasurementKind } from '../core/types-geometry';\nimport type { ResolvedElement } from './resolve';\n\nconst EPSILON = 1e-9;\nconst TWO_PI = Math.PI * 2;\n\ntype Point = [number, number];\ntype ArcElement = Extract<ResolvedElement, { kind: 'arc' }>;\n\nfunction distance(a: Point, b: Point): number {\n return Math.hypot(b[0] - a[0], b[1] - a[1]);\n}\n\nfunction midpoint(a: Point, b: Point): Point {\n return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n}\n\nfunction polygonEdge(vertices: Point[], edge: number): [Point, Point] | null {\n if (!Number.isInteger(edge) || edge < 0 || edge >= vertices.length) return null;\n const a = vertices[edge];\n const b = vertices[(edge + 1) % vertices.length];\n if (!a || !b) return null;\n return [a, b];\n}\n\n/**\n * \u5F27\u7684\u5B9E\u9645\u626B\u89D2\uFF1AendAngle \u2212 startAngle \u53EF\u80FD\u8DE8 2\u03C0\uFF08\u6216\u88AB\u5F52\u4E00\u5316\u5230\u53E6\u4E00\u4FA7\uFF09\uFF0C\n * \u56E0\u6B64\u6309 ccw \u65B9\u5411\u91CD\u65B0\u5F52\u4E00\u5316\uFF0C\u53D6 (0, 2\u03C0] \u5185\u7684\u6709\u7B26\u53F7\u626B\u89D2\uFF08ccw \u4E3A\u6B63\uFF0C\u987A\u65F6\u9488\u4E3A\u8D1F\uFF09\u3002\n */\nfunction arcSweepOf(arc: ArcElement): number | null {\n const raw = arc.endAngle - arc.startAngle;\n if (!Number.isFinite(raw) || Math.abs(raw) < EPSILON) return null;\n if (arc.ccw) {\n const sweep = ((raw % TWO_PI) + TWO_PI) % TWO_PI;\n return sweep < EPSILON ? TWO_PI : sweep;\n }\n const sweep = (((-raw % TWO_PI) + TWO_PI) % TWO_PI);\n return -(sweep < EPSILON ? TWO_PI : sweep);\n}\n\nfunction lengthOf(target: ResolvedElement, edge: number | undefined): number | null {\n if (target.kind === 'line') {\n if (target.extent !== 'segment' || !target.from || !target.to) return null;\n return distance(target.from, target.to);\n }\n if (target.kind === 'polygon') {\n const pair = polygonEdge(target.vertices, edge ?? 0);\n return pair ? distance(pair[0], pair[1]) : null;\n }\n return null;\n}\n\nfunction perimeterOf(target: ResolvedElement): number | null {\n switch (target.kind) {\n case 'polygon': {\n if (target.vertices.length < 3) return null;\n let sum = 0;\n for (let i = 0; i < target.vertices.length; i += 1) {\n const pair = polygonEdge(target.vertices, i);\n if (!pair) return null;\n sum += distance(pair[0], pair[1]);\n }\n return sum < EPSILON ? null : sum;\n }\n case 'circle':\n return target.radius > EPSILON ? TWO_PI * target.radius : null;\n case 'ellipse': {\n const { a, b } = target;\n if (a <= EPSILON || b <= EPSILON) return null;\n // Ramanujan \u8FD1\u4F3C\n return Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)));\n }\n case 'arc': {\n const sweep = arcSweepOf(target);\n if (sweep === null || target.radius <= EPSILON) return null;\n return 2 * target.radius + target.radius * Math.abs(sweep);\n }\n default:\n return null;\n }\n}\n\nfunction areaOf(target: ResolvedElement): number | null {\n switch (target.kind) {\n case 'polygon': {\n if (target.vertices.length < 3) return null;\n let sum = 0;\n for (let i = 0; i < target.vertices.length; i += 1) {\n const p = target.vertices[i];\n const q = target.vertices[(i + 1) % target.vertices.length];\n if (!p || !q) return null;\n sum += p[0] * q[1] - q[0] * p[1];\n }\n const area = Math.abs(sum) / 2;\n return area < EPSILON ? null : area;\n }\n case 'circle':\n return target.radius > EPSILON ? Math.PI * target.radius ** 2 : null;\n case 'ellipse': {\n const { a, b } = target;\n return a > EPSILON && b > EPSILON ? Math.PI * a * b : null;\n }\n case 'arc': {\n const sweep = arcSweepOf(target);\n if (sweep === null || target.radius <= EPSILON) return null;\n // \u6247\u5F62\u9762\u79EF \u00BDr\u00B2\u03B8\n return (target.radius ** 2 * Math.abs(sweep)) / 2;\n }\n default:\n return null;\n }\n}\n\n/** \u76EE\u6807\u7C7B\u578B\u4E0D\u652F\u6301\u3001\u53C2\u6570\u8D8A\u754C\u6216\u9000\u5316\u65F6\u8FD4\u56DE null */\nexport function measureResolved(input: {\n kind: MeasurementKind;\n target: ResolvedElement;\n edge?: number;\n}): number | null {\n const { kind, target, edge } = input;\n const value = kind === 'length'\n ? lengthOf(target, edge)\n : kind === 'perimeter'\n ? perimeterOf(target)\n : areaOf(target);\n if (value === null || !Number.isFinite(value) || value < 0) return null;\n return value;\n}\n\n/** \u9762\u79EF\u8D28\u5FC3\uFF1B\u9000\u5316\uFF08\u96F6\u9762\u79EF\uFF09\u65F6\u8FD4\u56DE null\uFF0C\u7531\u8C03\u7528\u65B9\u56DE\u9000\u5230\u9876\u70B9\u5747\u503C */\nfunction polygonCentroid(vertices: Point[]): Point | null {\n let twiceArea = 0;\n let x = 0;\n let y = 0;\n for (let i = 0; i < vertices.length; i += 1) {\n const p = vertices[i];\n const q = vertices[(i + 1) % vertices.length];\n if (!p || !q) return null;\n const cross = p[0] * q[1] - q[0] * p[1];\n twiceArea += cross;\n x += (p[0] + q[0]) * cross;\n y += (p[1] + q[1]) * cross;\n }\n if (Math.abs(twiceArea) < EPSILON) return null;\n const scale = 1 / (3 * twiceArea);\n return [x * scale, y * scale];\n}\n\nfunction vertexAverage(vertices: Point[]): Point | null {\n if (vertices.length === 0) return null;\n let x = 0;\n let y = 0;\n for (const vertex of vertices) {\n x += vertex[0];\n y += vertex[1];\n }\n return [x / vertices.length, y / vertices.length];\n}\n\n/**\n * \u6807\u7B7E\u951A\u70B9\uFF08\u4E16\u754C\u5750\u6807\uFF09\u3002\u957F\u5EA6\u53D6\u7EBF\u6BB5/\u591A\u8FB9\u5F62\u8FB9\u4E2D\u70B9\uFF0C\u5468\u957F\u4E0E\u9762\u79EF\u53D6\u591A\u8FB9\u5F62\u9762\u79EF\u8D28\u5FC3\n * \uFF08\u9000\u5316\u65F6\u9876\u70B9\u5747\u503C\uFF09\u3001\u5706/\u692D\u5706/\u5F27\u53D6\u5706\u5FC3\u3002\n * \u50CF\u7D20\u504F\u79FB\uFF08\u5982\u6CD5\u5411\u5916\u504F 14px\u3001\u4E2D\u5FC3\u4E0A\u65B9\u504F\u79FB\uFF09\u7531 UI/SVG \u6E32\u67D3\u5C42\u65BD\u52A0\u3002\n */\nexport function measureLabelPosition(\n kind: MeasurementKind,\n target: ResolvedElement,\n edge?: number,\n): Point | null {\n if (kind === 'length') {\n if (target.kind === 'line') {\n if (target.extent !== 'segment' || !target.from || !target.to) return null;\n return midpoint(target.from, target.to);\n }\n if (target.kind === 'polygon') {\n const pair = polygonEdge(target.vertices, edge ?? 0);\n return pair ? midpoint(pair[0], pair[1]) : null;\n }\n return null;\n }\n if (target.kind === 'polygon') {\n return polygonCentroid(target.vertices) ?? vertexAverage(target.vertices);\n }\n if (target.kind === 'circle' || target.kind === 'ellipse' || target.kind === 'arc') {\n return [target.center[0], target.center[1]];\n }\n return null;\n}\n\nconst MEASURE_PREFIX: Record<MeasurementKind, string> = {\n length: '|AB| = ',\n perimeter: 'C = ',\n area: 'S = ',\n};\n\nexport function formatMeasure(kind: MeasurementKind, value: number, precision = 2): string {\n return `${MEASURE_PREFIX[kind]}${value.toFixed(precision)}`;\n}\n", "/**\n * \u4EFF\u5C04\u53D8\u6362\u77E9\u9635\u4E0E\u5DF2\u89E3\u6790\u5143\u7D20\u7684\u57FA\u7840\u7C7B\u578B\u6620\u5C04\u3002\n *\n * \u77E9\u9635\u6309 SVG \u60EF\u4F8B\u8868\u793A\uFF1Ax' = a\u00B7x + b\u00B7y + c\uFF0Cy' = d\u00B7x + e\u00B7y + f\u3002\n * \u8986\u76D6\u70B9/\u7EBF/\u5706/\u591A\u8FB9\u5F62/\u5F27/\u692D\u5706/\u89D2\u5EA6/\u6587\u5B57\uFF1B\u5176\u4F59\u7C7B\u578B\u8FD4\u56DE null\u3002\n */\n\nimport type { TransformSpec } from '../core/types-geometry';\nimport type { ResolvedElement } from './resolve';\nimport {\n ellipseFrameOf,\n ellipseParamFromDirection,\n ellipsePointAt,\n} from './conic.js';\n\nconst TWO_PI = Math.PI * 2;\n\nexport interface AffineMatrix {\n a: number; b: number; c: number;\n d: number; e: number; f: number;\n}\n\nexport function applyPoint(m: AffineMatrix, p: [number, number]): [number, number] {\n return [m.a * p[0] + m.b * p[1] + m.c, m.d * p[0] + m.e * p[1] + m.f];\n}\n\nexport function applyVector(m: AffineMatrix, v: [number, number]): [number, number] {\n return [m.a * v[0] + m.b * v[1], m.d * v[0] + m.e * v[1]];\n}\n\nexport function similarityScale(m: AffineMatrix): number {\n return Math.sqrt(Math.abs(m.a * m.e - m.b * m.d));\n}\n\nexport function translateMatrix(dx: number, dy: number): AffineMatrix {\n return { a: 1, b: 0, c: dx, d: 0, e: 1, f: dy };\n}\n\nexport function rotateMatrix(center: [number, number], angleRad: number): AffineMatrix {\n const cos = Math.cos(angleRad);\n const sin = Math.sin(angleRad);\n return {\n a: cos,\n b: -sin,\n c: center[0] - cos * center[0] + sin * center[1],\n d: sin,\n e: cos,\n f: center[1] - sin * center[0] - cos * center[1],\n };\n}\n\nexport function reflectPointMatrix(center: [number, number]): AffineMatrix {\n return { a: -1, b: 0, c: 2 * center[0], d: 0, e: -1, f: 2 * center[1] };\n}\n\nexport function scaleMatrix(center: [number, number], factor: number): AffineMatrix {\n return {\n a: factor,\n b: 0,\n c: center[0] * (1 - factor),\n d: 0,\n e: factor,\n f: center[1] * (1 - factor),\n };\n}\n\nexport function reflectLineMatrix(\n origin: [number, number],\n direction: [number, number],\n): AffineMatrix {\n const length = Math.hypot(direction[0], direction[1]);\n const ux = direction[0] / length;\n const uy = direction[1] / length;\n const a = 2 * ux * ux - 1;\n const b = 2 * ux * uy;\n const d = 2 * ux * uy;\n const e = 2 * uy * uy - 1;\n return {\n a, b,\n c: origin[0] - (a * origin[0] + b * origin[1]),\n d, e,\n f: origin[1] - (d * origin[0] + e * origin[1]),\n };\n}\n\nexport function matrixForTransform(\n spec: TransformSpec,\n byId: ReadonlyMap<string, ResolvedElement>,\n): AffineMatrix | null {\n switch (spec.kind) {\n case 'translate':\n if (![spec.dx, spec.dy].every(Number.isFinite)) return null;\n return translateMatrix(spec.dx, spec.dy);\n case 'rotate': {\n const center = byId.get(spec.center);\n if (!center || center.kind !== 'point' || !Number.isFinite(spec.angleDeg)) return null;\n return rotateMatrix(center.position, (spec.angleDeg * Math.PI) / 180);\n }\n case 'reflectLine': {\n const axis = byId.get(spec.axis);\n if (!axis || axis.kind !== 'line') return null;\n if (!(Math.hypot(axis.direction[0], axis.direction[1]) > 1e-9)) return null;\n return reflectLineMatrix(axis.origin, axis.direction);\n }\n case 'reflectPoint': {\n const center = byId.get(spec.center);\n if (!center || center.kind !== 'point') return null;\n return reflectPointMatrix(center.position);\n }\n case 'scale': {\n const center = byId.get(spec.center);\n if (!center || center.kind !== 'point') return null;\n if (!Number.isFinite(spec.factor) || Math.abs(spec.factor) <= 1e-9) return null;\n return scaleMatrix(center.position, spec.factor);\n }\n }\n}\n\nfunction transformedAngle(m: AffineMatrix, angle: number): number {\n const v = applyVector(m, [Math.cos(angle), Math.sin(angle)]);\n return Math.atan2(v[1], v[0]);\n}\n\nexport function isFullEllipse(source: Extract<ResolvedElement, { kind: 'ellipse' }>): boolean {\n return source.startParam === 0\n && Math.abs((source.endParam ?? TWO_PI) - TWO_PI) < 1e-9;\n}\n\nfunction transformArc(\n source: Extract<ResolvedElement, { kind: 'arc' }>,\n m: AffineMatrix,\n id: string,\n): Extract<ResolvedElement, { kind: 'arc' }> {\n const det = m.a * m.e - m.b * m.d;\n const startAngle = transformedAngle(m, source.startAngle);\n const signedSweep = source.endAngle - source.startAngle;\n const sweep = det < 0 ? -signedSweep : signedSweep;\n return {\n kind: 'arc',\n id,\n center: applyPoint(m, source.center),\n radius: source.radius * similarityScale(m),\n startAngle,\n endAngle: startAngle + sweep,\n ccw: sweep > 0,\n arcKind: source.arcKind,\n };\n}\n\nfunction transformEllipse(\n source: Extract<ResolvedElement, { kind: 'ellipse' }>,\n m: AffineMatrix,\n id: string,\n): Extract<ResolvedElement, { kind: 'ellipse' }> | null {\n const det = m.a * m.e - m.b * m.d;\n const scale = similarityScale(m);\n const a = source.a * scale;\n const b = source.b * scale;\n if (!(a > 1e-9) || !(b > 1e-9)) return null;\n const center = applyPoint(m, source.center);\n const rotation = transformedAngle(m, source.rotation);\n if (isFullEllipse(source)) {\n return { kind: 'ellipse', id, center, a, b, rotation, startParam: 0, endParam: TWO_PI };\n }\n const oldFrame = ellipseFrameOf(source.center, source.a, source.b, source.rotation);\n const newFrame = ellipseFrameOf(center, a, b, rotation);\n const startPoint = applyPoint(m, ellipsePointAt(oldFrame.metrics, oldFrame.f1, source.startParam ?? 0));\n const endPoint = applyPoint(m, ellipsePointAt(oldFrame.metrics, oldFrame.f1, source.endParam ?? TWO_PI));\n let startParam = ellipseParamFromDirection(newFrame.f1, newFrame.f2, startPoint);\n let endParam = ellipseParamFromDirection(newFrame.f1, newFrame.f2, endPoint);\n if (det < 0) {\n [startParam, endParam] = [endParam, startParam];\n }\n while (endParam <= startParam + 1e-9) endParam += TWO_PI;\n return { kind: 'ellipse', id, center, a, b, rotation, startParam, endParam };\n}\n\nexport function transformResolved(\n source: ResolvedElement,\n m: AffineMatrix,\n id: string,\n): ResolvedElement | null {\n switch (source.kind) {\n case 'point':\n return { kind: 'point', id, position: applyPoint(m, source.position) };\n case 'line': {\n const origin = applyPoint(m, source.origin);\n const direction = applyVector(m, source.direction);\n const length = Math.hypot(direction[0], direction[1]);\n if (!(length > 1e-9)) return null;\n const line: Extract<ResolvedElement, { kind: 'line' }> = {\n kind: 'line',\n id,\n origin,\n direction: [direction[0] / length, direction[1] / length],\n extent: source.extent,\n };\n if (source.from) line.from = applyPoint(m, source.from);\n if (source.to) line.to = applyPoint(m, source.to);\n return line;\n }\n case 'circle':\n return {\n kind: 'circle',\n id,\n center: applyPoint(m, source.center),\n radius: source.radius * similarityScale(m),\n };\n case 'polygon':\n return { kind: 'polygon', id, vertices: source.vertices.map((p) => applyPoint(m, p)) };\n case 'arc':\n return transformArc(source, m, id);\n case 'ellipse':\n return transformEllipse(source, m, id);\n case 'angle': {\n const det = m.a * m.e - m.b * m.d;\n const sweep = det < 0 ? -source.sweep : source.sweep;\n return {\n kind: 'angle',\n id,\n vertex: applyPoint(m, source.vertex),\n startAngle: transformedAngle(m, source.startAngle),\n sweep,\n valueRad: Math.abs(sweep),\n };\n }\n case 'text':\n return {\n kind: 'text',\n id,\n position: applyPoint(m, source.position),\n raw: source.raw,\n format: source.format,\n };\n default:\n return null;\n }\n}\n", "/**\n * \u5C06 GeometryDocument \u5143\u7D20\u89E3\u6790\u4E3A\u6E32\u67D3/\u6D4B\u91CF\u5171\u7528\u7684\u51E0\u4F55\u7ED3\u679C\u3002\n *\n * \u8986\u76D6 point / midpoint / segment / line / ray / circle / circumcircle /\n * arc / semicircle / sector / ellipse / ellipseArc / polygon / parallelogram /\n * rhombus / intersection / perpendicular / parallel / angleBisector / angle /\n * circleTangent / glider / text / measure\u3002\u66F2\u7EBF\u6D3E\u751F\u7C7B\u578B\u8FD4\u56DE UNSUPPORTED_CURVE_DEPENDENT warning\uFF0C\u9000\u5316\u8F93\u5165\u8FD4\u56DE\n * DEGENERATE warning\uFF0C\u907F\u514D\u9759\u9ED8\u4E22\u5F03\u5143\u7D20\u3002\n */\n\nimport type { GeometryElementSpec, LocusSpec, MeasurementKind } from '../core/types-geometry';\nimport type { GeometryDiagnostic } from './kernel';\nimport { circumcircleThrough } from './arc.js';\nimport { ellipseMetricsOf, normalizeEllipseRange } from './conic.js';\nimport { gliderPosition } from './glider.js';\nimport { driverDomainOf } from './locus.js';\nimport { elementWithParentEdge, intersectResolved, resolvedLineOf } from './intersections.js';\nimport { formatMeasure, measureLabelPosition, measureResolved } from './measure.js';\nimport { matrixForTransform, transformResolved } from './transform.js';\n\nexport type ResolvedElement =\n | { kind: 'point'; id: string; position: [number, number] }\n | { kind: 'line'; id: string; origin: [number, number]; direction: [number, number]; extent: 'segment' | 'line' | 'ray'; from?: [number, number]; to?: [number, number] }\n | { kind: 'circle'; id: string; center: [number, number]; radius: number }\n | { kind: 'arc'; id: string; center: [number, number]; radius: number; startAngle: number; endAngle: number; ccw: boolean; arcKind?: 'arc' | 'semicircle' | 'sector' }\n | { kind: 'ellipse'; id: string; center: [number, number]; a: number; b: number; rotation: number; startParam?: number; endParam?: number }\n | { kind: 'polygon'; id: string; vertices: Array<[number, number]> }\n | { kind: 'angle'; id: string; vertex: [number, number]; startAngle: number; sweep: number; valueRad: number }\n | { kind: 'text'; id: string; position: [number, number]; raw: string; format: 'plain' | 'latex' }\n // \u8BBE\u8BA1\u89C4\u683C\u539F\u7A3F\u6B64\u5904\u7B2C\u4E8C\u4E2A\u5B57\u6BB5\u540D\u4E0E\u5224\u522B\u5B57\u6BB5 kind \u91CD\u540D\uFF0C\u6539\u7528 measurementKind\u3002\n | { kind: 'measure'; id: string; measurementKind: MeasurementKind; target: string; value: number; formatted: string; labelPosition: [number, number] }\n | { kind: 'locus'; id: string; points: Array<[number, number]>; closed: boolean };\n\nexport interface ResolveElementsResult {\n elements: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n}\n\nconst ZERO_EPSILON = 1e-9;\nconst TWO_PI = Math.PI * 2;\n\n/** \u6709\u7B26\u53F7\u89D2\u5DEE\u5F52\u4E00\u5230 (\u2212\u03C0, \u03C0]\uFF0C\u7528\u4E8E\u6247\u5F62\u53EA\u53D6\u52A3\u5F27 */\nfunction normalizeAngleDelta(delta: number): number {\n const next = ((delta + Math.PI) % TWO_PI + TWO_PI) % TWO_PI - Math.PI;\n if (next <= -Math.PI + ZERO_EPSILON) return Math.PI;\n return next;\n}\n\nfunction unitVector(dx: number, dy: number): [number, number] | null {\n const length = Math.hypot(dx, dy);\n if (length < ZERO_EPSILON) return null;\n return [dx / length, dy / length];\n}\n\ninterface ResolveCoreResult {\n elements: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n resolvedById: Map<string, ResolvedElement>;\n}\n\n/**\n * \u89E3\u6790\u975E\u8F68\u8FF9\u5143\u7D20\uFF1Boverrides \u6309\u5143\u7D20 id \u8986\u76D6\u7EA6\u675F\u70B9\u53C2\u6570\uFF08\u8F68\u8FF9\u91C7\u6837\u7528\uFF09\u3002\n */\nfunction resolveCore(\n elements: GeometryElementSpec[],\n overrides?: ReadonlyMap<string, number>,\n): ResolveCoreResult {\n const resolved: ResolvedElement[] = [];\n const diagnostics: GeometryDiagnostic[] = [];\n const positions = new Map<string, [number, number]>();\n /** \u5DF2\u89E3\u6790\u5143\u7D20\u8868\uFF1A\u4EA4\u70B9\u548C\u6D3E\u751F\u7EBF\u6309 id \u67E5\u627E\u7236\u5143\u7D20\uFF08\u542B\u7EBF\u6BB5/\u591A\u8FB9\u5F62/\u89D2\u5EA6\uFF09 */\n const resolvedById = new Map<string, ResolvedElement>();\n\n const add = (element: ResolvedElement): void => {\n resolved.push(element);\n resolvedById.set(element.id, element);\n };\n\n const pointOf = (id: string): [number, number] | null => positions.get(id) ?? null;\n const degenerate = (elementId: string, message: string): void => {\n diagnostics.push({ code: 'DEGENERATE', severity: 'warning', elementId, message });\n };\n\n for (const element of elements) {\n switch (element.type) {\n case 'point': {\n const position: [number, number] = [element.position[0], element.position[1]];\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'midpoint': {\n const [firstId, secondId] = element.parents;\n const first = pointOf(firstId);\n const second = pointOf(secondId);\n if (!first || !second) {\n degenerate(element.id, `\u4E2D\u70B9 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const position: [number, number] = [(first[0] + second[0]) / 2, (first[1] + second[1]) / 2];\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'segment':\n case 'line':\n case 'ray': {\n const origin = pointOf(element.parents[0]);\n const target = pointOf(element.parents[1]);\n if (!origin || !target) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const direction = unitVector(target[0] - origin[0], target[1] - origin[1]);\n if (!direction) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u957F\u5EA6\u4E3A\u96F6`);\n break;\n }\n const line: Extract<ResolvedElement, { kind: 'line' }> = {\n kind: 'line', id: element.id, origin: [origin[0], origin[1]], direction, extent: element.type,\n };\n if (element.type === 'segment') {\n line.from = [origin[0], origin[1]];\n line.to = [target[0], target[1]];\n } else if (element.type === 'ray') {\n line.from = [origin[0], origin[1]];\n }\n add(line);\n break;\n }\n case 'circle': {\n const center = pointOf(element.parents[0]);\n if (!center) {\n degenerate(element.id, `\u5706 \"${element.id}\" \u7684\u5706\u5FC3\u7F3A\u5931`);\n break;\n }\n let radius = 1;\n if (element.parents.length === 2) {\n const rim = pointOf(element.parents[1]);\n if (!rim) {\n degenerate(element.id, `\u5706 \"${element.id}\" \u7684\u534A\u5F84\u70B9\u7F3A\u5931`);\n break;\n }\n radius = Math.hypot(rim[0] - center[0], rim[1] - center[1]);\n }\n if (radius < ZERO_EPSILON) {\n degenerate(element.id, `\u5706 \"${element.id}\" \u534A\u5F84\u4E3A\u96F6`);\n break;\n }\n add({ kind: 'circle', id: element.id, center: [center[0], center[1]], radius });\n break;\n }\n case 'circumcircle': {\n const first = pointOf(element.parents[0]);\n const second = pointOf(element.parents[1]);\n const third = pointOf(element.parents[2]);\n if (!first || !second || !third) {\n degenerate(element.id, `\u5916\u63A5\u5706 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const circle = circumcircleThrough(first, second, third);\n if (!circle) {\n degenerate(element.id, `\u5916\u63A5\u5706 \"${element.id}\" \u7684\u4E09\u70B9\u5171\u7EBF\u6216\u91CD\u5408`);\n break;\n }\n add({\n kind: 'circle',\n id: element.id,\n center: [circle.center[0], circle.center[1]],\n radius: circle.radius,\n });\n break;\n }\n case 'arc': {\n const start = pointOf(element.parents[0]);\n const through = pointOf(element.parents[1]);\n const end = pointOf(element.parents[2]);\n if (!start || !through || !end) {\n degenerate(element.id, `\u5706\u5F27 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const circle = circumcircleThrough(start, through, end);\n if (!circle) {\n degenerate(element.id, `\u5706\u5F27 \"${element.id}\" \u7684\u4E09\u70B9\u5171\u7EBF\u6216\u91CD\u5408`);\n break;\n }\n add({\n kind: 'arc',\n id: element.id,\n center: [circle.center[0], circle.center[1]],\n radius: circle.radius,\n startAngle: circle.startAngle,\n endAngle: circle.startAngle + circle.sweep,\n ccw: circle.sweep > 0,\n arcKind: 'arc',\n });\n break;\n }\n case 'semicircle': {\n const start = pointOf(element.parents[0]);\n const end = pointOf(element.parents[1]);\n if (!start || !end) {\n degenerate(element.id, `\u534A\u5706 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const center: [number, number] = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];\n const radius = Math.hypot(end[0] - start[0], end[1] - start[1]) / 2;\n if (radius < ZERO_EPSILON) {\n degenerate(element.id, `\u534A\u5706 \"${element.id}\" \u7684\u76F4\u5F84\u4E3A\u96F6`);\n break;\n }\n const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);\n add({\n kind: 'arc',\n id: element.id,\n center,\n radius,\n startAngle,\n endAngle: startAngle + Math.PI,\n ccw: true,\n arcKind: 'semicircle',\n });\n break;\n }\n case 'sector': {\n const center = pointOf(element.parents[0]);\n const start = pointOf(element.parents[1]);\n const end = pointOf(element.parents[2]);\n if (!center || !start || !end) {\n degenerate(element.id, `\u6247\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const radius = Math.hypot(start[0] - center[0], start[1] - center[1]);\n const endRadius = Math.hypot(end[0] - center[0], end[1] - center[1]);\n if (radius < ZERO_EPSILON || endRadius < ZERO_EPSILON) {\n degenerate(element.id, `\u6247\u5F62 \"${element.id}\" \u7684\u534A\u5F84\u4E3A\u96F6`);\n break;\n }\n const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);\n const endDirection = Math.atan2(end[1] - center[1], end[0] - center[0]);\n const delta = normalizeAngleDelta(endDirection - startAngle);\n if (Math.abs(delta) < ZERO_EPSILON) {\n degenerate(element.id, `\u6247\u5F62 \"${element.id}\" \u7684\u5706\u5FC3\u89D2\u4E3A\u96F6`);\n break;\n }\n add({\n kind: 'arc',\n id: element.id,\n center: [center[0], center[1]],\n radius,\n startAngle,\n endAngle: startAngle + delta,\n ccw: delta > 0,\n arcKind: 'sector',\n });\n break;\n }\n case 'ellipse':\n case 'ellipseArc': {\n const firstFocus = pointOf(element.parents[0]);\n const secondFocus = pointOf(element.parents[1]);\n const rim = pointOf(element.parents[2]);\n if (!firstFocus || !secondFocus || !rim) {\n degenerate(element.id, `\u692D\u5706 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const metrics = ellipseMetricsOf(firstFocus, secondFocus, rim);\n if (!metrics) {\n degenerate(element.id, `\u692D\u5706 \"${element.id}\" \u7684\u7126\u70B9\u91CD\u5408\u6216\u70B9\u4F4D\u9000\u5316`);\n break;\n }\n const a = metrics.major / 2;\n const halfFocal = metrics.focal / 2;\n const b = Math.sqrt(a * a - halfFocal * halfFocal);\n if (!Number.isFinite(b) || b <= 0) {\n degenerate(element.id, `\u692D\u5706 \"${element.id}\" \u7684\u77ED\u534A\u8F74\u9000\u5316`);\n break;\n }\n const ellipse: Extract<ResolvedElement, { kind: 'ellipse' }> = {\n kind: 'ellipse',\n id: element.id,\n center: [(firstFocus[0] + secondFocus[0]) / 2, (firstFocus[1] + secondFocus[1]) / 2],\n a,\n b,\n rotation: metrics.axisAngle,\n startParam: 0,\n endParam: TWO_PI,\n };\n if (element.type === 'ellipseArc') {\n const span = element.endParam - element.startParam;\n if (!Number.isFinite(span)) {\n degenerate(element.id, `\u692D\u5706\u5F27 \"${element.id}\" \u7684\u8D77\u6B62\u53C2\u6570\u975E\u6CD5`);\n break;\n }\n if (Math.abs(span) >= TWO_PI - 1e-6) {\n // \u6574\u5468\u5F27\u7B49\u4EF7\u4E8E\u6574\u692D\u5706\uFF1B\u907F\u514D\u5F52\u4E00\u5316\u628A 2\u03C0 \u8BEF\u5224\u4E3A\u91CD\u5408\n ellipse.startParam = 0;\n ellipse.endParam = TWO_PI;\n } else {\n const range = normalizeEllipseRange(element.startParam, element.endParam);\n if (!range) {\n degenerate(element.id, `\u692D\u5706\u5F27 \"${element.id}\" \u7684\u8D77\u6B62\u53C2\u6570\u91CD\u5408`);\n break;\n }\n ellipse.startParam = range[0];\n ellipse.endParam = range[1];\n }\n }\n add(ellipse);\n break;\n }\n case 'polygon': {\n const vertices: Array<[number, number]> = [];\n let missingParent = false;\n for (const parentId of element.parents) {\n const point = pointOf(parentId);\n if (!point) {\n missingParent = true;\n break;\n }\n vertices.push([point[0], point[1]]);\n }\n if (missingParent) {\n degenerate(element.id, `\u591A\u8FB9\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n if (vertices.length < 3) {\n degenerate(element.id, `\u591A\u8FB9\u5F62 \"${element.id}\" \u7684\u9876\u70B9\u5C11\u4E8E 3 \u4E2A`);\n break;\n }\n add({ kind: 'polygon', id: element.id, vertices });\n break;\n }\n case 'parallelogram': {\n const a = pointOf(element.parents[0]);\n const b = pointOf(element.parents[1]);\n const c = pointOf(element.parents[2]);\n if (!a || !b || !c) {\n degenerate(element.id, `\u5E73\u884C\u56DB\u8FB9\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n add({\n kind: 'polygon',\n id: element.id,\n vertices: [a, b, c, [a[0] + c[0] - b[0], a[1] + c[1] - b[1]]],\n });\n break;\n }\n case 'rhombus': {\n const a = pointOf(element.parents[0]);\n const b = pointOf(element.parents[1]);\n const c = pointOf(element.parents[2]);\n if (!a || !b || !c) {\n degenerate(element.id, `\u83F1\u5F62 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const direction = unitVector(c[0] - b[0], c[1] - b[1]);\n const edgeLength = Math.hypot(b[0] - a[0], b[1] - a[1]);\n if (!direction || edgeLength < ZERO_EPSILON) {\n degenerate(element.id, `\u83F1\u5F62 \"${element.id}\" \u7684\u8FB9\u9000\u5316\u4E3A\u96F6`);\n break;\n }\n const cPrime: [number, number] = [b[0] + direction[0] * edgeLength, b[1] + direction[1] * edgeLength];\n const d: [number, number] = [a[0] + direction[0] * edgeLength, a[1] + direction[1] * edgeLength];\n add({ kind: 'polygon', id: element.id, vertices: [a, b, cPrime, d] });\n break;\n }\n case 'intersection': {\n const parentA = resolvedById.get(element.parents[0]);\n const parentB = resolvedById.get(element.parents[1]);\n if (!parentA || !parentB) {\n degenerate(element.id, `\u4EA4\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u7F3A\u5931`);\n break;\n }\n const shapeA = elementWithParentEdge(parentA, element.parentEdges?.[0] ?? null);\n const shapeB = elementWithParentEdge(parentB, element.parentEdges?.[1] ?? null);\n if (!shapeA || !shapeB) {\n degenerate(element.id, `\u4EA4\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u8FB9/\u817F\u8D85\u51FA\u8303\u56F4`);\n break;\n }\n const points = intersectResolved(shapeA, shapeB);\n const index = element.index ?? 0;\n if (!Number.isInteger(index) || index < 0 || index >= points.length) {\n degenerate(element.id, `\u4EA4\u70B9 \"${element.id}\" \u7684\u7B2C ${index} \u4E2A\u4EA4\u70B9\u4E0D\u5B58\u5728`);\n break;\n }\n const point = points[index]!;\n const position: [number, number] = [point[0], point[1]];\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'perpendicular':\n case 'parallel': {\n const base = resolvedById.get(element.parents[0]);\n const through = pointOf(element.parents[1]);\n if (!base || !through) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u7684\u7236\u7EBF\u6216\u8FC7\u70B9\u7F3A\u5931`);\n break;\n }\n const baseLine = resolvedLineOf(base, element.parentEdges?.[0] ?? null);\n if (!baseLine) {\n degenerate(element.id, `${element.type} \"${element.id}\" \u7684\u7236\u7EBF\u4E0D\u53EF\u7528`);\n break;\n }\n const direction: [number, number] = element.type === 'perpendicular'\n ? [-baseLine.direction[1], baseLine.direction[0]]\n : [baseLine.direction[0], baseLine.direction[1]];\n add({\n kind: 'line',\n id: element.id,\n origin: [through[0], through[1]],\n direction,\n extent: 'line',\n });\n break;\n }\n case 'angleBisector': {\n const first = pointOf(element.parents[0]);\n const vertex = pointOf(element.parents[1]);\n const second = pointOf(element.parents[2]);\n if (!first || !vertex || !second) {\n degenerate(element.id, `\u89D2\u5E73\u5206\u7EBF \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const firstDirection = unitVector(first[0] - vertex[0], first[1] - vertex[1]);\n const secondDirection = unitVector(second[0] - vertex[0], second[1] - vertex[1]);\n if (!firstDirection || !secondDirection) {\n degenerate(element.id, `\u89D2\u5E73\u5206\u7EBF \"${element.id}\" \u7684\u8FB9\u9000\u5316\u4E3A\u96F6`);\n break;\n }\n const direction = unitVector(\n firstDirection[0] + secondDirection[0],\n firstDirection[1] + secondDirection[1],\n );\n if (!direction) {\n degenerate(element.id, `\u89D2\u5E73\u5206\u7EBF \"${element.id}\" \u7684\u4E24\u8FB9\u53CD\u5411\u5171\u7EBF`);\n break;\n }\n add({\n kind: 'line',\n id: element.id,\n origin: [vertex[0], vertex[1]],\n direction,\n extent: 'line',\n });\n break;\n }\n case 'angle': {\n const first = pointOf(element.parents[0]);\n const vertex = pointOf(element.parents[1]);\n const second = pointOf(element.parents[2]);\n if (!first || !vertex || !second) {\n degenerate(element.id, `\u89D2 \"${element.id}\" \u7684\u7236\u70B9\u7F3A\u5931`);\n break;\n }\n const firstDirection = unitVector(first[0] - vertex[0], first[1] - vertex[1]);\n const secondDirection = unitVector(second[0] - vertex[0], second[1] - vertex[1]);\n if (!firstDirection || !secondDirection) {\n degenerate(element.id, `\u89D2 \"${element.id}\" \u7684\u8FB9\u9000\u5316\u4E3A\u96F6`);\n break;\n }\n const startAngle = Math.atan2(firstDirection[1], firstDirection[0]);\n const endAngle = Math.atan2(secondDirection[1], secondDirection[0]);\n const sweep = normalizeAngleDelta(endAngle - startAngle);\n if (Math.abs(sweep) < ZERO_EPSILON) {\n degenerate(element.id, `\u89D2 \"${element.id}\" \u7684\u5706\u5FC3\u89D2\u4E3A\u96F6`);\n break;\n }\n add({\n kind: 'angle',\n id: element.id,\n vertex: [vertex[0], vertex[1]],\n startAngle,\n sweep,\n valueRad: Math.abs(sweep),\n });\n break;\n }\n case 'circleTangent': {\n const circle = resolvedById.get(element.parents[0]);\n if (!circle || circle.kind !== 'circle') {\n degenerate(element.id, `\u5706\u5207\u7EBF \"${element.id}\" \u7684\u7236\u5706\u7F3A\u5931`);\n break;\n }\n const radial = unitVector(\n element.position[0] - circle.center[0],\n element.position[1] - circle.center[1],\n );\n if (!radial) {\n degenerate(element.id, `\u5706\u5207\u7EBF \"${element.id}\" \u7684\u5207\u70B9\u4E0E\u5706\u5FC3\u91CD\u5408`);\n break;\n }\n const tangentPoint: [number, number] = [\n circle.center[0] + radial[0] * circle.radius,\n circle.center[1] + radial[1] * circle.radius,\n ];\n add({\n kind: 'line',\n id: element.id,\n origin: tangentPoint,\n direction: [-radial[1], radial[0]],\n extent: 'line',\n });\n break;\n }\n case 'glider': {\n const parent = resolvedById.get(element.parent);\n if (!parent) {\n degenerate(element.id, `\u7EA6\u675F\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u7F3A\u5931`);\n break;\n }\n const position = gliderPosition({\n parent,\n param: overrides?.get(element.id) ?? element.param,\n parentEdge: element.parentEdge,\n });\n if (!position) {\n degenerate(element.id, `\u7EA6\u675F\u70B9 \"${element.id}\" \u7684\u7236\u5143\u7D20\u7C7B\u578B\u4E0D\u652F\u6301\u6216\u53C2\u6570\u8D8A\u754C`);\n break;\n }\n positions.set(element.id, position);\n add({ kind: 'point', id: element.id, position });\n break;\n }\n case 'measure': {\n const target = resolvedById.get(element.target);\n if (!target) {\n degenerate(element.id, `\u6D4B\u91CF \"${element.id}\" \u7684\u76EE\u6807\u5143\u7D20 \"${element.target}\" \u7F3A\u5931`);\n break;\n }\n const value = measureResolved({ kind: element.kind, target, edge: element.edge });\n const labelPosition = measureLabelPosition(element.kind, target, element.edge);\n if (value === null || !labelPosition) {\n degenerate(element.id, `\u6D4B\u91CF \"${element.id}\" \u7684\u76EE\u6807\u5143\u7D20\u4E0D\u53EF\u6D4B`);\n break;\n }\n add({\n kind: 'measure',\n id: element.id,\n measurementKind: element.kind,\n target: element.target,\n value,\n formatted: formatMeasure(element.kind, value, element.precision ?? 2),\n labelPosition,\n });\n break;\n }\n case 'text': {\n add({\n kind: 'text',\n id: element.id,\n position: [element.position[0], element.position[1]],\n raw: element.raw,\n format: element.format ?? 'plain',\n });\n break;\n }\n case 'transform': {\n const source = resolvedById.get(element.source);\n if (!source) {\n degenerate(element.id, `\u53D8\u6362 \"${element.id}\" \u7684\u6E90\u5143\u7D20\u7F3A\u5931`);\n break;\n }\n const matrix = matrixForTransform(element, resolvedById);\n if (!matrix) {\n degenerate(element.id, `\u53D8\u6362 \"${element.id}\" \u7684\u53C2\u6570\u6216\u4E2D\u5FC3/\u8F74\u4E0D\u53EF\u7528`);\n break;\n }\n const transformed = transformResolved(source, matrix, element.id);\n if (!transformed) {\n diagnostics.push({\n code: 'UNSUPPORTED_TYPE',\n severity: 'warning',\n elementId: element.id,\n message: `\u53D8\u6362 \"${element.id}\" \u4E0D\u652F\u6301\u6E90\u7C7B\u578B ${source.kind}`,\n });\n break;\n }\n add(transformed);\n break;\n }\n case 'curvePoint':\n case 'curveIntersection':\n case 'curveTangent':\n diagnostics.push({\n code: 'UNSUPPORTED_CURVE_DEPENDENT',\n severity: 'warning',\n elementId: element.id,\n message: `\u66F2\u7EBF\u6D3E\u751F\u5143\u7D20 \"${element.id}\"\uFF08${element.type}\uFF09\u5728 MCP v1 \u4E2D\u6682\u4E0D\u652F\u6301`,\n });\n break;\n }\n }\n\n return { elements: resolved, diagnostics, resolvedById };\n}\n\n/**\n * \u89E3\u6790\u5143\u7D20\u5217\u8868\uFF08\u6784\u9020\u987A\u5E8F\u5373\u4F9D\u8D56\u987A\u5E8F\uFF09\u3002\n * \u5DF2\u89E3\u6790\u7684\u5750\u6807\u70B9\u5B58\u5165 positions\uFF0C\u4F9B\u540E\u7EED\u7C7B\u578B\u6309 id \u67E5\u627E\u7236\u70B9\u3002\n * \u8F68\u8FF9\u5355\u72EC\u91C7\u6837\uFF1A\u5176\u4F59\u5143\u7D20\u89E3\u6790\u4E00\u6B21\uFF0C\u8F68\u8FF9\u6309\u9A71\u52A8\u53C2\u6570\u91CD\u590D\u89E3\u6790\u3002\n */\nexport function resolveElements(\n elements: GeometryElementSpec[],\n options: { overrides?: ReadonlyMap<string, number> } = {},\n): ResolveElementsResult {\n const nonLocus = elements.filter((el) => el.type !== 'locus');\n const core = resolveCore(nonLocus, options.overrides);\n const diagnostics = [...core.diagnostics];\n const loci: ResolvedElement[] = [];\n for (const spec of elements) {\n if (spec.type !== 'locus') continue;\n const result = resolveLocus(spec, nonLocus, core);\n if (result.locus) loci.push(result.locus);\n diagnostics.push(...result.diagnostics);\n }\n return { elements: [...core.elements, ...loci], diagnostics };\n}\n\ninterface LocusResolution {\n locus: Extract<ResolvedElement, { kind: 'locus' }> | null;\n diagnostics: GeometryDiagnostic[];\n}\n\n/** \u8F68\u8FF9\u91C7\u6837\u6B21\u6570\u4E0A\u9650\uFF1B\u6B65\u8FDB\u5FAA\u73AF\u56FA\u5B9A\u6267\u884C samples \u6B21\uFF0C\u4E0D\u5B58\u5728\u53D1\u6563\u5FAA\u73AF */\nconst MAX_LOCUS_SAMPLES = 400;\nconst MAX_LOCUS_WORK = 100_000;\n\nfunction resolveLocus(\n spec: LocusSpec,\n nonLocus: GeometryElementSpec[],\n core: ResolveCoreResult,\n): LocusResolution {\n const invalid = (message: string): LocusResolution => ({\n locus: null,\n diagnostics: [{ code: 'DEGENERATE', severity: 'warning', elementId: spec.id, message }],\n });\n const driverSpec = nonLocus.find((el) => el.id === spec.driver);\n if (!driverSpec || driverSpec.type !== 'glider') return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u9A71\u52A8\u5143\u7D20\u4E0D\u662F\u7EA6\u675F\u70B9`);\n const driverParent = core.resolvedById.get(driverSpec.parent);\n if (!driverParent) return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u9A71\u52A8\u7236\u5143\u7D20\u7F3A\u5931`);\n const domain = driverDomainOf(driverParent, driverSpec.parentEdge);\n if (!domain) {\n return {\n locus: null,\n diagnostics: [\n {\n code: 'UNSUPPORTED_TYPE',\n severity: 'warning',\n elementId: spec.id,\n message: `\u8F68\u8FF9 \"${spec.id}\" \u7684\u9A71\u52A8\u5BF9\u8C61\u65E0\u754C\u6216\u7C7B\u578B\u4E0D\u652F\u6301`,\n },\n ],\n };\n }\n const requested = spec.samples ?? 200;\n if (!Number.isFinite(requested)) return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u91C7\u6837\u6570\u975E\u6CD5`);\n // \u91C7\u6837\u6570\u6309\u89C4\u683C\u94B3\u5236\u5230 16\u2013400\uFF0C\u7F3A\u7701 200\n const samples = Math.max(16, Math.min(MAX_LOCUS_SAMPLES, Math.round(requested)));\n if (samples * nonLocus.length > MAX_LOCUS_WORK) {\n return {\n locus: null,\n diagnostics: [\n {\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n elementId: spec.id,\n message: `\u8F68\u8FF9 \"${spec.id}\" \u91C7\u6837\u91CF\u8D85\u9650\uFF08samples=${samples}\uFF0C\u5143\u7D20=${nonLocus.length}\uFF09`,\n },\n ],\n };\n }\n\n const step = domain.closed\n ? (domain.to - domain.from) / samples\n : (domain.to - domain.from) / (samples - 1);\n const points: Array<[number, number]> = [];\n let failed = 0;\n for (let i = 0; i < samples; i += 1) {\n const param = domain.from + step * i;\n const resolved = resolveCore(nonLocus, new Map([[spec.driver, param]]));\n const trace = resolved.resolvedById.get(spec.trace);\n if (!trace || trace.kind !== 'point') {\n failed += 1;\n continue;\n }\n points.push([trace.position[0], trace.position[1]]);\n }\n if (points.length < 2) return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u7684\u6709\u6548\u91C7\u6837\u70B9\u5C11\u4E8E 2 \u4E2A`);\n const first = points[0]!;\n if (points.every((p) => Math.hypot(p[0] - first[0], p[1] - first[1]) < 1e-9)) {\n return invalid(`\u8F68\u8FF9 \"${spec.id}\" \u9000\u5316\u4E3A\u4E00\u70B9`);\n }\n const diagnostics: GeometryDiagnostic[] = failed > 0\n ? [{\n code: 'DEGENERATE',\n severity: 'warning',\n elementId: spec.id,\n message: `\u8F68\u8FF9 \"${spec.id}\" \u6709 ${failed} \u4E2A\u91C7\u6837\u70B9\u89E3\u6790\u5931\u8D25`,\n }]\n : [];\n return { locus: { kind: 'locus', id: spec.id, points, closed: domain.closed }, diagnostics };\n}\n", "/**\n * \u89D2\u5EA6\u6807\u6CE8\u6587\u672C\u683C\u5F0F\u5316\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n * \u9ED8\u8BA4\u5355\u4F4D\u4E3A\u5EA6\uFF08deg\uFF09\uFF1A'90.0\u00B0'\uFF1B\u5F27\u5EA6\uFF08rad\uFF09\uFF1A'1.57 rad'\u3002\n */\n\nimport type { GeometryAngleUnit } from '../core/types-geometry';\n\nexport const DEFAULT_ANGLE_UNIT: GeometryAngleUnit = 'deg';\n\n/** \u5EA6\u6570 \u2192 \u6309\u5355\u4F4D\u683C\u5F0F\u5316\u7684\u663E\u793A\u6587\u672C */\nexport function formatAngle(degrees: number, unit: GeometryAngleUnit = DEFAULT_ANGLE_UNIT): string {\n if (!Number.isFinite(degrees)) return '';\n if (unit === 'rad') return `${((degrees * Math.PI) / 180).toFixed(2)} rad`;\n return `${degrees.toFixed(1)}\u00B0`;\n}\n", "/**\n * \u51E0\u4F55\u5185\u6838\u5165\u53E3\uFF1A\u6587\u6863\u6821\u9A8C + \u5143\u7D20\u89E3\u6790 + \u8BCA\u65AD\u6C47\u603B\u3002\n * \u4E0D\u629B\u5F02\u5E38\uFF0C\u6240\u6709\u9519\u8BEF\u8FDB\u5165 diagnostics\uFF1B\u4EC5\u5B58\u5728 error \u7EA7\u8BCA\u65AD\u65F6 ok \u4E3A false\u3002\n */\n\nimport type { GeometryDocument } from '../core/types-geometry';\nimport { validateConstruction } from './construction.js';\nimport { resolveElements, type ResolvedElement } from './resolve.js';\n\nexport type { ResolvedElement } from './resolve.js';\n\nexport { measureResolved, formatMeasure } from './measure.js';\nexport { formatAngle } from './angle.js';\nexport { driverDomainOf } from './locus.js';\nexport { dependsOn } from './construction.js';\nexport { transformResolved, matrixForTransform } from './transform.js';\n\nexport type GeometryDiagnosticCode =\n | 'INVALID_DOCUMENT' | 'DUPLICATE_ID' | 'UNKNOWN_PARENT' | 'CYCLE'\n | 'DEGENERATE' | 'UNSUPPORTED_TYPE' | 'UNSUPPORTED_CURVE_DEPENDENT'\n | 'LIMIT_EXCEEDED' | 'LATEX_RENDERER_MISSING';\n\nexport interface GeometryDiagnostic {\n code: GeometryDiagnosticCode;\n severity: 'error' | 'warning';\n elementId?: string;\n message: string;\n}\n\nexport interface ResolveResult {\n ok: boolean;\n document: GeometryDocument;\n elements: ResolvedElement[];\n diagnostics: GeometryDiagnostic[];\n}\n\nexport function resolveGeometryDocument(\n document: GeometryDocument,\n options: { maxElements?: number } = {},\n): ResolveResult {\n const diagnostics: GeometryDiagnostic[] = [];\n if (document.protocolVersion !== 1 || !Array.isArray(document.elements)) {\n return { ok: false, document, elements: [], diagnostics: [{ code: 'INVALID_DOCUMENT', severity: 'error', message: '\u4EC5\u652F\u6301 protocolVersion=1 \u7684 GeometryDocument' }] };\n }\n if (document.elements.length > (options.maxElements ?? 500)) {\n diagnostics.push({ code: 'LIMIT_EXCEEDED', severity: 'error', message: '\u5143\u7D20\u6570\u8D85\u8FC7 500' });\n }\n for (const issue of validateConstruction(document.elements)) {\n diagnostics.push({\n code: issue.rule === 'C1' ? 'DUPLICATE_ID' : issue.rule === 'C2' ? 'UNKNOWN_PARENT' : 'INVALID_DOCUMENT',\n severity: 'error',\n message: issue.message,\n });\n }\n const { elements, diagnostics: resolveDiagnostics } = resolveElements(document.elements);\n diagnostics.push(...resolveDiagnostics);\n return {\n ok: diagnostics.every((d) => d.severity !== 'error'),\n document,\n elements,\n diagnostics,\n };\n}\n", "/**\n * geometry_measure \u5DE5\u5177\uFF1A\u8BFB\u53D6 protocolVersion=1 \u5E73\u9762\u51E0\u4F55\u6587\u6863\u5185\u7684\u6D4B\u91CF/\u89D2\u5EA6\uFF0C\n * \u5E76\u652F\u6301\u4E34\u65F6\u67E5\u8BE2\uFF08\u4E24\u70B9\u8DDD\u79BB\u3001\u4E09\u70B9\u89D2\u5EA6\u3001\u6307\u5B9A\u76EE\u6807\u5143\u7D20\u6D4B\u91CF\uFF09\u3002\n * \u65E0 DOM\u3001\u65E0\u72B6\u6001\uFF0C\u76F4\u8C03 @studylab/viz/geometry-kernel\uFF1B\u67E5\u8BE2\u7ED3\u679C\u4E0D\u5199\u56DE\u6587\u6863\u3002\n * \u6587\u6863/\u67E5\u8BE2\u8BED\u4E49\u5931\u8D25\u4EE5 diagnostics \u8FD4\u56DE\uFF1B\u4EC5\u53C2\u6570\u7ED3\u6784\u635F\u574F\u624D isError\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n resolveGeometryDocument,\n measureResolved,\n formatMeasure,\n formatAngle,\n type GeometryDiagnostic,\n type ResolvedElement,\n} from '@studylab/viz/geometry-kernel';\n\n/** \u6587\u6863 JSON \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const DOCUMENT_JSON_MAX_LENGTH = 512 * 1024;\n\n/** \u4E34\u65F6\u67E5\u8BE2\u6761\u6570\u4E0A\u9650 */\nexport const MEASURE_QUERIES_MAX = 50;\n\ntype GeometryDocumentInput = Parameters<typeof resolveGeometryDocument>[0];\ntype AngleUnit = 'deg' | 'rad';\ntype MeasurementKind = 'length' | 'perimeter' | 'area';\ntype QueryPoint = [number, number];\n\nexport interface GeometryMeasureArgs {\n document: unknown;\n queries?: unknown;\n}\n\ninterface GeometryMeasurement {\n kind: MeasurementKind | 'angle';\n target?: string;\n value: number;\n formatted: string;\n}\n\ninterface GeometryMeasurePayload {\n measurements: GeometryMeasurement[];\n diagnostics: GeometryDiagnostic[];\n}\n\ntype ParsedQuery =\n | { mode: 'points-length'; points: [QueryPoint, QueryPoint] }\n | { mode: 'points-angle'; points: [QueryPoint, QueryPoint, QueryPoint] }\n | { mode: 'target'; measurementKind: MeasurementKind; target: string; edge?: number };\n\nfunction textResult(payload: GeometryMeasurePayload): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalidDocument(message: string): GeometryDiagnostic {\n return { code: 'INVALID_DOCUMENT', severity: 'error', message };\n}\n\nfunction limitDiagnostic(): GeometryDiagnostic {\n return {\n code: 'LIMIT_EXCEEDED',\n severity: 'error',\n message: `\u6587\u6863 JSON \u8D85\u8FC7 ${DOCUMENT_JSON_MAX_LENGTH} \u5B57\u8282\uFF08512KB\uFF09`,\n };\n}\n\nfunction queryDiagnostic(index: number, message: string): GeometryDiagnostic {\n return { code: 'DEGENERATE', severity: 'warning', message: `\u7B2C ${index + 1} \u4E2A\u67E5\u8BE2\uFF1A${message}` };\n}\n\n/** \u6587\u6863\u6837\u5F0F\u4E2D\u7684\u89D2\u5EA6\u5355\u4F4D\uFF0C\u7F3A\u7701\u4E3A\u5EA6 */\nfunction angleUnitOf(document: Record<string, unknown>): AngleUnit {\n const style = document.style;\n return isRecord(style) && style.angleUnit === 'rad' ? 'rad' : 'deg';\n}\n\nfunction parsePoint(value: unknown): QueryPoint | null {\n if (!Array.isArray(value) || value.length !== 2) return null;\n const [x, y] = value;\n if (typeof x !== 'number' || typeof y !== 'number') return null;\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return [x, y];\n}\n\nfunction parsePoints2(value: unknown): [QueryPoint, QueryPoint] | null {\n if (!Array.isArray(value) || value.length !== 2) return null;\n const first = parsePoint(value[0]);\n const second = parsePoint(value[1]);\n return first && second ? [first, second] : null;\n}\n\nfunction parsePoints3(value: unknown): [QueryPoint, QueryPoint, QueryPoint] | null {\n if (!Array.isArray(value) || value.length !== 3) return null;\n const first = parsePoint(value[0]);\n const second = parsePoint(value[1]);\n const third = parsePoint(value[2]);\n return first && second && third ? [first, second, third] : null;\n}\n\n/** \u7CBE\u786E\u533A\u5206 points \u4E0E target \u4E24\u79CD\u67E5\u8BE2\u5F62\u72B6\uFF1B\u7ED3\u6784\u635F\u574F\u8FD4\u56DE null */\nfunction parseQuery(value: unknown): ParsedQuery | null {\n if (!isRecord(value)) return null;\n const { kind, points, target, edge } = value;\n\n if (points !== undefined) {\n if (kind === 'length') {\n const parsed = parsePoints2(points);\n return parsed ? { mode: 'points-length', points: parsed } : null;\n }\n if (kind === 'angle') {\n const parsed = parsePoints3(points);\n return parsed ? { mode: 'points-angle', points: parsed } : null;\n }\n return null;\n }\n\n if (kind !== 'length' && kind !== 'perimeter' && kind !== 'area') return null;\n if (typeof target !== 'string' || target.length === 0) return null;\n if (edge !== undefined && (typeof edge !== 'number' || !Number.isInteger(edge))) return null;\n return edge === undefined\n ? { mode: 'target', measurementKind: kind, target }\n : { mode: 'target', measurementKind: kind, target, edge };\n}\n\nfunction unitVector(from: QueryPoint, origin: QueryPoint): QueryPoint | null {\n const dx = from[0] - origin[0];\n const dy = from[1] - origin[1];\n const length = Math.hypot(dx, dy);\n if (length < 1e-9) return null;\n return [dx / length, dy / length];\n}\n\n/** \u51F8\u89D2\uFF080..\u03C0\uFF09\u7684\u5F27\u5EA6\u503C\uFF1B\u4EFB\u4E00\u8FB9\u9000\u5316\u65F6\u8FD4\u56DE null */\nfunction convexAngle(a: QueryPoint, vertex: QueryPoint, b: QueryPoint): number | null {\n const first = unitVector(a, vertex);\n const second = unitVector(b, vertex);\n if (!first || !second) return null;\n const dot = first[0] * second[0] + first[1] * second[1];\n return Math.acos(Math.min(1, Math.max(-1, dot)));\n}\n\nfunction documentMeasurements(\n elements: ResolvedElement[],\n angleUnit: AngleUnit,\n): GeometryMeasurement[] {\n const measurements: GeometryMeasurement[] = [];\n for (const element of elements) {\n if (element.kind === 'measure') {\n measurements.push({\n kind: element.measurementKind,\n target: element.target,\n value: element.value,\n formatted: element.formatted,\n });\n } else if (element.kind === 'angle') {\n measurements.push({\n kind: 'angle',\n target: element.id,\n value: element.valueRad,\n formatted: formatAngle((element.valueRad * 180) / Math.PI, angleUnit),\n });\n }\n }\n return measurements;\n}\n\nexport async function handleGeometryMeasure(args: GeometryMeasureArgs): Promise<CallToolResult> {\n try {\n if (!args || typeof args !== 'object' || !('document' in args)) {\n return errorResult('\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981 { document: object, queries?: Array }');\n }\n\n const { document, queries } = args;\n if (queries !== undefined && !Array.isArray(queries)) {\n return errorResult('\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1Aqueries \u5FC5\u987B\u662F\u6570\u7EC4');\n }\n if (Array.isArray(queries) && queries.length > MEASURE_QUERIES_MAX) {\n return errorResult(`\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1Aqueries \u6700\u591A ${MEASURE_QUERIES_MAX} \u6761`);\n }\n\n const parsedQueries: ParsedQuery[] = [];\n if (Array.isArray(queries)) {\n for (let index = 0; index < queries.length; index += 1) {\n const parsed = parseQuery(queries[index]);\n if (!parsed) {\n return errorResult(\n `\u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF1A\u7B2C ${index + 1} \u4E2A\u67E5\u8BE2\u9700\u4E3A { kind:'length', points:[p,q] }\u3001{ kind:'angle', points:[a,v,b] } \u6216 { kind, target, edge? }`,\n );\n }\n parsedQueries.push(parsed);\n }\n }\n\n const documentJson = JSON.stringify(document);\n if (documentJson !== undefined && documentJson.length > DOCUMENT_JSON_MAX_LENGTH) {\n return textResult({ measurements: [], diagnostics: [limitDiagnostic()] });\n }\n\n if (!isRecord(document)) {\n return textResult({\n measurements: [],\n diagnostics: [invalidDocument('\u6587\u6863\u5FC5\u987B\u662F protocolVersion=1 \u7684 GeometryDocument \u5BF9\u8C61')],\n });\n }\n\n if (document.protocolVersion !== 1 || 'equations' in document) {\n return textResult({\n measurements: [],\n diagnostics: [invalidDocument('\u4EC5\u652F\u6301 protocolVersion=1 \u4E14\u4E0D\u542B equations \u7684 GeometryDocument')],\n });\n }\n\n const result = resolveGeometryDocument(document as unknown as GeometryDocumentInput);\n const diagnostics: GeometryDiagnostic[] = [...result.diagnostics];\n const measurements = documentMeasurements(result.elements, angleUnitOf(document));\n\n for (let index = 0; index < parsedQueries.length; index += 1) {\n const query = parsedQueries[index];\n if (!query) continue;\n\n if (query.mode === 'points-length') {\n const [first, second] = query.points;\n const value = Math.hypot(second[0] - first[0], second[1] - first[1]);\n measurements.push({\n kind: 'length',\n value,\n formatted: formatMeasure('length', value),\n });\n continue;\n }\n\n if (query.mode === 'points-angle') {\n const [first, vertex, second] = query.points;\n const value = convexAngle(first, vertex, second);\n if (value === null) {\n diagnostics.push(queryDiagnostic(index, '\u89D2\u5EA6\u67E5\u8BE2\u7684\u8FB9\u9000\u5316\u4E3A\u96F6\uFF08\u70B9\u91CD\u5408\uFF09'));\n continue;\n }\n measurements.push({\n kind: 'angle',\n value,\n formatted: formatAngle((value * 180) / Math.PI),\n });\n continue;\n }\n\n const target = result.elements.find((element) => element.id === query.target);\n if (!target) {\n diagnostics.push(queryDiagnostic(index, `\u76EE\u6807\u5143\u7D20 \"${query.target}\" \u4E0D\u5B58\u5728`));\n continue;\n }\n const value = measureResolved({\n kind: query.measurementKind,\n target,\n edge: query.edge,\n });\n if (value === null) {\n diagnostics.push(queryDiagnostic(index, `\u76EE\u6807\u5143\u7D20 \"${query.target}\" \u4E0D\u53EF\u6D4B\uFF08${query.measurementKind}\uFF09`));\n continue;\n }\n measurements.push({\n kind: query.measurementKind,\n target: query.target,\n value,\n formatted: formatMeasure(query.measurementKind, value),\n });\n }\n\n return textResult({ measurements, diagnostics });\n } catch (error) {\n // \u515C\u5E95\uFF1A\u53C2\u6570\u5F02\u5E38\uFF08\u5982\u5FAA\u73AF\u5F15\u7528\uFF09\u6216\u5185\u6838\u610F\u5916\u5F02\u5E38\u90FD\u4E0D\u80FD\u8BA9 stdio \u8FDB\u7A0B\u5D29\u6E83\n return errorResult(error instanceof Error ? error.message : String(error));\n }\n}\n\nconst pointSchema = z.tuple([z.number(), z.number()]);\n\nconst querySchema = z.union([\n z.object({\n kind: z.literal('length'),\n points: z.tuple([pointSchema, pointSchema]),\n }),\n z.object({\n kind: z.literal('angle'),\n points: z.tuple([pointSchema, pointSchema, pointSchema]),\n }),\n z.object({\n kind: z.enum(['length', 'perimeter', 'area']),\n target: z.string(),\n edge: z.number().int().optional(),\n }),\n]);\n\n/** \u5411 MCP server \u6CE8\u518C geometry_measure \u5DE5\u5177 */\nexport function registerGeometryMeasureTool(server: McpServer): void {\n server.registerTool(\n 'geometry_measure',\n {\n title: '\u51E0\u4F55\u6D4B\u91CF',\n description:\n '\u6D4B\u91CF protocolVersion=1 \u5E73\u9762\u51E0\u4F55\u6587\u6863\u5185\u5DF2\u6807\u6CE8\u7684 measure \u4E0E angle \u5143\u7D20\uFF0C\u5E76\u652F\u6301\u4E34\u65F6\u67E5\u8BE2\u4E24\u70B9\u8DDD\u79BB\u3001\u4E09\u70B9\u89D2\u5EA6\u6216\u6307\u5B9A\u5143\u7D20\u7684\u957F\u5EA6/\u5468\u957F/\u9762\u79EF\uFF08\u7ED3\u679C\u4E0D\u5199\u56DE\u6587\u6863\uFF09\uFF1B\u8FD4\u56DE measurements \u4E0E diagnostics\u3002',\n inputSchema: {\n document: z.record(z.unknown()),\n queries: z.array(querySchema).max(MEASURE_QUERIES_MAX).optional(),\n },\n },\n (args) => handleGeometryMeasure(args),\n );\n}\n", "/**\n * geometry_render \u5DE5\u5177\uFF1A\u5C06 protocolVersion=1 \u7EAF\u51E0\u4F55\u6587\u6863\u6E32\u67D3\u4E3A SVG \u6216 PNG\u3002\n * \u65E0 DOM\u3001\u65E0\u72B6\u6001\uFF0C\u76F4\u8C03 @studylab/viz/geometry-svg\uFF1BLaTeX \u6807\u6CE8\u6309\u9700\u6CE8\u5165 MathJax \u6E32\u67D3\u5668\u3002\n * \u6587\u6863/\u53C2\u6570\u7C7B\u5931\u8D25\u4EE5 isError \u8FD4\u56DE\u9519\u8BEF\u7801\u6587\u672C\uFF0C\u7EDD\u4E0D throw\u3002\n */\nimport { z } from 'zod';\nimport { Resvg } from '@resvg/resvg-js';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n renderGeometrySvg,\n type GeometryRenderResult,\n} from '@studylab/viz/geometry-svg';\nimport { resolveGeometryDocument } from '@studylab/viz/geometry-kernel';\n\n/** \u6587\u6863 JSON \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const DOCUMENT_JSON_MAX_LENGTH = 512 * 1024;\n\n/** \u753B\u5E03\u5C3A\u5BF8\u8303\u56F4\u4E0E\u9ED8\u8BA4\u503C\uFF08\u65B9\u6848 \u00A79.1\uFF09 */\nexport const SIZE_MIN = 200;\nexport const SIZE_MAX = 2000;\nexport const DEFAULT_WIDTH = 800;\nexport const DEFAULT_HEIGHT = 500;\n\n/** PNG base64 \u957F\u5EA6\u4E0A\u9650\uFF08\u65B9\u6848 \u00A711.4\uFF09 */\nexport const PNG_BASE64_MAX_LENGTH = 8 * 1024 * 1024;\n\ntype GeometryDocumentInput = Parameters<typeof renderGeometrySvg>[0];\n\nexport interface GeometryRenderArgs {\n document: unknown;\n format?: 'svg' | 'png';\n width?: number;\n height?: number;\n theme?: 'light' | 'dark';\n grid?: boolean;\n axis?: boolean;\n showLabels?: boolean;\n}\n\ninterface RenderTiming {\n resolveMs: number;\n renderMs: number;\n encodeMs: number;\n}\n\nfunction textResult(payload: object): CallToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(payload) }] };\n}\n\nfunction errorResult(message: string): CallToolResult {\n return { isError: true, content: [{ type: 'text', text: message }] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction clampSize(value: number | undefined, fallback: number): number {\n if (value == null || !Number.isFinite(value)) return fallback;\n return Math.round(Math.min(SIZE_MAX, Math.max(SIZE_MIN, value)));\n}\n\n/** \u6587\u6863\u4E2D\u5B58\u5728 LaTeX \u6587\u672C\u65F6\u624D\u6CE8\u5165\u6E32\u67D3\u5668\uFF0C\u907F\u514D\u65E0\u8C13\u521D\u59CB\u5316 MathJax */\nfunction needsLatexRenderer(document: Record<string, unknown>): boolean {\n const elements = Array.isArray(document.elements) ? document.elements : [];\n return elements.some(\n (element) => isRecord(element) && element.type === 'text' && element.format === 'latex',\n );\n}\n\nfunction elapsedMs(start: number): number {\n return Math.round((performance.now() - start) * 1000) / 1000;\n}\n\n/** \u6309\u9700\u52A0\u8F7D MathJax \u6E32\u67D3\u5668\uFF1A\u65E0 LaTeX \u6587\u6863\u4E0D\u5F15\u5165 mathjax-full */\nasync function loadLatexToSvg(): Promise<(latex: string) => string> {\n const module = await import('@studylab/formula/latex-svg');\n return module.renderLatexToSvg;\n}\n\nexport async function handleGeometryRender(args: GeometryRenderArgs): Promise<CallToolResult> {\n try {\n if (!args || typeof args !== 'object' || !('document' in args)) {\n return errorResult(\n \"INVALID_DOCUMENT: \u53C2\u6570\u7ED3\u6784\u65E0\u6548\uFF0C\u9700\u8981 { document: object, format?: 'svg'|'png', width?: number, height?: number, theme?: 'light'|'dark', grid?: boolean, axis?: boolean, showLabels?: boolean }\",\n );\n }\n\n const { document } = args;\n\n let documentJson: string | undefined;\n try {\n documentJson = JSON.stringify(document);\n } catch {\n return errorResult('INVALID_DOCUMENT: document \u65E0\u6CD5\u5E8F\u5217\u5316\u4E3A JSON');\n }\n if (documentJson === undefined || documentJson.length > DOCUMENT_JSON_MAX_LENGTH) {\n return errorResult(`LIMIT_EXCEEDED: \u6587\u6863 JSON \u8D85\u8FC7 ${DOCUMENT_JSON_MAX_LENGTH} \u5B57\u8282\uFF08512KB\uFF09`);\n }\n\n if (!isRecord(document)) {\n return errorResult('INVALID_DOCUMENT: document \u5FC5\u987B\u662F protocolVersion=1 \u7684 GeometryDocument \u5BF9\u8C61');\n }\n if (document.protocolVersion !== 1 || 'equations' in document) {\n return errorResult('UNSUPPORTED_DOCUMENT: \u4EC5\u652F\u6301 protocolVersion=1 \u4E14\u4E0D\u542B equations \u7684\u7EAF\u51E0\u4F55\u6587\u6863');\n }\n\n const format = args.format === 'png' ? 'png' : 'svg';\n const width = clampSize(args.width, DEFAULT_WIDTH);\n const height = clampSize(args.height, DEFAULT_HEIGHT);\n\n const resolveStarted = performance.now();\n resolveGeometryDocument(document as unknown as GeometryDocumentInput);\n const latexToSvg = needsLatexRenderer(document) ? await loadLatexToSvg() : undefined;\n const resolveMs = elapsedMs(resolveStarted);\n\n const renderStarted = performance.now();\n const result: GeometryRenderResult = renderGeometrySvg(\n document as unknown as GeometryDocumentInput,\n {\n width,\n height,\n theme: args.theme ?? 'light',\n grid: args.grid,\n axis: args.axis,\n showLabels: args.showLabels,\n ...(latexToSvg ? { latexToSvg } : {}),\n },\n );\n const renderMs = elapsedMs(renderStarted);\n\n const elementCount = Array.isArray(document.elements) ? document.elements.length : 0;\n const summary = `${result.width}\u00D7${result.height}\uFF0C${elementCount} \u4E2A\u5143\u7D20`;\n\n if (format === 'png') {\n const encodeStarted = performance.now();\n const png = new Resvg(result.svg, {\n fitTo: { mode: 'width', value: result.width },\n })\n .render()\n .asPng();\n const dataBase64 = png.toString('base64');\n const encodeMs = elapsedMs(encodeStarted);\n\n if (dataBase64.length > PNG_BASE64_MAX_LENGTH) {\n return errorResult(\n `LIMIT_EXCEEDED: PNG base64 \u8D85\u8FC7 ${PNG_BASE64_MAX_LENGTH} \u5B57\u8282\uFF088MB\uFF09\uFF0C\u8BF7\u6539\u7528 format:'svg'`,\n );\n }\n\n const timing: RenderTiming = { resolveMs, renderMs, encodeMs };\n return textResult({\n format: 'png',\n mime: 'image/png',\n dataBase64,\n width: result.width,\n height: result.height,\n summary,\n timing,\n diagnostics: result.diagnostics,\n });\n }\n\n const timing: RenderTiming = { resolveMs, renderMs, encodeMs: 0 };\n return textResult({\n format: 'svg',\n mime: 'image/svg+xml',\n svg: result.svg,\n width: result.width,\n height: result.height,\n summary,\n timing,\n diagnostics: result.diagnostics,\n });\n } catch (error) {\n return errorResult(\n `RENDER_FAILED: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\n/** \u5411 MCP server \u6CE8\u518C geometry_render \u5DE5\u5177 */\nexport function registerGeometryRenderTool(server: McpServer): void {\n server.registerTool(\n 'geometry_render',\n {\n title: '\u51E0\u4F55\u6587\u6863\u6E32\u67D3',\n description:\n '\u5C06 protocolVersion=1 \u7684\u7EAF\u51E0\u4F55\u6587\u6863\u6E32\u67D3\u4E3A SVG \u6216 PNG\uFF08\u652F\u6301\u4E3B\u9898\u3001\u7F51\u683C\u3001\u5750\u6807\u8F74\u3001\u6807\u7B7E\u4E0E LaTeX \u6807\u6CE8\uFF09\uFF0C\u8FD4\u56DE\u56FE\u50CF\u5185\u5BB9\u3001\u5C3A\u5BF8\u3001\u6458\u8981\u4E0E\u8017\u65F6\uFF1BPNG \u4EE5 base64 \u8FD4\u56DE\u3002',\n inputSchema: {\n document: z.record(z.unknown()),\n format: z.enum(['svg', 'png']).default('svg'),\n width: z.number().min(SIZE_MIN).max(SIZE_MAX).optional(),\n height: z.number().min(SIZE_MIN).max(SIZE_MAX).optional(),\n theme: z.enum(['light', 'dark']).default('light'),\n grid: z.boolean().optional(),\n axis: z.boolean().optional(),\n showLabels: z.boolean().optional(),\n },\n },\n (args) => handleGeometryRender(args),\n );\n}\n", "/**\n * \u51E0\u4F55\u6837\u5F0F\u8BED\u4E49 \u2192 \u5F15\u64CE\u6570\u503C\u6620\u5C04\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u586B\u5145\u6837\u5F0F\u7EDF\u4E00\u4E3A\u4E09\u6863\uFF1A\u5B9E\u5FC3 / \u534A\u900F\u660E / \u65E0\u80CC\u666F\uFF1B\u7F3A\u7701\u4E3A\u534A\u900F\u660E\u3002\n * \u5386\u53F2\u6587\u6863\u672A\u58F0\u660E fill\uFF0C\u4E00\u5F8B\u6309\u7F3A\u7701\u6E32\u67D3\uFF0C\u4ECE\u800C\u7EDF\u4E00\u5706/\u591A\u8FB9\u5F62\u7684\u586B\u5145\u89C2\u611F\u3002\n */\n\nimport type {\n FillStyle,\n GeometryElementRole,\n GeometryElementSpec,\n GeometryFontSize,\n GeometryLabelPosition,\n GeometryLineWidth,\n} from '../core/types-geometry';\n\nexport const FILL_STYLES: readonly FillStyle[] = ['solid', 'translucent', 'none'];\n\n/** \u9ED8\u8BA4\u586B\u5145\uFF1A\u534A\u900F\u660E */\nexport const DEFAULT_FILL: FillStyle = 'translucent';\n\nconst FILL_OPACITY: Record<FillStyle, number> = {\n solid: 1,\n translucent: 0.3,\n none: 0,\n};\n\n/** \u586B\u5145\u6837\u5F0F \u2192 \u586B\u5145\u4E0D\u900F\u660E\u5EA6\uFF08\u7F3A\u7701\u56DE\u9000\u534A\u900F\u660E\uFF09 */\nexport function fillOpacityOf(fill?: FillStyle): number {\n return FILL_OPACITY[fill ?? DEFAULT_FILL];\n}\n\n/** \u9ED8\u8BA4\u89D2\u8272\uFF1A\u5173\u952E\u70B9\uFF08\u7528\u6237\u4E3B\u52A8\u653E\u7F6E\uFF09 */\nexport const DEFAULT_ROLE: GeometryElementRole = 'key';\n\n/** \u6240\u6709\u70B9\u7EDF\u4E00\u534A\u5F84\uFF08\u56FE\u5F62\u70B9 / \u4F5C\u56FE\u70B9 / \u4EA4\u4E92\u70B9\u4E00\u81F4\uFF09 */\nexport const DEFAULT_POINT_SIZE = 3;\n\n/** \u70B9\u586B\u5145\u4E0D\u900F\u660E\u5EA6\uFF1A\u9ED8\u8BA4\u5168\u90E8\u5B9E\u5FC3\uFF08\u53EF\u901A\u8FC7\u5355\u5143\u7D20 fill \u6837\u5F0F\u6539\u4E3A\u534A\u900F\u660E/\u7A7A\u5FC3\uFF09 */\nconst POINT_OPACITY: Record<GeometryElementRole, number> = { key: 1, auxiliary: 1 };\n\nexport function pointSizeOf(_role?: GeometryElementRole): number {\n return DEFAULT_POINT_SIZE;\n}\n\n/** \u70B9\u5927\u5C0F\u6863\u4F4D \u2192 \u50CF\u7D20\uFF08\u4F9B\u9009\u4E2D\u70B9\u6279\u91CF\u8C03\u6574\uFF1B\u4E09\u6863\u62C9\u5F00\u5DEE\u8DDD\uFF0C\u5C0F\u6863\u660E\u663E\u66F4\u5C0F\uFF09 */\nexport const POINT_SIZE_LEVELS: Readonly<Record<'small' | 'medium' | 'large', number>> = {\n small: 2,\n medium: DEFAULT_POINT_SIZE,\n large: 5,\n};\n\n/** \u70B9\u6807\u7B7E\u4E5D\u5BAB\u683C\u4F4D\u7F6E \u2192 JSXGraph \u6807\u7B7E offset / anchor */\nexport interface LabelPositionStyle {\n offset: [number, number];\n anchorX: 'left' | 'middle' | 'right';\n anchorY: 'top' | 'middle' | 'bottom';\n}\n\nexport const LABEL_POSITIONS: readonly GeometryLabelPosition[] = [\n 'tl', 't', 'tr', 'l', 'c', 'r', 'bl', 'b', 'br',\n];\n\nconst LABEL_POSITION_STYLE: Record<GeometryLabelPosition, LabelPositionStyle> = {\n tl: { offset: [-12, 10], anchorX: 'right', anchorY: 'bottom' },\n t: { offset: [0, 10], anchorX: 'middle', anchorY: 'bottom' },\n tr: { offset: [12, 10], anchorX: 'left', anchorY: 'bottom' },\n l: { offset: [-12, 0], anchorX: 'right', anchorY: 'middle' },\n c: { offset: [0, 0], anchorX: 'middle', anchorY: 'middle' },\n r: { offset: [12, 0], anchorX: 'left', anchorY: 'middle' },\n bl: { offset: [-12, -10], anchorX: 'right', anchorY: 'top' },\n b: { offset: [0, -10], anchorX: 'middle', anchorY: 'top' },\n br: { offset: [12, -10], anchorX: 'left', anchorY: 'top' },\n};\n\n/** \u70B9\u6807\u7B7E\u4F4D\u7F6E\uFF08\u7F3A\u7701\u53F3\u4E0A\uFF0C\u4FDD\u6301\u65E2\u6709\u89C2\u611F\uFF09 */\nexport function labelPositionStyleOf(position?: GeometryLabelPosition): LabelPositionStyle {\n return LABEL_POSITION_STYLE[position ?? 'tr'];\n}\n\nexport function pointOpacityOf(role?: GeometryElementRole): number {\n return POINT_OPACITY[role ?? DEFAULT_ROLE]!;\n}\n\n/** \u9009\u4E2D\u70B9\u7684\u989D\u5916\u534A\u5F84\uFF08\u5F3A\u8C03\u5E45\u5EA6\uFF0C\u4FDD\u6301\u514B\u5236\uFF09 */\nexport const POINT_SELECT_GROW = 0.75;\n\n/** \u7EBF\u5BBD\u6863\u4F4D \u2192 \u50CF\u7D20 */\nconst LINE_WIDTH_PX: Record<GeometryLineWidth, number> = {\n thin: 1.5,\n medium: 2,\n thick: 3,\n};\n\n/** \u5B57\u53F7\u6863\u4F4D \u2192 \u50CF\u7D20 */\nconst FONT_SIZE_PX: Record<GeometryFontSize, number> = {\n small: 12,\n medium: 15,\n large: 18,\n};\n\n/** \u56FE\u5F62\u5143\u7D20\u7EDF\u4E00\u7EBF\u5BBD\uFF08\u7F3A\u7701 medium\uFF09 */\nexport function lineWidthOf(level?: GeometryLineWidth): number {\n return LINE_WIDTH_PX[level ?? 'medium'] as number;\n}\n\n/** \u81EA\u52A8\u523B\u5EA6\uFF1A\u76F8\u90BB\u523B\u5EA6\u6570\u5B57\u7684\u6700\u5C0F\u5C4F\u5E55\u95F4\u8DDD\uFF08\u50CF\u7D20\uFF09 */\nexport const AUTO_TICK_MIN_PX = 44;\n\n/** 1/2/5\u00D710^n \u5411\u4E0A\u53D6\u6574\u5230\u201C\u597D\u770B\u201D\u7684\u6B65\u957F */\nexport function niceStep(raw: number): number {\n if (!Number.isFinite(raw) || raw <= 0) return 1;\n const exp = Math.floor(Math.log10(raw));\n const base = 10 ** exp;\n for (const m of [1, 2, 5]) {\n if (m * base >= raw - 1e-9) return m * base;\n }\n return 10 * base;\n}\n\n/**\n * \u5750\u6807\u8F74\u81EA\u52A8\u6B65\u957F\uFF1AX/Y \u5171\u7528\u540C\u4E00\u4E2A\u6570\u503C\u95F4\u9694\uFF0C\u907F\u514D\u4E24\u8F74\u523B\u5EA6\u5BC6\u5EA6\u4E0D\u4E00\u81F4\u3002\n * unitPx \u53D6\u4E24\u8F74\u4E2D\u8F83\u5C0F\u7684\u201C\u6BCF\u5355\u4F4D\u50CF\u7D20\u6570\u201D\uFF08\u66F4\u5BC6\u7684\u4E00\u8F74\uFF09\uFF0C\u4FDD\u8BC1\u4EFB\u4F55\u4E00\u8F74\u90FD\u4E0D\u62E5\u6324\u3002\n */\nexport function autoAxisStep(unitPx: number, minPx = AUTO_TICK_MIN_PX): number {\n if (!Number.isFinite(unitPx) || unitPx <= 0) return 1;\n return niceStep(minPx / unitPx);\n}\n\n/** \u6807\u7B7E/\u6570\u5B57\u5B57\u53F7\uFF08\u7F3A\u7701 medium\uFF09 */\nexport function fontSizeOf(level?: GeometryFontSize): number {\n return FONT_SIZE_PX[level ?? 'medium'] as number;\n}\n\n/**\n * \u6D3E\u751F\u6784\u9020\uFF08\u4E2D\u70B9/\u4EA4\u70B9/\u5782\u7EBF/\u5E73\u884C\u7EBF/\u89D2\u5E73\u5206\u7EBF/\u89D2\u5EA6\uFF09\u7EDF\u4E00\u8272\u7CFB\u7528\u7684\u54E8\u5175 colorIndex\u3002\n * \u4F7F\u7528\u8BE5\u503C\uFF08\u5728\u8C03\u8272\u677F\u4E0B\u6807\u4E4B\u5916\uFF09\u7684\u5143\u7D20\u6E32\u67D3\u4E3A\u300C\u4E13\u5C5E\u6784\u9020\u8272\u300D\uFF0C\u4F7F\u540C\u4E00\u6784\u9020\u7684\u8F85\u52A9\u70B9\u4E0E\u7ED3\u679C\u8272\u7CFB\u4E00\u81F4\u3001\n * \u4E14\u4E0E\u7528\u6237\u624B\u7ED8\u5143\u7D20\u533A\u5206\u5F00\u3002\n */\nexport const CONSTRUCT_COLOR_INDEX = -1;\n\n/** \u6D3E\u751F\u6784\u9020\u7C7B\u578B\uFF08\u7ED3\u679C\u5143\u7D20\uFF09\uFF1A\u4F7F\u7528\u4E13\u5C5E\u6784\u9020\u8272 */\nconst CONSTRUCT_TYPES = new Set<GeometryElementSpec['type']>([\n 'midpoint', 'intersection', 'perpendicular', 'parallel', 'angleBisector', 'angle',\n]);\n\n/** \u8BE5\u5143\u7D20\u7C7B\u578B\u662F\u5426\u4E3A\u6D3E\u751F\u6784\u9020\u7ED3\u679C */\nexport function isConstructResult(type: GeometryElementSpec['type']): boolean {\n return CONSTRUCT_TYPES.has(type);\n}\n", "/**\n * \u70B9\u7684\u81EA\u52A8\u6807\u7B7E\u4F4D\u7F6E\u9009\u62E9\uFF08\u5F15\u64CE\u65E0\u5173\uFF0C\u53EF\u5355\u6D4B\uFF09\u3002\n *\n * \u5728\u4E5D\u5BAB\u683C\u5019\u9009\u4E2D\u6309\u300C\u4E0E\u5173\u8054\u7EBF\u6BB5\u65B9\u5411\u5206\u79BB\u5EA6\u300D\u8BC4\u5206\uFF0C\u5E76\u5BF9\u753B\u5E03\u6EA2\u51FA\u4E0E\u90BB\u8FD1\u70B9\u6807\u7B7E\u78B0\u649E\u6263\u5206\u3002\n * \u4E0E\u9002\u914D\u5668\u5171\u4EAB\u540C\u4E00\u5957 offset \u5B9A\u4E49\uFF08`labelPositionStyleOf`\uFF09\uFF0C\u907F\u514D\u504F\u79FB\u8868\u91CD\u590D\u3002\n */\n\nimport type { GeometryLabelPosition } from '../core/types-geometry';\nimport { labelPositionStyleOf } from './style.js';\n\nconst PRIORITY: readonly GeometryLabelPosition[] = ['tr', 'tl', 'br', 'bl', 't', 'b', 'r', 'l'];\n\nexport interface LabelPlacementInput {\n origin: [number, number];\n others: Array<[number, number]>;\n canvas: { width: number; height: number };\n directions: number[];\n}\n\nfunction separation(a: number, b: number): number {\n const d = Math.abs(a - b) % Math.PI;\n return Math.min(d, Math.PI - d);\n}\n\nexport function autoLabelPosition(input: LabelPlacementInput): GeometryLabelPosition {\n const { origin, others, canvas, directions } = input;\n let best: GeometryLabelPosition = 'tr';\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const position of PRIORITY) {\n const style = labelPositionStyleOf(position);\n const angle = Math.atan2(style.offset[1], style.offset[0]);\n let score = directions.length\n ? Math.min(...directions.map((d) => separation(angle, d)))\n : Math.PI / 4;\n const x = origin[0] + style.offset[0];\n const y = origin[1] - style.offset[1];\n // \u8D34\u8FB9\u8F6F\u60E9\u7F5A\uFF1A\u4FDD\u6301\u4E0E\u62BD\u53D6\u524D\u7684\u9002\u914D\u5668\u8BC4\u5206\u5B8C\u5168\u4E00\u81F4\n const edge = 18;\n if (x < edge || x > canvas.width - edge) score -= 2.5;\n if (y < edge || y > canvas.height - edge) score -= 3.5;\n const overflowX = Math.max(0, 10 - x, x + 10 - canvas.width);\n const overflowY = Math.max(0, 14 - y, y + 14 - canvas.height);\n if (overflowX > 0 || overflowY > 0) score -= 50 + overflowX + overflowY;\n for (const other of others) {\n const d = Math.hypot(x - other[0], y - other[1]);\n if (d < 24) score -= (24 - d) / 5;\n }\n if (score > bestScore + 1e-9) {\n bestScore = score;\n best = position;\n }\n }\n return best;\n}\n", "/**\n * \u51E0\u4F55 SVG \u65E0\u5934\u6E32\u67D3\u7684\u9759\u6001\u4E3B\u9898\u3002\n *\n * \u670D\u52A1\u7AEF\uFF08MCP / resvg\uFF09\u6CA1\u6709 CSS \u53D8\u91CF\uFF0C\u56E0\u6B64 light/dark \u5404\u7528\u4E00\u5957\u56FA\u5B9A hex\uFF1B\n * \u7CFB\u5217\u8272\u4E0E plot-svg \u7684 LIGHT_PALETTE / DARK_PALETTE \u4FDD\u6301\u4E00\u81F4\uFF0C\n * \u80CC\u666F/\u8F74/\u7F51\u683C/\u6587\u5B57\u8272\u6CBF\u7528\u540C\u4E00\u7EA6\u5B9A\uFF0C\u4FDD\u8BC1\u4E24\u7C7B\u5BFC\u51FA\u56FE\u89C2\u611F\u7EDF\u4E00\u3002\n */\n\nexport type GeometryRenderThemeName = 'light' | 'dark';\n\nexport interface GeometryRenderTheme {\n background: string;\n axis: string;\n grid: string;\n ink: string;\n /** \u6D3E\u751F\u6784\u9020\u5143\u7D20\u4E13\u7528\u8272\uFF08\u5BF9\u9F50 ui-kit --viz-construct\uFF09 */\n construct: string;\n palette: readonly string[];\n}\n\nconst LIGHT_PALETTE = [\n '#2563eb', '#dc2626', '#16a34a', '#d97706', '#7c3aed', '#0891b2', '#db2777', '#4b5563',\n] as const;\n\nconst DARK_PALETTE = [\n '#60a5fa', '#f87171', '#4ade80', '#fbbf24', '#a78bfa', '#22d3ee', '#f472b6', '#9ca3af',\n] as const;\n\nexport const GEOMETRY_RENDER_THEMES: Readonly<Record<GeometryRenderThemeName, GeometryRenderTheme>> = {\n light: {\n background: '#ffffff',\n axis: '#666666',\n grid: '#e5e5e5',\n ink: '#374151',\n construct: '#0f766e',\n palette: LIGHT_PALETTE,\n },\n dark: {\n background: '#1c1f25',\n axis: '#9ca3af',\n grid: '#2d333b',\n ink: '#9ca3af',\n construct: '#2dd4bf',\n palette: DARK_PALETTE,\n },\n};\n\nexport function geometryRenderThemeOf(name: GeometryRenderThemeName = 'light'): GeometryRenderTheme {\n return GEOMETRY_RENDER_THEMES[name];\n}\n\n/** \u7EBF\u5BBD / \u5B57\u53F7\u6620\u5C04\u590D\u7528 style.ts\uFF0C\u4FDD\u6301\u4EA4\u4E92\u753B\u5E03\u4E0E\u65E0\u5934\u6E32\u67D3\u4E00\u81F4\u3002 */\nexport { fontSizeOf, lineWidthOf } from './style.js';\n", "/**\n * \u51E0\u4F55\u6587\u6863 \u2192 \u5B57\u7B26\u4E32 SVG\uFF08\u65E0 DOM \u4F9D\u8D56\uFF0C\u4F9B MCP / resvg \u5BFC\u51FA\u590D\u7528\uFF09\u3002\n *\n * \u89C6\u53E3\u6620\u5C04\u4FDD\u6301\u5BBD\u9AD8\u6BD4\uFF1A\u7EDF\u4E00\u7F29\u653E scale = min(width/xRange, height/yRange)\uFF0C\n * \u5185\u5BB9\u5728\u753B\u5E03\u5185\u5C45\u4E2D\uFF08letterbox\uFF09\uFF0C\u56E0\u6B64\u5706/\u692D\u5706\u4E0D\u4F1A\u88AB\u62C9\u4F38\u3002\n * \u5143\u7D20\u4E00\u5F8B\u5148\u7ECF resolveGeometryDocument \u89E3\u6790\uFF1B\u7F3A\u5931/\u9000\u5316\u5143\u7D20\u8FDB\u5165 diagnostics\u3002\n */\n\nimport type {\n GeometryCanvasStyle,\n GeometryDocument,\n GeometryElementSpec,\n GeometryElementStyle,\n} from '../core/types-geometry';\nimport type { GeometryDiagnostic } from './kernel';\nimport { resolveGeometryDocument, type ResolvedElement } from './kernel.js';\nimport { formatAngle } from './angle.js';\nimport { ellipseFrameOf, ellipsePointAt } from './conic.js';\nimport { autoLabelPosition } from './label-position.js';\nimport { xmlEscape } from '../plot-svg/svg-builder.js';\nimport {\n CONSTRUCT_COLOR_INDEX,\n autoAxisStep,\n fillOpacityOf,\n fontSizeOf,\n labelPositionStyleOf,\n lineWidthOf,\n pointSizeOf,\n} from './style.js';\nimport {\n geometryRenderThemeOf,\n type GeometryRenderTheme,\n type GeometryRenderThemeName,\n} from './render-theme.js';\n\nexport interface GeometryRenderOptions {\n /** \u753B\u5E03\u5BBD\u5EA6\uFF08px\uFF09\uFF0C\u94B3\u5236 200..2000\uFF0C\u9ED8\u8BA4 800 */\n width?: number;\n /** \u753B\u5E03\u9AD8\u5EA6\uFF08px\uFF09\uFF0C\u94B3\u5236 200..2000\uFF0C\u9ED8\u8BA4 500 */\n height?: number;\n theme?: GeometryRenderThemeName;\n grid?: boolean;\n axis?: boolean;\n showLabels?: boolean;\n /** LaTeX \u2192 SVG \u7247\u6BB5\uFF1B\u8FD4\u56DE null \u65F6\u56DE\u9000\u4E3A\u7EAF\u6587\u672C\u5E76\u8BB0\u5F55\u8BCA\u65AD */\n latexToSvg?: (latex: string) => string | null;\n}\n\nexport interface GeometryRenderResult {\n svg: string;\n width: number;\n height: number;\n diagnostics: GeometryDiagnostic[];\n}\n\ntype Vec = [number, number];\ntype TextAnchor = 'start' | 'middle' | 'end';\ntype TextBaseline = 'auto' | 'hanging' | 'middle';\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 500;\nconst SIZE_MIN = 200;\nconst SIZE_MAX = 2000;\nconst AXIS_FONT_SIZE = 12;\nconst TICK_HALF = 3;\nconst ARROW_SIZE = 8;\nconst MAX_TICKS = 200;\nconst AUXILIARY_POINT_OPACITY = 0.55;\nconst TWO_PI = Math.PI * 2;\n\ninterface ViewTransform {\n scale: number;\n left: number;\n top: number;\n right: number;\n bottom: number;\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n toX: (x: number) => number;\n toY: (y: number) => number;\n}\n\ninterface RenderContext {\n t: ViewTransform;\n theme: GeometryRenderTheme;\n canvasStyle: GeometryCanvasStyle;\n width: number;\n height: number;\n showLabels: boolean;\n specs: Map<string, GeometryElementSpec>;\n resolvedById: Map<string, ResolvedElement>;\n /** \u5168\u90E8\u5DF2\u89E3\u6790\u70B9\u7684\u5C4F\u5E55\u5750\u6807\uFF0C\u4F9B\u81EA\u52A8\u6807\u7B7E\u907F\u8BA9 */\n pointScreens: Array<{ id: string; screen: Vec }>;\n latexToSvg?: (latex: string) => string | null;\n diagnostics: GeometryDiagnostic[];\n}\n\nexport function renderGeometrySvg(\n document: GeometryDocument,\n options: GeometryRenderOptions = {},\n): GeometryRenderResult {\n const width = clampSize(options.width, DEFAULT_WIDTH);\n const height = clampSize(options.height, DEFAULT_HEIGHT);\n const canvasStyle: GeometryCanvasStyle = document.style ?? {};\n const theme = geometryRenderThemeOf(options.theme ?? (canvasStyle.invert ? 'dark' : 'light'));\n const resolved = resolveGeometryDocument(document);\n const diagnostics = [...resolved.diagnostics];\n\n const specs = new Map<string, GeometryElementSpec>();\n for (const spec of Array.isArray(document.elements) ? document.elements : []) {\n specs.set(spec.id, spec);\n }\n\n const transform = createViewTransform(document.viewport, width, height);\n const context: RenderContext = {\n t: transform,\n theme,\n canvasStyle,\n width,\n height,\n showLabels: options.showLabels !== false && canvasStyle.showLabels !== false,\n specs,\n resolvedById: new Map(resolved.elements.map((element) => [element.id, element])),\n pointScreens: resolved.elements\n .filter((element): element is Extract<ResolvedElement, { kind: 'point' }> => element.kind === 'point')\n .map((element) => ({\n id: element.id,\n screen: [transform.toX(element.position[0]), transform.toY(element.position[1])] as Vec,\n })),\n latexToSvg: options.latexToSvg,\n diagnostics,\n };\n\n const content: string[] = [];\n if (options.grid !== false && canvasStyle.gridVisible !== false) {\n content.push(...gridMarkup(transform, theme, canvasStyle));\n }\n if (options.axis !== false && canvasStyle.showAxes !== false) {\n content.push(...axisMarkup(transform, theme, canvasStyle));\n }\n for (const element of resolved.elements) {\n if (specs.get(element.id)?.style?.visible === false) continue;\n content.push(...renderElement(element, context));\n }\n\n const finite = finiteCanvasRect(canvasStyle, transform);\n const parts: string[] = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n ];\n if (finite) {\n parts.push(\n `<defs><clipPath id=\"geometry-canvas-clip\"><rect x=\"${fmt(finite.x)}\" y=\"${fmt(finite.y)}\" width=\"${fmt(finite.width)}\" height=\"${fmt(finite.height)}\" /></clipPath></defs>`,\n );\n }\n parts.push(`<rect x=\"0\" y=\"0\" width=\"${width}\" height=\"${height}\" fill=\"${canvasStyle.background ?? theme.background}\" />`);\n if (finite) {\n parts.push(\n `<rect x=\"${fmt(finite.x)}\" y=\"${fmt(finite.y)}\" width=\"${fmt(finite.width)}\" height=\"${fmt(finite.height)}\" fill=\"${canvasStyle.background ?? theme.background}\" stroke=\"${theme.axis}\" stroke-width=\"1\" stroke-dasharray=\"4 4\" />`,\n );\n parts.push(`<g clip-path=\"url(#geometry-canvas-clip)\">${content.join('')}</g>`);\n } else {\n parts.push(content.join(''));\n }\n parts.push('</svg>');\n return { svg: parts.join(''), width, height, diagnostics };\n}\n\n/** \u6709\u9650\u753B\u5E03\u5728\u5C4F\u5E55\u4E0A\u7684\u77E9\u5F62\uFF1BcanvasMode \u975E finite \u6216 bounds \u975E\u6CD5\u65F6\u8FD4\u56DE null */\nfunction finiteCanvasRect(\n style: GeometryCanvasStyle,\n t: ViewTransform,\n): { x: number; y: number; width: number; height: number } | null {\n if (style.canvasMode !== 'finite') return null;\n const bounds = style.canvasBounds;\n if (!bounds) return null;\n const valid = Number.isFinite(bounds.xMin) && Number.isFinite(bounds.xMax)\n && Number.isFinite(bounds.yMin) && Number.isFinite(bounds.yMax)\n && bounds.xMax > bounds.xMin && bounds.yMax > bounds.yMin;\n if (!valid) return null;\n const x = t.toX(bounds.xMin);\n const y = t.toY(bounds.yMax);\n return { x, y, width: (bounds.xMax - bounds.xMin) * t.scale, height: (bounds.yMax - bounds.yMin) * t.scale };\n}\n\nfunction renderElement(element: ResolvedElement, context: RenderContext): string[] {\n const { t, theme, canvasStyle, showLabels } = context;\n const spec = context.specs.get(element.id);\n const style = effectiveStyleOf(spec, context.specs, element.kind);\n const color = colorOf(style, theme);\n const strokeWidth = style?.width ?? lineWidthOf(canvasStyle.lineWidth);\n\n switch (element.kind) {\n case 'point': {\n const cx = t.toX(element.position[0]);\n const cy = t.toY(element.position[1]);\n const radius = style?.pointSize ?? pointSizeOf(style?.role);\n const opacity = style?.role === 'auxiliary'\n ? ` opacity=\"${fmt(AUXILIARY_POINT_OPACITY)}\"`\n : '';\n const out = [\n `<circle cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" r=\"${fmt(radius)}\" fill=\"${color}\"${opacity} />`,\n ];\n if (showLabels && style?.label && style.labelVisible !== false) {\n out.push(pointLabelMarkup(cx, cy, element.id, style, context));\n }\n return out;\n }\n case 'line': {\n const clipped = clipLine(element, t);\n if (!clipped) return [];\n const [[x1, y1], [x2, y2]] = clipped;\n return [\n `<line x1=\"${fmt(x1)}\" y1=\"${fmt(y1)}\" x2=\"${fmt(x2)}\" y2=\"${fmt(y2)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n ];\n }\n case 'circle': {\n const cx = t.toX(element.center[0]);\n const cy = t.toY(element.center[1]);\n return [\n `<circle cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" r=\"${fmt(element.radius * t.scale)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n ];\n }\n case 'arc': {\n if (element.arcKind === 'sector') {\n return [\n `<path d=\"${sectorPathD(element.center, element.radius, element.startAngle, element.endAngle, t)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n ];\n }\n const parts = [\n `<path d=\"${arcPathD(element.center, element.radius, element.startAngle, element.endAngle, t)}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n ];\n if (element.arcKind === 'semicircle') {\n const arc = arcScreenGeometry(element.center, element.radius, element.startAngle, element.endAngle, t);\n parts.push(\n `<line x1=\"${fmt(arc.start[0])}\" y1=\"${fmt(arc.start[1])}\" x2=\"${fmt(arc.end[0])}\" y2=\"${fmt(arc.end[1])}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n );\n }\n return parts;\n }\n case 'ellipse': {\n if (ellipseArcSpan(element) < TWO_PI - 1e-6) {\n return [\n `<path d=\"${ellipseArcPathD(element, t)}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`,\n ];\n }\n const cx = t.toX(element.center[0]);\n const cy = t.toY(element.center[1]);\n const rotation = fmt((element.rotation * 180) / -Math.PI);\n return [\n `<ellipse cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" rx=\"${fmt(element.a * t.scale)}\" ry=\"${fmt(element.b * t.scale)}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} transform=\"rotate(${rotation} ${fmt(cx)} ${fmt(cy)})\" />`,\n ];\n }\n case 'polygon': {\n const d = element.vertices\n .map((vertex, index) => `${index === 0 ? 'M' : 'L'}${fmt(t.toX(vertex[0]))} ${fmt(t.toY(vertex[1]))}`)\n .join(' ');\n return [\n `<path d=\"${d} Z\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n ];\n }\n case 'angle': {\n const radius = Math.min(t.xMax - t.xMin, t.yMax - t.yMin) / 8;\n const d = sectorPathD(element.vertex, radius, element.startAngle, element.startAngle + element.sweep, t);\n const mid = element.startAngle + element.sweep / 2;\n const labelX = t.toX(element.vertex[0] + Math.cos(mid) * radius * 1.3);\n const labelY = t.toY(element.vertex[1] + Math.sin(mid) * radius * 1.3);\n const text = formatAngle((element.valueRad * 180) / Math.PI, canvasStyle.angleUnit);\n return [\n `<path d=\"${d}\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\"${fillAttrs(color, style?.fill)} />`,\n textMarkup(labelX, labelY, text, color, fontSizeOf(canvasStyle.fontSize), 'middle', 'middle', true),\n ];\n }\n case 'text': {\n const x = t.toX(element.position[0]);\n const y = t.toY(element.position[1]);\n // \u4E0E\u753B\u5E03\u4E00\u81F4\uFF1A\u6587\u5B57\u6807\u6CE8\u4F7F\u7528\u5143\u7D20\u914D\u8272\uFF08\u663E\u5F0F color \u4F18\u5148\uFF0C\u5426\u5219\u6309 colorIndex\uFF09\n const fill = color;\n if (element.format === 'latex') {\n const fragment = context.latexToSvg ? context.latexToSvg(element.raw) : null;\n if (fragment) {\n // MathJax \u7247\u6BB5\u7528 fill=\"currentColor\" \u53D6\u8272\uFF1BSVG \u65E0 CSS \u4E0A\u4E0B\u6587\uFF0C\u5FC5\u987B\u6CE8\u5165\u5143\u7D20\u989C\u8272\uFF0C\n // \u5426\u5219\u9ED8\u8BA4\u89E3\u6790\u4E3A\u9ED1\u8272\uFF0C\u6DF1\u8272\u4E3B\u9898\u5BFC\u51FA\u540E\u4E0D\u53EF\u89C1\uFF0C\u4E14\u4E0E\u753B\u5E03\u5185\u7684\u516C\u5F0F\u914D\u8272\u4E0D\u4E00\u81F4\u3002\n const colored = fragment.replace(/currentColor/g, fill);\n return [`<g transform=\"translate(${fmt(x)} ${fmt(y)})\">${colored}</g>`];\n }\n context.diagnostics.push({\n code: 'LATEX_RENDERER_MISSING',\n severity: 'warning',\n elementId: element.id,\n message: context.latexToSvg\n ? `LaTeX \u6E32\u67D3\u5668\u672A\u8FD4\u56DE SVG \u7247\u6BB5\uFF08\"${element.id}\"\uFF09\uFF0C\u5DF2\u56DE\u9000\u4E3A\u7EAF\u6587\u672C`\n : `\u672A\u63D0\u4F9B LaTeX \u6E32\u67D3\u5668\uFF08\"${element.id}\"\uFF09\uFF0C\u5DF2\u56DE\u9000\u4E3A\u7EAF\u6587\u672C`,\n });\n }\n return [\n textMarkup(x, y, element.raw, fill, fontSizeOf(canvasStyle.fontSize), 'middle', 'middle', true),\n ];\n }\n case 'measure': {\n const baseX = t.toX(element.labelPosition[0]);\n const baseY = t.toY(element.labelPosition[1]);\n const offset = measureOffsetPx(element, spec, context);\n return [\n textMarkup(\n baseX + offset[0],\n baseY + offset[1],\n element.formatted,\n style?.color ?? theme.ink,\n fontSizeOf(canvasStyle.fontSize),\n 'middle',\n 'middle',\n true,\n ),\n ];\n }\n case 'locus': {\n if (element.points.length < 2) return [];\n const d = element.points\n .map((p, i) => `${i === 0 ? 'M' : 'L'}${fmt(t.toX(p[0]))} ${fmt(t.toY(p[1]))}`)\n .join(' ') + (element.closed ? ' Z' : '');\n return [`<path d=\"${d}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${fmt(strokeWidth)}\" />`];\n }\n default:\n return [];\n }\n}\n\n/** \u53D8\u6362\u5143\u7D20\u7EE7\u627F\u6E90\u6837\u5F0F\uFF08\u53EF\u8986\u76D6\uFF09\uFF1B\u70B9\u6807\u7B7E\u5728\u539F\u6807\u7B7E\u540E\u8FFD\u52A0 prime\uFF0C\u907F\u514D\u4E0E\u6E90\u70B9\u540C\u540D */\nfunction effectiveStyleOf(\n spec: GeometryElementSpec | undefined,\n specs: ReadonlyMap<string, GeometryElementSpec>,\n resultKind: ResolvedElement['kind'],\n): GeometryElementStyle | undefined {\n if (!spec) return undefined;\n if (spec.type !== 'transform') return spec.style;\n const sourceStyle = effectiveStyleOf(specs.get(spec.source), specs, resultKind);\n const own = spec.style ?? {};\n const label = own.label\n ?? (resultKind === 'point' && sourceStyle?.label ? `${sourceStyle.label}\u2032` : undefined);\n return { ...sourceStyle, ...own, ...(label !== undefined ? { label } : {}) };\n}\n\n/** \u6D4B\u91CF\u6587\u672C\u7684\u50CF\u7D20\u504F\u79FB\uFF1A\u957F\u5EA6\u6CBF\u76EE\u6807\u7EBF\u6BB5\u6CD5\u5411\u5916\u504F 14px\uFF0C\u5176\u4F59\u7EDF\u4E00\u4E0A\u79FB 14px */\nfunction measureOffsetPx(\n element: Extract<ResolvedElement, { kind: 'measure' }>,\n spec: GeometryElementSpec | undefined,\n context: RenderContext,\n): Vec {\n const UP: Vec = [0, -14];\n if (element.measurementKind !== 'length') return UP;\n const target = context.resolvedById.get(element.target);\n if (!target) return UP;\n let from: Vec | null = null;\n let to: Vec | null = null;\n if (target.kind === 'line' && target.extent === 'segment' && target.from && target.to) {\n from = target.from;\n to = target.to;\n } else if (target.kind === 'polygon' && spec?.type === 'measure') {\n const edge = spec.edge ?? 0;\n const vertices = target.vertices;\n if (Number.isInteger(edge) && edge >= 0 && edge < vertices.length) {\n from = vertices[edge]!;\n to = vertices[(edge + 1) % vertices.length]!;\n }\n }\n if (!from || !to) return UP;\n const { t } = context;\n const dx = t.toX(to[0]) - t.toX(from[0]);\n const dy = t.toY(to[1]) - t.toY(from[1]);\n const length = Math.hypot(dx, dy);\n if (length < 1e-9) return UP;\n return [(dy / length) * 14, (-dx / length) * 14];\n}\n\nfunction clampSize(value: number | undefined, fallback: number): number {\n if (value == null || !Number.isFinite(value)) return fallback;\n return Math.round(Math.min(SIZE_MAX, Math.max(SIZE_MIN, value)));\n}\n\nfunction createViewTransform(\n viewport: { xMin: number; xMax: number; yMin: number; yMax: number } | undefined,\n width: number,\n height: number,\n): ViewTransform {\n const raw = viewport ?? { xMin: -6, xMax: 6, yMin: -6, yMax: 6 };\n const valid = Number.isFinite(raw.xMin) && Number.isFinite(raw.xMax)\n && Number.isFinite(raw.yMin) && Number.isFinite(raw.yMax)\n && raw.xMax > raw.xMin && raw.yMax > raw.yMin;\n const xMin = valid ? raw.xMin : -6;\n const xMax = valid ? raw.xMax : 6;\n const yMin = valid ? raw.yMin : -6;\n const yMax = valid ? raw.yMax : 6;\n const scale = Math.min(width / (xMax - xMin), height / (yMax - yMin));\n const contentWidth = (xMax - xMin) * scale;\n const contentHeight = (yMax - yMin) * scale;\n const left = (width - contentWidth) / 2;\n const top = (height - contentHeight) / 2;\n return {\n scale,\n left,\n top,\n right: left + contentWidth,\n bottom: top + contentHeight,\n xMin,\n xMax,\n yMin,\n yMax,\n toX: (x) => left + (x - xMin) * scale,\n toY: (y) => top + (yMax - y) * scale,\n };\n}\n\nfunction colorOf(style: GeometryElementStyle | undefined, theme: GeometryRenderTheme): string {\n if (style?.color) return style.color;\n if (style?.colorIndex === CONSTRUCT_COLOR_INDEX) return theme.construct;\n const index = style?.colorIndex == null ? 0 : Math.trunc(style.colorIndex);\n const count = theme.palette.length;\n return theme.palette[((index % count) + count) % count]!;\n}\n\nfunction fillAttrs(color: string, fill: GeometryElementStyle['fill']): string {\n const opacity = fillOpacityOf(fill);\n if (opacity <= 0) return ' fill=\"none\"';\n return ` fill=\"${color}\" fill-opacity=\"${fmt(opacity)}\"`;\n}\n\nfunction pointLabelMarkup(\n cx: number,\n cy: number,\n id: string,\n style: GeometryElementStyle,\n context: RenderContext,\n): string {\n const position = style.labelPosition ?? autoLabelPosition({\n origin: [cx, cy],\n others: context.pointScreens.filter((point) => point.id !== id).map((point) => point.screen),\n canvas: { width: context.width, height: context.height },\n directions: [],\n });\n const placement = labelPositionStyleOf(position);\n const anchor: TextAnchor = placement.anchorX === 'left'\n ? 'start'\n : placement.anchorX === 'right' ? 'end' : 'middle';\n const baseline: TextBaseline = placement.anchorY === 'top'\n ? 'hanging'\n : placement.anchorY === 'middle' ? 'middle' : 'auto';\n return textMarkup(\n cx + placement.offset[0],\n cy - placement.offset[1],\n style.label ?? '',\n context.theme.ink,\n fontSizeOf(context.canvasStyle.fontSize),\n anchor,\n baseline,\n );\n}\n\nfunction textMarkup(\n x: number,\n y: number,\n text: string,\n fill: string,\n fontSize: number,\n anchor: TextAnchor,\n baseline: TextBaseline,\n halo = false,\n): string {\n const stroke = halo ? ' stroke=\"#ffffff\" stroke-width=\"2\" paint-order=\"stroke\"' : '';\n return `<text x=\"${fmt(x)}\" y=\"${fmt(y)}\" fill=\"${fill}\" font-size=\"${fmt(fontSize)}\" text-anchor=\"${anchor}\" dominant-baseline=\"${baseline}\"${stroke}>${xmlEscape(text)}</text>`;\n}\n\nfunction clipLine(\n element: Extract<ResolvedElement, { kind: 'line' }>,\n t: ViewTransform,\n): [Vec, Vec] | null {\n let base: Vec;\n let direction: Vec;\n let tMin: number;\n let tMax: number;\n if (element.extent === 'segment') {\n if (!element.from || !element.to) return null;\n base = element.from;\n direction = [element.to[0] - element.from[0], element.to[1] - element.from[1]];\n tMin = 0;\n tMax = 1;\n } else {\n base = element.origin;\n direction = element.direction;\n tMin = element.extent === 'ray' ? 0 : Number.NEGATIVE_INFINITY;\n tMax = Number.POSITIVE_INFINITY;\n }\n const clipped = clipToBox(base, direction, tMin, tMax, t);\n if (!clipped) return null;\n return [\n [t.toX(clipped[0][0]), t.toY(clipped[0][1])],\n [t.toX(clipped[1][0]), t.toY(clipped[1][1])],\n ];\n}\n\n/** Liang-Barsky\uFF1A\u628A p + t\u00B7d \u88C1\u526A\u5230\u89C6\u53E3\u4E16\u754C\u77E9\u5F62\uFF0C\u8FD4\u56DE\u4E24\u7AEF\u70B9 */\nfunction clipToBox(\n p: Vec,\n d: Vec,\n tMin: number,\n tMax: number,\n t: ViewTransform,\n): [Vec, Vec] | null {\n let t0 = tMin;\n let t1 = tMax;\n const edges: Array<[number, number]> = [\n [-d[0], p[0] - t.xMin],\n [d[0], t.xMax - p[0]],\n [-d[1], p[1] - t.yMin],\n [d[1], t.yMax - p[1]],\n ];\n for (const [denominator, numerator] of edges) {\n if (denominator === 0) {\n if (numerator < 0) return null;\n continue;\n }\n const ratio = numerator / denominator;\n if (denominator < 0) {\n if (ratio > t1) return null;\n if (ratio > t0) t0 = ratio;\n } else {\n if (ratio < t0) return null;\n if (ratio < t1) t1 = ratio;\n }\n }\n if (t0 > t1 || !Number.isFinite(t0) || !Number.isFinite(t1)) return null;\n return [\n [p[0] + t0 * d[0], p[1] + t0 * d[1]],\n [p[0] + t1 * d[0], p[1] + t1 * d[1]],\n ];\n}\n\nfunction arcScreenGeometry(\n center: Vec,\n radius: number,\n startAngle: number,\n endAngle: number,\n t: ViewTransform,\n): { start: Vec; end: Vec; radiusPx: number; largeArc: number; sweepFlag: number } {\n const sweep = endAngle - startAngle;\n return {\n start: [\n t.toX(center[0] + radius * Math.cos(startAngle)),\n t.toY(center[1] + radius * Math.sin(startAngle)),\n ],\n end: [\n t.toX(center[0] + radius * Math.cos(endAngle)),\n t.toY(center[1] + radius * Math.sin(endAngle)),\n ],\n radiusPx: radius * t.scale,\n largeArc: Math.abs(sweep) > Math.PI ? 1 : 0,\n // \u6570\u5B66\u5750\u6807\u9006\u65F6\u9488\uFF08sweep>0\uFF09\u5728\u5C4F\u5E55 y \u5411\u4E0B\u65F6\u5BF9\u5E94 SVG sweep-flag 0\n sweepFlag: sweep >= 0 ? 0 : 1,\n };\n}\n\nfunction arcPathD(\n center: Vec,\n radius: number,\n startAngle: number,\n endAngle: number,\n t: ViewTransform,\n): string {\n const arc = arcScreenGeometry(center, radius, startAngle, endAngle, t);\n const r = fmt(arc.radiusPx);\n return `M ${fmt(arc.start[0])} ${fmt(arc.start[1])} A ${r} ${r} 0 ${arc.largeArc} ${arc.sweepFlag} ${fmt(arc.end[0])} ${fmt(arc.end[1])}`;\n}\n\nfunction sectorPathD(\n center: Vec,\n radius: number,\n startAngle: number,\n endAngle: number,\n t: ViewTransform,\n): string {\n const arc = arcScreenGeometry(center, radius, startAngle, endAngle, t);\n const r = fmt(arc.radiusPx);\n return `M ${fmt(t.toX(center[0]))} ${fmt(t.toY(center[1]))} L ${fmt(arc.start[0])} ${fmt(arc.start[1])} A ${r} ${r} 0 ${arc.largeArc} ${arc.sweepFlag} ${fmt(arc.end[0])} ${fmt(arc.end[1])} Z`;\n}\n\nfunction ellipseArcSpan(element: Extract<ResolvedElement, { kind: 'ellipse' }>): number {\n return (element.endParam ?? TWO_PI) - (element.startParam ?? 0);\n}\n\n/**\n * \u692D\u5706\u53C2\u6570 \u03C6\uFF08\u7ED5\u7126\u70B9 F1\u3001\u76F8\u5BF9 F1\u2192F2 \u65B9\u5411\u7684\u6781\u89D2\uFF09\u2192 \u4E16\u754C\u70B9\u3002\n * \u4E0E `ellipsePointAt` / glider / intersections \u7684\u53C2\u6570\u8BED\u4E49\u4E00\u81F4\uFF1B\u4E0D\u5F97\u7528\u4E2D\u5FC3\u53C2\u6570\u5316\u3002\n */\nfunction ellipseWorldPointAt(\n element: Extract<ResolvedElement, { kind: 'ellipse' }>,\n param: number,\n): Vec {\n const frame = ellipseFrameOf(element.center, element.a, element.b, element.rotation);\n return ellipsePointAt(frame.metrics, frame.f1, param);\n}\n\nfunction ellipseArcPathD(\n element: Extract<ResolvedElement, { kind: 'ellipse' }>,\n t: ViewTransform,\n): string {\n const startParam = element.startParam ?? 0;\n const endParam = element.endParam ?? TWO_PI;\n const start = ellipseWorldPointAt(element, startParam);\n const end = ellipseWorldPointAt(element, endParam);\n const sweep = endParam - startParam;\n const largeArc = Math.abs(sweep) > Math.PI ? 1 : 0;\n const rotation = fmt((element.rotation * 180) / -Math.PI);\n return `M ${fmt(t.toX(start[0]))} ${fmt(t.toY(start[1]))} A ${fmt(element.a * t.scale)} ${fmt(element.b * t.scale)} ${rotation} ${largeArc} ${sweep >= 0 ? 0 : 1} ${fmt(t.toX(end[0]))} ${fmt(t.toY(end[1]))}`;\n}\n\nfunction axisStepOf(t: ViewTransform, style: GeometryCanvasStyle): number {\n const configured = style.axisStep;\n if (configured != null && Number.isFinite(configured) && configured > 0) return configured;\n return autoAxisStep(t.scale);\n}\n\nfunction tickValues(min: number, max: number, step: number): number[] {\n const values: number[] = [];\n if (!(step > 0) || !Number.isFinite(step)) return values;\n const first = Math.ceil(min / step - 1e-9);\n const last = Math.floor(max / step + 1e-9);\n for (let i = first; i <= last && values.length < MAX_TICKS; i += 1) {\n const value = i * step;\n values.push(Object.is(value, -0) ? 0 : value);\n }\n return values;\n}\n\nfunction tickLabel(value: number, step: number): string {\n if (value === 0) return '0';\n const decimals = step >= 1 ? 0 : Math.min(6, Math.ceil(-Math.log10(step)));\n return String(Number(value.toFixed(decimals)));\n}\n\nfunction gridMarkup(\n t: ViewTransform,\n theme: GeometryRenderTheme,\n style: GeometryCanvasStyle,\n): string[] {\n const color = style.grid ?? theme.grid;\n const step = axisStepOf(t, style);\n const out: string[] = [];\n for (const value of tickValues(t.xMin, t.xMax, step)) {\n const x = fmt(t.toX(value));\n out.push(`<line x1=\"${x}\" y1=\"${fmt(t.top)}\" x2=\"${x}\" y2=\"${fmt(t.bottom)}\" stroke=\"${color}\" stroke-width=\"0.5\" />`);\n }\n for (const value of tickValues(t.yMin, t.yMax, step)) {\n const y = fmt(t.toY(value));\n out.push(`<line x1=\"${fmt(t.left)}\" y1=\"${y}\" x2=\"${fmt(t.right)}\" y2=\"${y}\" stroke=\"${color}\" stroke-width=\"0.5\" />`);\n }\n return out;\n}\n\nfunction axisMarkup(\n t: ViewTransform,\n theme: GeometryRenderTheme,\n style: GeometryCanvasStyle,\n): string[] {\n const out: string[] = [];\n const axisXColor = style.axisX ?? theme.axis;\n const axisYColor = style.axisY ?? theme.axis;\n const dash = (style.axisStyle ?? 'solid') === 'dashed' ? ' stroke-dasharray=\"4 4\"' : '';\n const arrows = style.axisArrows ?? 'single';\n const step = axisStepOf(t, style);\n const showTicks = (style.tickStyle ?? 'integer') !== 'none';\n const hasXAxis = t.yMin <= 0 && t.yMax >= 0;\n const hasYAxis = t.xMin <= 0 && t.xMax >= 0;\n\n if (hasXAxis) {\n const y = t.toY(0);\n out.push(`<line x1=\"${fmt(t.left)}\" y1=\"${fmt(y)}\" x2=\"${fmt(t.right)}\" y2=\"${fmt(y)}\" stroke=\"${axisXColor}\" stroke-width=\"1\"${dash} />`);\n if (arrows !== 'none') out.push(arrowHead(t.right, y, 1, 0, axisXColor));\n if (arrows === 'double') out.push(arrowHead(t.left, y, -1, 0, axisXColor));\n for (const value of tickValues(t.xMin, t.xMax, step)) {\n const x = t.toX(value);\n if (showTicks) {\n out.push(`<line x1=\"${fmt(x)}\" y1=\"${fmt(y - TICK_HALF)}\" x2=\"${fmt(x)}\" y2=\"${fmt(y + TICK_HALF)}\" stroke=\"${axisXColor}\" stroke-width=\"1\" />`);\n }\n if (value !== 0) {\n out.push(textMarkup(x, y + 16, tickLabel(value, step), axisXColor, AXIS_FONT_SIZE, 'middle', 'hanging'));\n }\n }\n }\n\n if (hasYAxis) {\n const x = t.toX(0);\n out.push(`<line x1=\"${fmt(x)}\" y1=\"${fmt(t.top)}\" x2=\"${fmt(x)}\" y2=\"${fmt(t.bottom)}\" stroke=\"${axisYColor}\" stroke-width=\"1\"${dash} />`);\n if (arrows !== 'none') out.push(arrowHead(x, t.top, 0, -1, axisYColor));\n if (arrows === 'double') out.push(arrowHead(x, t.bottom, 0, 1, axisYColor));\n for (const value of tickValues(t.yMin, t.yMax, step)) {\n const y = t.toY(value);\n if (showTicks) {\n out.push(`<line x1=\"${fmt(x - TICK_HALF)}\" y1=\"${fmt(y)}\" x2=\"${fmt(x + TICK_HALF)}\" y2=\"${fmt(y)}\" stroke=\"${axisYColor}\" stroke-width=\"1\" />`);\n }\n if (value !== 0) {\n out.push(textMarkup(x - 8, y, tickLabel(value, step), axisYColor, AXIS_FONT_SIZE, 'end', 'middle'));\n }\n }\n }\n\n if (hasXAxis && hasYAxis) {\n out.push(textMarkup(t.toX(0) - 6, t.toY(0) + 14, '0', axisXColor, AXIS_FONT_SIZE, 'end', 'hanging'));\n }\n return out;\n}\n\nfunction arrowHead(x: number, y: number, dx: number, dy: number, color: string): string {\n const tipX = x;\n const tipY = y;\n const baseX = x - dx * ARROW_SIZE;\n const baseY = y - dy * ARROW_SIZE;\n const wingX = -dy * ARROW_SIZE * 0.5;\n const wingY = dx * ARROW_SIZE * 0.5;\n return `<path d=\"M ${fmt(tipX)} ${fmt(tipY)} L ${fmt(baseX + wingX)} ${fmt(baseY + wingY)} L ${fmt(baseX - wingX)} ${fmt(baseY - wingY)} Z\" fill=\"${color}\" />`;\n}\n\nfunction fmt(value: number): string {\n return Number(value.toFixed(3)).toString();\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["mathjax", "TeX", "SVG", "liteAdaptor", "RegisterHTMLHandler", "AllPackages", "katex", "katex", "z", "parse", "node", "n", "textResult", "errorResult", "payload", "z", "z", "TWO_PI", "EPS", "distance", "TWO_PI", "EPS", "TWO_PI", "EPS", "EPS", "EPS", "TWO_PI", "distance", "TWO_PI", "sweep", "TWO_PI", "TWO_PI", "unitVector", "textResult", "errorResult", "z", "z", "DOCUMENT_JSON_MAX_LENGTH", "textResult", "errorResult", "isRecord", "invalidDocument", "unitVector", "value", "z", "z", "Resvg", "niceStep", "LIGHT_PALETTE", "DARK_PALETTE", "SIZE_MAX", "TWO_PI", "fmt", "DOCUMENT_JSON_MAX_LENGTH", "SIZE_MIN", "SIZE_MAX", "DEFAULT_WIDTH", "DEFAULT_HEIGHT", "textResult", "errorResult", "isRecord", "clampSize", "Resvg", "timing", "z"]
|
|
7
7
|
}
|