canvu-react 0.3.5

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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/camera-BwQjm5oh.d.cts +50 -0
  4. package/dist/camera-KwCYYPhm.d.ts +50 -0
  5. package/dist/chatbot.cjs +221 -0
  6. package/dist/chatbot.cjs.map +1 -0
  7. package/dist/chatbot.d.cts +36 -0
  8. package/dist/chatbot.d.ts +36 -0
  9. package/dist/chatbot.js +218 -0
  10. package/dist/chatbot.js.map +1 -0
  11. package/dist/index.cjs +1920 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.cts +276 -0
  14. package/dist/index.d.ts +276 -0
  15. package/dist/index.js +1867 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/native.cjs +2572 -0
  18. package/dist/native.cjs.map +1 -0
  19. package/dist/native.d.cts +217 -0
  20. package/dist/native.d.ts +217 -0
  21. package/dist/native.js +2562 -0
  22. package/dist/native.js.map +1 -0
  23. package/dist/react.cjs +8540 -0
  24. package/dist/react.cjs.map +1 -0
  25. package/dist/react.d.cts +481 -0
  26. package/dist/react.d.ts +481 -0
  27. package/dist/react.js +8492 -0
  28. package/dist/react.js.map +1 -0
  29. package/dist/realtime.cjs +2338 -0
  30. package/dist/realtime.cjs.map +1 -0
  31. package/dist/realtime.d.cts +309 -0
  32. package/dist/realtime.d.ts +309 -0
  33. package/dist/realtime.js +2317 -0
  34. package/dist/realtime.js.map +1 -0
  35. package/dist/shape-builders-DTYvub8W.d.ts +93 -0
  36. package/dist/shape-builders-DxPoOecg.d.cts +93 -0
  37. package/dist/tldraw.cjs +1948 -0
  38. package/dist/tldraw.cjs.map +1 -0
  39. package/dist/tldraw.d.cts +98 -0
  40. package/dist/tldraw.d.ts +98 -0
  41. package/dist/tldraw.js +1941 -0
  42. package/dist/tldraw.js.map +1 -0
  43. package/dist/types--ALu1mF-.d.ts +356 -0
  44. package/dist/types-B58i5k-u.d.cts +35 -0
  45. package/dist/types-CB0TZZuk.d.cts +157 -0
  46. package/dist/types-CB0TZZuk.d.ts +157 -0
  47. package/dist/types-D1ftVsOQ.d.cts +356 -0
  48. package/dist/types-DgEArHkA.d.ts +35 -0
  49. package/package.json +103 -0
package/dist/tldraw.js ADDED
@@ -0,0 +1,1941 @@
1
+ import getStroke from 'perfect-freehand';
2
+
3
+ // src/math/rect.ts
4
+ function normalizeRect(r) {
5
+ const x0 = r.width >= 0 ? r.x : r.x + r.width;
6
+ const y0 = r.height >= 0 ? r.y : r.y + r.height;
7
+ return {
8
+ x: x0,
9
+ y: y0,
10
+ width: Math.abs(r.width),
11
+ height: Math.abs(r.height)
12
+ };
13
+ }
14
+
15
+ // src/scene/custom-shape.ts
16
+ function expandCustomShapeTemplate(template, width, height) {
17
+ return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
18
+ }
19
+ function resolveCustomInner(content, size) {
20
+ if ("render" in content) {
21
+ return content.render(size);
22
+ }
23
+ return expandCustomShapeTemplate(content.svg, size.width, size.height);
24
+ }
25
+ function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
26
+ const b = normalizeRect(bounds);
27
+ const sx = b.width / intrinsic.width;
28
+ const sy = b.height / intrinsic.height;
29
+ return `<g transform="scale(${sx},${sy})">${inner}</g>`;
30
+ }
31
+ function createCustomShapeItem(id, bounds, content) {
32
+ const r = normalizeRect(bounds);
33
+ const intrinsic = { width: r.width, height: r.height };
34
+ const inner = resolveCustomInner(content, intrinsic);
35
+ return {
36
+ id,
37
+ x: r.x,
38
+ y: r.y,
39
+ bounds: { ...r },
40
+ toolKind: "custom",
41
+ customIntrinsicSize: intrinsic,
42
+ customInnerSvg: inner,
43
+ childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
44
+ };
45
+ }
46
+
47
+ // src/scene/freehand-path.ts
48
+ function dedupeFreehandPoints(points, minDist) {
49
+ if (points.length <= 2) {
50
+ return points.map((p) => ({ ...p }));
51
+ }
52
+ const minSq = minDist * minDist;
53
+ const first = points[0];
54
+ if (!first) return [];
55
+ const out = [{ ...first }];
56
+ for (let i = 1; i < points.length - 1; i++) {
57
+ const p = points[i];
58
+ const last = out[out.length - 1];
59
+ if (!p || !last) continue;
60
+ const dx = p.x - last.x;
61
+ const dy = p.y - last.y;
62
+ if (dx * dx + dy * dy >= minSq) {
63
+ out.push({ ...p });
64
+ }
65
+ }
66
+ const end = points[points.length - 1];
67
+ const lastKept = out[out.length - 1];
68
+ if (!end || !lastKept) return out;
69
+ if ((end.x - lastKept.x) ** 2 + (end.y - lastKept.y) ** 2 > 1e-12) {
70
+ out.push({ ...end });
71
+ }
72
+ return out;
73
+ }
74
+ function smoothFreehandPointsToPathD(points) {
75
+ const n = points.length;
76
+ if (n === 0) return "";
77
+ if (n === 1) {
78
+ const p = points[0];
79
+ if (!p) return "";
80
+ return `M ${p.x} ${p.y}`;
81
+ }
82
+ if (n === 2) {
83
+ const a = points[0];
84
+ const b = points[1];
85
+ if (!a || !b) return "";
86
+ return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
87
+ }
88
+ const p0 = points[0];
89
+ if (!p0) return "";
90
+ let d = `M ${p0.x} ${p0.y}`;
91
+ let i = 1;
92
+ for (; i < n - 2; i++) {
93
+ const pi = points[i];
94
+ const pi1 = points[i + 1];
95
+ if (!pi || !pi1) continue;
96
+ const xc = (pi.x + pi1.x) / 2;
97
+ const yc = (pi.y + pi1.y) / 2;
98
+ d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
99
+ }
100
+ const pLast = points[i];
101
+ const pEnd = points[i + 1];
102
+ if (!pLast || !pEnd) return d;
103
+ d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
104
+ return d;
105
+ }
106
+ function outlineStrokeToClosedPathD(outline) {
107
+ const len = outline.length;
108
+ if (len === 0) return "";
109
+ const first = outline[0];
110
+ if (!first) return "";
111
+ if (len < 3) {
112
+ let d2 = `M ${first[0]} ${first[1]}`;
113
+ for (let i = 1; i < len; i++) {
114
+ const pt = outline[i];
115
+ if (!pt) continue;
116
+ d2 += ` L ${pt[0]} ${pt[1]}`;
117
+ }
118
+ return `${d2} Z`;
119
+ }
120
+ let d = `M ${first[0]} ${first[1]} Q`;
121
+ for (let i = 0; i < len; i++) {
122
+ const p0 = outline[i];
123
+ const p1 = outline[(i + 1) % len];
124
+ if (!p0 || !p1) continue;
125
+ d += ` ${p0[0]} ${p0[1]} ${(p0[0] + p1[0]) / 2} ${(p0[1] + p1[1]) / 2}`;
126
+ }
127
+ return `${d} Z`;
128
+ }
129
+
130
+ // src/scene/text-svg.ts
131
+ function escapeSvgTextContent(s) {
132
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
133
+ }
134
+ function escapeHtmlText(s) {
135
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
136
+ }
137
+ var DEFAULT_TEXT_FONT_SIZE = 18;
138
+ var LINE_HEIGHT_RATIO = 22 / 18;
139
+ var FIRST_LINE_BASELINE_RATIO = 24 / 18;
140
+ var PLACEHOLDER = "Tap to type";
141
+ var MIN_TEXT_BOX_W = 40;
142
+ var MIN_TEXT_BOX_H = 36;
143
+ var TEXT_PAD_X = 4;
144
+ var MAX_TEXT_MEASURE_CACHE_ENTRIES = 2e3;
145
+ var sharedMeasureContext;
146
+ var textMeasureCache = /* @__PURE__ */ new Map();
147
+ function getSharedMeasureContext() {
148
+ if (sharedMeasureContext !== void 0) {
149
+ return sharedMeasureContext;
150
+ }
151
+ if (typeof document === "undefined") {
152
+ sharedMeasureContext = null;
153
+ return sharedMeasureContext;
154
+ }
155
+ const canvas = document.createElement("canvas");
156
+ sharedMeasureContext = canvas.getContext("2d");
157
+ return sharedMeasureContext;
158
+ }
159
+ function textMeasureCacheKey(content, fontSize) {
160
+ return `${fontSize}
161
+ ${content}`;
162
+ }
163
+ function cacheMeasuredBounds(key, bounds) {
164
+ if (textMeasureCache.size >= MAX_TEXT_MEASURE_CACHE_ENTRIES) {
165
+ textMeasureCache.clear();
166
+ }
167
+ textMeasureCache.set(key, bounds);
168
+ }
169
+ function lineHeightFor(fontSize) {
170
+ return fontSize * LINE_HEIGHT_RATIO;
171
+ }
172
+ function firstLineBaselineY(fontSize) {
173
+ return fontSize * FIRST_LINE_BASELINE_RATIO;
174
+ }
175
+ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
176
+ const cacheKey = textMeasureCacheKey(content, fontSize);
177
+ const cached = textMeasureCache.get(cacheKey);
178
+ if (cached) {
179
+ return cached;
180
+ }
181
+ const lh = lineHeightFor(fontSize);
182
+ const baselineY = firstLineBaselineY(fontSize);
183
+ const trimmed = content.trim();
184
+ const lines = trimmed.length === 0 ? [PLACEHOLDER] : content.split("\n");
185
+ let maxInnerW = 0;
186
+ const ctx = getSharedMeasureContext();
187
+ if (ctx) {
188
+ ctx.font = `${fontSize}px system-ui, sans-serif`;
189
+ for (const line of lines) {
190
+ const toMeasure = trimmed.length === 0 ? PLACEHOLDER : line.length === 0 ? " " : line;
191
+ maxInnerW = Math.max(maxInnerW, ctx.measureText(toMeasure).width);
192
+ }
193
+ }
194
+ if (maxInnerW === 0) {
195
+ for (const line of lines) {
196
+ const toMeasure = trimmed.length === 0 ? PLACEHOLDER : line.length === 0 ? " " : line;
197
+ maxInnerW = Math.max(maxInnerW, toMeasure.length * fontSize * 0.52);
198
+ }
199
+ }
200
+ const minW = Math.max(MIN_TEXT_BOX_W, TEXT_PAD_X * 2 + fontSize);
201
+ const width = Math.max(minW, TEXT_PAD_X * 2 + maxInnerW);
202
+ const height = Math.max(
203
+ MIN_TEXT_BOX_H,
204
+ baselineY + (lines.length - 1) * lh + Math.max(8, fontSize * 0.35)
205
+ );
206
+ const measured = { width, height };
207
+ cacheMeasuredBounds(cacheKey, measured);
208
+ return measured;
209
+ }
210
+ function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
211
+ const lh = lineHeightFor(fontSize);
212
+ const y0 = firstLineBaselineY(fontSize);
213
+ const trimmed = content.trim();
214
+ if (trimmed.length === 0) {
215
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
216
+ }
217
+ const lines = content.split("\n");
218
+ if (lines.length === 1) {
219
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
220
+ }
221
+ const parts = [];
222
+ for (let i = 0; i < lines.length; i++) {
223
+ const line = lines[i] ?? "";
224
+ if (i === 0) {
225
+ parts.push(`<tspan x="4">${escapeSvgTextContent(line)}</tspan>`);
226
+ } else {
227
+ parts.push(`<tspan x="4" dy="${lh}">${escapeSvgTextContent(line)}</tspan>`);
228
+ }
229
+ }
230
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${parts.join("")}</text>`;
231
+ }
232
+ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
233
+ const w = Math.max(1, width);
234
+ const h = Math.max(1, height);
235
+ const lh = lineHeightFor(fontSize);
236
+ const trimmed = content.trim();
237
+ if (trimmed.length === 0) {
238
+ return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:2px 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:system-ui,sans-serif;white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:#94a3b8;font-style:italic">${escapeHtmlText(PLACEHOLDER)}</div></foreignObject>`;
239
+ }
240
+ const body = escapeHtmlText(content);
241
+ return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:2px 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:system-ui,sans-serif;white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:${fillColor}">${body}</div></foreignObject>`;
242
+ }
243
+
244
+ // src/scene/shape-builders.ts
245
+ var DEFAULT_STROKE_STYLE = {
246
+ stroke: "#2563eb",
247
+ strokeWidth: 2
248
+ };
249
+ function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware = false) {
250
+ const sw = style.strokeWidth;
251
+ const base = {
252
+ last: strokeComplete,
253
+ simulatePressure: true
254
+ };
255
+ if (toolKind === "draw" || toolKind === "pencil") {
256
+ if (pressureAware && toolKind === "draw") {
257
+ return {
258
+ ...base,
259
+ size: Math.max(2, sw * 1.05),
260
+ thinning: 0.42,
261
+ smoothing: 0.56,
262
+ streamline: 0.18,
263
+ simulatePressure: false
264
+ };
265
+ }
266
+ return {
267
+ ...base,
268
+ size: Math.max(2, sw * 1.18),
269
+ thinning: 0.12,
270
+ smoothing: 0.72,
271
+ streamline: 0.42,
272
+ simulatePressure: false
273
+ };
274
+ }
275
+ if (toolKind === "brush") {
276
+ return {
277
+ ...base,
278
+ size: Math.max(4, sw * 1.22),
279
+ thinning: 0.52,
280
+ smoothing: 0.64,
281
+ streamline: 0.68
282
+ };
283
+ }
284
+ return {
285
+ ...base,
286
+ size: Math.max(6, sw * 1.08),
287
+ thinning: 0.08,
288
+ smoothing: 0.88,
289
+ streamline: 0.84,
290
+ simulatePressure: false
291
+ };
292
+ }
293
+ function resolveStrokeStyle(item) {
294
+ return {
295
+ stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
296
+ strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
297
+ strokeOpacity: item.strokeOpacity
298
+ };
299
+ }
300
+ function strokeOpacityAttr(style) {
301
+ return style.strokeOpacity != null ? ` stroke-opacity="${style.strokeOpacity}"` : "";
302
+ }
303
+ function buildRectSvg(width, height, style = DEFAULT_STROKE_STYLE) {
304
+ return `<rect width="${width}" height="${height}" fill="none" stroke="${style.stroke}" stroke-width="${style.strokeWidth}" rx="4"${strokeOpacityAttr(style)} />`;
305
+ }
306
+ function buildEllipseSvg(width, height, style = DEFAULT_STROKE_STYLE) {
307
+ const rx = width / 2;
308
+ const ry = height / 2;
309
+ return `<ellipse cx="${rx}" cy="${ry}" rx="${rx}" ry="${ry}" fill="none" stroke="${style.stroke}" stroke-width="${style.strokeWidth}"${strokeOpacityAttr(style)} />`;
310
+ }
311
+ function buildLineSvg(line, style = DEFAULT_STROKE_STYLE) {
312
+ return `<line x1="${line.x1}" y1="${line.y1}" x2="${line.x2}" y2="${line.y2}" stroke="${style.stroke}" stroke-width="${style.strokeWidth}"${strokeOpacityAttr(style)} />`;
313
+ }
314
+ function computeStraightArrowGeometry(line, strokeWidth) {
315
+ const dx = line.x2 - line.x1;
316
+ const dy = line.y2 - line.y1;
317
+ const len = Math.hypot(dx, dy);
318
+ if (len < 1e-6) return null;
319
+ const ux = dx / len;
320
+ const uy = dy / len;
321
+ const headLength = Math.min(Math.max(strokeWidth * 4.2, 12), len * 0.38);
322
+ const headAngle = Math.PI / 6;
323
+ const cos = Math.cos(headAngle);
324
+ const sin = Math.sin(headAngle);
325
+ const rx1 = ux * cos - uy * sin;
326
+ const ry1 = ux * sin + uy * cos;
327
+ const rx2 = ux * cos + uy * sin;
328
+ const ry2 = -ux * sin + uy * cos;
329
+ return {
330
+ shaftEndX: line.x2,
331
+ shaftEndY: line.y2,
332
+ headTipX: line.x2,
333
+ headTipY: line.y2,
334
+ headLeftX: line.x2 - rx1 * headLength,
335
+ headLeftY: line.y2 - ry1 * headLength,
336
+ headRightX: line.x2 - rx2 * headLength,
337
+ headRightY: line.y2 - ry2 * headLength
338
+ };
339
+ }
340
+ function buildArrowSvg(itemId, line, style = DEFAULT_STROKE_STYLE) {
341
+ const geometry = computeStraightArrowGeometry(line, style.strokeWidth);
342
+ if (!geometry) {
343
+ return buildLineSvg(line, style);
344
+ }
345
+ const op = strokeOpacityAttr(style);
346
+ return `
347
+ <line x1="${line.x1}" y1="${line.y1}" x2="${geometry.shaftEndX}" y2="${geometry.shaftEndY}" stroke="${style.stroke}" stroke-width="${style.strokeWidth}" stroke-linecap="round"${op} />
348
+ <path d="M ${geometry.headLeftX} ${geometry.headLeftY} L ${geometry.headTipX} ${geometry.headTipY} L ${geometry.headRightX} ${geometry.headRightY}" fill="none" stroke="${style.stroke}" stroke-width="${style.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op} />
349
+ `;
350
+ }
351
+ function buildDrawDotSvg(r, style = DEFAULT_STROKE_STYLE) {
352
+ const op = style.strokeOpacity != null ? ` fill-opacity="${style.strokeOpacity}"` : "";
353
+ return `<circle cx="${r}" cy="${r}" r="${r}" fill="${style.stroke}" shape-rendering="geometricPrecision"${op} />`;
354
+ }
355
+ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
356
+ if (pathPointsLocal.length === 0) return null;
357
+ if (pathPointsLocal.length === 1) {
358
+ const p = pathPointsLocal[0];
359
+ if (!p) return null;
360
+ const r = Math.max(0.5, style.strokeWidth / 2);
361
+ return {
362
+ kind: "circle",
363
+ cx: p.x,
364
+ cy: p.y,
365
+ r,
366
+ fill: style.stroke,
367
+ fillOpacity: style.strokeOpacity
368
+ };
369
+ }
370
+ const minDist = Math.min(0.25, Math.max(0.02, style.strokeWidth * 0.02));
371
+ const pts = dedupeFreehandPoints(pathPointsLocal, minDist);
372
+ if (pts.length === 0) return null;
373
+ if (pts.length === 1) {
374
+ const p = pts[0];
375
+ if (!p) return null;
376
+ const r = Math.max(0.5, style.strokeWidth / 2);
377
+ return {
378
+ kind: "circle",
379
+ cx: p.x,
380
+ cy: p.y,
381
+ r,
382
+ fill: style.stroke,
383
+ fillOpacity: style.strokeOpacity
384
+ };
385
+ }
386
+ const hasPressure = toolKind === "draw" && pts.some((p) => p.pressure != null && Number.isFinite(p.pressure));
387
+ if (toolKind === "draw" && !hasPressure) {
388
+ const d2 = smoothFreehandPointsToPathD(pts);
389
+ return {
390
+ kind: "strokePath",
391
+ d: d2,
392
+ stroke: style.stroke,
393
+ strokeWidth: style.strokeWidth,
394
+ strokeOpacity: style.strokeOpacity
395
+ };
396
+ }
397
+ const input = hasPressure ? pts.map(
398
+ (p) => [p.x, p.y, Math.min(1, Math.max(0, p.pressure ?? 0.5))]
399
+ ) : pts.map((p) => [p.x, p.y]);
400
+ const opts = perfectFreehandOptions(toolKind, style, strokeComplete, hasPressure);
401
+ let outline = [];
402
+ try {
403
+ const raw = getStroke(input, opts);
404
+ outline = raw.map(([x, y]) => [x, y]);
405
+ } catch {
406
+ outline = [];
407
+ }
408
+ if (outline.length >= 3) {
409
+ const d2 = outlineStrokeToClosedPathD(outline);
410
+ return {
411
+ kind: "fillPath",
412
+ d: d2,
413
+ fill: style.stroke,
414
+ fillOpacity: style.strokeOpacity
415
+ };
416
+ }
417
+ const d = smoothFreehandPointsToPathD(pts);
418
+ return {
419
+ kind: "strokePath",
420
+ d,
421
+ stroke: style.stroke,
422
+ strokeWidth: style.strokeWidth,
423
+ strokeOpacity: style.strokeOpacity
424
+ };
425
+ }
426
+ function freehandPayloadToSvgString(payload) {
427
+ if (payload.kind === "circle") {
428
+ const op2 = payload.fillOpacity != null ? ` fill-opacity="${payload.fillOpacity}"` : "";
429
+ return `<circle cx="${payload.cx}" cy="${payload.cy}" r="${payload.r}" fill="${payload.fill}" shape-rendering="geometricPrecision"${op2} />`;
430
+ }
431
+ if (payload.kind === "fillPath") {
432
+ const op2 = payload.fillOpacity != null ? ` fill-opacity="${payload.fillOpacity}"` : "";
433
+ return `<path d="${payload.d}" fill="${payload.fill}" fill-rule="nonzero" stroke="none"${op2} shape-rendering="geometricPrecision" />`;
434
+ }
435
+ const op = strokeOpacityAttr({
436
+ stroke: payload.stroke,
437
+ strokeWidth: payload.strokeWidth,
438
+ strokeOpacity: payload.strokeOpacity
439
+ });
440
+ return `<path d="${payload.d}" fill="none" stroke="${payload.stroke}" stroke-width="${payload.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op} />`;
441
+ }
442
+ function buildFreehandPathSvg(pathPointsLocal, style, toolKind, strokeComplete = true) {
443
+ const payload = computeFreehandSvgPayload(
444
+ pathPointsLocal,
445
+ style,
446
+ toolKind,
447
+ strokeComplete
448
+ );
449
+ if (!payload) return "";
450
+ return freehandPayloadToSvgString(payload);
451
+ }
452
+ function lineEndpointsToLocal(bounds, worldEndA, worldEndB) {
453
+ const b = normalizeRect(bounds);
454
+ return {
455
+ x1: worldEndA.x - b.x,
456
+ y1: worldEndA.y - b.y,
457
+ x2: worldEndB.x - b.x,
458
+ y2: worldEndB.y - b.y
459
+ };
460
+ }
461
+ function rebuildItemSvg(item) {
462
+ const style = resolveStrokeStyle(item);
463
+ const k = item.toolKind;
464
+ if (k === "rect") {
465
+ const b = normalizeRect(item.bounds);
466
+ return {
467
+ ...item,
468
+ childrenSvg: buildRectSvg(b.width, b.height, style)
469
+ };
470
+ }
471
+ if (k === "ellipse") {
472
+ const b = normalizeRect(item.bounds);
473
+ return {
474
+ ...item,
475
+ childrenSvg: buildEllipseSvg(b.width, b.height, style)
476
+ };
477
+ }
478
+ if ((k === "line" || k === "arrow") && item.line) {
479
+ const line = item.line;
480
+ const childrenSvg = k === "arrow" ? buildArrowSvg(item.id, line, style) : buildLineSvg(line, style);
481
+ return { ...item, childrenSvg };
482
+ }
483
+ if (k === "text" && item.text !== void 0) {
484
+ const fs = item.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
485
+ if (item.textFixedBounds) {
486
+ const b2 = normalizeRect(item.bounds);
487
+ return {
488
+ ...item,
489
+ x: b2.x,
490
+ y: b2.y,
491
+ bounds: b2,
492
+ childrenSvg: buildTextFixedBoundsSvg(
493
+ item.text,
494
+ b2.width,
495
+ b2.height,
496
+ style.stroke,
497
+ fs
498
+ )
499
+ };
500
+ }
501
+ const m = measureTextBoundsLocal(item.text, fs);
502
+ const b = normalizeRect({
503
+ x: item.x,
504
+ y: item.y,
505
+ width: m.width,
506
+ height: m.height
507
+ });
508
+ return {
509
+ ...item,
510
+ x: b.x,
511
+ y: b.y,
512
+ bounds: b,
513
+ childrenSvg: buildTextSvg(item.text, b.width, b.height, style.stroke, fs)
514
+ };
515
+ }
516
+ if ((k === "draw" || k === "pencil" || k === "brush" || k === "marker") && item.pathPointsLocal && item.pathPointsLocal.length > 0) {
517
+ return {
518
+ ...item,
519
+ childrenSvg: buildFreehandPathSvg(item.pathPointsLocal, style, k)
520
+ };
521
+ }
522
+ if (k === "draw") {
523
+ const b = normalizeRect(item.bounds);
524
+ const r = Math.min(b.width, b.height) / 2;
525
+ return {
526
+ ...item,
527
+ childrenSvg: buildDrawDotSvg(r, style)
528
+ };
529
+ }
530
+ if (k === "image" && item.imageRasterHref && item.imageIntrinsicSize) {
531
+ const b = normalizeRect(item.bounds);
532
+ return {
533
+ ...item,
534
+ childrenSvg: buildRasterImageChildrenSvg(
535
+ item.imageRasterHref,
536
+ item.imageIntrinsicSize,
537
+ b
538
+ )
539
+ };
540
+ }
541
+ if (k === "custom" && item.customIntrinsicSize && item.customInnerSvg) {
542
+ const b = normalizeRect(item.bounds);
543
+ return {
544
+ ...item,
545
+ x: b.x,
546
+ y: b.y,
547
+ bounds: b,
548
+ childrenSvg: buildCustomShapeChildrenSvg(
549
+ item.customInnerSvg,
550
+ item.customIntrinsicSize,
551
+ b
552
+ )
553
+ };
554
+ }
555
+ return item;
556
+ }
557
+ function createLineItem(id, bounds, line, toolKind, style, arrowBind) {
558
+ const r = normalizeRect(bounds);
559
+ const s = { ...DEFAULT_STROKE_STYLE, ...style };
560
+ return rebuildItemSvg({
561
+ id,
562
+ x: r.x,
563
+ y: r.y,
564
+ bounds: { ...r },
565
+ toolKind,
566
+ line: { ...line },
567
+ stroke: s.stroke,
568
+ strokeWidth: s.strokeWidth,
569
+ ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
570
+ ...{},
571
+ childrenSvg: ""
572
+ });
573
+ }
574
+ function escapeSvgAttr(s) {
575
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
576
+ }
577
+ function buildRasterImageChildrenSvg(dataUrl, intrinsic, bounds) {
578
+ const r = normalizeRect(bounds);
579
+ const iw = Math.max(1e-6, intrinsic.width);
580
+ const ih = Math.max(1e-6, intrinsic.height);
581
+ const href = escapeSvgAttr(dataUrl);
582
+ const arB = r.width / Math.max(1e-9, r.height);
583
+ const arI = iw / ih;
584
+ if (Math.abs(arB - arI) < 1e-3) {
585
+ const s2 = r.width / iw;
586
+ return `<g transform="scale(${s2})"><image href="${href}" x="0" y="0" width="${iw}" height="${ih}" /></g>`;
587
+ }
588
+ const s = Math.min(r.width / iw, r.height / ih);
589
+ const tx = (r.width - iw * s) / 2;
590
+ const ty = (r.height - ih * s) / 2;
591
+ return `<g transform="translate(${tx}, ${ty}) scale(${s})"><image href="${href}" x="0" y="0" width="${iw}" height="${ih}" /></g>`;
592
+ }
593
+ function createImageItem(id, bounds, imageRasterHref, imageIntrinsicSize) {
594
+ const r = normalizeRect(bounds);
595
+ const iw = Math.max(1e-6, imageIntrinsicSize.width);
596
+ const ih = Math.max(1e-6, imageIntrinsicSize.height);
597
+ return {
598
+ id,
599
+ x: r.x,
600
+ y: r.y,
601
+ bounds: { ...r },
602
+ toolKind: "image",
603
+ imageRasterHref,
604
+ imageIntrinsicSize: { width: iw, height: ih },
605
+ childrenSvg: buildRasterImageChildrenSvg(
606
+ imageRasterHref,
607
+ { width: iw, height: ih },
608
+ r
609
+ )
610
+ };
611
+ }
612
+
613
+ // src/plugins/tldraw/index.ts
614
+ var IDENTITY_MATRIX = {
615
+ a: 1,
616
+ b: 0,
617
+ c: 0,
618
+ d: 1,
619
+ e: 0,
620
+ f: 0
621
+ };
622
+ var TL_COLORS = {
623
+ black: "#1d1d1d",
624
+ gray: "#6b7280",
625
+ grey: "#6b7280",
626
+ "light-gray": "#9ca3af",
627
+ "dark-gray": "#374151",
628
+ blue: "#3b82f6",
629
+ "light-blue": "#93c5fd",
630
+ green: "#22c55e",
631
+ "light-green": "#86efac",
632
+ "dark-green": "#16a34a",
633
+ red: "#ef4444",
634
+ "light-red": "#fca5a5",
635
+ "dark-red": "#dc2626",
636
+ violet: "#8b5cf6",
637
+ "light-violet": "#c4b5fd",
638
+ "dark-violet": "#7c3aed",
639
+ yellow: "#eab308",
640
+ orange: "#f97316",
641
+ "light-orange": "#fdba74"
642
+ };
643
+ function isRecord(value) {
644
+ return typeof value === "object" && value !== null && !Array.isArray(value);
645
+ }
646
+ function asRecord(value) {
647
+ return isRecord(value) ? value : void 0;
648
+ }
649
+ function asArray(value) {
650
+ return Array.isArray(value) ? value : void 0;
651
+ }
652
+ function getString(value) {
653
+ return typeof value === "string" ? value : void 0;
654
+ }
655
+ function getNumber(value) {
656
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
657
+ }
658
+ function clamp01(value) {
659
+ return Math.max(0, Math.min(1, value));
660
+ }
661
+ function resolveColor(value) {
662
+ if (!value) return "#1d1d1d";
663
+ return TL_COLORS[value] ?? value;
664
+ }
665
+ function escapeXml(value) {
666
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
667
+ }
668
+ function escapeHtmlText2(value) {
669
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
670
+ }
671
+ function formatNumber(value) {
672
+ if (!Number.isFinite(value)) return "0";
673
+ const rounded = Math.round(value * 100) / 100;
674
+ return Number.isInteger(rounded) ? String(rounded) : String(rounded);
675
+ }
676
+ function multiplyMatrix(left, right) {
677
+ return {
678
+ a: left.a * right.a + left.c * right.b,
679
+ b: left.b * right.a + left.d * right.b,
680
+ c: left.a * right.c + left.c * right.d,
681
+ d: left.b * right.c + left.d * right.d,
682
+ e: left.a * right.e + left.c * right.f + left.e,
683
+ f: left.b * right.e + left.d * right.f + left.f
684
+ };
685
+ }
686
+ function translationMatrix(x, y) {
687
+ return { a: 1, b: 0, c: 0, d: 1, e: x, f: y };
688
+ }
689
+ function rotationMatrix(radians) {
690
+ const cos = Math.cos(radians);
691
+ const sin = Math.sin(radians);
692
+ return { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
693
+ }
694
+ function applyMatrix(matrix, point) {
695
+ return {
696
+ x: matrix.a * point.x + matrix.c * point.y + matrix.e,
697
+ y: matrix.b * point.x + matrix.d * point.y + matrix.f
698
+ };
699
+ }
700
+ function matrixRotation(matrix) {
701
+ return Math.atan2(matrix.b, matrix.a);
702
+ }
703
+ function opacityToUnit(value) {
704
+ const numeric = getNumber(value);
705
+ if (numeric == null) return 1;
706
+ return numeric > 1 ? clamp01(numeric / 100) : clamp01(numeric);
707
+ }
708
+ function sizeToStrokePx(size, base = 2) {
709
+ if (size === "s") return base * 0.75;
710
+ if (size === "l") return base * 1.5;
711
+ if (size === "xl") return base * 2.25;
712
+ return base;
713
+ }
714
+ function sizeToFontPx(size) {
715
+ if (size === "s") return 14;
716
+ if (size === "l") return 24;
717
+ if (size === "xl") return 32;
718
+ return 18;
719
+ }
720
+ function dashArrayForStyle(dash, strokeWidth) {
721
+ if (!dash || dash === "solid") return void 0;
722
+ if (dash === "dotted") {
723
+ return `${formatNumber(Math.max(1.25, strokeWidth))} ${formatNumber(Math.max(2.5, strokeWidth * 2.2))}`;
724
+ }
725
+ if (dash === "dashed") {
726
+ return `${formatNumber(Math.max(5, strokeWidth * 4))} ${formatNumber(Math.max(3, strokeWidth * 2.2))}`;
727
+ }
728
+ if (dash === "draw") {
729
+ return `${formatNumber(Math.max(3, strokeWidth * 2.4))} ${formatNumber(Math.max(2.4, strokeWidth * 1.6))}`;
730
+ }
731
+ return dash;
732
+ }
733
+ function fillAttrs(fill, stroke) {
734
+ if (!fill || fill === "none") return 'fill="none"';
735
+ if (fill === "solid") return `fill="${stroke}" fill-opacity="0.16"`;
736
+ if (fill === "semi") return `fill="${stroke}" fill-opacity="0.1"`;
737
+ if (fill === "pattern") return `fill="${stroke}" fill-opacity="0.07"`;
738
+ return `fill="${resolveColor(fill)}" fill-opacity="0.16"`;
739
+ }
740
+ function strokeAttrs(style) {
741
+ const dashArray = dashArrayForStyle(style.dash, style.strokeWidth);
742
+ const dashAttr = dashArray ? ` stroke-dasharray="${dashArray}"` : "";
743
+ const lineCap = style.lineCap ? ` stroke-linecap="${style.lineCap}"` : "";
744
+ const lineJoin = style.lineJoin ? ` stroke-linejoin="${style.lineJoin}"` : "";
745
+ return `stroke="${style.stroke}" stroke-width="${formatNumber(style.strokeWidth)}"${dashAttr}${lineCap}${lineJoin}`;
746
+ }
747
+ function wrapOpacity(svg, opacity) {
748
+ if (opacity >= 0.999) return svg;
749
+ return `<g opacity="${formatNumber(opacity)}">${svg}</g>`;
750
+ }
751
+ function buildForeignObjectTextSvg(options) {
752
+ const x = options.x ?? 0;
753
+ const y = options.y ?? 0;
754
+ const w = Math.max(1, options.width);
755
+ const h = Math.max(1, options.height);
756
+ const padding = options.padding ?? 0;
757
+ const align = options.align === "end" || options.align === "right" ? "right" : options.align === "middle" || options.align === "center" ? "center" : "left";
758
+ const justify = align === "right" ? "flex-end" : align === "center" ? "center" : "flex-start";
759
+ const vertical = options.verticalAlign === "end" || options.verticalAlign === "bottom" ? "flex-end" : options.verticalAlign === "middle" || options.verticalAlign === "center" ? "center" : "flex-start";
760
+ const lineHeight = options.fontSize * (options.lineHeightRatio ?? 1.25);
761
+ const weight = options.fontWeight != null ? `font-weight:${options.fontWeight};` : "";
762
+ const fontStyle = options.italic ? "font-style:italic;" : "";
763
+ const background = options.background ? `background:${options.background};` : "";
764
+ const radius = options.borderRadius != null ? `border-radius:${formatNumber(options.borderRadius)}px;` : "";
765
+ return `<foreignObject x="${formatNumber(x)}" y="${formatNumber(y)}" width="${formatNumber(w)}" height="${formatNumber(h)}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${formatNumber(padding)}px;display:flex;align-items:${vertical};justify-content:${justify};text-align:${align};white-space:pre-wrap;word-break:break-word;overflow:hidden;color:${options.color};font-size:${formatNumber(options.fontSize)}px;line-height:${formatNumber(lineHeight)}px;font-family:system-ui,sans-serif;${weight}${fontStyle}${background}${radius}">${escapeHtmlText2(options.text)}</div></foreignObject>`;
766
+ }
767
+ function richTextToPlainText(value) {
768
+ const parts = [];
769
+ function walk(node) {
770
+ if (typeof node === "string") {
771
+ parts.push(node);
772
+ return;
773
+ }
774
+ if (Array.isArray(node)) {
775
+ for (const child of node) walk(child);
776
+ return;
777
+ }
778
+ const record = asRecord(node);
779
+ if (!record) return;
780
+ const type = getString(record.type);
781
+ if (type === "hardBreak") {
782
+ parts.push("\n");
783
+ return;
784
+ }
785
+ const text = getString(record.text);
786
+ if (text) parts.push(text);
787
+ const content = asArray(record.content);
788
+ if (content) {
789
+ const before = parts.length;
790
+ for (const child of content) walk(child);
791
+ if (parts.length > before && (type === "paragraph" || type === "heading" || type === "blockquote" || type === "listItem") && parts[parts.length - 1] !== "\n") {
792
+ parts.push("\n");
793
+ }
794
+ }
795
+ }
796
+ walk(value);
797
+ return parts.join("").replace(/\n{3,}/g, "\n\n").trimEnd();
798
+ }
799
+ function extractPlainText(props) {
800
+ const direct = getString(props.text);
801
+ if (direct != null) return direct;
802
+ const plain = getString(props.plainText);
803
+ if (plain != null) return plain;
804
+ const rich = richTextToPlainText(props.richText);
805
+ if (rich) return rich;
806
+ return "";
807
+ }
808
+ function numberOr(value, fallback) {
809
+ return getNumber(value) ?? fallback;
810
+ }
811
+ function decodeBase64Bytes(base64) {
812
+ if (!base64) return new Uint8Array();
813
+ const uint8ArrayCtor = Uint8Array;
814
+ if (typeof uint8ArrayCtor.fromBase64 === "function") {
815
+ return uint8ArrayCtor.fromBase64(base64);
816
+ }
817
+ if (typeof atob === "function") {
818
+ const binary = atob(base64);
819
+ const bytes = new Uint8Array(binary.length);
820
+ for (let index = 0; index < binary.length; index++) {
821
+ bytes[index] = binary.charCodeAt(index);
822
+ }
823
+ return bytes;
824
+ }
825
+ const globalBuffer = globalThis.Buffer;
826
+ if (globalBuffer) {
827
+ return new Uint8Array(globalBuffer.from(base64, "base64"));
828
+ }
829
+ return new Uint8Array();
830
+ }
831
+ function float16BitsToNumber(bits) {
832
+ const sign = (bits & 32768) !== 0 ? -1 : 1;
833
+ const exponent = bits >> 10 & 31;
834
+ const fraction = bits & 1023;
835
+ if (exponent === 0) {
836
+ if (fraction === 0) return sign === -1 ? -0 : 0;
837
+ return sign * (fraction / 1024) * 2 ** -14;
838
+ }
839
+ if (exponent === 31) {
840
+ return fraction === 0 ? sign * Infinity : Number.NaN;
841
+ }
842
+ return sign * (1 + fraction / 1024) * 2 ** (exponent - 15);
843
+ }
844
+ function getFloat16(dataView, offset) {
845
+ const native = dataView;
846
+ if (typeof native.getFloat16 === "function") {
847
+ return native.getFloat16(offset, true);
848
+ }
849
+ return float16BitsToNumber(dataView.getUint16(offset, true));
850
+ }
851
+ function isUsableDecodedPoints(points) {
852
+ return points.length > 0 && points.every(
853
+ (point) => Number.isFinite(point.x) && Number.isFinite(point.y) && (point.pressure == null || Number.isFinite(point.pressure))
854
+ );
855
+ }
856
+ function decodeDrawPathAsDeltaPoints(path) {
857
+ const bytes = decodeBase64Bytes(path);
858
+ if (bytes.length < 12 || (bytes.length - 12) % 6 !== 0) return [];
859
+ const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
860
+ let x = dataView.getFloat32(0, true);
861
+ let y = dataView.getFloat32(4, true);
862
+ let z = dataView.getFloat32(8, true);
863
+ const points = [{ x, y, pressure: clamp01(z) }];
864
+ for (let offset = 12; offset < bytes.length; offset += 6) {
865
+ x += getFloat16(dataView, offset);
866
+ y += getFloat16(dataView, offset + 2);
867
+ z += getFloat16(dataView, offset + 4);
868
+ points.push({ x, y, pressure: clamp01(z) });
869
+ }
870
+ return isUsableDecodedPoints(points) ? points : [];
871
+ }
872
+ function decodeDrawPathAsLegacyPoints(path) {
873
+ const bytes = decodeBase64Bytes(path);
874
+ if (bytes.length === 0 || bytes.length % 6 !== 0) return [];
875
+ const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
876
+ const points = [];
877
+ for (let offset = 0; offset < bytes.length; offset += 6) {
878
+ points.push({
879
+ x: getFloat16(dataView, offset),
880
+ y: getFloat16(dataView, offset + 2),
881
+ pressure: clamp01(getFloat16(dataView, offset + 4))
882
+ });
883
+ }
884
+ return isUsableDecodedPoints(points) ? points : [];
885
+ }
886
+ function decodeDrawPath(path) {
887
+ const deltaPoints = decodeDrawPathAsDeltaPoints(path);
888
+ return deltaPoints.length > 0 ? deltaPoints : decodeDrawPathAsLegacyPoints(path);
889
+ }
890
+ function unwrapStoredRecord(value) {
891
+ const record = asRecord(value);
892
+ if (!record) return null;
893
+ const state = asRecord(record.state);
894
+ return state ?? record;
895
+ }
896
+ function inferPageIdFromSession(input) {
897
+ if (!isRecord(input) || !("session" in input)) return void 0;
898
+ const session = asRecord(input.session);
899
+ if (!session) return void 0;
900
+ const direct = getString(session.currentPageId) ?? getString(session.pageId);
901
+ if (direct) return direct;
902
+ const pageState = asRecord(session.pageState);
903
+ if (!pageState) return void 0;
904
+ return getString(pageState.pageId) ?? getString(pageState.currentPageId);
905
+ }
906
+ function recordsFromDocument(document2) {
907
+ const snapshot = asRecord(document2);
908
+ if (!snapshot) return [];
909
+ if (isRecord(snapshot.store)) {
910
+ return Object.values(snapshot.store);
911
+ }
912
+ return Object.values(snapshot).map((value) => unwrapStoredRecord(value)).filter((value) => value != null);
913
+ }
914
+ function normalizeSnapshot(input, options) {
915
+ const rawInput = asRecord(input) ?? {};
916
+ let records = [];
917
+ if (Array.isArray(rawInput.documents)) {
918
+ records = rawInput.documents.map((value) => unwrapStoredRecord(value)).filter((value) => value != null);
919
+ } else if (isRecord(rawInput.store)) {
920
+ records = Object.values(rawInput.store);
921
+ } else if ("document" in rawInput) {
922
+ records = recordsFromDocument(rawInput.document);
923
+ }
924
+ const recordsMap = /* @__PURE__ */ new Map();
925
+ const shapes = /* @__PURE__ */ new Map();
926
+ const assets = /* @__PURE__ */ new Map();
927
+ const bindings = /* @__PURE__ */ new Map();
928
+ const pages = /* @__PURE__ */ new Map();
929
+ for (const record of records) {
930
+ const raw = asRecord(record);
931
+ const id = raw ? getString(raw.id) : void 0;
932
+ const typeName = raw ? getString(raw.typeName) : void 0;
933
+ if (!id || !typeName) continue;
934
+ recordsMap.set(id, record);
935
+ if (typeName === "shape") {
936
+ shapes.set(id, record);
937
+ continue;
938
+ }
939
+ if (typeName === "asset") {
940
+ assets.set(id, record);
941
+ continue;
942
+ }
943
+ if (typeName === "binding") {
944
+ bindings.set(id, record);
945
+ continue;
946
+ }
947
+ if (typeName === "page") {
948
+ pages.set(id, record);
949
+ }
950
+ }
951
+ const selectedPageId = options.pageId ?? inferPageIdFromSession(input) ?? pages.keys().next().value ?? Array.from(shapes.values()).find((shape) => shape.parentId.startsWith("page:"))?.parentId;
952
+ return {
953
+ records: recordsMap,
954
+ shapes,
955
+ assets,
956
+ bindings,
957
+ pages,
958
+ selectedPageId,
959
+ options,
960
+ localBoundsCache: /* @__PURE__ */ new Map(),
961
+ worldMatrixCache: /* @__PURE__ */ new Map(),
962
+ rootPageCache: /* @__PURE__ */ new Map()
963
+ };
964
+ }
965
+ function normalizePoint(value) {
966
+ if (Array.isArray(value)) {
967
+ const [x2, y2, pressure2] = value;
968
+ const px = getNumber(x2);
969
+ const py = getNumber(y2);
970
+ if (px == null || py == null) return null;
971
+ return {
972
+ x: px,
973
+ y: py,
974
+ ...getNumber(pressure2) != null ? { pressure: clamp01(getNumber(pressure2) ?? 0.5) } : {}
975
+ };
976
+ }
977
+ const record = asRecord(value);
978
+ if (!record) return null;
979
+ const x = getNumber(record.x);
980
+ const y = getNumber(record.y);
981
+ if (x == null || y == null) return null;
982
+ const pressure = getNumber(record.pressure) ?? getNumber(record.z);
983
+ return {
984
+ x,
985
+ y,
986
+ ...pressure != null ? { pressure: clamp01(pressure) } : {},
987
+ ...getString(record.index) ? { index: getString(record.index) } : {}
988
+ };
989
+ }
990
+ function collectStrokePoints(props) {
991
+ const segments = asArray(props.segments) ?? [];
992
+ const points = [];
993
+ const scaleX = getNumber(props.scaleX) ?? getNumber(props.scale) ?? 1;
994
+ const scaleY = getNumber(props.scaleY) ?? getNumber(props.scale) ?? 1;
995
+ for (const segment of segments) {
996
+ const record = asRecord(segment);
997
+ const encodedPath = record ? getString(record.path) : void 0;
998
+ const rawPoints = record ? asArray(record.points) ?? [] : [];
999
+ const segmentIsAbsolute = Boolean(encodedPath);
1000
+ const decodedPoints = rawPoints.length > 0 ? rawPoints.map((value) => normalizePoint(value)).filter((value) => value != null) : encodedPath ? decodeDrawPath(encodedPath) : [];
1001
+ const previous = points[points.length - 1];
1002
+ for (let index = 0; index < decodedPoints.length; index++) {
1003
+ const point = decodedPoints[index];
1004
+ if (!point) continue;
1005
+ const scaledPoint = {
1006
+ x: point.x * scaleX,
1007
+ y: point.y * scaleY,
1008
+ ...point.pressure != null ? { pressure: point.pressure } : {}
1009
+ };
1010
+ if (index === 0 && previous && !segmentIsAbsolute) {
1011
+ points.push({
1012
+ x: previous.x + scaledPoint.x,
1013
+ y: previous.y + scaledPoint.y,
1014
+ ...scaledPoint.pressure != null ? { pressure: scaledPoint.pressure } : {}
1015
+ });
1016
+ } else {
1017
+ points.push(scaledPoint);
1018
+ }
1019
+ }
1020
+ }
1021
+ return points;
1022
+ }
1023
+ function collectLinePoints(props) {
1024
+ const pointsRecord = asRecord(props.points);
1025
+ if (pointsRecord) {
1026
+ const points = Object.values(pointsRecord).map((value) => normalizePoint(value)).filter((value) => value != null).sort((left, right) => (left.index ?? "").localeCompare(right.index ?? ""));
1027
+ if (points.length > 0) return points;
1028
+ }
1029
+ const arrayPoints = asArray(props.points);
1030
+ if (arrayPoints) {
1031
+ const points = arrayPoints.map((value) => normalizePoint(value)).filter((value) => value != null);
1032
+ if (points.length > 0) return points;
1033
+ }
1034
+ const start = normalizePoint(props.start);
1035
+ const end = normalizePoint(props.end);
1036
+ if (start && end) return [start, end];
1037
+ return [];
1038
+ }
1039
+ function freehandPadding(strokeWidth) {
1040
+ return Math.max(strokeWidth * 0.75 + 4, strokeWidth / 2 + 6);
1041
+ }
1042
+ function boundsFromPoints(points, padding = 0) {
1043
+ if (points.length === 0) return null;
1044
+ let minX = Number.POSITIVE_INFINITY;
1045
+ let minY = Number.POSITIVE_INFINITY;
1046
+ let maxX = Number.NEGATIVE_INFINITY;
1047
+ let maxY = Number.NEGATIVE_INFINITY;
1048
+ for (const point of points) {
1049
+ minX = Math.min(minX, point.x);
1050
+ minY = Math.min(minY, point.y);
1051
+ maxX = Math.max(maxX, point.x);
1052
+ maxY = Math.max(maxY, point.y);
1053
+ }
1054
+ return {
1055
+ x: minX - padding,
1056
+ y: minY - padding,
1057
+ width: Math.max(maxX - minX + padding * 2, 1),
1058
+ height: Math.max(maxY - minY + padding * 2, 1)
1059
+ };
1060
+ }
1061
+ function computeArrowRoute(props) {
1062
+ const start = normalizePoint(props.start);
1063
+ const end = normalizePoint(props.end);
1064
+ if (!start || !end) return null;
1065
+ const kind = getString(props.kind);
1066
+ const bend = getNumber(props.bend) ?? 0;
1067
+ if (kind === "elbow") {
1068
+ const t = Math.max(0.1, Math.min(0.9, getNumber(props.elbowMidPoint) ?? 0.5));
1069
+ const midX = start.x + (end.x - start.x) * t;
1070
+ const p1 = { x: midX, y: start.y };
1071
+ const p2 = { x: midX, y: end.y };
1072
+ return {
1073
+ kind: "polyline",
1074
+ points: [start, p1, p2, end],
1075
+ startRef: p1,
1076
+ endRef: p2
1077
+ };
1078
+ }
1079
+ if (kind === "arc" || Math.abs(bend) > 0.5) {
1080
+ const midX = (start.x + end.x) / 2;
1081
+ const midY = (start.y + end.y) / 2;
1082
+ const dx = end.x - start.x;
1083
+ const dy = end.y - start.y;
1084
+ const length = Math.hypot(dx, dy) || 1;
1085
+ const nx = -dy / length;
1086
+ const ny = dx / length;
1087
+ const control = { x: midX + nx * bend, y: midY + ny * bend };
1088
+ return {
1089
+ kind: "quadratic",
1090
+ points: [start, end],
1091
+ control,
1092
+ startRef: control,
1093
+ endRef: control
1094
+ };
1095
+ }
1096
+ return {
1097
+ kind: "line",
1098
+ points: [start, end],
1099
+ startRef: end,
1100
+ endRef: start
1101
+ };
1102
+ }
1103
+ function defaultShapeRect(type, props) {
1104
+ const w = getNumber(props.w);
1105
+ const h = getNumber(props.h);
1106
+ if (type === "image" || type === "video") {
1107
+ return { x: 0, y: 0, width: w ?? 240, height: h ?? 160 };
1108
+ }
1109
+ if (type === "bookmark") {
1110
+ return { x: 0, y: 0, width: w ?? 320, height: h ?? 180 };
1111
+ }
1112
+ if (type === "embed") {
1113
+ return { x: 0, y: 0, width: w ?? 360, height: h ?? 220 };
1114
+ }
1115
+ if (type === "frame") {
1116
+ return { x: 0, y: 0, width: w ?? 360, height: h ?? 240 };
1117
+ }
1118
+ if (type === "note") {
1119
+ return { x: 0, y: 0, width: w ?? 220, height: h ?? 180 };
1120
+ }
1121
+ return { x: 0, y: 0, width: w ?? 120, height: h ?? 90 };
1122
+ }
1123
+ function resolveShapeLocalBounds(snapshot, shapeId, stack = /* @__PURE__ */ new Set()) {
1124
+ const cached = snapshot.localBoundsCache.get(shapeId);
1125
+ if (cached) return cached;
1126
+ if (stack.has(shapeId)) return { x: 0, y: 0, width: 120, height: 90 };
1127
+ stack.add(shapeId);
1128
+ const shape = snapshot.shapes.get(shapeId);
1129
+ if (!shape) {
1130
+ stack.delete(shapeId);
1131
+ return { x: 0, y: 0, width: 120, height: 90 };
1132
+ }
1133
+ const props = asRecord(shape.props) ?? {};
1134
+ const strokeWidth = sizeToStrokePx(
1135
+ getString(props.size),
1136
+ shape.type === "highlight" ? 16 : 2.5
1137
+ );
1138
+ let bounds = null;
1139
+ if (shape.type === "draw" || shape.type === "highlight") {
1140
+ bounds = boundsFromPoints(
1141
+ collectStrokePoints(props),
1142
+ freehandPadding(strokeWidth)
1143
+ );
1144
+ } else if (shape.type === "line") {
1145
+ bounds = boundsFromPoints(
1146
+ collectLinePoints(props),
1147
+ Math.max(6, strokeWidth * 2.5)
1148
+ );
1149
+ } else if (shape.type === "arrow") {
1150
+ const route = computeArrowRoute(props);
1151
+ if (route) {
1152
+ const routePoints = [...route.points, route.startRef, route.endRef];
1153
+ if (route.control) routePoints.push(route.control);
1154
+ bounds = boundsFromPoints(routePoints, Math.max(10, strokeWidth * 5));
1155
+ }
1156
+ } else if (shape.type === "text") {
1157
+ const text = extractPlainText(props);
1158
+ const fontSize = getNumber(props.fontSize) ?? sizeToFontPx(getString(props.size));
1159
+ const w = getNumber(props.w);
1160
+ const h = getNumber(props.h);
1161
+ bounds = w != null && h != null ? { x: 0, y: 0, width: Math.max(1, w), height: Math.max(1, h) } : { x: 0, y: 0, ...measureTextBoundsLocal(text, fontSize) };
1162
+ } else if (shape.type === "group") {
1163
+ const childRects = Array.from(snapshot.shapes.values()).filter((candidate) => candidate.parentId === shape.id).map((candidate) => {
1164
+ const childBounds = resolveShapeLocalBounds(snapshot, candidate.id, stack);
1165
+ return {
1166
+ x: candidate.x + childBounds.x,
1167
+ y: candidate.y + childBounds.y,
1168
+ width: childBounds.width,
1169
+ height: childBounds.height
1170
+ };
1171
+ });
1172
+ if (childRects.length > 0) {
1173
+ const points = [];
1174
+ for (const rect of childRects) {
1175
+ points.push({ x: rect.x, y: rect.y });
1176
+ points.push({ x: rect.x + rect.width, y: rect.y + rect.height });
1177
+ }
1178
+ bounds = boundsFromPoints(points, 0);
1179
+ }
1180
+ }
1181
+ if (!bounds) {
1182
+ bounds = defaultShapeRect(shape.type, props);
1183
+ }
1184
+ const normalized = normalizeRect(bounds);
1185
+ snapshot.localBoundsCache.set(shapeId, normalized);
1186
+ stack.delete(shapeId);
1187
+ return normalized;
1188
+ }
1189
+ function resolveShapeWorldMatrix(snapshot, shapeId, stack = /* @__PURE__ */ new Set()) {
1190
+ const cached = snapshot.worldMatrixCache.get(shapeId);
1191
+ if (cached) return cached;
1192
+ if (stack.has(shapeId)) return IDENTITY_MATRIX;
1193
+ stack.add(shapeId);
1194
+ const shape = snapshot.shapes.get(shapeId);
1195
+ if (!shape) {
1196
+ stack.delete(shapeId);
1197
+ return IDENTITY_MATRIX;
1198
+ }
1199
+ const localBounds = resolveShapeLocalBounds(snapshot, shapeId, stack);
1200
+ const cx = localBounds.x + localBounds.width / 2;
1201
+ const cy = localBounds.y + localBounds.height / 2;
1202
+ const localMatrix = multiplyMatrix(
1203
+ translationMatrix(shape.x, shape.y),
1204
+ multiplyMatrix(
1205
+ translationMatrix(cx, cy),
1206
+ multiplyMatrix(
1207
+ rotationMatrix(shape.rotation ?? 0),
1208
+ translationMatrix(-cx, -cy)
1209
+ )
1210
+ )
1211
+ );
1212
+ const parentShape = snapshot.shapes.get(shape.parentId);
1213
+ const world = parentShape ? multiplyMatrix(
1214
+ resolveShapeWorldMatrix(snapshot, parentShape.id, stack),
1215
+ localMatrix
1216
+ ) : localMatrix;
1217
+ snapshot.worldMatrixCache.set(shapeId, world);
1218
+ stack.delete(shapeId);
1219
+ return world;
1220
+ }
1221
+ function resolveRootPageId(snapshot, shapeId) {
1222
+ const cached = snapshot.rootPageCache.get(shapeId);
1223
+ if (cached !== void 0) return cached ?? void 0;
1224
+ const visited = /* @__PURE__ */ new Set();
1225
+ let current = snapshot.shapes.get(shapeId);
1226
+ while (current && !visited.has(current.id)) {
1227
+ visited.add(current.id);
1228
+ if (snapshot.pages.has(current.parentId) || current.parentId.startsWith("page:")) {
1229
+ snapshot.rootPageCache.set(shapeId, current.parentId);
1230
+ return current.parentId;
1231
+ }
1232
+ current = snapshot.shapes.get(current.parentId);
1233
+ }
1234
+ snapshot.rootPageCache.set(shapeId, null);
1235
+ return void 0;
1236
+ }
1237
+ function shouldIncludeShape(snapshot, shape) {
1238
+ if (!snapshot.selectedPageId) return true;
1239
+ const rootPageId = resolveRootPageId(snapshot, shape.id);
1240
+ if (!rootPageId) return snapshot.pages.size === 0;
1241
+ return rootPageId === snapshot.selectedPageId;
1242
+ }
1243
+ function buildItemPlacement(snapshot, shape, localBounds) {
1244
+ const world = resolveShapeWorldMatrix(snapshot, shape.id);
1245
+ const itemMatrix = multiplyMatrix(
1246
+ world,
1247
+ translationMatrix(localBounds.x, localBounds.y)
1248
+ );
1249
+ const origin = applyMatrix(itemMatrix, { x: 0, y: 0 });
1250
+ return {
1251
+ x: origin.x,
1252
+ y: origin.y,
1253
+ rotation: matrixRotation(itemMatrix),
1254
+ bounds: {
1255
+ x: origin.x,
1256
+ y: origin.y,
1257
+ width: Math.max(1, localBounds.width),
1258
+ height: Math.max(1, localBounds.height)
1259
+ }
1260
+ };
1261
+ }
1262
+ function buildItemBase(snapshot, shape, localBounds) {
1263
+ const placement = buildItemPlacement(snapshot, shape, localBounds);
1264
+ return {
1265
+ id: shape.id,
1266
+ x: placement.x,
1267
+ y: placement.y,
1268
+ bounds: placement.bounds,
1269
+ rotation: placement.rotation,
1270
+ locked: shape.isLocked
1271
+ };
1272
+ }
1273
+ function shapeOpacity(shape) {
1274
+ const props = asRecord(shape.props) ?? {};
1275
+ return clamp01((shape.opacity ?? 1) * opacityToUnit(props.opacity));
1276
+ }
1277
+ function shapeStrokeColor(shape) {
1278
+ const props = asRecord(shape.props) ?? {};
1279
+ return resolveColor(getString(props.color));
1280
+ }
1281
+ function createCustomImportedItem(snapshot, shape, localBounds, innerSvg, style) {
1282
+ const base = buildItemBase(snapshot, shape, localBounds);
1283
+ const custom = createCustomShapeItem(shape.id, base.bounds, { svg: innerSvg });
1284
+ return {
1285
+ ...custom,
1286
+ ...base,
1287
+ ...style?.stroke ? { stroke: style.stroke } : {},
1288
+ ...style?.strokeWidth != null ? { strokeWidth: style.strokeWidth } : {}
1289
+ };
1290
+ }
1291
+ function polygonPath(points) {
1292
+ if (points.length === 0) return "";
1293
+ let path = `M${formatNumber(points[0]?.x ?? 0)} ${formatNumber(points[0]?.y ?? 0)}`;
1294
+ for (let index = 1; index < points.length; index++) {
1295
+ const point = points[index];
1296
+ if (!point) continue;
1297
+ path += ` L${formatNumber(point.x)} ${formatNumber(point.y)}`;
1298
+ }
1299
+ return `${path} Z`;
1300
+ }
1301
+ function cloudPath(width, height) {
1302
+ const r = Math.min(width, height) * 0.15;
1303
+ return `M${formatNumber(r)} ${formatNumber(height * 0.3)} Q0 ${formatNumber(height * 0.1)} ${formatNumber(width * 0.15)} ${formatNumber(height * 0.05)} Q${formatNumber(width * 0.3)} 0 ${formatNumber(width * 0.45)} ${formatNumber(height * 0.1)} Q${formatNumber(width * 0.7)} 0 ${formatNumber(width * 0.8)} ${formatNumber(height * 0.15)} Q${formatNumber(width)} ${formatNumber(height * 0.2)} ${formatNumber(width * 0.9)} ${formatNumber(height * 0.5)} Q${formatNumber(width)} ${formatNumber(height * 0.7)} ${formatNumber(width * 0.85)} ${formatNumber(height * 0.8)} Q${formatNumber(width * 0.7)} ${formatNumber(height)} ${formatNumber(width * 0.5)} ${formatNumber(height * 0.9)} Q${formatNumber(width * 0.3)} ${formatNumber(height)} ${formatNumber(width * 0.15)} ${formatNumber(height * 0.85)} Q0 ${formatNumber(height * 0.8)} ${formatNumber(r * 0.5)} ${formatNumber(height * 0.6)} Q0 ${formatNumber(height * 0.5)} ${formatNumber(r)} ${formatNumber(height * 0.3)} Z`;
1304
+ }
1305
+ function geoPath(geo, width, height) {
1306
+ if (geo === "diamond" || geo === "rhombus") {
1307
+ return polygonPath([
1308
+ { x: width / 2, y: 0 },
1309
+ { x: width, y: height / 2 },
1310
+ { x: width / 2, y: height },
1311
+ { x: 0, y: height / 2 }
1312
+ ]);
1313
+ }
1314
+ if (geo === "triangle") {
1315
+ return polygonPath([
1316
+ { x: width / 2, y: 0 },
1317
+ { x: width, y: height },
1318
+ { x: 0, y: height }
1319
+ ]);
1320
+ }
1321
+ if (geo === "trapezoid") {
1322
+ return polygonPath([
1323
+ { x: width * 0.2, y: 0 },
1324
+ { x: width * 0.8, y: 0 },
1325
+ { x: width, y: height },
1326
+ { x: 0, y: height }
1327
+ ]);
1328
+ }
1329
+ if (geo === "hexagon") {
1330
+ return polygonPath([
1331
+ { x: width * 0.25, y: 0 },
1332
+ { x: width * 0.75, y: 0 },
1333
+ { x: width, y: height / 2 },
1334
+ { x: width * 0.75, y: height },
1335
+ { x: width * 0.25, y: height },
1336
+ { x: 0, y: height / 2 }
1337
+ ]);
1338
+ }
1339
+ if (geo === "star") {
1340
+ const cx = width / 2;
1341
+ const cy = height / 2;
1342
+ const rOut = Math.min(width, height) / 2;
1343
+ const rIn = rOut * 0.45;
1344
+ const points = [];
1345
+ for (let index = 0; index < 5; index++) {
1346
+ const outer = index * 2 * Math.PI / 5 - Math.PI / 2;
1347
+ const inner = outer + Math.PI / 5;
1348
+ points.push({
1349
+ x: cx + rOut * Math.cos(outer),
1350
+ y: cy + rOut * Math.sin(outer)
1351
+ });
1352
+ points.push({ x: cx + rIn * Math.cos(inner), y: cy + rIn * Math.sin(inner) });
1353
+ }
1354
+ return polygonPath(points);
1355
+ }
1356
+ if (geo === "cloud") {
1357
+ return cloudPath(width, height);
1358
+ }
1359
+ if (geo === "arrow-right") {
1360
+ return polygonPath([
1361
+ { x: 0, y: height * 0.25 },
1362
+ { x: width * 0.62, y: height * 0.25 },
1363
+ { x: width * 0.62, y: 0 },
1364
+ { x: width, y: height / 2 },
1365
+ { x: width * 0.62, y: height },
1366
+ { x: width * 0.62, y: height * 0.75 },
1367
+ { x: 0, y: height * 0.75 }
1368
+ ]);
1369
+ }
1370
+ if (geo === "arrow-left") {
1371
+ return polygonPath([
1372
+ { x: width, y: height * 0.25 },
1373
+ { x: width * 0.38, y: height * 0.25 },
1374
+ { x: width * 0.38, y: 0 },
1375
+ { x: 0, y: height / 2 },
1376
+ { x: width * 0.38, y: height },
1377
+ { x: width * 0.38, y: height * 0.75 },
1378
+ { x: width, y: height * 0.75 }
1379
+ ]);
1380
+ }
1381
+ if (geo === "check-box") {
1382
+ return `M0 0 H${formatNumber(width)} V${formatNumber(height)} H0 Z M${formatNumber(width * 0.2)} ${formatNumber(height * 0.56)} L${formatNumber(width * 0.42)} ${formatNumber(height * 0.78)} L${formatNumber(width * 0.82)} ${formatNumber(height * 0.24)}`;
1383
+ }
1384
+ if (geo === "x-box") {
1385
+ return `M0 0 H${formatNumber(width)} V${formatNumber(height)} H0 Z M${formatNumber(width * 0.22)} ${formatNumber(height * 0.22)} L${formatNumber(width * 0.78)} ${formatNumber(height * 0.78)} M${formatNumber(width * 0.78)} ${formatNumber(height * 0.22)} L${formatNumber(width * 0.22)} ${formatNumber(height * 0.78)}`;
1386
+ }
1387
+ return null;
1388
+ }
1389
+ function renderGeoShape(snapshot, shape) {
1390
+ const props = asRecord(shape.props) ?? {};
1391
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1392
+ const width = localBounds.width;
1393
+ const height = localBounds.height;
1394
+ const geo = getString(props.geo) ?? "rectangle";
1395
+ const stroke = shapeStrokeColor(shape);
1396
+ const strokeWidth = sizeToStrokePx(getString(props.size));
1397
+ const dash = getString(props.dash);
1398
+ const text = extractPlainText(props);
1399
+ const fontSize = getNumber(props.fontSize) ?? sizeToFontPx(getString(props.size));
1400
+ const customPath = geoPath(geo, width, height);
1401
+ const shapeMarkup = geo === "ellipse" || geo === "oval" ? `<ellipse cx="${formatNumber(width / 2)}" cy="${formatNumber(height / 2)}" rx="${formatNumber(width / 2)}" ry="${formatNumber(height / 2)}" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : customPath ? `<path d="${customPath}" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : `<rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="8" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1402
+ const textMarkup = text ? buildForeignObjectTextSvg({
1403
+ width,
1404
+ height,
1405
+ text,
1406
+ fontSize,
1407
+ color: stroke,
1408
+ padding: 10,
1409
+ align: getString(props.align),
1410
+ verticalAlign: getString(props.verticalAlign)
1411
+ }) : "";
1412
+ return createCustomImportedItem(
1413
+ snapshot,
1414
+ shape,
1415
+ localBounds,
1416
+ wrapOpacity(`${shapeMarkup}${textMarkup}`, shapeOpacity(shape)),
1417
+ { stroke, strokeWidth }
1418
+ );
1419
+ }
1420
+ function renderTextShape(snapshot, shape) {
1421
+ const props = asRecord(shape.props) ?? {};
1422
+ const text = extractPlainText(props);
1423
+ const stroke = shapeStrokeColor(shape);
1424
+ const fontSize = getNumber(props.fontSize) ?? sizeToFontPx(getString(props.size));
1425
+ const hasFixedBounds = getNumber(props.w) != null && getNumber(props.h) != null;
1426
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1427
+ const base = buildItemBase(snapshot, shape, localBounds);
1428
+ const childrenSvg = hasFixedBounds ? wrapOpacity(
1429
+ buildForeignObjectTextSvg({
1430
+ width: localBounds.width,
1431
+ height: localBounds.height,
1432
+ text,
1433
+ fontSize,
1434
+ color: stroke,
1435
+ padding: 4,
1436
+ align: getString(props.textAlign) ?? getString(props.align),
1437
+ verticalAlign: getString(props.verticalAlign)
1438
+ }),
1439
+ shapeOpacity(shape)
1440
+ ) : wrapOpacity(
1441
+ buildTextSvg(text, localBounds.width, localBounds.height, stroke, fontSize),
1442
+ shapeOpacity(shape)
1443
+ );
1444
+ return {
1445
+ ...base,
1446
+ toolKind: "text",
1447
+ stroke,
1448
+ text,
1449
+ textFontSize: fontSize,
1450
+ ...hasFixedBounds ? { textFixedBounds: true } : {},
1451
+ childrenSvg
1452
+ };
1453
+ }
1454
+ function renderStrokeShape(snapshot, shape) {
1455
+ const props = asRecord(shape.props) ?? {};
1456
+ const points = collectStrokePoints(props);
1457
+ if (points.length === 0) return null;
1458
+ const stroke = shapeStrokeColor(shape);
1459
+ const toolKind = shape.type === "highlight" ? "marker" : "draw";
1460
+ const strokeWidth = shape.type === "highlight" ? sizeToStrokePx(getString(props.size), 16) : sizeToStrokePx(getString(props.size), 2.5);
1461
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1462
+ const base = buildItemBase(snapshot, shape, localBounds);
1463
+ const opacity = shapeOpacity(shape) * (shape.type === "highlight" ? 0.45 : 1);
1464
+ const pathPointsLocal = points.map((point) => ({
1465
+ x: point.x - localBounds.x,
1466
+ y: point.y - localBounds.y,
1467
+ ...point.pressure != null ? { pressure: point.pressure } : {}
1468
+ }));
1469
+ const style = {
1470
+ stroke,
1471
+ strokeWidth,
1472
+ ...opacity < 0.999 ? { strokeOpacity: opacity } : {}
1473
+ };
1474
+ return {
1475
+ ...base,
1476
+ toolKind,
1477
+ stroke,
1478
+ strokeWidth,
1479
+ ...style.strokeOpacity != null ? { strokeOpacity: style.strokeOpacity } : {},
1480
+ pathPointsLocal,
1481
+ childrenSvg: buildFreehandPathSvg(pathPointsLocal, style, toolKind)
1482
+ };
1483
+ }
1484
+ function polylinePath(points) {
1485
+ if (points.length === 0) return "";
1486
+ let path = `M${formatNumber(points[0]?.x ?? 0)} ${formatNumber(points[0]?.y ?? 0)}`;
1487
+ for (let index = 1; index < points.length; index++) {
1488
+ const point = points[index];
1489
+ if (!point) continue;
1490
+ path += ` L${formatNumber(point.x)} ${formatNumber(point.y)}`;
1491
+ }
1492
+ return path;
1493
+ }
1494
+ function buildArrowHeadSvg(options) {
1495
+ const type = options.type;
1496
+ if (!type || type === "none") return "";
1497
+ const dx = options.tip.x - options.from.x;
1498
+ const dy = options.tip.y - options.from.y;
1499
+ const length = Math.hypot(dx, dy);
1500
+ if (length < 1e-6) return "";
1501
+ const ux = dx / length;
1502
+ const uy = dy / length;
1503
+ const headLength = Math.max(10, options.strokeWidth * 4.5);
1504
+ const headWidth = Math.max(7, options.strokeWidth * 3);
1505
+ const bx = options.tip.x - ux * headLength;
1506
+ const by = options.tip.y - uy * headLength;
1507
+ const px = -uy;
1508
+ const py = ux;
1509
+ const left = { x: bx + px * (headWidth / 2), y: by + py * (headWidth / 2) };
1510
+ const right = { x: bx - px * (headWidth / 2), y: by - py * (headWidth / 2) };
1511
+ if (type === "triangle") {
1512
+ return `<polygon points="${formatNumber(options.tip.x)},${formatNumber(options.tip.y)} ${formatNumber(left.x)},${formatNumber(left.y)} ${formatNumber(right.x)},${formatNumber(right.y)}" fill="${options.stroke}" />`;
1513
+ }
1514
+ if (type === "dot") {
1515
+ return `<circle cx="${formatNumber(options.tip.x)}" cy="${formatNumber(options.tip.y)}" r="${formatNumber(Math.max(3, options.strokeWidth * 1.5))}" fill="${options.stroke}" />`;
1516
+ }
1517
+ if (type === "diamond") {
1518
+ const mid = {
1519
+ x: options.tip.x - ux * (headLength / 2),
1520
+ y: options.tip.y - uy * (headLength / 2)
1521
+ };
1522
+ return `<polygon points="${formatNumber(options.tip.x)},${formatNumber(options.tip.y)} ${formatNumber(left.x)},${formatNumber(left.y)} ${formatNumber(mid.x - ux * (headLength / 2))},${formatNumber(mid.y - uy * (headLength / 2))} ${formatNumber(right.x)},${formatNumber(right.y)}" fill="none" stroke="${options.stroke}" stroke-width="${formatNumber(options.strokeWidth)}" stroke-linejoin="round" />`;
1523
+ }
1524
+ if (type === "bar") {
1525
+ return `<line x1="${formatNumber(left.x)}" y1="${formatNumber(left.y)}" x2="${formatNumber(right.x)}" y2="${formatNumber(right.y)}" stroke="${options.stroke}" stroke-width="${formatNumber(options.strokeWidth)}" stroke-linecap="round" />`;
1526
+ }
1527
+ return `<path d="M ${formatNumber(left.x)} ${formatNumber(left.y)} L ${formatNumber(options.tip.x)} ${formatNumber(options.tip.y)} L ${formatNumber(right.x)} ${formatNumber(right.y)}" fill="none" stroke="${options.stroke}" stroke-width="${formatNumber(options.strokeWidth)}" stroke-linecap="round" stroke-linejoin="round" />`;
1528
+ }
1529
+ function renderLineShape(snapshot, shape) {
1530
+ const props = asRecord(shape.props) ?? {};
1531
+ const points = collectLinePoints(props);
1532
+ if (points.length < 2) return null;
1533
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1534
+ const stroke = shapeStrokeColor(shape);
1535
+ const strokeWidth = sizeToStrokePx(getString(props.size));
1536
+ const dash = getString(props.dash);
1537
+ const opacity = shapeOpacity(shape);
1538
+ const simple = points.length === 2 && (getString(props.spline) == null || getString(props.spline) === "line") && (!dash || dash === "solid");
1539
+ if (simple) {
1540
+ const start = points[0];
1541
+ const end = points[1];
1542
+ if (!start || !end) return null;
1543
+ const placement = buildItemPlacement(snapshot, shape, localBounds);
1544
+ const line = lineEndpointsToLocal(
1545
+ placement.bounds,
1546
+ {
1547
+ x: placement.x + (start.x - localBounds.x),
1548
+ y: placement.y + (start.y - localBounds.y)
1549
+ },
1550
+ {
1551
+ x: placement.x + (end.x - localBounds.x),
1552
+ y: placement.y + (end.y - localBounds.y)
1553
+ }
1554
+ );
1555
+ const item = createLineItem(shape.id, placement.bounds, line, "line", {
1556
+ stroke,
1557
+ strokeWidth,
1558
+ ...opacity < 0.999 ? { strokeOpacity: opacity } : {}
1559
+ });
1560
+ return {
1561
+ ...item,
1562
+ rotation: buildItemBase(snapshot, shape, localBounds).rotation,
1563
+ locked: shape.isLocked
1564
+ };
1565
+ }
1566
+ const translated = points.map((point) => ({
1567
+ ...point,
1568
+ x: point.x - localBounds.x,
1569
+ y: point.y - localBounds.y
1570
+ }));
1571
+ const inner = `<path d="${polylinePath(translated)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1572
+ return createCustomImportedItem(
1573
+ snapshot,
1574
+ shape,
1575
+ localBounds,
1576
+ wrapOpacity(inner, opacity),
1577
+ { stroke, strokeWidth }
1578
+ );
1579
+ }
1580
+ function renderArrowShape(snapshot, shape) {
1581
+ const props = asRecord(shape.props) ?? {};
1582
+ const route = computeArrowRoute(props);
1583
+ if (!route) return null;
1584
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1585
+ const stroke = shapeStrokeColor(shape);
1586
+ const strokeWidth = sizeToStrokePx(getString(props.size));
1587
+ const dash = getString(props.dash);
1588
+ const opacity = shapeOpacity(shape);
1589
+ const translate = (point) => ({
1590
+ x: point.x - localBounds.x,
1591
+ y: point.y - localBounds.y
1592
+ });
1593
+ const translatedPoints = route.points.map(translate);
1594
+ const translatedStartRef = translate(route.startRef);
1595
+ const translatedEndRef = translate(route.endRef);
1596
+ const shaft = route.kind === "quadratic" && route.control ? `<path d="M${formatNumber(translatedPoints[0]?.x ?? 0)} ${formatNumber(translatedPoints[0]?.y ?? 0)} Q${formatNumber(route.control.x - localBounds.x)} ${formatNumber(route.control.y - localBounds.y)} ${formatNumber(translatedPoints[1]?.x ?? 0)} ${formatNumber(translatedPoints[1]?.y ?? 0)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : `<path d="${polylinePath(translatedPoints)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1597
+ const startPoint = translatedPoints[0];
1598
+ const endPoint = translatedPoints[translatedPoints.length - 1];
1599
+ if (!startPoint || !endPoint) return null;
1600
+ const inner = `${shaft}${buildArrowHeadSvg({ tip: startPoint, from: translatedStartRef, type: getString(props.arrowheadStart), stroke, strokeWidth })}${buildArrowHeadSvg({ tip: endPoint, from: translatedEndRef, type: getString(props.arrowheadEnd), stroke, strokeWidth })}`;
1601
+ return createCustomImportedItem(
1602
+ snapshot,
1603
+ shape,
1604
+ localBounds,
1605
+ wrapOpacity(inner, opacity),
1606
+ { stroke, strokeWidth }
1607
+ );
1608
+ }
1609
+ function resolveAsset(snapshot, shape) {
1610
+ const props = asRecord(shape.props) ?? {};
1611
+ const assetId = getString(props.assetId);
1612
+ if (!assetId) return void 0;
1613
+ return snapshot.assets.get(assetId);
1614
+ }
1615
+ function resolveAssetUrl(snapshot, shape, asset) {
1616
+ if (!asset) return void 0;
1617
+ const custom = snapshot.options.assetUrlResolver?.(asset, shape);
1618
+ if (custom) return custom;
1619
+ const props = asRecord(asset.props) ?? {};
1620
+ return getString(props.src) ?? getString(asRecord(shape.props)?.url);
1621
+ }
1622
+ function renderMissingAssetPlaceholder(snapshot, shape, kind, detail) {
1623
+ const props = asRecord(shape.props) ?? {};
1624
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1625
+ const width = localBounds.width;
1626
+ const height = localBounds.height;
1627
+ const inner = `
1628
+ <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="12" fill="#f4f4f5" stroke="#a1a1aa" stroke-width="1.5" stroke-dasharray="6 6" />
1629
+ <path d="M${formatNumber(width * 0.18)} ${formatNumber(height * 0.7)} L${formatNumber(width * 0.38)} ${formatNumber(height * 0.48)} L${formatNumber(width * 0.56)} ${formatNumber(height * 0.62)} L${formatNumber(width * 0.76)} ${formatNumber(height * 0.32)} L${formatNumber(width * 0.84)} ${formatNumber(height * 0.4)} L${formatNumber(width * 0.84)} ${formatNumber(height * 0.82)} L${formatNumber(width * 0.18)} ${formatNumber(height * 0.82)} Z" fill="#d4d4d8" stroke="#a1a1aa" stroke-width="1" />
1630
+ <circle cx="${formatNumber(width * 0.34)}" cy="${formatNumber(height * 0.34)}" r="${formatNumber(Math.max(6, Math.min(width, height) * 0.05))}" fill="#a1a1aa" />
1631
+ ${buildForeignObjectTextSvg({ x: 10, y: height * 0.04, width: width - 20, height: height * 0.28, text: kind, fontSize: 15, color: "#18181b", fontWeight: 700, align: "center", verticalAlign: "middle" })}
1632
+ ${buildForeignObjectTextSvg({ x: 14, y: height * 0.78, width: width - 28, height: height * 0.16, text: detail, fontSize: 12, color: "#52525b", align: "center", verticalAlign: "middle" })}
1633
+ `;
1634
+ return createCustomImportedItem(
1635
+ snapshot,
1636
+ shape,
1637
+ localBounds,
1638
+ wrapOpacity(inner, shapeOpacity(shape)),
1639
+ {
1640
+ stroke: shapeStrokeColor(shape),
1641
+ strokeWidth: sizeToStrokePx(getString(props.size))
1642
+ }
1643
+ );
1644
+ }
1645
+ function renderImageShape(snapshot, shape) {
1646
+ const props = asRecord(shape.props) ?? {};
1647
+ const asset = resolveAsset(snapshot, shape);
1648
+ const src = resolveAssetUrl(snapshot, shape, asset);
1649
+ const shapeBounds = resolveShapeLocalBounds(snapshot, shape.id);
1650
+ if (!src) {
1651
+ return renderMissingAssetPlaceholder(
1652
+ snapshot,
1653
+ shape,
1654
+ "Image asset missing",
1655
+ asset ? asset.id : getString(props.assetId) ?? "asset unavailable"
1656
+ );
1657
+ }
1658
+ const assetProps = asRecord(asset?.props) ?? {};
1659
+ const intrinsicWidth = getNumber(assetProps.w) ?? getNumber(props.w) ?? shapeBounds.width;
1660
+ const intrinsicHeight = getNumber(assetProps.h) ?? getNumber(props.h) ?? shapeBounds.height;
1661
+ const base = buildItemBase(snapshot, shape, shapeBounds);
1662
+ const image = createImageItem(shape.id, base.bounds, src, {
1663
+ width: Math.max(1, intrinsicWidth),
1664
+ height: Math.max(1, intrinsicHeight)
1665
+ });
1666
+ return {
1667
+ ...image,
1668
+ ...base,
1669
+ childrenSvg: wrapOpacity(image.childrenSvg, shapeOpacity(shape)),
1670
+ locked: shape.isLocked,
1671
+ rotation: base.rotation
1672
+ };
1673
+ }
1674
+ function renderVideoShape(snapshot, shape) {
1675
+ const asset = resolveAsset(snapshot, shape);
1676
+ const src = resolveAssetUrl(snapshot, shape, asset);
1677
+ const props = asRecord(shape.props) ?? {};
1678
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1679
+ const width = localBounds.width;
1680
+ const height = localBounds.height;
1681
+ const label = getString(asRecord(asset?.props)?.name) ?? "Video";
1682
+ const subtitle = src ?? getString(props.assetId) ?? "Playback not supported in this importer";
1683
+ const inner = `
1684
+ <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="14" fill="#18181b" stroke="#3f3f46" stroke-width="1.5" />
1685
+ <rect x="${formatNumber(width * 0.04)}" y="${formatNumber(height * 0.08)}" width="${formatNumber(width * 0.92)}" height="${formatNumber(height * 0.66)}" rx="10" fill="#27272a" />
1686
+ <circle cx="${formatNumber(width * 0.5)}" cy="${formatNumber(height * 0.41)}" r="${formatNumber(Math.min(width, height) * 0.12)}" fill="#fafafa" fill-opacity="0.92" />
1687
+ <polygon points="${formatNumber(width * 0.48)},${formatNumber(height * 0.35)} ${formatNumber(width * 0.48)},${formatNumber(height * 0.47)} ${formatNumber(width * 0.57)},${formatNumber(height * 0.41)}" fill="#18181b" />
1688
+ ${buildForeignObjectTextSvg({ x: 16, y: height * 0.76, width: width - 32, height: height * 0.12, text: label, fontSize: 15, color: "#fafafa", fontWeight: 700 })}
1689
+ ${buildForeignObjectTextSvg({ x: 16, y: height * 0.88, width: width - 32, height: height * 0.08, text: subtitle, fontSize: 11, color: "#d4d4d8" })}
1690
+ `;
1691
+ return createCustomImportedItem(
1692
+ snapshot,
1693
+ shape,
1694
+ localBounds,
1695
+ wrapOpacity(inner, shapeOpacity(shape)),
1696
+ {
1697
+ stroke: shapeStrokeColor(shape),
1698
+ strokeWidth: sizeToStrokePx(getString(props.size))
1699
+ }
1700
+ );
1701
+ }
1702
+ function renderNoteShape(snapshot, shape) {
1703
+ const props = asRecord(shape.props) ?? {};
1704
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1705
+ const width = localBounds.width;
1706
+ const height = localBounds.height;
1707
+ const noteColor = resolveColor(getString(props.color) ?? "yellow");
1708
+ const labelColor = resolveColor(getString(props.labelColor) ?? "#18181b");
1709
+ const text = extractPlainText(props);
1710
+ const fold = Math.min(width, height) * 0.14;
1711
+ const fontSize = sizeToFontPx(getString(props.size)) + numberOr(props.fontSizeAdjustment, 0);
1712
+ const inner = `
1713
+ <path d="M0 0 H${formatNumber(width - fold)} L${formatNumber(width)} ${formatNumber(fold)} V${formatNumber(height)} H0 Z" fill="${noteColor}" fill-opacity="0.22" stroke="${noteColor}" stroke-width="1.5" />
1714
+ <path d="M${formatNumber(width - fold)} 0 V${formatNumber(fold)} H${formatNumber(width)}" fill="${noteColor}" fill-opacity="0.3" stroke="${noteColor}" stroke-width="1.5" />
1715
+ ${buildForeignObjectTextSvg({ x: 12, y: 12, width: width - 24, height: height - 24, text, fontSize, color: labelColor, padding: 4, align: getString(props.align), verticalAlign: getString(props.verticalAlign) })}
1716
+ `;
1717
+ return createCustomImportedItem(
1718
+ snapshot,
1719
+ shape,
1720
+ localBounds,
1721
+ wrapOpacity(inner, shapeOpacity(shape)),
1722
+ {
1723
+ stroke: noteColor,
1724
+ strokeWidth: 1.5
1725
+ }
1726
+ );
1727
+ }
1728
+ function renderBookmarkShape(snapshot, shape) {
1729
+ const props = asRecord(shape.props) ?? {};
1730
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1731
+ const width = localBounds.width;
1732
+ const height = localBounds.height;
1733
+ const title = getString(props.title) ?? getString(props.label) ?? "Bookmark";
1734
+ const url = getString(props.url) ?? "URL unavailable";
1735
+ const description = getString(props.description) ?? getString(props.hostname) ?? "Imported from tldraw";
1736
+ const stroke = shapeStrokeColor(shape);
1737
+ const inner = `
1738
+ <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="16" fill="#ffffff" stroke="${stroke}" stroke-width="1.5" />
1739
+ <rect x="16" y="16" width="${formatNumber(Math.max(56, width * 0.22))}" height="${formatNumber(Math.max(56, height * 0.32))}" rx="12" fill="${stroke}" fill-opacity="0.12" />
1740
+ <path d="M${formatNumber(width * 0.11)} ${formatNumber(height * 0.25)} H${formatNumber(width * 0.18)}" stroke="${stroke}" stroke-width="3" stroke-linecap="round" />
1741
+ ${buildForeignObjectTextSvg({ x: width * 0.28, y: 16, width: width * 0.64, height: height * 0.22, text: title, fontSize: 17, color: "#111827", fontWeight: 700 })}
1742
+ ${buildForeignObjectTextSvg({ x: width * 0.28, y: height * 0.24, width: width * 0.64, height: height * 0.14, text: url, fontSize: 12, color: stroke })}
1743
+ ${buildForeignObjectTextSvg({ x: 16, y: height * 0.46, width: width - 32, height: height * 0.42, text: description, fontSize: 13, color: "#4b5563" })}
1744
+ `;
1745
+ return createCustomImportedItem(
1746
+ snapshot,
1747
+ shape,
1748
+ localBounds,
1749
+ wrapOpacity(inner, shapeOpacity(shape)),
1750
+ {
1751
+ stroke,
1752
+ strokeWidth: 1.5
1753
+ }
1754
+ );
1755
+ }
1756
+ function renderEmbedShape(snapshot, shape) {
1757
+ const props = asRecord(shape.props) ?? {};
1758
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1759
+ const width = localBounds.width;
1760
+ const height = localBounds.height;
1761
+ const stroke = shapeStrokeColor(shape);
1762
+ const title = getString(props.title) ?? getString(props.embedTitle) ?? "Embed";
1763
+ const url = getString(props.url) ?? getString(props.src) ?? "Embedded content";
1764
+ const inner = `
1765
+ <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="16" fill="#0f172a" stroke="${stroke}" stroke-width="1.5" />
1766
+ <rect x="16" y="16" width="${formatNumber(width - 32)}" height="${formatNumber(height * 0.62)}" rx="12" fill="#111827" stroke="#334155" stroke-width="1" />
1767
+ <circle cx="${formatNumber(width * 0.14)}" cy="${formatNumber(height * 0.14)}" r="5" fill="#ef4444" />
1768
+ <circle cx="${formatNumber(width * 0.18)}" cy="${formatNumber(height * 0.14)}" r="5" fill="#f59e0b" />
1769
+ <circle cx="${formatNumber(width * 0.22)}" cy="${formatNumber(height * 0.14)}" r="5" fill="#22c55e" />
1770
+ ${buildForeignObjectTextSvg({ x: 18, y: height * 0.72, width: width - 36, height: height * 0.11, text: title, fontSize: 16, color: "#f8fafc", fontWeight: 700 })}
1771
+ ${buildForeignObjectTextSvg({ x: 18, y: height * 0.84, width: width - 36, height: height * 0.08, text: url, fontSize: 11, color: "#cbd5e1" })}
1772
+ `;
1773
+ return createCustomImportedItem(
1774
+ snapshot,
1775
+ shape,
1776
+ localBounds,
1777
+ wrapOpacity(inner, shapeOpacity(shape)),
1778
+ {
1779
+ stroke,
1780
+ strokeWidth: 1.5
1781
+ }
1782
+ );
1783
+ }
1784
+ function renderFrameShape(snapshot, shape) {
1785
+ const props = asRecord(shape.props) ?? {};
1786
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1787
+ const width = localBounds.width;
1788
+ const height = localBounds.height;
1789
+ const stroke = shapeStrokeColor(shape);
1790
+ const title = (getString(props.name) ?? getString(props.title) ?? extractPlainText(props)) || "Frame";
1791
+ const inner = `
1792
+ <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="14" fill="none" stroke="${stroke}" stroke-width="2" stroke-dasharray="10 6" />
1793
+ <rect x="12" y="10" width="${formatNumber(Math.min(width - 24, Math.max(88, width * 0.34)))}" height="28" rx="8" fill="#ffffff" stroke="${stroke}" stroke-width="1.5" />
1794
+ ${buildForeignObjectTextSvg({ x: 16, y: 12, width: Math.min(width - 32, Math.max(80, width * 0.3)), height: 24, text: title, fontSize: 13, color: "#111827", fontWeight: 700, verticalAlign: "middle" })}
1795
+ `;
1796
+ return createCustomImportedItem(
1797
+ snapshot,
1798
+ shape,
1799
+ localBounds,
1800
+ wrapOpacity(inner, shapeOpacity(shape)),
1801
+ {
1802
+ stroke,
1803
+ strokeWidth: 2
1804
+ }
1805
+ );
1806
+ }
1807
+ function renderAnnotationShape(snapshot, shape) {
1808
+ const props = asRecord(shape.props) ?? {};
1809
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1810
+ const width = localBounds.width;
1811
+ const height = localBounds.height;
1812
+ const radius = Math.min(width, height) / 2;
1813
+ const fill = shape.type === "annotationBubble" ? "#3b82f6" : resolveColor(getString(props.color) ?? "#ef4444");
1814
+ const text = shape.type === "label" ? extractPlainText(props) : String(getNumber(props.number) ?? "");
1815
+ const inner = `
1816
+ <circle cx="${formatNumber(width / 2)}" cy="${formatNumber(height / 2)}" r="${formatNumber(radius)}" fill="${fill}" stroke="#ffffff" stroke-width="2" />
1817
+ <text x="${formatNumber(width / 2)}" y="${formatNumber(height / 2 + 4)}" fill="#ffffff" font-size="10" font-weight="700" text-anchor="middle" dominant-baseline="central">${escapeXml(text)}</text>
1818
+ `;
1819
+ return createCustomImportedItem(
1820
+ snapshot,
1821
+ shape,
1822
+ localBounds,
1823
+ wrapOpacity(inner, shapeOpacity(shape)),
1824
+ {
1825
+ stroke: fill,
1826
+ strokeWidth: 2
1827
+ }
1828
+ );
1829
+ }
1830
+ function renderUnsupportedShape(snapshot, shape, reason) {
1831
+ snapshot.options.onUnsupportedShape?.(shape, reason);
1832
+ const props = asRecord(shape.props) ?? {};
1833
+ const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1834
+ const width = localBounds.width;
1835
+ const height = localBounds.height;
1836
+ const inner = `
1837
+ <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="12" fill="#fafafa" stroke="#a1a1aa" stroke-width="1.5" stroke-dasharray="6 6" />
1838
+ ${buildForeignObjectTextSvg({ x: 12, y: 14, width: width - 24, height: 28, text: shape.type, fontSize: 14, color: "#111827", fontWeight: 700, verticalAlign: "middle" })}
1839
+ ${buildForeignObjectTextSvg({ x: 12, y: 50, width: width - 24, height: Math.max(20, height - 62), text: reason, fontSize: 12, color: "#52525b" })}
1840
+ `;
1841
+ return createCustomImportedItem(
1842
+ snapshot,
1843
+ shape,
1844
+ localBounds,
1845
+ wrapOpacity(inner, shapeOpacity(shape)),
1846
+ {
1847
+ stroke: shapeStrokeColor(shape),
1848
+ strokeWidth: sizeToStrokePx(getString(props.size))
1849
+ }
1850
+ );
1851
+ }
1852
+ function shapeToItem(snapshot, shape) {
1853
+ if (!shouldIncludeShape(snapshot, shape)) return null;
1854
+ if (shape.type === "group") return null;
1855
+ if (shape.type === "geo") return renderGeoShape(snapshot, shape);
1856
+ if (shape.type === "text") return renderTextShape(snapshot, shape);
1857
+ if (shape.type === "draw" || shape.type === "highlight") {
1858
+ return renderStrokeShape(snapshot, shape);
1859
+ }
1860
+ if (shape.type === "line") return renderLineShape(snapshot, shape);
1861
+ if (shape.type === "arrow") return renderArrowShape(snapshot, shape);
1862
+ if (shape.type === "image") return renderImageShape(snapshot, shape);
1863
+ if (shape.type === "video") return renderVideoShape(snapshot, shape);
1864
+ if (shape.type === "note") return renderNoteShape(snapshot, shape);
1865
+ if (shape.type === "bookmark") return renderBookmarkShape(snapshot, shape);
1866
+ if (shape.type === "embed") return renderEmbedShape(snapshot, shape);
1867
+ if (shape.type === "frame") return renderFrameShape(snapshot, shape);
1868
+ if (shape.type === "annotationBubble" || shape.type === "label") {
1869
+ return renderAnnotationShape(snapshot, shape);
1870
+ }
1871
+ return renderUnsupportedShape(
1872
+ snapshot,
1873
+ shape,
1874
+ "Unsupported tldraw shape rendered as placeholder."
1875
+ );
1876
+ }
1877
+ function tldrawSnapshotToItems(input, options = {}) {
1878
+ const snapshot = normalizeSnapshot(input, options);
1879
+ const shapes = Array.from(snapshot.shapes.values()).sort(
1880
+ (left, right) => (left.index ?? "").localeCompare(right.index ?? "")
1881
+ );
1882
+ const items = [];
1883
+ for (const shape of shapes) {
1884
+ const item = shapeToItem(snapshot, shape);
1885
+ if (item) items.push(item);
1886
+ }
1887
+ return items;
1888
+ }
1889
+ function itemsToTldrawSnapshot(items) {
1890
+ const documents = [];
1891
+ for (const item of items) {
1892
+ const type = item.toolKind ?? "draw";
1893
+ const props = {
1894
+ w: item.bounds.width,
1895
+ h: item.bounds.height,
1896
+ color: item.stroke ?? "#1d1d1d"
1897
+ };
1898
+ if (item.strokeWidth) {
1899
+ props.size = item.strokeWidth <= 1.5 ? "s" : item.strokeWidth >= 4 ? "l" : "m";
1900
+ }
1901
+ if (item.strokeOpacity != null) {
1902
+ props.opacity = Math.round(item.strokeOpacity * 100);
1903
+ }
1904
+ if (type === "rect" || type === "ellipse") {
1905
+ props.geo = type === "ellipse" ? "ellipse" : "rectangle";
1906
+ props.fill = "none";
1907
+ } else if (type === "draw" || type === "marker") {
1908
+ props.segments = [
1909
+ { points: (item.pathPointsLocal ?? []).map((point) => [point.x, point.y]) }
1910
+ ];
1911
+ } else if (type === "text") {
1912
+ props.text = item.text ?? "";
1913
+ props.fontSize = item.textFontSize ?? 16;
1914
+ } else if (type === "arrow" || type === "line") {
1915
+ props.start = { x: item.line?.x1 ?? 0, y: item.line?.y1 ?? 0 };
1916
+ props.end = { x: item.line?.x2 ?? 10, y: item.line?.y2 ?? 10 };
1917
+ props.arrowheadEnd = type === "arrow" ? "triangle" : "none";
1918
+ } else {
1919
+ continue;
1920
+ }
1921
+ documents.push({
1922
+ id: item.id,
1923
+ typeName: "shape",
1924
+ type,
1925
+ x: item.x,
1926
+ y: item.y,
1927
+ rotation: item.rotation ?? 0,
1928
+ index: "a1",
1929
+ parentId: "page:page",
1930
+ opacity: item.strokeOpacity ?? 1,
1931
+ isLocked: item.locked ?? false,
1932
+ props,
1933
+ meta: {}
1934
+ });
1935
+ }
1936
+ return { documents, schema: { storeVersion: 1, recordVersion: 0 }, clock: 0 };
1937
+ }
1938
+
1939
+ export { itemsToTldrawSnapshot, tldrawSnapshotToItems };
1940
+ //# sourceMappingURL=tldraw.js.map
1941
+ //# sourceMappingURL=tldraw.js.map