@xom11/whiteboard 0.9.1 → 0.10.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.
Files changed (40) hide show
  1. package/dist/{chunk-ZVN356JZ.mjs → chunk-D257NCQW.mjs} +3 -3
  2. package/dist/{chunk-ZVN356JZ.mjs.map → chunk-D257NCQW.mjs.map} +1 -1
  3. package/dist/{chunk-KEYZ5EZT.mjs → chunk-G7FR3AIV.mjs} +44 -5
  4. package/dist/chunk-G7FR3AIV.mjs.map +1 -0
  5. package/dist/chunk-PWIMZIB6.mjs +62 -0
  6. package/dist/chunk-PWIMZIB6.mjs.map +1 -0
  7. package/dist/chunk-WQOABS6N.mjs +197 -0
  8. package/dist/chunk-WQOABS6N.mjs.map +1 -0
  9. package/dist/{chunk-DU3RHKT5.mjs → chunk-YVJP7NRG.mjs} +4 -4
  10. package/dist/{chunk-DU3RHKT5.mjs.map → chunk-YVJP7NRG.mjs.map} +1 -1
  11. package/dist/geometry-2d.js +102 -18
  12. package/dist/geometry-2d.js.map +1 -1
  13. package/dist/geometry-2d.mjs +2 -2
  14. package/dist/geometry-3d.js +152 -93
  15. package/dist/geometry-3d.js.map +1 -1
  16. package/dist/geometry-3d.mjs +2 -2
  17. package/dist/graph-2d.js +88 -20
  18. package/dist/graph-2d.js.map +1 -1
  19. package/dist/graph-2d.mjs +1 -1
  20. package/dist/{host-PIIDSMVE.mjs → host-N6ACNJKI.mjs} +51 -12
  21. package/dist/host-N6ACNJKI.mjs.map +1 -0
  22. package/dist/{host-LZH2FZ2N.mjs → host-NKGV6RF2.mjs} +91 -23
  23. package/dist/host-NKGV6RF2.mjs.map +1 -0
  24. package/dist/{host-VDNAJMLC.mjs → host-XVK7UCRE.mjs} +62 -18
  25. package/dist/host-XVK7UCRE.mjs.map +1 -0
  26. package/dist/index.d.mts +127 -1
  27. package/dist/index.d.ts +127 -1
  28. package/dist/index.js +1336 -177
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +993 -52
  31. package/dist/index.mjs.map +1 -1
  32. package/package.json +4 -1
  33. package/dist/chunk-DU2NFHRR.mjs +0 -103
  34. package/dist/chunk-DU2NFHRR.mjs.map +0 -1
  35. package/dist/chunk-IUVV52HO.mjs +0 -144
  36. package/dist/chunk-IUVV52HO.mjs.map +0 -1
  37. package/dist/chunk-KEYZ5EZT.mjs.map +0 -1
  38. package/dist/host-LZH2FZ2N.mjs.map +0 -1
  39. package/dist/host-PIIDSMVE.mjs.map +0 -1
  40. package/dist/host-VDNAJMLC.mjs.map +0 -1
@@ -4,7 +4,7 @@ import { lazy } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
6
6
  var Graph2DStampHost = lazy(
7
- () => import('./host-LZH2FZ2N.mjs').then((m) => ({ default: m.Graph2DStampHost }))
7
+ () => import('./host-NKGV6RF2.mjs').then((m) => ({ default: m.Graph2DStampHost }))
8
8
  );
9
9
  var Graph2DIcon = /* @__PURE__ */ jsxs(
10
10
  "svg",
@@ -54,5 +54,5 @@ var graph2dStamp = {
54
54
  };
55
55
 
56
56
  export { graph2dStamp };
57
- //# sourceMappingURL=chunk-ZVN356JZ.mjs.map
58
- //# sourceMappingURL=chunk-ZVN356JZ.mjs.map
57
+ //# sourceMappingURL=chunk-D257NCQW.mjs.map
58
+ //# sourceMappingURL=chunk-D257NCQW.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stamps/graph-2d/index.tsx"],"names":[],"mappings":";;;;AAaA,IAAM,gBAAA,GAAmB,IAAA;AAAA,EAAK,MAC5B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,gBAAA,EAAiB,CAAE;AAChE,CAAA;AAEA,IAAM,WAAA,mBACJ,IAAA;AAAA,EAAC,KAAA;AAAA,EAAA;AAAA,IACC,KAAA,EAAM,IAAA;AAAA,IACN,MAAA,EAAO,IAAA;AAAA,IACP,OAAA,EAAQ,WAAA;AAAA,IACR,IAAA,EAAK,MAAA;AAAA,IACL,MAAA,EAAO,cAAA;AAAA,IACP,WAAA,EAAY,KAAA;AAAA,IACZ,aAAA,EAAc,OAAA;AAAA,IACd,cAAA,EAAe,OAAA;AAAA,IACf,aAAA,EAAY,MAAA;AAAA,IAEZ,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,GAAE,UAAA,EAAW,CAAA;AAAA,sBACnB,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,WAAA,EAAY,CAAA;AAAA,sBACpB,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,yBAAA,EAA0B;AAAA;AAAA;AACpC,CAAA;AAGK,IAAM,YAAA,GAA0B;AAAA,EACrC,IAAA,EAAM,SAAA;AAAA,EACN,YAAA,EAAc,IAAA;AAAA,EACd,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,sCAAA;AAAA,EACd,WAAA,EAAa,WAAA;AAAA,EACb,aAAA,EAAe,uBAAA;AAAA,EACf,iBAAA,EAAmB,mBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,mBAAA,CAAoB,IAAI,CAAA,EAAG;AAC9B,MAAA,MAAM,IAAI,MAAM,6EAAqE,CAAA;AAAA,IACvF;AACA,IAAA,OAAO,yBAAA,CAA0B,KAAK,SAAS,CAAA;AAAA,EACjD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,mBAAA,CAAoB,IAAI,CAAA,EAAG,OAAO,IAAA;AACvC,IAAA,MAAM,SAAA,GAAY,MAAM,yBAAA,CAA0B,IAAA,CAAK,SAAS,CAAA;AAChE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GACJ,4BAAA,IACC,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AACjF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-ZVN356JZ.mjs","sourcesContent":["'use client';\n\nimport { lazy } from 'react';\nimport { renderGraph2dSvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { isGraph2DCustomData, type Graph2DCustomData } from './types';\n\nexport { isGraph2DCustomData };\nexport type { Graph2DCustomData };\n\nconst Graph2DStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.Graph2DStampHost })),\n);\n\nconst Graph2DIcon = (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M3 21 V3\" />\n <path d=\"M3 21 H21\" />\n <path d=\"M5 19 C8 5, 14 5, 19 17\" />\n </svg>\n);\n\nexport const graph2dStamp: StampType = {\n kind: 'graph2d',\n experimental: true,\n shortcutKey: 'h',\n toolbarLabel: 'H',\n toolbarTitle: 'Chèn đồ thị 2D (H)',\n toolbarIcon: Graph2DIcon,\n toolbarTestId: 'stamp-toolbar-graph2d',\n matchesCustomData: isGraph2DCustomData,\n async renderSvgFromCustomData(data) {\n if (!isGraph2DCustomData(data)) {\n throw new Error('graph2dStamp.renderSvgFromCustomData: customData không phải graph2d');\n }\n return renderGraph2dSvgFromState(data.jsonState);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as Graph2DCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isGraph2DCustomData(data)) return null;\n const svgString = await renderGraph2dSvgFromState(data.jsonState);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL =\n 'data:image/svg+xml;base64,' +\n (typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64'));\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: Graph2DStampHost,\n};\n"]}
1
+ {"version":3,"sources":["../src/stamps/graph-2d/index.tsx"],"names":[],"mappings":";;;;AAaA,IAAM,gBAAA,GAAmB,IAAA;AAAA,EAAK,MAC5B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,gBAAA,EAAiB,CAAE;AAChE,CAAA;AAEA,IAAM,WAAA,mBACJ,IAAA;AAAA,EAAC,KAAA;AAAA,EAAA;AAAA,IACC,KAAA,EAAM,IAAA;AAAA,IACN,MAAA,EAAO,IAAA;AAAA,IACP,OAAA,EAAQ,WAAA;AAAA,IACR,IAAA,EAAK,MAAA;AAAA,IACL,MAAA,EAAO,cAAA;AAAA,IACP,WAAA,EAAY,KAAA;AAAA,IACZ,aAAA,EAAc,OAAA;AAAA,IACd,cAAA,EAAe,OAAA;AAAA,IACf,aAAA,EAAY,MAAA;AAAA,IAEZ,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,GAAE,UAAA,EAAW,CAAA;AAAA,sBACnB,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,WAAA,EAAY,CAAA;AAAA,sBACpB,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,yBAAA,EAA0B;AAAA;AAAA;AACpC,CAAA;AAGK,IAAM,YAAA,GAA0B;AAAA,EACrC,IAAA,EAAM,SAAA;AAAA,EACN,YAAA,EAAc,IAAA;AAAA,EACd,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,sCAAA;AAAA,EACd,WAAA,EAAa,WAAA;AAAA,EACb,aAAA,EAAe,uBAAA;AAAA,EACf,iBAAA,EAAmB,mBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,mBAAA,CAAoB,IAAI,CAAA,EAAG;AAC9B,MAAA,MAAM,IAAI,MAAM,6EAAqE,CAAA;AAAA,IACvF;AACA,IAAA,OAAO,yBAAA,CAA0B,KAAK,SAAS,CAAA;AAAA,EACjD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,mBAAA,CAAoB,IAAI,CAAA,EAAG,OAAO,IAAA;AACvC,IAAA,MAAM,SAAA,GAAY,MAAM,yBAAA,CAA0B,IAAA,CAAK,SAAS,CAAA;AAChE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GACJ,4BAAA,IACC,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AACjF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-D257NCQW.mjs","sourcesContent":["'use client';\n\nimport { lazy } from 'react';\nimport { renderGraph2dSvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { isGraph2DCustomData, type Graph2DCustomData } from './types';\n\nexport { isGraph2DCustomData };\nexport type { Graph2DCustomData };\n\nconst Graph2DStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.Graph2DStampHost })),\n);\n\nconst Graph2DIcon = (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M3 21 V3\" />\n <path d=\"M3 21 H21\" />\n <path d=\"M5 19 C8 5, 14 5, 19 17\" />\n </svg>\n);\n\nexport const graph2dStamp: StampType = {\n kind: 'graph2d',\n experimental: true,\n shortcutKey: 'h',\n toolbarLabel: 'H',\n toolbarTitle: 'Chèn đồ thị 2D (H)',\n toolbarIcon: Graph2DIcon,\n toolbarTestId: 'stamp-toolbar-graph2d',\n matchesCustomData: isGraph2DCustomData,\n async renderSvgFromCustomData(data) {\n if (!isGraph2DCustomData(data)) {\n throw new Error('graph2dStamp.renderSvgFromCustomData: customData không phải graph2d');\n }\n return renderGraph2dSvgFromState(data.jsonState);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as Graph2DCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isGraph2DCustomData(data)) return null;\n const svgString = await renderGraph2dSvgFromState(data.jsonState);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL =\n 'data:image/svg+xml;base64,' +\n (typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64'));\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: Graph2DStampHost,\n};\n"]}
@@ -56,7 +56,17 @@ function deserializeIntoBoard(board, serialized, options = {}) {
56
56
  const palette = options.palette ?? paletteFor(false);
57
57
  const idMap = /* @__PURE__ */ new Map();
58
58
  const resolve = (a) => {
59
- if (typeof a === "string" && idMap.has(a)) return idMap.get(a);
59
+ if (typeof a === "string") {
60
+ if (idMap.has(a)) return idMap.get(a);
61
+ const m = /^(.+):border:(\d+)$/.exec(a);
62
+ if (m) {
63
+ const poly = idMap.get(m[1]);
64
+ const idx = parseInt(m[2], 10);
65
+ if (poly && Array.isArray(poly.borders) && poly.borders[idx]) {
66
+ return poly.borders[idx];
67
+ }
68
+ }
69
+ }
60
70
  if (Array.isArray(a)) return a.map(resolve);
61
71
  return a;
62
72
  };
@@ -94,6 +104,34 @@ function safeJsx(label, fn, fallback) {
94
104
  }
95
105
 
96
106
  // src/stamps/geometry-2d/render.ts
107
+ var PIXELS_PER_UNIT = 20;
108
+ var MIN_DIM = 100;
109
+ var MAX_DIM = 1200;
110
+ var FALLBACK_W = 400;
111
+ var FALLBACK_H = 300;
112
+ function containerDimsForBbox(bbox) {
113
+ const [xmin, ymax, xmax, ymin] = bbox;
114
+ const w = Math.abs(xmax - xmin);
115
+ const h = Math.abs(ymax - ymin);
116
+ if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
117
+ return { width: FALLBACK_W, height: FALLBACK_H };
118
+ }
119
+ let width = w * PIXELS_PER_UNIT;
120
+ let height = h * PIXELS_PER_UNIT;
121
+ const maxAxis = Math.max(width, height);
122
+ if (maxAxis > MAX_DIM) {
123
+ const ratio = MAX_DIM / maxAxis;
124
+ width *= ratio;
125
+ height *= ratio;
126
+ }
127
+ const minAxis = Math.min(width, height);
128
+ if (minAxis < MIN_DIM) {
129
+ const ratio = MIN_DIM / minAxis;
130
+ width *= ratio;
131
+ height *= ratio;
132
+ }
133
+ return { width: Math.round(width), height: Math.round(height) };
134
+ }
97
135
  async function renderGeometrySvgFromState(jsonState) {
98
136
  const parsed = JSON.parse(jsonState);
99
137
  const palette = paletteFor(false);
@@ -116,10 +154,11 @@ async function renderGeometrySvgFromState(jsonState) {
116
154
  opts.grid.strokeColor = palette.grid;
117
155
  }
118
156
  });
157
+ const { width, height } = containerDimsForBbox(parsed.bbox);
119
158
  const container = document.createElement("div");
120
159
  const containerId = "jxg_offscreen_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
121
160
  container.id = containerId;
122
- container.style.cssText = "position:absolute;top:-99999px;left:-99999px;width:400px;height:300px;visibility:hidden;pointer-events:none;";
161
+ container.style.cssText = `position:absolute;top:-99999px;left:-99999px;width:${width}px;height:${height}px;visibility:hidden;pointer-events:none;`;
123
162
  document.body.appendChild(container);
124
163
  let board = null;
125
164
  try {
@@ -129,7 +168,7 @@ async function renderGeometrySvgFromState(jsonState) {
129
168
  grid: !!parsed.showGrid,
130
169
  showCopyright: false,
131
170
  showNavigation: false,
132
- keepAspectRatio: false
171
+ keepAspectRatio: true
133
172
  });
134
173
  deserializeIntoBoard(board, parsed, { palette });
135
174
  board.update();
@@ -150,5 +189,5 @@ function isGeometryCustomData(data) {
150
189
  }
151
190
 
152
191
  export { isGeometryCustomData, renderGeometrySvgFromState, safeJsx, serializeBoard };
153
- //# sourceMappingURL=chunk-KEYZ5EZT.mjs.map
154
- //# sourceMappingURL=chunk-KEYZ5EZT.mjs.map
192
+ //# sourceMappingURL=chunk-G7FR3AIV.mjs.map
193
+ //# sourceMappingURL=chunk-G7FR3AIV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stamps/geometry-2d/renderInline.ts","../src/stamps/geometry-2d/serialize.ts","../src/stamps/shared/safeJsx.ts","../src/stamps/geometry-2d/render.ts","../src/stamps/geometry-2d/types.ts"],"names":[],"mappings":"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","file":"chunk-G7FR3AIV.mjs","sourcesContent":["export function renderGeometryToSvg(boardContainer: HTMLElement): string {\n const svgEl = boardContainer.querySelector('svg');\n if (!svgEl) throw new Error('renderGeometryToSvg: no SVG found in board container');\n const clone = svgEl.cloneNode(true) as SVGElement;\n if (!clone.getAttribute('xmlns')) {\n clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');\n }\n return new XMLSerializer().serializeToString(clone);\n}\n","// JSXGraph không có built-in getJSON. Component giữ MỘT LOG riêng của các create() call\n// do user trigger, pass log đó vào serializeBoard. Replay = gọi board.create() theo thứ tự log.\n//\n// type === 'transform': args là [refs đến điểm/đường/scalar], attrs là { type: 'translate'|'rotate'|'reflect'|'scale', ... }.\n// Object trả về (kết quả board.create('transform', ...)) được đăng ký vào idMap như mọi element khác\n// để point/line phụ thuộc reference được bằng id ('j5' → JSXGraph transform object).\n//\n// Log lưu màu dưới dạng sentinel ('@stroke', '@axis', '@grid', '@label') để\n// theme-neutral. Khi replay, palette resolve thành màu thực theo `isDark` hiện\n// tại (truyền qua options.palette).\n\nimport { paletteFor, resolveAttrColors, type GeomPalette } from './editor/theme';\n\nexport interface SerializedElement {\n type: string;\n args: unknown[];\n attrs: Record<string, unknown>;\n id: string;\n}\n\nexport interface SerializedBoard {\n bbox: [number, number, number, number];\n elements: SerializedElement[];\n showAxis?: boolean;\n showGrid?: boolean;\n}\n\ninterface BoardLike {\n getBoundingBox(): [number, number, number, number];\n create(type: string, args: unknown[], attrs: Record<string, unknown>): unknown;\n}\n\nexport function serializeBoard(\n board: BoardLike,\n log: SerializedElement[],\n options: { showAxis?: boolean; showGrid?: boolean } = {},\n): SerializedBoard {\n return {\n bbox: board.getBoundingBox(),\n elements: log.map(e => ({ type: e.type, args: e.args, attrs: e.attrs, id: e.id })),\n showAxis: !!options.showAxis,\n showGrid: !!options.showGrid,\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction createValueLabel(board: any, target: any): unknown {\n if (!board || !target) return null;\n const e = (target.elType ?? target.type ?? '').toString().toLowerCase();\n if (e === 'segment' || e === 'line' || e === 'arrow') {\n const p1 = target.point1, p2 = target.point2;\n if (!p1 || !p2) return null;\n return board.create('text', [\n () => (p1.X() + p2.X()) / 2 + 0.15,\n () => (p1.Y() + p2.Y()) / 2 + 0.25,\n () => {\n const len = Math.hypot(p2.X() - p1.X(), p2.Y() - p1.Y());\n const name = typeof target.name === 'string' && target.name ? target.name : 'd';\n return `${name} = ${len.toFixed(2)}`;\n },\n ], { fontSize: 12, color: '#dc2626', fixed: true, highlight: false });\n }\n if (e === 'circle' || e === 'circumcircle') {\n const center = target.center ?? target.midpoint ?? target.point1;\n if (!center) return null;\n return board.create('text', [\n () => center.X() + 0.3,\n () => center.Y() + 0.3,\n () => {\n const r = typeof target.Radius === 'function' ? target.Radius() : 0;\n const name = typeof target.name === 'string' && target.name ? target.name : 'r';\n return `${name} = ${r.toFixed(2)}`;\n },\n ], { fontSize: 12, color: '#dc2626', fixed: true, highlight: false });\n }\n return null;\n}\n\nexport interface DeserializeOptions {\n /** Theme-aware palette để resolve sentinel attrs. Mặc định = light. */\n palette?: GeomPalette;\n}\n\nexport function deserializeIntoBoard(\n board: BoardLike,\n serialized: SerializedBoard,\n options: DeserializeOptions = {},\n): void {\n // Replay: args may contain references to earlier elements by our serialized id (\"j0\", \"j1\"…).\n // We resolve those to actual JSXGraph objects via a local id→object map. Nested\n // arrays are also resolved recursively — needed for dilate, which logs the\n // transform parent of a transformed point as [\"j2\",\"j3\",\"j4\"] (a chain of 3\n // transforms passed to `board.create('point', [src, [t1,t2,t3]])`).\n const palette = options.palette ?? paletteFor(false);\n const idMap = new Map<string, unknown>();\n // \"<polyId>:border:<i>\" → polygon.borders[i]. Polygon borders are auto-created\n // sub-segments of a (regular)polygon that user-driven tools can reference as\n // line slots (vd. perpendicular từ điểm tới cạnh đa giác).\n const resolve = (a: unknown): unknown => {\n if (typeof a === 'string') {\n if (idMap.has(a)) return idMap.get(a);\n const m = /^(.+):border:(\\d+)$/.exec(a);\n if (m) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const poly = idMap.get(m[1]) as any;\n const idx = parseInt(m[2], 10);\n if (poly && Array.isArray(poly.borders) && poly.borders[idx]) {\n return poly.borders[idx];\n }\n }\n }\n if (Array.isArray(a)) return a.map(resolve);\n return a;\n };\n for (const el of serialized.elements) {\n const resolvedArgs = el.args.map(resolve);\n if (el.type === 'valueLabel') {\n const target = resolvedArgs[0];\n const txt = createValueLabel(board, target);\n if (txt) idMap.set(el.id, txt);\n continue;\n }\n const themedAttrs = resolveAttrColors({ ...el.attrs }, palette);\n const created = board.create(el.type, resolvedArgs, themedAttrs);\n idMap.set(el.id, created);\n }\n}\n","const isDev = (() => {\n try {\n return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';\n } catch {\n return false;\n }\n})();\n\n/**\n * Wrap JSXGraph operations that can throw on stale/missing state.\n * In dev mode: log to console. In prod: silent swallow + return fallback.\n *\n * @param label Short tag for grep-ability (vd \"removeObject\", \"board.update\").\n * @param fn Operation to execute.\n * @param fallback Value to return if fn throws (default: undefined).\n */\nexport function safeJsx<T>(label: string, fn: () => T): T | undefined;\nexport function safeJsx<T>(label: string, fn: () => T, fallback: T): T;\nexport function safeJsx<T>(label: string, fn: () => T, fallback?: T): T | undefined {\n try {\n return fn();\n } catch (err) {\n if (isDev) {\n // eslint-disable-next-line no-console\n console.warn('[whiteboard:jsxgraph]', label, err);\n }\n return fallback;\n }\n}\n","import { renderGeometryToSvg } from './renderInline';\nimport { deserializeIntoBoard, type SerializedBoard } from './serialize';\nimport { paletteFor } from './editor/theme';\nimport { safeJsx } from '../shared/safeJsx';\n\n/**\n * Re-render geometry SVG từ jsonState đã serialize. Dùng cho:\n * 1. Restore math-stamp file sau khi reload page (Excalidraw mất binary files).\n * 2. Generate SVG lúc INSERT (thay vì clone DOM với màu theo theme editor).\n *\n * LƯU Ý quan trọng — luôn dùng LIGHT palette (nét đậm). Excalidraw apply CSS\n * `filter: invert(93%) hue-rotate(180deg)` lên canvas trong dark mode → nét\n * đậm tự đảo thành sáng. Nếu ta bake nét sáng vào SVG cho dark mode, filter\n * sẽ đảo thành đậm → chìm vào nền tối. Giải pháp: luôn dùng nét đậm + để\n * Excalidraw tự lo invert.\n *\n * Implementation: tạo 1 div ẩn (off-screen, real dimensions để JSXGraph render\n * chuẩn), initBoard, replay creation log từ jsonState, dump SVG, dọn dẹp.\n *\n * Container dimensions phải MATCH aspect ratio của bbox (đã được editor lưu\n * sau khi JSXGraph adjust với keepAspectRatio:true). Trước đây hardcode\n * 400×300 + keepAspectRatio:false làm shape bị kéo dãn (circle thành ellipse,\n * góc vuông lệch) khi bbox không 4:3 → ảnh hiển thị khác với editor lúc\n * double-click. Fix: tính container W/H từ bbox + keepAspectRatio:true để\n * SVG output khớp với view trong editor.\n *\n * Lý do JXG.Options.text.display = 'internal': JSXGraph mặc định render\n * label bằng HTML <div> overlay → clone SVG export sẽ thiếu label.\n */\n\nconst PIXELS_PER_UNIT = 20;\nconst MIN_DIM = 100;\nconst MAX_DIM = 1200;\nconst FALLBACK_W = 400;\nconst FALLBACK_H = 300;\n\nexport function containerDimsForBbox(bbox: [number, number, number, number]): { width: number; height: number } {\n const [xmin, ymax, xmax, ymin] = bbox;\n const w = Math.abs(xmax - xmin);\n const h = Math.abs(ymax - ymin);\n if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {\n return { width: FALLBACK_W, height: FALLBACK_H };\n }\n let width = w * PIXELS_PER_UNIT;\n let height = h * PIXELS_PER_UNIT;\n const maxAxis = Math.max(width, height);\n if (maxAxis > MAX_DIM) {\n const ratio = MAX_DIM / maxAxis;\n width *= ratio;\n height *= ratio;\n }\n const minAxis = Math.min(width, height);\n if (minAxis < MIN_DIM) {\n const ratio = MIN_DIM / minAxis;\n width *= ratio;\n height *= ratio;\n }\n return { width: Math.round(width), height: Math.round(height) };\n}\n\nexport async function renderGeometrySvgFromState(jsonState: string): Promise<string> {\n const parsed = JSON.parse(jsonState) as SerializedBoard;\n // Stamps inserted vào Excalidraw canvas → luôn dùng light palette.\n // Excalidraw's THEME_FILTER tự đảo nét trong dark mode.\n const palette = paletteFor(false);\n const JXG = (await import('jsxgraph')).default;\n safeJsx('render.applyOptions', () => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const opts = (JXG as any).Options;\n if (opts) {\n opts.text = opts.text || {};\n opts.text.display = 'internal';\n opts.text.useASCIIMathML = false;\n opts.text.useMathJax = false;\n opts.text.useKatex = false;\n opts.text.strokeColor = palette.label;\n opts.label = opts.label || {};\n opts.label.display = 'internal';\n opts.label.strokeColor = palette.label;\n opts.axis = opts.axis || {};\n opts.axis.strokeColor = palette.axis;\n opts.grid = opts.grid || {};\n opts.grid.strokeColor = palette.grid;\n }\n });\n const { width, height } = containerDimsForBbox(parsed.bbox);\n const container = document.createElement('div');\n const containerId = 'jxg_offscreen_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);\n container.id = containerId;\n container.style.cssText = `position:absolute;top:-99999px;left:-99999px;width:${width}px;height:${height}px;visibility:hidden;pointer-events:none;`;\n document.body.appendChild(container);\n let board: unknown = null;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n board = (JXG as any).JSXGraph.initBoard(containerId, {\n boundingbox: parsed.bbox,\n axis: !!parsed.showAxis,\n grid: !!parsed.showGrid,\n showCopyright: false,\n showNavigation: false,\n keepAspectRatio: true,\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n deserializeIntoBoard(board as any, parsed, { palette });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (board as any).update();\n return renderGeometryToSvg(container);\n } finally {\n safeJsx('render.freeBoard', () => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (board) (JXG as any).JSXGraph.freeBoard(board);\n });\n if (container.parentNode) container.parentNode.removeChild(container);\n }\n}\n","import type { BaseStampCustomData } from '../shared/types';\n\nexport interface GeometryCustomData extends BaseStampCustomData {\n kind: 'geometry';\n version: 1;\n jsonState: string;\n svgWidth: number;\n svgHeight: number;\n}\n\nexport function isGeometryCustomData(data: unknown): data is GeometryCustomData {\n if (!data || typeof data !== 'object') return false;\n const d = data as Partial<GeometryCustomData>;\n return d.kind === 'geometry' && d.version === 1 && typeof d.jsonState === 'string';\n}\n"]}
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { isGeometry3DCustomData, renderGeometry3DSvgFromState } from './chunk-WQOABS6N.mjs';
3
+ import { lazy } from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var Geometry3DStampHost = lazy(
7
+ () => import('./host-N6ACNJKI.mjs').then((m) => ({ default: m.Geometry3DStampHost }))
8
+ );
9
+ var Geometry3DIcon = /* @__PURE__ */ jsxs(
10
+ "svg",
11
+ {
12
+ width: "20",
13
+ height: "20",
14
+ viewBox: "0 0 24 24",
15
+ fill: "none",
16
+ stroke: "currentColor",
17
+ strokeWidth: "1.6",
18
+ strokeLinecap: "round",
19
+ strokeLinejoin: "round",
20
+ "aria-hidden": "true",
21
+ children: [
22
+ /* @__PURE__ */ jsx("path", { d: "M4 9 L4 20 L14 20 L14 9 Z" }),
23
+ /* @__PURE__ */ jsx("path", { d: "M4 9 L10 4 L20 4 L14 9 Z" }),
24
+ /* @__PURE__ */ jsx("path", { d: "M14 9 L20 4 L20 15 L14 20 Z" })
25
+ ]
26
+ }
27
+ );
28
+ var geometry3dStamp = {
29
+ kind: "geometry3d",
30
+ experimental: true,
31
+ shortcutKey: "d",
32
+ toolbarLabel: "D",
33
+ toolbarTitle: "H\xECnh 3D (D)",
34
+ toolbarIcon: Geometry3DIcon,
35
+ toolbarTestId: "stamp-toolbar-geometry3d",
36
+ matchesCustomData: isGeometry3DCustomData,
37
+ async renderSvgFromCustomData(data) {
38
+ if (!isGeometry3DCustomData(data)) {
39
+ throw new Error("geometry3dStamp.renderSvgFromCustomData: customData kh\xF4ng ph\u1EA3i geometry3d");
40
+ }
41
+ const { svgString } = await renderGeometry3DSvgFromState(data.jsonState);
42
+ return svgString;
43
+ },
44
+ restoreFileFromCustomData: async (element) => {
45
+ const data = element.customData;
46
+ const fileId = element.fileId;
47
+ if (!data || !fileId) return null;
48
+ if (!isGeometry3DCustomData(data)) return null;
49
+ try {
50
+ const { svgString } = await renderGeometry3DSvgFromState(data.jsonState);
51
+ const dataURL = `data:image/svg+xml;base64,${typeof btoa !== "undefined" ? btoa(unescape(encodeURIComponent(svgString))) : Buffer.from(svgString).toString("base64")}`;
52
+ return { fileId, dataURL, mimeType: "image/svg+xml" };
53
+ } catch {
54
+ return null;
55
+ }
56
+ },
57
+ Host: Geometry3DStampHost
58
+ };
59
+
60
+ export { geometry3dStamp };
61
+ //# sourceMappingURL=chunk-PWIMZIB6.mjs.map
62
+ //# sourceMappingURL=chunk-PWIMZIB6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stamps/geometry-3d/index.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,mBAAA,GAAsB,IAAA;AAAA,EAAK,MAC/B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,mBAAA,EAAoB,CAAE;AACnE,CAAA;AAEA,IAAM,cAAA,mBACJ,IAAA;AAAA,EAAC,KAAA;AAAA,EAAA;AAAA,IACC,KAAA,EAAM,IAAA;AAAA,IACN,MAAA,EAAO,IAAA;AAAA,IACP,OAAA,EAAQ,WAAA;AAAA,IACR,IAAA,EAAK,MAAA;AAAA,IACL,MAAA,EAAO,cAAA;AAAA,IACP,WAAA,EAAY,KAAA;AAAA,IACZ,aAAA,EAAc,OAAA;AAAA,IACd,cAAA,EAAe,OAAA;AAAA,IACf,aAAA,EAAY,MAAA;AAAA,IAGZ,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,GAAE,2BAAA,EAA4B,CAAA;AAAA,sBAEpC,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,0BAAA,EAA2B,CAAA;AAAA,sBAEnC,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,6BAAA,EAA8B;AAAA;AAAA;AACxC,CAAA;AAGK,IAAM,eAAA,GAA6B;AAAA,EACxC,IAAA,EAAM,YAAA;AAAA,EACN,YAAA,EAAc,IAAA;AAAA,EACd,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,gBAAA;AAAA,EACd,WAAA,EAAa,cAAA;AAAA,EACb,aAAA,EAAe,0BAAA;AAAA,EACf,iBAAA,EAAmB,sBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAgC;AAC5D,IAAA,IAAI,CAAC,sBAAA,CAAuB,IAAI,CAAA,EAAG;AACjC,MAAA,MAAM,IAAI,MAAM,mFAA2E,CAAA;AAAA,IAC7F;AACA,IAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,4BAAA,CAA6B,KAAK,SAAS,CAAA;AACvE,IAAA,OAAO,SAAA;AAAA,EACT,CAAA;AAAA,EACA,yBAAA,EAA2B,OAAO,OAAA,KAA+C;AAC/E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,sBAAA,CAAuB,IAAI,CAAA,EAAG,OAAO,IAAA;AAC1C,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,4BAAA,CAA6B,KAAK,SAAS,CAAA;AACvE,MAAA,MAAM,UAAU,CAAA,0BAAA,EACd,OAAO,SAAS,WAAA,GACZ,IAAA,CAAK,SAAS,kBAAA,CAAmB,SAAS,CAAC,CAAC,IAC5C,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,CAAE,QAAA,CAAS,QAAQ,CAC9C,CAAA,CAAA;AACA,MAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,IACtD,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-PWIMZIB6.mjs","sourcesContent":["'use client';\n\nimport { lazy, type ReactNode } from 'react';\nimport {\n isGeometry3DCustomData,\n type Geometry3DCustomData,\n} from './serialize';\nimport { renderGeometry3DSvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\n\nexport { isGeometry3DCustomData };\nexport type { Geometry3DCustomData };\n\nconst Geometry3DStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.Geometry3DStampHost })),\n);\n\nconst Geometry3DIcon: ReactNode = (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {/* Mặt trước */}\n <path d=\"M4 9 L4 20 L14 20 L14 9 Z\" />\n {/* Mặt trên */}\n <path d=\"M4 9 L10 4 L20 4 L14 9 Z\" />\n {/* Mặt phải */}\n <path d=\"M14 9 L20 4 L20 15 L14 20 Z\" />\n </svg>\n);\n\nexport const geometry3dStamp: StampType = {\n kind: 'geometry3d',\n experimental: true,\n shortcutKey: 'd',\n toolbarLabel: 'D',\n toolbarTitle: 'Hình 3D (D)',\n toolbarIcon: Geometry3DIcon,\n toolbarTestId: 'stamp-toolbar-geometry3d',\n matchesCustomData: isGeometry3DCustomData,\n async renderSvgFromCustomData(data: unknown): Promise<string> {\n if (!isGeometry3DCustomData(data)) {\n throw new Error('geometry3dStamp.renderSvgFromCustomData: customData không phải geometry3d');\n }\n const { svgString } = await renderGeometry3DSvgFromState(data.jsonState);\n return svgString;\n },\n restoreFileFromCustomData: async (element): Promise<RestoredStampFile | null> => {\n const data = element.customData as Geometry3DCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isGeometry3DCustomData(data)) return null;\n try {\n const { svgString } = await renderGeometry3DSvgFromState(data.jsonState);\n const dataURL = `data:image/svg+xml;base64,${\n typeof btoa !== 'undefined'\n ? btoa(unescape(encodeURIComponent(svgString)))\n : Buffer.from(svgString).toString('base64')\n }`;\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n } catch {\n return null;\n }\n },\n Host: Geometry3DStampHost,\n};\n"]}
@@ -0,0 +1,197 @@
1
+ "use client";
2
+ import { paletteFor } from './chunk-HTBLO5JO.mjs';
3
+
4
+ // src/stamps/geometry-3d/serialize.ts
5
+ function isGeometry3DCustomData(data) {
6
+ if (!data || typeof data !== "object") return false;
7
+ const d = data;
8
+ return d.kind === "geometry3d" && (d.version === 1 || d.version === 2) && typeof d.jsonState === "string";
9
+ }
10
+ function serializeBoard3D(state) {
11
+ return JSON.stringify(state);
12
+ }
13
+ function parseSerializedBoard3D(json) {
14
+ const parsed = JSON.parse(json);
15
+ if (!parsed || typeof parsed !== "object") {
16
+ throw new Error("parseSerializedBoard3D: not an object");
17
+ }
18
+ const p = parsed;
19
+ if (p.version !== 1 && p.version !== 2) {
20
+ throw new Error(`parseSerializedBoard3D: unsupported version ${String(p.version)}`);
21
+ }
22
+ if (!Array.isArray(p.elements)) {
23
+ throw new Error("parseSerializedBoard3D: elements missing");
24
+ }
25
+ return parsed;
26
+ }
27
+
28
+ // src/stamps/geometry-3d/editor/theme.ts
29
+ function paletteFor2(isDark) {
30
+ const base = paletteFor(isDark);
31
+ return {
32
+ ...base,
33
+ view3dBg: isDark ? "#1a1a1a" : "#ffffff",
34
+ axisX: "#d63b3b",
35
+ axisY: "#2d8a2d",
36
+ axisZ: "#2d6dd6"
37
+ };
38
+ }
39
+ var DEFAULT_VIEW3D = {
40
+ azimuth: 0.7,
41
+ elevation: 0.4,
42
+ bbox3D: [-3, -3, -3, 3, 3, 3]
43
+ };
44
+ var VIEW3D_ATTRS = (isDark) => {
45
+ const p = paletteFor2(isDark);
46
+ const axisLabel = (color) => ({
47
+ strokeColor: color,
48
+ fontSize: 14,
49
+ offset: [10, 0]
50
+ });
51
+ return {
52
+ az: { slider: { visible: false }, point2: { visible: false } },
53
+ el: { slider: { visible: false } },
54
+ projection: "central",
55
+ // GeoGebra-style: axes pass through origin (0,0,0) instead of bbox border.
56
+ axesPosition: "center",
57
+ xAxis: {
58
+ strokeColor: p.axisX,
59
+ strokeWidth: 2,
60
+ lastArrow: { type: 2, size: 8 },
61
+ name: "x",
62
+ withLabel: true,
63
+ label: axisLabel(p.axisX)
64
+ },
65
+ yAxis: {
66
+ strokeColor: p.axisY,
67
+ strokeWidth: 2,
68
+ lastArrow: { type: 2, size: 8 },
69
+ name: "y",
70
+ withLabel: true,
71
+ label: axisLabel(p.axisY)
72
+ },
73
+ zAxis: {
74
+ strokeColor: p.axisZ,
75
+ strokeWidth: 2,
76
+ lastArrow: { type: 2, size: 8 },
77
+ name: "z",
78
+ withLabel: true,
79
+ label: axisLabel(p.axisZ)
80
+ },
81
+ // GeoGebra-style: hide ALL bbox wall planes; the XY ground plane is drawn
82
+ // explicitly at z=0 via the helper below (so it coincides with Ox/Oy).
83
+ xPlaneRear: { visible: false, mesh3d: { visible: false } },
84
+ yPlaneRear: { visible: false, mesh3d: { visible: false } },
85
+ zPlaneRear: { visible: false, mesh3d: { visible: false } }
86
+ };
87
+ };
88
+ var GROUND_PLANE_ATTRS = (isDark) => ({
89
+ fillColor: isDark ? "#2a2a2a" : "#e6e6e6",
90
+ fillOpacity: isDark ? 0.5 : 0.55,
91
+ strokeColor: isDark ? "#3a3a3a" : "#cfcfcf",
92
+ strokeOpacity: 0.7,
93
+ strokeWidth: 1,
94
+ fixed: true,
95
+ highlight: false,
96
+ withLabel: false,
97
+ layer: 0
98
+ });
99
+ var GROUND_PLANE_RANGE = [-3, 3];
100
+
101
+ // src/stamps/geometry-3d/render.ts
102
+ var OUTPUT_WIDTH = 1024;
103
+ var OUTPUT_HEIGHT = 768;
104
+ async function renderGeometry3DSvgFromState(jsonState) {
105
+ const state = parseSerializedBoard3D(jsonState);
106
+ const JXG = (await import('jsxgraph')).default;
107
+ const div = document.createElement("div");
108
+ div.style.cssText = `position:absolute;left:-9999px;top:-9999px;width:${OUTPUT_WIDTH}px;height:${OUTPUT_HEIGHT}px;`;
109
+ document.body.appendChild(div);
110
+ try {
111
+ JXG.Options.text.display = "internal";
112
+ const board = JXG.JSXGraph.initBoard(div, {
113
+ boundingbox: state.bbox,
114
+ keepaspectratio: true,
115
+ axis: false,
116
+ showCopyright: false,
117
+ showNavigation: false,
118
+ renderer: "svg"
119
+ });
120
+ const baseAttrs = VIEW3D_ATTRS(false);
121
+ const view = board.create(
122
+ "view3d",
123
+ [
124
+ [-5, -5],
125
+ [10, 10],
126
+ [
127
+ [state.view.bbox3D[0], state.view.bbox3D[3]],
128
+ [state.view.bbox3D[1], state.view.bbox3D[4]],
129
+ [state.view.bbox3D[2], state.view.bbox3D[5]]
130
+ ]
131
+ ],
132
+ {
133
+ ...baseAttrs,
134
+ // JSXGraph view3d đọc azimuth/elevation từ az.slider.start (không phải
135
+ // az.value). Nếu pass `value` → JSXGraph bỏ qua → render rơi về default
136
+ // (1.0 rad / 0.3 rad), không khớp góc user xoay trong editor.
137
+ az: { ...baseAttrs.az, slider: { ...baseAttrs.az.slider, start: state.view.azimuth } },
138
+ el: { ...baseAttrs.el, slider: { ...baseAttrs.el.slider, start: state.view.elevation } }
139
+ }
140
+ );
141
+ try {
142
+ const v = view;
143
+ v?.az_slide?.setValue?.(state.view.azimuth);
144
+ v?.el_slide?.setValue?.(state.view.elevation);
145
+ v?.board?.update?.();
146
+ } catch {
147
+ }
148
+ if (!state.showAxes) {
149
+ view.defaultAxes = [];
150
+ }
151
+ try {
152
+ view.create(
153
+ "plane3d",
154
+ [
155
+ [0, 0, 0],
156
+ [1, 0, 0],
157
+ [0, 1, 0],
158
+ GROUND_PLANE_RANGE,
159
+ GROUND_PLANE_RANGE
160
+ ],
161
+ GROUND_PLANE_ATTRS(false)
162
+ );
163
+ } catch {
164
+ }
165
+ const idMap = /* @__PURE__ */ new Map();
166
+ for (const el of state.elements) {
167
+ const parents = el.parents.map(
168
+ (p) => typeof p === "string" && p.startsWith("@id:") ? idMap.get(p.slice(4)) : p
169
+ );
170
+ const obj = view.create(el.type, parents, {
171
+ ...el.attributes,
172
+ id: el.id,
173
+ name: el.label
174
+ });
175
+ idMap.set(el.id, obj);
176
+ }
177
+ const svg = div.querySelector("svg");
178
+ if (!svg) {
179
+ throw new Error("renderGeometry3DSvgFromState: SVG not produced");
180
+ }
181
+ const clone = svg.cloneNode(true);
182
+ clone.setAttribute("width", String(OUTPUT_WIDTH));
183
+ clone.setAttribute("height", String(OUTPUT_HEIGHT));
184
+ const svgString = new XMLSerializer().serializeToString(clone);
185
+ try {
186
+ JXG.JSXGraph.freeBoard(board);
187
+ } catch {
188
+ }
189
+ return { svgString, width: OUTPUT_WIDTH, height: OUTPUT_HEIGHT };
190
+ } finally {
191
+ document.body.removeChild(div);
192
+ }
193
+ }
194
+
195
+ export { DEFAULT_VIEW3D, GROUND_PLANE_ATTRS, GROUND_PLANE_RANGE, VIEW3D_ATTRS, isGeometry3DCustomData, paletteFor2 as paletteFor, parseSerializedBoard3D, renderGeometry3DSvgFromState, serializeBoard3D };
196
+ //# sourceMappingURL=chunk-WQOABS6N.mjs.map
197
+ //# sourceMappingURL=chunk-WQOABS6N.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stamps/geometry-3d/serialize.ts","../src/stamps/geometry-3d/editor/theme.ts","../src/stamps/geometry-3d/render.ts"],"names":["paletteFor"],"mappings":"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","file":"chunk-WQOABS6N.mjs","sourcesContent":["import type { BaseStampCustomData } from '../shared/types';\nimport type { Constraint } from './editor/scene/types';\n\nexport interface Geometry3DCustomData extends BaseStampCustomData {\n kind: 'geometry3d';\n version: 1 | 2;\n jsonState: string;\n svgWidth: number;\n svgHeight: number;\n}\n\nexport function isGeometry3DCustomData(data: unknown): data is Geometry3DCustomData {\n if (!data || typeof data !== 'object') return false;\n const d = data as Partial<Geometry3DCustomData>;\n return (\n d.kind === 'geometry3d' &&\n (d.version === 1 || d.version === 2) &&\n typeof d.jsonState === 'string'\n );\n}\n\nexport type Element3DType =\n | 'point3d'\n | 'line3d'\n | 'plane3d'\n | 'polygon3d'\n | 'sphere3d'\n | 'text3d';\n\nexport interface SerializedElement3D {\n type: Element3DType;\n /**\n * Parents passed to JSXGraph view.create. Either literal values (numbers,\n * strings) or `\"@id:<id>\"` placeholder strings referencing earlier created\n * objects in the log (resolved at deserialize time).\n */\n parents: unknown[];\n attributes: Record<string, unknown>;\n id: string;\n label?: string;\n /** v2 only — present on point3d elements to encode the surface constraint. */\n constraint?: Constraint;\n}\n\nexport interface SerializedBoard3D {\n version: 1 | 2;\n bbox: [number, number, number, number];\n view: {\n azimuth: number;\n elevation: number;\n bbox3D: [number, number, number, number, number, number];\n };\n showAxes: boolean;\n showMesh: boolean;\n elements: SerializedElement3D[];\n}\n\nexport function serializeBoard3D(state: SerializedBoard3D): string {\n return JSON.stringify(state);\n}\n\nexport function parseSerializedBoard3D(json: string): SerializedBoard3D {\n const parsed = JSON.parse(json) as unknown;\n if (!parsed || typeof parsed !== 'object') {\n throw new Error('parseSerializedBoard3D: not an object');\n }\n const p = parsed as Partial<SerializedBoard3D>;\n if (p.version !== 1 && p.version !== 2) {\n throw new Error(`parseSerializedBoard3D: unsupported version ${String(p.version)}`);\n }\n if (!Array.isArray(p.elements)) {\n throw new Error('parseSerializedBoard3D: elements missing');\n }\n return parsed as SerializedBoard3D;\n}\n","import {\n paletteFor as palette2D,\n type GeomPalette,\n} from '../../geometry-2d/editor/theme';\n\nexport type Geom3DPalette = GeomPalette & {\n view3dBg: string;\n axisX: string;\n axisY: string;\n axisZ: string;\n};\n\nexport function paletteFor(isDark: boolean): Geom3DPalette {\n const base = palette2D(isDark);\n return {\n ...base,\n view3dBg: isDark ? '#1a1a1a' : '#ffffff',\n axisX: '#d63b3b',\n axisY: '#2d8a2d',\n axisZ: '#2d6dd6',\n };\n}\n\nexport const DEFAULT_VIEW3D: {\n azimuth: number;\n elevation: number;\n bbox3D: [number, number, number, number, number, number];\n} = {\n azimuth: 0.7,\n elevation: 0.4,\n bbox3D: [-3, -3, -3, 3, 3, 3],\n};\n\nexport const VIEW3D_ATTRS = (isDark: boolean) => {\n const p = paletteFor(isDark);\n const axisLabel = (color: string) => ({\n strokeColor: color,\n fontSize: 14,\n offset: [10, 0] as [number, number],\n });\n return {\n az: { slider: { visible: false }, point2: { visible: false } },\n el: { slider: { visible: false } },\n projection: 'central' as const,\n // GeoGebra-style: axes pass through origin (0,0,0) instead of bbox border.\n axesPosition: 'center' as const,\n xAxis: {\n strokeColor: p.axisX,\n strokeWidth: 2,\n lastArrow: { type: 2, size: 8 },\n name: 'x',\n withLabel: true,\n label: axisLabel(p.axisX),\n },\n yAxis: {\n strokeColor: p.axisY,\n strokeWidth: 2,\n lastArrow: { type: 2, size: 8 },\n name: 'y',\n withLabel: true,\n label: axisLabel(p.axisY),\n },\n zAxis: {\n strokeColor: p.axisZ,\n strokeWidth: 2,\n lastArrow: { type: 2, size: 8 },\n name: 'z',\n withLabel: true,\n label: axisLabel(p.axisZ),\n },\n // GeoGebra-style: hide ALL bbox wall planes; the XY ground plane is drawn\n // explicitly at z=0 via the helper below (so it coincides with Ox/Oy).\n xPlaneRear: { visible: false, mesh3d: { visible: false } },\n yPlaneRear: { visible: false, mesh3d: { visible: false } },\n zPlaneRear: { visible: false, mesh3d: { visible: false } },\n };\n};\n\nexport const GROUND_PLANE_ATTRS = (isDark: boolean) => ({\n fillColor: isDark ? '#2a2a2a' : '#e6e6e6',\n fillOpacity: isDark ? 0.5 : 0.55,\n strokeColor: isDark ? '#3a3a3a' : '#cfcfcf',\n strokeOpacity: 0.7,\n strokeWidth: 1,\n fixed: true,\n highlight: false,\n withLabel: false,\n layer: 0,\n});\n\n/** XY ground plane extent (square around origin in user units). */\nexport const GROUND_PLANE_RANGE: [number, number] = [-3, 3];\n","\"use client\";\n\nimport { parseSerializedBoard3D } from './serialize';\nimport { GROUND_PLANE_ATTRS, GROUND_PLANE_RANGE, VIEW3D_ATTRS } from './editor/theme';\n\nexport interface RenderResult {\n svgString: string;\n width: number;\n height: number;\n}\n\nconst OUTPUT_WIDTH = 1024;\nconst OUTPUT_HEIGHT = 768;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype JxgObj = any;\n\nexport async function renderGeometry3DSvgFromState(\n jsonState: string,\n): Promise<RenderResult> {\n const state = parseSerializedBoard3D(jsonState);\n const JXG = (await import('jsxgraph')).default;\n\n const div = document.createElement('div');\n div.style.cssText = `position:absolute;left:-9999px;top:-9999px;width:${OUTPUT_WIDTH}px;height:${OUTPUT_HEIGHT}px;`;\n document.body.appendChild(div);\n\n try {\n JXG.Options.text.display = 'internal';\n\n const board = JXG.JSXGraph.initBoard(div, {\n boundingbox: state.bbox,\n keepaspectratio: true,\n axis: false,\n showCopyright: false,\n showNavigation: false,\n renderer: 'svg',\n }) as { create: (k: string, p: unknown[], a: unknown) => JxgObj };\n\n const baseAttrs = VIEW3D_ATTRS(false);\n const view: JxgObj = board.create(\n 'view3d',\n [\n [-5, -5],\n [10, 10],\n [\n [state.view.bbox3D[0], state.view.bbox3D[3]],\n [state.view.bbox3D[1], state.view.bbox3D[4]],\n [state.view.bbox3D[2], state.view.bbox3D[5]],\n ],\n ],\n {\n ...baseAttrs,\n // JSXGraph view3d đọc azimuth/elevation từ az.slider.start (không phải\n // az.value). Nếu pass `value` → JSXGraph bỏ qua → render rơi về default\n // (1.0 rad / 0.3 rad), không khớp góc user xoay trong editor.\n az: { ...baseAttrs.az, slider: { ...baseAttrs.az.slider, start: state.view.azimuth } },\n el: { ...baseAttrs.el, slider: { ...baseAttrs.el.slider, start: state.view.elevation } },\n },\n );\n\n // Defensive: setValue post-creation để chắc chắn slider khớp với serialized\n // angle, kể cả khi JSXGraph version khác xử lý `start` khác.\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const v = view as any;\n v?.az_slide?.setValue?.(state.view.azimuth);\n v?.el_slide?.setValue?.(state.view.elevation);\n v?.board?.update?.();\n } catch {\n /* swallow — older JSXGraph may not expose az_slide on view3d */\n }\n\n if (!state.showAxes) {\n (view as { defaultAxes?: unknown[] }).defaultAxes = [];\n }\n\n // XY ground plane through origin (matches editor's MiniBoard3D).\n try {\n (view as { create: (k: string, p: unknown[], a: unknown) => JxgObj }).create(\n 'plane3d',\n [\n [0, 0, 0],\n [1, 0, 0],\n [0, 1, 0],\n GROUND_PLANE_RANGE,\n GROUND_PLANE_RANGE,\n ],\n GROUND_PLANE_ATTRS(false),\n );\n } catch {\n /* swallow */\n }\n\n const idMap = new Map<string, JxgObj>();\n for (const el of state.elements) {\n const parents = el.parents.map((p) =>\n typeof p === 'string' && p.startsWith('@id:')\n ? idMap.get(p.slice(4))\n : p,\n );\n const obj = (\n view as { create: (k: string, p: unknown[], a: unknown) => JxgObj }\n ).create(el.type, parents, {\n ...el.attributes,\n id: el.id,\n name: el.label,\n });\n idMap.set(el.id, obj);\n }\n\n const svg = div.querySelector('svg');\n if (!svg) {\n throw new Error('renderGeometry3DSvgFromState: SVG not produced');\n }\n const clone = svg.cloneNode(true) as SVGElement;\n clone.setAttribute('width', String(OUTPUT_WIDTH));\n clone.setAttribute('height', String(OUTPUT_HEIGHT));\n const svgString = new XMLSerializer().serializeToString(clone);\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n JXG.JSXGraph.freeBoard(board as any);\n } catch {\n /* ignore teardown */\n }\n\n return { svgString, width: OUTPUT_WIDTH, height: OUTPUT_HEIGHT };\n } finally {\n document.body.removeChild(div);\n }\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { isGeometryCustomData, renderGeometrySvgFromState } from './chunk-KEYZ5EZT.mjs';
2
+ import { isGeometryCustomData, renderGeometrySvgFromState } from './chunk-G7FR3AIV.mjs';
3
3
  import { lazy } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
6
6
  var GeometryStampHost = lazy(
7
- () => import('./host-VDNAJMLC.mjs').then((m) => ({ default: m.GeometryStampHost }))
7
+ () => import('./host-XVK7UCRE.mjs').then((m) => ({ default: m.GeometryStampHost }))
8
8
  );
9
9
  var GeometryIcon = /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
10
10
  /* @__PURE__ */ jsx("polygon", { points: "4,20 20,20 12,5" }),
@@ -40,5 +40,5 @@ var geometryStamp = {
40
40
  };
41
41
 
42
42
  export { geometryStamp };
43
- //# sourceMappingURL=chunk-DU3RHKT5.mjs.map
44
- //# sourceMappingURL=chunk-DU3RHKT5.mjs.map
43
+ //# sourceMappingURL=chunk-YVJP7NRG.mjs.map
44
+ //# sourceMappingURL=chunk-YVJP7NRG.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stamps/geometry-2d/index.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,iBAAA,GAAoB,IAAA;AAAA,EAAK,MAC7B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,iBAAA,EAAkB,CAAE;AACjE,CAAA;AAEA,IAAM,YAAA,wBACH,KAAA,EAAA,EAAI,KAAA,EAAM,MAAK,MAAA,EAAO,IAAA,EAAK,SAAQ,WAAA,EAAY,IAAA,EAAK,QAAO,MAAA,EAAO,cAAA,EAAe,aAAY,KAAA,EAAM,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,eAAY,MAAA,EAC3J,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAQ,QAAO,iBAAA,EAAkB,CAAA;AAAA,kBAClC,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBACjE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBAClE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,GAAA,EAAI,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO;AAAA,CAAA,EACnE,CAAA;AAGK,IAAM,aAAA,GAA2B;AAAA,EACtC,IAAA,EAAM,UAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,8BAAA;AAAA,EACd,WAAA,EAAa,YAAA;AAAA,EACb,aAAA,EAAe,wBAAA;AAAA,EACf,iBAAA,EAAmB,oBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG;AAC/B,MAAA,MAAM,IAAI,MAAM,+EAAuE,CAAA;AAAA,IACzF;AACA,IAAA,OAAO,0BAAA,CAA2B,KAAK,SAAS,CAAA;AAAA,EAClD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG,OAAO,IAAA;AACxC,IAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,CAA2B,IAAA,CAAK,SAAS,CAAA;AACjE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GAAU,4BAAA,IACd,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AAEhF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-DU3RHKT5.mjs","sourcesContent":["'use client';\n\nimport { lazy, type ReactNode } from 'react';\nimport { renderGeometrySvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport {\n isGeometryCustomData,\n type GeometryCustomData,\n} from './types';\n\nexport { isGeometryCustomData };\nexport type { GeometryCustomData };\n\nconst GeometryStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.GeometryStampHost })),\n);\n\nconst GeometryIcon: ReactNode = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4,20 20,20 12,5\" />\n <circle cx=\"4\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"20\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"5\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\nexport const geometryStamp: StampType = {\n kind: 'geometry',\n shortcutKey: 'g',\n toolbarLabel: 'G',\n toolbarTitle: 'Chèn hình học (G)',\n toolbarIcon: GeometryIcon,\n toolbarTestId: 'stamp-toolbar-geometry',\n matchesCustomData: isGeometryCustomData,\n async renderSvgFromCustomData(data) {\n if (!isGeometryCustomData(data)) {\n throw new Error('geometryStamp.renderSvgFromCustomData: customData không phải geometry');\n }\n return renderGeometrySvgFromState(data.jsonState);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as GeometryCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isGeometryCustomData(data)) return null;\n const svgString = await renderGeometrySvgFromState(data.jsonState);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL = 'data:image/svg+xml;base64,' + (\n typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64')\n );\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: GeometryStampHost,\n};\n"]}
1
+ {"version":3,"sources":["../src/stamps/geometry-2d/index.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,iBAAA,GAAoB,IAAA;AAAA,EAAK,MAC7B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,iBAAA,EAAkB,CAAE;AACjE,CAAA;AAEA,IAAM,YAAA,wBACH,KAAA,EAAA,EAAI,KAAA,EAAM,MAAK,MAAA,EAAO,IAAA,EAAK,SAAQ,WAAA,EAAY,IAAA,EAAK,QAAO,MAAA,EAAO,cAAA,EAAe,aAAY,KAAA,EAAM,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,eAAY,MAAA,EAC3J,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAQ,QAAO,iBAAA,EAAkB,CAAA;AAAA,kBAClC,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBACjE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBAClE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,GAAA,EAAI,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO;AAAA,CAAA,EACnE,CAAA;AAGK,IAAM,aAAA,GAA2B;AAAA,EACtC,IAAA,EAAM,UAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,8BAAA;AAAA,EACd,WAAA,EAAa,YAAA;AAAA,EACb,aAAA,EAAe,wBAAA;AAAA,EACf,iBAAA,EAAmB,oBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG;AAC/B,MAAA,MAAM,IAAI,MAAM,+EAAuE,CAAA;AAAA,IACzF;AACA,IAAA,OAAO,0BAAA,CAA2B,KAAK,SAAS,CAAA;AAAA,EAClD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG,OAAO,IAAA;AACxC,IAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,CAA2B,IAAA,CAAK,SAAS,CAAA;AACjE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GAAU,4BAAA,IACd,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AAEhF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-YVJP7NRG.mjs","sourcesContent":["'use client';\n\nimport { lazy, type ReactNode } from 'react';\nimport { renderGeometrySvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport {\n isGeometryCustomData,\n type GeometryCustomData,\n} from './types';\n\nexport { isGeometryCustomData };\nexport type { GeometryCustomData };\n\nconst GeometryStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.GeometryStampHost })),\n);\n\nconst GeometryIcon: ReactNode = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4,20 20,20 12,5\" />\n <circle cx=\"4\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"20\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"5\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\nexport const geometryStamp: StampType = {\n kind: 'geometry',\n shortcutKey: 'g',\n toolbarLabel: 'G',\n toolbarTitle: 'Chèn hình học (G)',\n toolbarIcon: GeometryIcon,\n toolbarTestId: 'stamp-toolbar-geometry',\n matchesCustomData: isGeometryCustomData,\n async renderSvgFromCustomData(data) {\n if (!isGeometryCustomData(data)) {\n throw new Error('geometryStamp.renderSvgFromCustomData: customData không phải geometry');\n }\n return renderGeometrySvgFromState(data.jsonState);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as GeometryCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isGeometryCustomData(data)) return null;\n const svgString = await renderGeometrySvgFromState(data.jsonState);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL = 'data:image/svg+xml;base64,' + (\n typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64')\n );\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: GeometryStampHost,\n};\n"]}