@fieldnotes/core 0.8.7 → 0.8.8

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.cjs CHANGED
@@ -542,6 +542,13 @@ var Background = class {
542
542
  color;
543
543
  dotRadius;
544
544
  lineWidth;
545
+ cachedCanvas = null;
546
+ cachedCtx = null;
547
+ lastZoom = -1;
548
+ lastOffsetX = -Infinity;
549
+ lastOffsetY = -Infinity;
550
+ lastWidth = 0;
551
+ lastHeight = 0;
545
552
  constructor(options = {}) {
546
553
  this.pattern = options.pattern ?? DEFAULTS.pattern;
547
554
  this.spacing = options.spacing ?? DEFAULTS.spacing;
@@ -557,13 +564,69 @@ var Background = class {
557
564
  ctx.save();
558
565
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
559
566
  ctx.clearRect(0, 0, cssWidth, cssHeight);
567
+ if (this.pattern === "none") {
568
+ ctx.restore();
569
+ return;
570
+ }
571
+ const spacing = this.adaptSpacing(this.spacing, camera.zoom);
572
+ const keyZoom = camera.zoom;
573
+ const keyX = Math.floor(camera.position.x % spacing);
574
+ const keyY = Math.floor(camera.position.y % spacing);
575
+ if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
576
+ ctx.drawImage(this.cachedCanvas, 0, 0);
577
+ ctx.restore();
578
+ return;
579
+ }
580
+ this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
581
+ if (this.cachedCtx === null) {
582
+ if (this.pattern === "dots") {
583
+ this.renderDots(ctx, camera, cssWidth, cssHeight);
584
+ } else if (this.pattern === "grid") {
585
+ this.renderGrid(ctx, camera, cssWidth, cssHeight);
586
+ }
587
+ ctx.restore();
588
+ return;
589
+ }
590
+ const offCtx = this.cachedCtx;
591
+ offCtx.clearRect(0, 0, cssWidth, cssHeight);
560
592
  if (this.pattern === "dots") {
561
- this.renderDots(ctx, camera, cssWidth, cssHeight);
593
+ this.renderDots(offCtx, camera, cssWidth, cssHeight);
562
594
  } else if (this.pattern === "grid") {
563
- this.renderGrid(ctx, camera, cssWidth, cssHeight);
564
- }
595
+ this.renderGrid(offCtx, camera, cssWidth, cssHeight);
596
+ }
597
+ this.lastZoom = keyZoom;
598
+ this.lastOffsetX = keyX;
599
+ this.lastOffsetY = keyY;
600
+ this.lastWidth = cssWidth;
601
+ this.lastHeight = cssHeight;
602
+ ctx.drawImage(this.cachedCanvas, 0, 0);
565
603
  ctx.restore();
566
604
  }
605
+ ensureCachedCanvas(cssWidth, cssHeight, dpr) {
606
+ if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
607
+ return;
608
+ }
609
+ const physWidth = Math.round(cssWidth * dpr);
610
+ const physHeight = Math.round(cssHeight * dpr);
611
+ if (typeof OffscreenCanvas !== "undefined") {
612
+ this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
613
+ } else if (typeof document !== "undefined") {
614
+ const el = document.createElement("canvas");
615
+ el.width = physWidth;
616
+ el.height = physHeight;
617
+ this.cachedCanvas = el;
618
+ } else {
619
+ this.cachedCanvas = null;
620
+ this.cachedCtx = null;
621
+ return;
622
+ }
623
+ const offCtx = this.cachedCanvas.getContext("2d");
624
+ if (offCtx !== null) {
625
+ offCtx.scale(dpr, dpr);
626
+ }
627
+ this.cachedCtx = offCtx;
628
+ this.lastZoom = -1;
629
+ }
567
630
  adaptSpacing(baseSpacing, zoom) {
568
631
  let spacing = baseSpacing * zoom;
569
632
  while (spacing < MIN_PATTERN_SPACING) {
@@ -1046,6 +1109,10 @@ var ElementStore = class {
1046
1109
  const existing = this.elements.get(id);
1047
1110
  if (!existing) return;
1048
1111
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
1112
+ if (updated.type === "arrow") {
1113
+ const arrow = updated;
1114
+ arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1115
+ }
1049
1116
  this.elements.set(id, updated);
1050
1117
  const newBounds = getElementBounds(updated);
1051
1118
  if (newBounds) {
@@ -1301,6 +1368,25 @@ function smoothToSegments(points) {
1301
1368
  return segments;
1302
1369
  }
1303
1370
 
1371
+ // src/elements/stroke-cache.ts
1372
+ var cache = /* @__PURE__ */ new WeakMap();
1373
+ function computeStrokeSegments(stroke) {
1374
+ const segments = smoothToSegments(stroke.points);
1375
+ const widths = [];
1376
+ for (const seg of segments) {
1377
+ const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
1378
+ widths.push(w);
1379
+ }
1380
+ const data = { segments, widths };
1381
+ cache.set(stroke, data);
1382
+ return data;
1383
+ }
1384
+ function getStrokeRenderData(stroke) {
1385
+ const cached = cache.get(stroke);
1386
+ if (cached) return cached;
1387
+ return computeStrokeSegments(stroke);
1388
+ }
1389
+
1304
1390
  // src/elements/grid-renderer.ts
1305
1391
  function getSquareGridLines(bounds, cellSize) {
1306
1392
  if (cellSize <= 0) return { verticals: [], horizontals: [] };
@@ -1465,9 +1551,11 @@ var ElementRenderer = class {
1465
1551
  ctx.lineCap = "round";
1466
1552
  ctx.lineJoin = "round";
1467
1553
  ctx.globalAlpha = stroke.opacity;
1468
- const segments = smoothToSegments(stroke.points);
1469
- for (const seg of segments) {
1470
- const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
1554
+ const { segments, widths } = getStrokeRenderData(stroke);
1555
+ for (let i = 0; i < segments.length; i++) {
1556
+ const seg = segments[i];
1557
+ const w = widths[i];
1558
+ if (!seg || w === void 0) continue;
1471
1559
  ctx.lineWidth = w;
1472
1560
  ctx.beginPath();
1473
1561
  ctx.moveTo(seg.start.x, seg.start.y);
@@ -1488,7 +1576,7 @@ var ElementRenderer = class {
1488
1576
  ctx.beginPath();
1489
1577
  ctx.moveTo(visualFrom.x, visualFrom.y);
1490
1578
  if (arrow.bend !== 0) {
1491
- const cp = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1579
+ const cp = arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1492
1580
  ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
1493
1581
  } else {
1494
1582
  ctx.lineTo(visualTo.x, visualTo.y);
@@ -1629,15 +1717,33 @@ var ElementRenderer = class {
1629
1717
  renderImage(ctx, image) {
1630
1718
  const img = this.getImage(image.src);
1631
1719
  if (!img) return;
1632
- ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
1720
+ ctx.drawImage(
1721
+ img,
1722
+ image.position.x,
1723
+ image.position.y,
1724
+ image.size.w,
1725
+ image.size.h
1726
+ );
1633
1727
  }
1634
1728
  getImage(src) {
1635
1729
  const cached = this.imageCache.get(src);
1636
- if (cached) return cached.complete ? cached : null;
1730
+ if (cached) {
1731
+ if (cached instanceof HTMLImageElement) return cached.complete ? cached : null;
1732
+ return cached;
1733
+ }
1637
1734
  const img = new Image();
1638
1735
  img.src = src;
1639
1736
  this.imageCache.set(src, img);
1640
- img.onload = () => this.onImageLoad?.();
1737
+ img.onload = () => {
1738
+ this.onImageLoad?.();
1739
+ if (typeof createImageBitmap !== "undefined") {
1740
+ createImageBitmap(img).then((bitmap) => {
1741
+ this.imageCache.set(src, bitmap);
1742
+ this.onImageLoad?.();
1743
+ }).catch(() => {
1744
+ });
1745
+ }
1746
+ };
1641
1747
  return null;
1642
1748
  }
1643
1749
  };
@@ -2013,6 +2119,7 @@ function createNote(input) {
2013
2119
  };
2014
2120
  }
2015
2121
  function createArrow(input) {
2122
+ const bend = input.bend ?? 0;
2016
2123
  const result = {
2017
2124
  id: createId("arrow"),
2018
2125
  type: "arrow",
@@ -2022,9 +2129,10 @@ function createArrow(input) {
2022
2129
  layerId: input.layerId ?? "",
2023
2130
  from: input.from,
2024
2131
  to: input.to,
2025
- bend: input.bend ?? 0,
2132
+ bend,
2026
2133
  color: input.color ?? "#000000",
2027
- width: input.width ?? 2
2134
+ width: input.width ?? 2,
2135
+ cachedControlPoint: getArrowControlPoint(input.from, input.to, bend)
2028
2136
  };
2029
2137
  if (input.fromBinding) result.fromBinding = input.fromBinding;
2030
2138
  if (input.toBinding) result.toBinding = input.toBinding;
@@ -2275,19 +2383,19 @@ function loadImages(elements) {
2275
2383
  const imageElements = elements.filter(
2276
2384
  (el) => el.type === "image" && "src" in el
2277
2385
  );
2278
- const cache = /* @__PURE__ */ new Map();
2279
- if (imageElements.length === 0) return Promise.resolve(cache);
2386
+ const cache2 = /* @__PURE__ */ new Map();
2387
+ if (imageElements.length === 0) return Promise.resolve(cache2);
2280
2388
  return new Promise((resolve) => {
2281
2389
  let remaining = imageElements.length;
2282
2390
  const done = () => {
2283
2391
  remaining--;
2284
- if (remaining <= 0) resolve(cache);
2392
+ if (remaining <= 0) resolve(cache2);
2285
2393
  };
2286
2394
  for (const el of imageElements) {
2287
2395
  const img = new Image();
2288
2396
  img.crossOrigin = "anonymous";
2289
2397
  img.onload = () => {
2290
- cache.set(el.id, img);
2398
+ cache2.set(el.id, img);
2291
2399
  done();
2292
2400
  };
2293
2401
  img.onerror = done;
@@ -3535,6 +3643,7 @@ var PencilTool = class {
3535
3643
  layerId: ctx.activeLayerId ?? ""
3536
3644
  });
3537
3645
  ctx.store.add(stroke);
3646
+ computeStrokeSegments(stroke);
3538
3647
  this.points = [];
3539
3648
  ctx.requestRender();
3540
3649
  }
@@ -4609,7 +4718,7 @@ var UpdateLayerCommand = class {
4609
4718
  };
4610
4719
 
4611
4720
  // src/index.ts
4612
- var VERSION = "0.8.7";
4721
+ var VERSION = "0.8.8";
4613
4722
  // Annotate the CommonJS export names for ESM import in node:
4614
4723
  0 && (module.exports = {
4615
4724
  AddElementCommand,
package/dist/index.d.cts CHANGED
@@ -72,6 +72,8 @@ interface ArrowElement extends BaseElement {
72
72
  width: number;
73
73
  fromBinding?: Binding;
74
74
  toBinding?: Binding;
75
+ /** Derived from from/to/bend. Redundant in serialized state — safe to omit. */
76
+ cachedControlPoint?: Point;
75
77
  }
76
78
  interface ImageElement extends BaseElement {
77
79
  type: 'image';
@@ -273,8 +275,16 @@ declare class Background {
273
275
  private readonly color;
274
276
  private readonly dotRadius;
275
277
  private readonly lineWidth;
278
+ private cachedCanvas;
279
+ private cachedCtx;
280
+ private lastZoom;
281
+ private lastOffsetX;
282
+ private lastOffsetY;
283
+ private lastWidth;
284
+ private lastHeight;
276
285
  constructor(options?: BackgroundOptions);
277
286
  render(ctx: CanvasRenderingContext2D, camera: Camera): void;
287
+ private ensureCachedCanvas;
278
288
  private adaptSpacing;
279
289
  private renderDots;
280
290
  private renderGrid;
@@ -914,6 +924,6 @@ declare class UpdateLayerCommand implements Command {
914
924
  undo(_store: ElementStore): void;
915
925
  }
916
926
 
917
- declare const VERSION = "0.8.7";
927
+ declare const VERSION = "0.8.8";
918
928
 
919
929
  export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type GridElement, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, type Layer, LayerManager, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createText, exportImage, exportState, findBindTarget, findBoundArrows, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, isBindable, isNearBezier, parseState, snapPoint, unbindArrow, updateBoundArrow };
package/dist/index.d.ts CHANGED
@@ -72,6 +72,8 @@ interface ArrowElement extends BaseElement {
72
72
  width: number;
73
73
  fromBinding?: Binding;
74
74
  toBinding?: Binding;
75
+ /** Derived from from/to/bend. Redundant in serialized state — safe to omit. */
76
+ cachedControlPoint?: Point;
75
77
  }
76
78
  interface ImageElement extends BaseElement {
77
79
  type: 'image';
@@ -273,8 +275,16 @@ declare class Background {
273
275
  private readonly color;
274
276
  private readonly dotRadius;
275
277
  private readonly lineWidth;
278
+ private cachedCanvas;
279
+ private cachedCtx;
280
+ private lastZoom;
281
+ private lastOffsetX;
282
+ private lastOffsetY;
283
+ private lastWidth;
284
+ private lastHeight;
276
285
  constructor(options?: BackgroundOptions);
277
286
  render(ctx: CanvasRenderingContext2D, camera: Camera): void;
287
+ private ensureCachedCanvas;
278
288
  private adaptSpacing;
279
289
  private renderDots;
280
290
  private renderGrid;
@@ -914,6 +924,6 @@ declare class UpdateLayerCommand implements Command {
914
924
  undo(_store: ElementStore): void;
915
925
  }
916
926
 
917
- declare const VERSION = "0.8.7";
927
+ declare const VERSION = "0.8.8";
918
928
 
919
929
  export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type GridElement, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, type Layer, LayerManager, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createText, exportImage, exportState, findBindTarget, findBoundArrows, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, isBindable, isNearBezier, parseState, snapPoint, unbindArrow, updateBoundArrow };
package/dist/index.js CHANGED
@@ -457,6 +457,13 @@ var Background = class {
457
457
  color;
458
458
  dotRadius;
459
459
  lineWidth;
460
+ cachedCanvas = null;
461
+ cachedCtx = null;
462
+ lastZoom = -1;
463
+ lastOffsetX = -Infinity;
464
+ lastOffsetY = -Infinity;
465
+ lastWidth = 0;
466
+ lastHeight = 0;
460
467
  constructor(options = {}) {
461
468
  this.pattern = options.pattern ?? DEFAULTS.pattern;
462
469
  this.spacing = options.spacing ?? DEFAULTS.spacing;
@@ -472,13 +479,69 @@ var Background = class {
472
479
  ctx.save();
473
480
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
474
481
  ctx.clearRect(0, 0, cssWidth, cssHeight);
482
+ if (this.pattern === "none") {
483
+ ctx.restore();
484
+ return;
485
+ }
486
+ const spacing = this.adaptSpacing(this.spacing, camera.zoom);
487
+ const keyZoom = camera.zoom;
488
+ const keyX = Math.floor(camera.position.x % spacing);
489
+ const keyY = Math.floor(camera.position.y % spacing);
490
+ if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
491
+ ctx.drawImage(this.cachedCanvas, 0, 0);
492
+ ctx.restore();
493
+ return;
494
+ }
495
+ this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
496
+ if (this.cachedCtx === null) {
497
+ if (this.pattern === "dots") {
498
+ this.renderDots(ctx, camera, cssWidth, cssHeight);
499
+ } else if (this.pattern === "grid") {
500
+ this.renderGrid(ctx, camera, cssWidth, cssHeight);
501
+ }
502
+ ctx.restore();
503
+ return;
504
+ }
505
+ const offCtx = this.cachedCtx;
506
+ offCtx.clearRect(0, 0, cssWidth, cssHeight);
475
507
  if (this.pattern === "dots") {
476
- this.renderDots(ctx, camera, cssWidth, cssHeight);
508
+ this.renderDots(offCtx, camera, cssWidth, cssHeight);
477
509
  } else if (this.pattern === "grid") {
478
- this.renderGrid(ctx, camera, cssWidth, cssHeight);
479
- }
510
+ this.renderGrid(offCtx, camera, cssWidth, cssHeight);
511
+ }
512
+ this.lastZoom = keyZoom;
513
+ this.lastOffsetX = keyX;
514
+ this.lastOffsetY = keyY;
515
+ this.lastWidth = cssWidth;
516
+ this.lastHeight = cssHeight;
517
+ ctx.drawImage(this.cachedCanvas, 0, 0);
480
518
  ctx.restore();
481
519
  }
520
+ ensureCachedCanvas(cssWidth, cssHeight, dpr) {
521
+ if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
522
+ return;
523
+ }
524
+ const physWidth = Math.round(cssWidth * dpr);
525
+ const physHeight = Math.round(cssHeight * dpr);
526
+ if (typeof OffscreenCanvas !== "undefined") {
527
+ this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
528
+ } else if (typeof document !== "undefined") {
529
+ const el = document.createElement("canvas");
530
+ el.width = physWidth;
531
+ el.height = physHeight;
532
+ this.cachedCanvas = el;
533
+ } else {
534
+ this.cachedCanvas = null;
535
+ this.cachedCtx = null;
536
+ return;
537
+ }
538
+ const offCtx = this.cachedCanvas.getContext("2d");
539
+ if (offCtx !== null) {
540
+ offCtx.scale(dpr, dpr);
541
+ }
542
+ this.cachedCtx = offCtx;
543
+ this.lastZoom = -1;
544
+ }
482
545
  adaptSpacing(baseSpacing, zoom) {
483
546
  let spacing = baseSpacing * zoom;
484
547
  while (spacing < MIN_PATTERN_SPACING) {
@@ -961,6 +1024,10 @@ var ElementStore = class {
961
1024
  const existing = this.elements.get(id);
962
1025
  if (!existing) return;
963
1026
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
1027
+ if (updated.type === "arrow") {
1028
+ const arrow = updated;
1029
+ arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1030
+ }
964
1031
  this.elements.set(id, updated);
965
1032
  const newBounds = getElementBounds(updated);
966
1033
  if (newBounds) {
@@ -1216,6 +1283,25 @@ function smoothToSegments(points) {
1216
1283
  return segments;
1217
1284
  }
1218
1285
 
1286
+ // src/elements/stroke-cache.ts
1287
+ var cache = /* @__PURE__ */ new WeakMap();
1288
+ function computeStrokeSegments(stroke) {
1289
+ const segments = smoothToSegments(stroke.points);
1290
+ const widths = [];
1291
+ for (const seg of segments) {
1292
+ const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
1293
+ widths.push(w);
1294
+ }
1295
+ const data = { segments, widths };
1296
+ cache.set(stroke, data);
1297
+ return data;
1298
+ }
1299
+ function getStrokeRenderData(stroke) {
1300
+ const cached = cache.get(stroke);
1301
+ if (cached) return cached;
1302
+ return computeStrokeSegments(stroke);
1303
+ }
1304
+
1219
1305
  // src/elements/grid-renderer.ts
1220
1306
  function getSquareGridLines(bounds, cellSize) {
1221
1307
  if (cellSize <= 0) return { verticals: [], horizontals: [] };
@@ -1380,9 +1466,11 @@ var ElementRenderer = class {
1380
1466
  ctx.lineCap = "round";
1381
1467
  ctx.lineJoin = "round";
1382
1468
  ctx.globalAlpha = stroke.opacity;
1383
- const segments = smoothToSegments(stroke.points);
1384
- for (const seg of segments) {
1385
- const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
1469
+ const { segments, widths } = getStrokeRenderData(stroke);
1470
+ for (let i = 0; i < segments.length; i++) {
1471
+ const seg = segments[i];
1472
+ const w = widths[i];
1473
+ if (!seg || w === void 0) continue;
1386
1474
  ctx.lineWidth = w;
1387
1475
  ctx.beginPath();
1388
1476
  ctx.moveTo(seg.start.x, seg.start.y);
@@ -1403,7 +1491,7 @@ var ElementRenderer = class {
1403
1491
  ctx.beginPath();
1404
1492
  ctx.moveTo(visualFrom.x, visualFrom.y);
1405
1493
  if (arrow.bend !== 0) {
1406
- const cp = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1494
+ const cp = arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1407
1495
  ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
1408
1496
  } else {
1409
1497
  ctx.lineTo(visualTo.x, visualTo.y);
@@ -1544,15 +1632,33 @@ var ElementRenderer = class {
1544
1632
  renderImage(ctx, image) {
1545
1633
  const img = this.getImage(image.src);
1546
1634
  if (!img) return;
1547
- ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
1635
+ ctx.drawImage(
1636
+ img,
1637
+ image.position.x,
1638
+ image.position.y,
1639
+ image.size.w,
1640
+ image.size.h
1641
+ );
1548
1642
  }
1549
1643
  getImage(src) {
1550
1644
  const cached = this.imageCache.get(src);
1551
- if (cached) return cached.complete ? cached : null;
1645
+ if (cached) {
1646
+ if (cached instanceof HTMLImageElement) return cached.complete ? cached : null;
1647
+ return cached;
1648
+ }
1552
1649
  const img = new Image();
1553
1650
  img.src = src;
1554
1651
  this.imageCache.set(src, img);
1555
- img.onload = () => this.onImageLoad?.();
1652
+ img.onload = () => {
1653
+ this.onImageLoad?.();
1654
+ if (typeof createImageBitmap !== "undefined") {
1655
+ createImageBitmap(img).then((bitmap) => {
1656
+ this.imageCache.set(src, bitmap);
1657
+ this.onImageLoad?.();
1658
+ }).catch(() => {
1659
+ });
1660
+ }
1661
+ };
1556
1662
  return null;
1557
1663
  }
1558
1664
  };
@@ -1928,6 +2034,7 @@ function createNote(input) {
1928
2034
  };
1929
2035
  }
1930
2036
  function createArrow(input) {
2037
+ const bend = input.bend ?? 0;
1931
2038
  const result = {
1932
2039
  id: createId("arrow"),
1933
2040
  type: "arrow",
@@ -1937,9 +2044,10 @@ function createArrow(input) {
1937
2044
  layerId: input.layerId ?? "",
1938
2045
  from: input.from,
1939
2046
  to: input.to,
1940
- bend: input.bend ?? 0,
2047
+ bend,
1941
2048
  color: input.color ?? "#000000",
1942
- width: input.width ?? 2
2049
+ width: input.width ?? 2,
2050
+ cachedControlPoint: getArrowControlPoint(input.from, input.to, bend)
1943
2051
  };
1944
2052
  if (input.fromBinding) result.fromBinding = input.fromBinding;
1945
2053
  if (input.toBinding) result.toBinding = input.toBinding;
@@ -2190,19 +2298,19 @@ function loadImages(elements) {
2190
2298
  const imageElements = elements.filter(
2191
2299
  (el) => el.type === "image" && "src" in el
2192
2300
  );
2193
- const cache = /* @__PURE__ */ new Map();
2194
- if (imageElements.length === 0) return Promise.resolve(cache);
2301
+ const cache2 = /* @__PURE__ */ new Map();
2302
+ if (imageElements.length === 0) return Promise.resolve(cache2);
2195
2303
  return new Promise((resolve) => {
2196
2304
  let remaining = imageElements.length;
2197
2305
  const done = () => {
2198
2306
  remaining--;
2199
- if (remaining <= 0) resolve(cache);
2307
+ if (remaining <= 0) resolve(cache2);
2200
2308
  };
2201
2309
  for (const el of imageElements) {
2202
2310
  const img = new Image();
2203
2311
  img.crossOrigin = "anonymous";
2204
2312
  img.onload = () => {
2205
- cache.set(el.id, img);
2313
+ cache2.set(el.id, img);
2206
2314
  done();
2207
2315
  };
2208
2316
  img.onerror = done;
@@ -3450,6 +3558,7 @@ var PencilTool = class {
3450
3558
  layerId: ctx.activeLayerId ?? ""
3451
3559
  });
3452
3560
  ctx.store.add(stroke);
3561
+ computeStrokeSegments(stroke);
3453
3562
  this.points = [];
3454
3563
  ctx.requestRender();
3455
3564
  }
@@ -4524,7 +4633,7 @@ var UpdateLayerCommand = class {
4524
4633
  };
4525
4634
 
4526
4635
  // src/index.ts
4527
- var VERSION = "0.8.7";
4636
+ var VERSION = "0.8.8";
4528
4637
  export {
4529
4638
  AddElementCommand,
4530
4639
  ArrowTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/core",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "description": "Vanilla TypeScript infinite canvas engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",