@fieldnotes/core 0.50.8 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -447,15 +447,32 @@ declare class HistoryStack {
447
447
  private notifyChange;
448
448
  }
449
449
 
450
- interface ExportImageOptions {
450
+ interface ExportImageOptions extends ExportResourceOptions {
451
451
  scale?: number;
452
452
  padding?: number;
453
453
  background?: string;
454
454
  filter?: (element: CanvasElement) => boolean;
455
455
  }
456
+ type ExportAssetErrorReason = 'load' | 'timeout' | 'encode';
457
+ interface ExportAssetError {
458
+ elementId: string;
459
+ src: string;
460
+ reason: ExportAssetErrorReason;
461
+ cause?: unknown;
462
+ }
463
+ interface ExportResourceOptions {
464
+ /** Maximum wait for each image asset. Defaults to 10 seconds. */
465
+ imageTimeoutMs?: number;
466
+ /** Maximum width or height of any allocated export canvas. Defaults to 16,384. */
467
+ maxDimension?: number;
468
+ /** Maximum pixel count of any allocated export canvas. Defaults to 67,108,864. */
469
+ maxPixels?: number;
470
+ /** Called when an image cannot be loaded or embedded. The export continues. */
471
+ onAssetError?: (error: ExportAssetError) => void;
472
+ }
456
473
  declare function exportImage(store: ElementStore, options?: ExportImageOptions, layerManager?: LayerManager): Promise<Blob | null>;
457
474
 
458
- interface ExportSvgOptions {
475
+ interface ExportSvgOptions extends ExportResourceOptions {
459
476
  padding?: number;
460
477
  background?: string;
461
478
  filter?: (el: CanvasElement) => boolean;
@@ -1123,6 +1140,6 @@ declare class LaserTool implements Tool {
1123
1140
  private notifyOptionsChange;
1124
1141
  }
1125
1142
 
1126
- declare const VERSION = "0.50.8";
1143
+ declare const VERSION = "0.51.0";
1127
1144
 
1128
- export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
1145
+ export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.d.ts CHANGED
@@ -447,15 +447,32 @@ declare class HistoryStack {
447
447
  private notifyChange;
448
448
  }
449
449
 
450
- interface ExportImageOptions {
450
+ interface ExportImageOptions extends ExportResourceOptions {
451
451
  scale?: number;
452
452
  padding?: number;
453
453
  background?: string;
454
454
  filter?: (element: CanvasElement) => boolean;
455
455
  }
456
+ type ExportAssetErrorReason = 'load' | 'timeout' | 'encode';
457
+ interface ExportAssetError {
458
+ elementId: string;
459
+ src: string;
460
+ reason: ExportAssetErrorReason;
461
+ cause?: unknown;
462
+ }
463
+ interface ExportResourceOptions {
464
+ /** Maximum wait for each image asset. Defaults to 10 seconds. */
465
+ imageTimeoutMs?: number;
466
+ /** Maximum width or height of any allocated export canvas. Defaults to 16,384. */
467
+ maxDimension?: number;
468
+ /** Maximum pixel count of any allocated export canvas. Defaults to 67,108,864. */
469
+ maxPixels?: number;
470
+ /** Called when an image cannot be loaded or embedded. The export continues. */
471
+ onAssetError?: (error: ExportAssetError) => void;
472
+ }
456
473
  declare function exportImage(store: ElementStore, options?: ExportImageOptions, layerManager?: LayerManager): Promise<Blob | null>;
457
474
 
458
- interface ExportSvgOptions {
475
+ interface ExportSvgOptions extends ExportResourceOptions {
459
476
  padding?: number;
460
477
  background?: string;
461
478
  filter?: (el: CanvasElement) => boolean;
@@ -1123,6 +1140,6 @@ declare class LaserTool implements Tool {
1123
1140
  private notifyOptionsChange;
1124
1141
  }
1125
1142
 
1126
- declare const VERSION = "0.50.8";
1143
+ declare const VERSION = "0.51.0";
1127
1144
 
1128
- export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
1145
+ export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.js CHANGED
@@ -5356,6 +5356,9 @@ function renderTextOnCanvas(ctx, text) {
5356
5356
  }
5357
5357
 
5358
5358
  // src/canvas/export-image.ts
5359
+ var DEFAULT_IMAGE_TIMEOUT_MS = 1e4;
5360
+ var DEFAULT_MAX_DIMENSION = 16384;
5361
+ var DEFAULT_MAX_PIXELS = 67108864;
5359
5362
  var center = (b) => ({ x: b.x + b.w / 2, y: b.y + b.h / 2 });
5360
5363
  function getStrokeBounds(el) {
5361
5364
  if (el.type !== "stroke") return null;
@@ -5465,12 +5468,53 @@ function renderGridForBounds(ctx, grid, bounds) {
5465
5468
  );
5466
5469
  }
5467
5470
  }
5468
- function loadImages(elements) {
5471
+ function positiveOption(value, fallback, name) {
5472
+ const resolved = value ?? fallback;
5473
+ if (!Number.isFinite(resolved) || resolved <= 0) {
5474
+ throw new RangeError(`${name} must be a finite number greater than 0`);
5475
+ }
5476
+ return resolved;
5477
+ }
5478
+ function nonNegativeOption(value, fallback, name) {
5479
+ const resolved = value ?? fallback;
5480
+ if (!Number.isFinite(resolved) || resolved < 0) {
5481
+ throw new RangeError(`${name} must be a finite number greater than or equal to 0`);
5482
+ }
5483
+ return resolved;
5484
+ }
5485
+ function assertExportSize(width, height, options) {
5486
+ const maxDimension = positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
5487
+ const maxPixels = positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
5488
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
5489
+ throw new RangeError("Export dimensions must be finite numbers greater than 0");
5490
+ }
5491
+ if (width > maxDimension || height > maxDimension) {
5492
+ throw new RangeError(
5493
+ `Export dimensions ${width}x${height} exceed the maximum dimension of ${maxDimension}`
5494
+ );
5495
+ }
5496
+ if (width * height > maxPixels) {
5497
+ throw new RangeError(
5498
+ `Export size ${width}x${height} exceeds the maximum of ${maxPixels} pixels`
5499
+ );
5500
+ }
5501
+ }
5502
+ function validateExportResourceOptions(options) {
5503
+ positiveOption(options.imageTimeoutMs, DEFAULT_IMAGE_TIMEOUT_MS, "imageTimeoutMs");
5504
+ positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
5505
+ positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
5506
+ }
5507
+ function loadImages(elements, options = {}) {
5469
5508
  const imageElements = elements.filter(
5470
5509
  (el) => el.type === "image" && "src" in el
5471
5510
  );
5472
5511
  const cache3 = /* @__PURE__ */ new Map();
5473
5512
  if (imageElements.length === 0) return Promise.resolve(cache3);
5513
+ const timeoutMs = positiveOption(
5514
+ options.imageTimeoutMs,
5515
+ DEFAULT_IMAGE_TIMEOUT_MS,
5516
+ "imageTimeoutMs"
5517
+ );
5474
5518
  return new Promise((resolve) => {
5475
5519
  let remaining = imageElements.length;
5476
5520
  const done = () => {
@@ -5480,19 +5524,41 @@ function loadImages(elements) {
5480
5524
  for (const el of imageElements) {
5481
5525
  const img = new Image();
5482
5526
  img.crossOrigin = "anonymous";
5527
+ let settled = false;
5528
+ const timer = setTimeout(() => {
5529
+ if (settled) return;
5530
+ settled = true;
5531
+ img.onload = null;
5532
+ img.onerror = null;
5533
+ options.onAssetError?.({ elementId: el.id, src: el.src, reason: "timeout" });
5534
+ done();
5535
+ }, timeoutMs);
5536
+ const settle = () => {
5537
+ if (settled) return false;
5538
+ settled = true;
5539
+ clearTimeout(timer);
5540
+ img.onload = null;
5541
+ img.onerror = null;
5542
+ return true;
5543
+ };
5483
5544
  img.onload = () => {
5545
+ if (!settle()) return;
5484
5546
  cache3.set(el.id, img);
5485
5547
  done();
5486
5548
  };
5487
- img.onerror = done;
5488
- const sep = el.src.includes("?") ? "&" : "?";
5489
- img.src = `${el.src}${sep}_cors=1`;
5549
+ img.onerror = (cause) => {
5550
+ if (!settle()) return;
5551
+ options.onAssetError?.({ elementId: el.id, src: el.src, reason: "load", cause });
5552
+ done();
5553
+ };
5554
+ img.src = el.src;
5490
5555
  }
5491
5556
  });
5492
5557
  }
5493
5558
  async function exportImage(store, options = {}, layerManager) {
5494
- const scale = options.scale ?? 2;
5495
- const padding = options.padding ?? 0;
5559
+ const scale = positiveOption(options.scale, 2, "scale");
5560
+ const padding = nonNegativeOption(options.padding, 0, "padding");
5561
+ validateExportResourceOptions(options);
5496
5562
  const background = options.background ?? "#ffffff";
5497
5563
  const filter = options.filter;
5498
5564
  const allElements = store.getAll();
@@ -5502,10 +5568,13 @@ async function exportImage(store, options = {}, layerManager) {
5502
5568
  }
5503
5569
  const bounds = computeBounds(visibleElements, padding);
5504
5570
  if (!bounds) return null;
5505
- const imageCache = await loadImages(visibleElements);
5571
+ const width = Math.ceil(bounds.w * scale);
5572
+ const height = Math.ceil(bounds.h * scale);
5573
+ assertExportSize(width, height, options);
5574
+ const imageCache = await loadImages(visibleElements, options);
5506
5575
  const canvas = document.createElement("canvas");
5507
- canvas.width = Math.ceil(bounds.w * scale);
5508
- canvas.height = Math.ceil(bounds.h * scale);
5576
+ canvas.width = width;
5577
+ canvas.height = height;
5509
5578
  const ctx = canvas.getContext("2d");
5510
5579
  if (!ctx) return null;
5511
5580
  ctx.scale(scale, scale);
@@ -5688,14 +5757,17 @@ function emitImage(image, dataUri) {
5688
5757
  const { w, h } = image.size;
5689
5758
  return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
5690
5759
  }
5691
- function emitText(text, rasterScale) {
5760
+ function emitText(text, rasterScale, resourceOptions) {
5692
5761
  if (!text.text) return "";
5693
5762
  const { x, y } = text.position;
5694
5763
  const { w, h } = text.size;
5695
5764
  if (typeof document === "undefined") return "";
5765
+ const width = Math.max(1, Math.ceil(w * rasterScale));
5766
+ const height = Math.max(1, Math.ceil(h * rasterScale));
5767
+ assertExportSize(width, height, resourceOptions);
5696
5768
  const canvas = document.createElement("canvas");
5697
- canvas.width = Math.max(1, Math.ceil(w * rasterScale));
5698
- canvas.height = Math.max(1, Math.ceil(h * rasterScale));
5769
+ canvas.width = width;
5770
+ canvas.height = height;
5699
5771
  const ctx = canvas.getContext("2d");
5700
5772
  if (!ctx) return "";
5701
5773
  ctx.scale(rasterScale, rasterScale);
@@ -5710,13 +5782,16 @@ function emitText(text, rasterScale) {
5710
5782
  if (!dataUri || !dataUri.startsWith("data:")) return "";
5711
5783
  return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
5712
5784
  }
5713
- function emitNote(note, rasterScale) {
5785
+ function emitNote(note, rasterScale, resourceOptions) {
5714
5786
  const { x, y } = note.position;
5715
5787
  const { w, h } = note.size;
5716
5788
  if (typeof document === "undefined") return emitNotePlaceholder(note);
5789
+ const width = Math.max(1, Math.ceil(w * rasterScale));
5790
+ const height = Math.max(1, Math.ceil(h * rasterScale));
5791
+ assertExportSize(width, height, resourceOptions);
5717
5792
  const canvas = document.createElement("canvas");
5718
- canvas.width = Math.max(1, Math.ceil(w * rasterScale));
5719
- canvas.height = Math.max(1, Math.ceil(h * rasterScale));
5793
+ canvas.width = width;
5794
+ canvas.height = height;
5720
5795
  const ctx = canvas.getContext("2d");
5721
5796
  if (!ctx) return emitNotePlaceholder(note);
5722
5797
  ctx.scale(rasterScale, rasterScale);
@@ -5877,8 +5952,9 @@ function emitHexTemplate(t, grid) {
5877
5952
  return `<path d="${d}" fill="${esc(t.fillColor)}" stroke="${esc(t.strokeColor)}" stroke-width="${n(t.strokeWidth)}" opacity="${n(t.opacity)}" />`;
5878
5953
  }
5879
5954
  async function exportSvg(store, options = {}, layerManager) {
5880
- const padding = options.padding ?? 0;
5881
- const rasterScale = options.rasterScale ?? 2;
5955
+ const padding = nonNegativeOption(options.padding, 0, "padding");
5956
+ const rasterScale = positiveOption(options.rasterScale, 2, "rasterScale");
5957
+ validateExportResourceOptions(options);
5882
5958
  const filter = options.filter;
5883
5959
  const allElements = store.getAll();
5884
5960
  let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
@@ -5887,11 +5963,12 @@ async function exportSvg(store, options = {}, layerManager) {
5887
5963
  if (!bounds) {
5888
5964
  return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
5889
5965
  }
5966
+ assertExportSize(Math.ceil(bounds.w), Math.ceil(bounds.h), options);
5890
5967
  const remoteImages = visibleElements.filter(
5891
5968
  (el) => el.type === "image" && !el.src.startsWith("data:")
5892
5969
  );
5893
- const imageCache = await loadImages(remoteImages);
5894
- const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale);
5970
+ const imageCache = await loadImages(remoteImages, options);
5971
+ const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale, options);
5895
5972
  const grids = visibleElements.filter((el) => el.type === "grid");
5896
5973
  const firstGrid = grids[0];
5897
5974
  let body = "";
@@ -5900,7 +5977,7 @@ async function exportSvg(store, options = {}, layerManager) {
5900
5977
  }
5901
5978
  const layerBodies = /* @__PURE__ */ new Map();
5902
5979
  for (const el of visibleElements) {
5903
- const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store);
5980
+ const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store, options);
5904
5981
  layerBodies.set(el.layerId, (layerBodies.get(el.layerId) ?? "") + emitted);
5905
5982
  }
5906
5983
  for (const [layerId, emitted] of layerBodies) {
@@ -5914,7 +5991,7 @@ async function exportSvg(store, options = {}, layerManager) {
5914
5991
  }
5915
5992
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${n(bounds.w)}" height="${n(bounds.h)}" viewBox="${n(bounds.x)} ${n(bounds.y)} ${n(bounds.w)} ${n(bounds.h)}">${body}</svg>`;
5916
5993
  }
5917
- function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
5994
+ function emitElement(el, imageDataUris, rasterScale, firstGrid, store, resourceOptions) {
5918
5995
  switch (el.type) {
5919
5996
  case "stroke":
5920
5997
  return withRotationSvg(el, emitStroke(el));
@@ -5925,9 +6002,9 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
5925
6002
  case "image":
5926
6003
  return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
5927
6004
  case "text":
5928
- return withRotationSvg(el, emitText(el, rasterScale));
6005
+ return withRotationSvg(el, emitText(el, rasterScale, resourceOptions));
5929
6006
  case "note":
5930
- return withRotationSvg(el, emitNote(el, rasterScale));
6007
+ return withRotationSvg(el, emitNote(el, rasterScale, resourceOptions));
5931
6008
  case "template":
5932
6009
  return emitTemplate(el, firstGrid);
5933
6010
  case "grid":
@@ -5938,7 +6015,7 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
5938
6015
  return "";
5939
6016
  }
5940
6017
  }
5941
- function encodeImages(elements, imageCache, rasterScale) {
6018
+ function encodeImages(elements, imageCache, rasterScale, resourceOptions) {
5942
6019
  const out = /* @__PURE__ */ new Map();
5943
6020
  for (const el of elements) {
5944
6021
  if (el.type !== "image") continue;
@@ -5948,16 +6025,24 @@ function encodeImages(elements, imageCache, rasterScale) {
5948
6025
  }
5949
6026
  const img = imageCache.get(el.id);
5950
6027
  if (!img || typeof document === "undefined") continue;
6028
+ const width = Math.max(1, Math.ceil(el.size.w * rasterScale));
6029
+ const height = Math.max(1, Math.ceil(el.size.h * rasterScale));
6030
+ assertExportSize(width, height, resourceOptions);
5951
6031
  const canvas = document.createElement("canvas");
5952
- canvas.width = Math.max(1, Math.ceil(el.size.w * rasterScale));
5953
- canvas.height = Math.max(1, Math.ceil(el.size.h * rasterScale));
6032
+ canvas.width = width;
6033
+ canvas.height = height;
5954
6034
  const ctx = canvas.getContext("2d");
5955
- if (!ctx) continue;
6035
+ if (!ctx) {
6036
+ resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
6037
+ continue;
6038
+ }
5956
6039
  try {
5957
6040
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
5958
6041
  const uri = canvas.toDataURL();
5959
6042
  if (uri.startsWith("data:")) out.set(el.id, uri);
5960
- } catch {
6043
+ else resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
6044
+ } catch (cause) {
6045
+ resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode", cause });
5961
6046
  }
5962
6047
  }
5963
6048
  return out;
@@ -6506,7 +6591,7 @@ var RenderLoop = class {
6506
6591
  layerGroups = /* @__PURE__ */ new Map();
6507
6592
  gridCacheCanvas = null;
6508
6593
  gridCacheCtx = null;
6509
- lastGridRef = null;
6594
+ lastGridRefs = [];
6510
6595
  constructor(deps) {
6511
6596
  this.canvasEl = deps.canvasEl;
6512
6597
  this.camera = deps.camera;
@@ -6706,8 +6791,8 @@ var RenderLoop = class {
6706
6791
  }
6707
6792
  if (gridElements.length > 0) {
6708
6793
  const gridT0 = performance.now();
6709
- const gridRef = gridElements[0];
6710
- const gridDirty = this.gridCacheDirty || gridRef !== this.lastGridRef;
6794
+ const gridsChanged = gridElements.length !== this.lastGridRefs.length || gridElements.some((grid, index) => grid !== this.lastGridRefs[index]);
6795
+ const gridDirty = this.gridCacheDirty || gridsChanged;
6711
6796
  if (gridDirty) {
6712
6797
  this.ensureGridCache();
6713
6798
  if (this.gridCacheCtx && this.gridCacheCanvas) {
@@ -6735,7 +6820,7 @@ var RenderLoop = class {
6735
6820
  }
6736
6821
  }
6737
6822
  this.gridCacheDirty = false;
6738
- this.lastGridRef = gridRef;
6823
+ this.lastGridRefs = [...gridElements];
6739
6824
  }
6740
6825
  if (this.gridCacheCanvas) {
6741
6826
  const offset = this.marginViewport.compositeOffset(
@@ -6755,6 +6840,8 @@ var RenderLoop = class {
6755
6840
  }
6756
6841
  }
6757
6842
  gridMs = performance.now() - gridT0;
6843
+ } else {
6844
+ this.lastGridRefs = [];
6758
6845
  }
6759
6846
  const overlayT0 = performance.now();
6760
6847
  const activeTool = this.toolManager.activeTool;
@@ -10728,7 +10815,7 @@ var LaserTool = class {
10728
10815
  };
10729
10816
 
10730
10817
  // src/index.ts
10731
- var VERSION = "0.50.8";
10818
+ var VERSION = "0.51.0";
10732
10819
  export {
10733
10820
  ArrowTool,
10734
10821
  AutoSave,