@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/README.md +683 -675
- package/dist/index.cjs +120 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +120 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5443,6 +5443,9 @@ function renderTextOnCanvas(ctx, text) {
|
|
|
5443
5443
|
}
|
|
5444
5444
|
|
|
5445
5445
|
// src/canvas/export-image.ts
|
|
5446
|
+
var DEFAULT_IMAGE_TIMEOUT_MS = 1e4;
|
|
5447
|
+
var DEFAULT_MAX_DIMENSION = 16384;
|
|
5448
|
+
var DEFAULT_MAX_PIXELS = 67108864;
|
|
5446
5449
|
var center = (b) => ({ x: b.x + b.w / 2, y: b.y + b.h / 2 });
|
|
5447
5450
|
function getStrokeBounds(el) {
|
|
5448
5451
|
if (el.type !== "stroke") return null;
|
|
@@ -5552,12 +5555,53 @@ function renderGridForBounds(ctx, grid, bounds) {
|
|
|
5552
5555
|
);
|
|
5553
5556
|
}
|
|
5554
5557
|
}
|
|
5555
|
-
function
|
|
5558
|
+
function positiveOption(value, fallback, name) {
|
|
5559
|
+
const resolved = value ?? fallback;
|
|
5560
|
+
if (!Number.isFinite(resolved) || resolved <= 0) {
|
|
5561
|
+
throw new RangeError(`${name} must be a finite number greater than 0`);
|
|
5562
|
+
}
|
|
5563
|
+
return resolved;
|
|
5564
|
+
}
|
|
5565
|
+
function nonNegativeOption(value, fallback, name) {
|
|
5566
|
+
const resolved = value ?? fallback;
|
|
5567
|
+
if (!Number.isFinite(resolved) || resolved < 0) {
|
|
5568
|
+
throw new RangeError(`${name} must be a finite number greater than or equal to 0`);
|
|
5569
|
+
}
|
|
5570
|
+
return resolved;
|
|
5571
|
+
}
|
|
5572
|
+
function assertExportSize(width, height, options) {
|
|
5573
|
+
const maxDimension = positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
|
|
5574
|
+
const maxPixels = positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
|
|
5575
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
5576
|
+
throw new RangeError("Export dimensions must be finite numbers greater than 0");
|
|
5577
|
+
}
|
|
5578
|
+
if (width > maxDimension || height > maxDimension) {
|
|
5579
|
+
throw new RangeError(
|
|
5580
|
+
`Export dimensions ${width}x${height} exceed the maximum dimension of ${maxDimension}`
|
|
5581
|
+
);
|
|
5582
|
+
}
|
|
5583
|
+
if (width * height > maxPixels) {
|
|
5584
|
+
throw new RangeError(
|
|
5585
|
+
`Export size ${width}x${height} exceeds the maximum of ${maxPixels} pixels`
|
|
5586
|
+
);
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
function validateExportResourceOptions(options) {
|
|
5590
|
+
positiveOption(options.imageTimeoutMs, DEFAULT_IMAGE_TIMEOUT_MS, "imageTimeoutMs");
|
|
5591
|
+
positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
|
|
5592
|
+
positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
|
|
5593
|
+
}
|
|
5594
|
+
function loadImages(elements, options = {}) {
|
|
5556
5595
|
const imageElements = elements.filter(
|
|
5557
5596
|
(el) => el.type === "image" && "src" in el
|
|
5558
5597
|
);
|
|
5559
5598
|
const cache3 = /* @__PURE__ */ new Map();
|
|
5560
5599
|
if (imageElements.length === 0) return Promise.resolve(cache3);
|
|
5600
|
+
const timeoutMs = positiveOption(
|
|
5601
|
+
options.imageTimeoutMs,
|
|
5602
|
+
DEFAULT_IMAGE_TIMEOUT_MS,
|
|
5603
|
+
"imageTimeoutMs"
|
|
5604
|
+
);
|
|
5561
5605
|
return new Promise((resolve) => {
|
|
5562
5606
|
let remaining = imageElements.length;
|
|
5563
5607
|
const done = () => {
|
|
@@ -5567,19 +5611,41 @@ function loadImages(elements) {
|
|
|
5567
5611
|
for (const el of imageElements) {
|
|
5568
5612
|
const img = new Image();
|
|
5569
5613
|
img.crossOrigin = "anonymous";
|
|
5614
|
+
let settled = false;
|
|
5615
|
+
const timer = setTimeout(() => {
|
|
5616
|
+
if (settled) return;
|
|
5617
|
+
settled = true;
|
|
5618
|
+
img.onload = null;
|
|
5619
|
+
img.onerror = null;
|
|
5620
|
+
options.onAssetError?.({ elementId: el.id, src: el.src, reason: "timeout" });
|
|
5621
|
+
done();
|
|
5622
|
+
}, timeoutMs);
|
|
5623
|
+
const settle = () => {
|
|
5624
|
+
if (settled) return false;
|
|
5625
|
+
settled = true;
|
|
5626
|
+
clearTimeout(timer);
|
|
5627
|
+
img.onload = null;
|
|
5628
|
+
img.onerror = null;
|
|
5629
|
+
return true;
|
|
5630
|
+
};
|
|
5570
5631
|
img.onload = () => {
|
|
5632
|
+
if (!settle()) return;
|
|
5571
5633
|
cache3.set(el.id, img);
|
|
5572
5634
|
done();
|
|
5573
5635
|
};
|
|
5574
|
-
img.onerror =
|
|
5575
|
-
|
|
5576
|
-
|
|
5636
|
+
img.onerror = (cause) => {
|
|
5637
|
+
if (!settle()) return;
|
|
5638
|
+
options.onAssetError?.({ elementId: el.id, src: el.src, reason: "load", cause });
|
|
5639
|
+
done();
|
|
5640
|
+
};
|
|
5641
|
+
img.src = el.src;
|
|
5577
5642
|
}
|
|
5578
5643
|
});
|
|
5579
5644
|
}
|
|
5580
5645
|
async function exportImage(store, options = {}, layerManager) {
|
|
5581
|
-
const scale = options.scale
|
|
5582
|
-
const padding = options.padding
|
|
5646
|
+
const scale = positiveOption(options.scale, 2, "scale");
|
|
5647
|
+
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
5648
|
+
validateExportResourceOptions(options);
|
|
5583
5649
|
const background = options.background ?? "#ffffff";
|
|
5584
5650
|
const filter = options.filter;
|
|
5585
5651
|
const allElements = store.getAll();
|
|
@@ -5589,10 +5655,13 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5589
5655
|
}
|
|
5590
5656
|
const bounds = computeBounds(visibleElements, padding);
|
|
5591
5657
|
if (!bounds) return null;
|
|
5592
|
-
const
|
|
5658
|
+
const width = Math.ceil(bounds.w * scale);
|
|
5659
|
+
const height = Math.ceil(bounds.h * scale);
|
|
5660
|
+
assertExportSize(width, height, options);
|
|
5661
|
+
const imageCache = await loadImages(visibleElements, options);
|
|
5593
5662
|
const canvas = document.createElement("canvas");
|
|
5594
|
-
canvas.width =
|
|
5595
|
-
canvas.height =
|
|
5663
|
+
canvas.width = width;
|
|
5664
|
+
canvas.height = height;
|
|
5596
5665
|
const ctx = canvas.getContext("2d");
|
|
5597
5666
|
if (!ctx) return null;
|
|
5598
5667
|
ctx.scale(scale, scale);
|
|
@@ -5775,14 +5844,17 @@ function emitImage(image, dataUri) {
|
|
|
5775
5844
|
const { w, h } = image.size;
|
|
5776
5845
|
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5777
5846
|
}
|
|
5778
|
-
function emitText(text, rasterScale) {
|
|
5847
|
+
function emitText(text, rasterScale, resourceOptions) {
|
|
5779
5848
|
if (!text.text) return "";
|
|
5780
5849
|
const { x, y } = text.position;
|
|
5781
5850
|
const { w, h } = text.size;
|
|
5782
5851
|
if (typeof document === "undefined") return "";
|
|
5852
|
+
const width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5853
|
+
const height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5854
|
+
assertExportSize(width, height, resourceOptions);
|
|
5783
5855
|
const canvas = document.createElement("canvas");
|
|
5784
|
-
canvas.width =
|
|
5785
|
-
canvas.height =
|
|
5856
|
+
canvas.width = width;
|
|
5857
|
+
canvas.height = height;
|
|
5786
5858
|
const ctx = canvas.getContext("2d");
|
|
5787
5859
|
if (!ctx) return "";
|
|
5788
5860
|
ctx.scale(rasterScale, rasterScale);
|
|
@@ -5797,13 +5869,16 @@ function emitText(text, rasterScale) {
|
|
|
5797
5869
|
if (!dataUri || !dataUri.startsWith("data:")) return "";
|
|
5798
5870
|
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5799
5871
|
}
|
|
5800
|
-
function emitNote(note, rasterScale) {
|
|
5872
|
+
function emitNote(note, rasterScale, resourceOptions) {
|
|
5801
5873
|
const { x, y } = note.position;
|
|
5802
5874
|
const { w, h } = note.size;
|
|
5803
5875
|
if (typeof document === "undefined") return emitNotePlaceholder(note);
|
|
5876
|
+
const width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5877
|
+
const height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5878
|
+
assertExportSize(width, height, resourceOptions);
|
|
5804
5879
|
const canvas = document.createElement("canvas");
|
|
5805
|
-
canvas.width =
|
|
5806
|
-
canvas.height =
|
|
5880
|
+
canvas.width = width;
|
|
5881
|
+
canvas.height = height;
|
|
5807
5882
|
const ctx = canvas.getContext("2d");
|
|
5808
5883
|
if (!ctx) return emitNotePlaceholder(note);
|
|
5809
5884
|
ctx.scale(rasterScale, rasterScale);
|
|
@@ -5964,8 +6039,9 @@ function emitHexTemplate(t, grid) {
|
|
|
5964
6039
|
return `<path d="${d}" fill="${esc(t.fillColor)}" stroke="${esc(t.strokeColor)}" stroke-width="${n(t.strokeWidth)}" opacity="${n(t.opacity)}" />`;
|
|
5965
6040
|
}
|
|
5966
6041
|
async function exportSvg(store, options = {}, layerManager) {
|
|
5967
|
-
const padding = options.padding
|
|
5968
|
-
const rasterScale = options.rasterScale
|
|
6042
|
+
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
6043
|
+
const rasterScale = positiveOption(options.rasterScale, 2, "rasterScale");
|
|
6044
|
+
validateExportResourceOptions(options);
|
|
5969
6045
|
const filter = options.filter;
|
|
5970
6046
|
const allElements = store.getAll();
|
|
5971
6047
|
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
@@ -5974,11 +6050,12 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5974
6050
|
if (!bounds) {
|
|
5975
6051
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
5976
6052
|
}
|
|
6053
|
+
assertExportSize(Math.ceil(bounds.w), Math.ceil(bounds.h), options);
|
|
5977
6054
|
const remoteImages = visibleElements.filter(
|
|
5978
6055
|
(el) => el.type === "image" && !el.src.startsWith("data:")
|
|
5979
6056
|
);
|
|
5980
|
-
const imageCache = await loadImages(remoteImages);
|
|
5981
|
-
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale);
|
|
6057
|
+
const imageCache = await loadImages(remoteImages, options);
|
|
6058
|
+
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale, options);
|
|
5982
6059
|
const grids = visibleElements.filter((el) => el.type === "grid");
|
|
5983
6060
|
const firstGrid = grids[0];
|
|
5984
6061
|
let body = "";
|
|
@@ -5987,7 +6064,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5987
6064
|
}
|
|
5988
6065
|
const layerBodies = /* @__PURE__ */ new Map();
|
|
5989
6066
|
for (const el of visibleElements) {
|
|
5990
|
-
const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store);
|
|
6067
|
+
const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store, options);
|
|
5991
6068
|
layerBodies.set(el.layerId, (layerBodies.get(el.layerId) ?? "") + emitted);
|
|
5992
6069
|
}
|
|
5993
6070
|
for (const [layerId, emitted] of layerBodies) {
|
|
@@ -6001,7 +6078,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
6001
6078
|
}
|
|
6002
6079
|
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>`;
|
|
6003
6080
|
}
|
|
6004
|
-
function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
6081
|
+
function emitElement(el, imageDataUris, rasterScale, firstGrid, store, resourceOptions) {
|
|
6005
6082
|
switch (el.type) {
|
|
6006
6083
|
case "stroke":
|
|
6007
6084
|
return withRotationSvg(el, emitStroke(el));
|
|
@@ -6012,9 +6089,9 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
6012
6089
|
case "image":
|
|
6013
6090
|
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
6014
6091
|
case "text":
|
|
6015
|
-
return withRotationSvg(el, emitText(el, rasterScale));
|
|
6092
|
+
return withRotationSvg(el, emitText(el, rasterScale, resourceOptions));
|
|
6016
6093
|
case "note":
|
|
6017
|
-
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
6094
|
+
return withRotationSvg(el, emitNote(el, rasterScale, resourceOptions));
|
|
6018
6095
|
case "template":
|
|
6019
6096
|
return emitTemplate(el, firstGrid);
|
|
6020
6097
|
case "grid":
|
|
@@ -6025,7 +6102,7 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
6025
6102
|
return "";
|
|
6026
6103
|
}
|
|
6027
6104
|
}
|
|
6028
|
-
function encodeImages(elements, imageCache, rasterScale) {
|
|
6105
|
+
function encodeImages(elements, imageCache, rasterScale, resourceOptions) {
|
|
6029
6106
|
const out = /* @__PURE__ */ new Map();
|
|
6030
6107
|
for (const el of elements) {
|
|
6031
6108
|
if (el.type !== "image") continue;
|
|
@@ -6035,16 +6112,24 @@ function encodeImages(elements, imageCache, rasterScale) {
|
|
|
6035
6112
|
}
|
|
6036
6113
|
const img = imageCache.get(el.id);
|
|
6037
6114
|
if (!img || typeof document === "undefined") continue;
|
|
6115
|
+
const width = Math.max(1, Math.ceil(el.size.w * rasterScale));
|
|
6116
|
+
const height = Math.max(1, Math.ceil(el.size.h * rasterScale));
|
|
6117
|
+
assertExportSize(width, height, resourceOptions);
|
|
6038
6118
|
const canvas = document.createElement("canvas");
|
|
6039
|
-
canvas.width =
|
|
6040
|
-
canvas.height =
|
|
6119
|
+
canvas.width = width;
|
|
6120
|
+
canvas.height = height;
|
|
6041
6121
|
const ctx = canvas.getContext("2d");
|
|
6042
|
-
if (!ctx)
|
|
6122
|
+
if (!ctx) {
|
|
6123
|
+
resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
6043
6126
|
try {
|
|
6044
6127
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
6045
6128
|
const uri = canvas.toDataURL();
|
|
6046
6129
|
if (uri.startsWith("data:")) out.set(el.id, uri);
|
|
6047
|
-
|
|
6130
|
+
else resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
|
|
6131
|
+
} catch (cause) {
|
|
6132
|
+
resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode", cause });
|
|
6048
6133
|
}
|
|
6049
6134
|
}
|
|
6050
6135
|
return out;
|
|
@@ -6593,7 +6678,7 @@ var RenderLoop = class {
|
|
|
6593
6678
|
layerGroups = /* @__PURE__ */ new Map();
|
|
6594
6679
|
gridCacheCanvas = null;
|
|
6595
6680
|
gridCacheCtx = null;
|
|
6596
|
-
|
|
6681
|
+
lastGridRefs = [];
|
|
6597
6682
|
constructor(deps) {
|
|
6598
6683
|
this.canvasEl = deps.canvasEl;
|
|
6599
6684
|
this.camera = deps.camera;
|
|
@@ -6793,8 +6878,8 @@ var RenderLoop = class {
|
|
|
6793
6878
|
}
|
|
6794
6879
|
if (gridElements.length > 0) {
|
|
6795
6880
|
const gridT0 = performance.now();
|
|
6796
|
-
const
|
|
6797
|
-
const gridDirty = this.gridCacheDirty ||
|
|
6881
|
+
const gridsChanged = gridElements.length !== this.lastGridRefs.length || gridElements.some((grid, index) => grid !== this.lastGridRefs[index]);
|
|
6882
|
+
const gridDirty = this.gridCacheDirty || gridsChanged;
|
|
6798
6883
|
if (gridDirty) {
|
|
6799
6884
|
this.ensureGridCache();
|
|
6800
6885
|
if (this.gridCacheCtx && this.gridCacheCanvas) {
|
|
@@ -6822,7 +6907,7 @@ var RenderLoop = class {
|
|
|
6822
6907
|
}
|
|
6823
6908
|
}
|
|
6824
6909
|
this.gridCacheDirty = false;
|
|
6825
|
-
this.
|
|
6910
|
+
this.lastGridRefs = [...gridElements];
|
|
6826
6911
|
}
|
|
6827
6912
|
if (this.gridCacheCanvas) {
|
|
6828
6913
|
const offset = this.marginViewport.compositeOffset(
|
|
@@ -6842,6 +6927,8 @@ var RenderLoop = class {
|
|
|
6842
6927
|
}
|
|
6843
6928
|
}
|
|
6844
6929
|
gridMs = performance.now() - gridT0;
|
|
6930
|
+
} else {
|
|
6931
|
+
this.lastGridRefs = [];
|
|
6845
6932
|
}
|
|
6846
6933
|
const overlayT0 = performance.now();
|
|
6847
6934
|
const activeTool = this.toolManager.activeTool;
|
|
@@ -10815,7 +10902,7 @@ var LaserTool = class {
|
|
|
10815
10902
|
};
|
|
10816
10903
|
|
|
10817
10904
|
// src/index.ts
|
|
10818
|
-
var VERSION = "0.
|
|
10905
|
+
var VERSION = "0.51.0";
|
|
10819
10906
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10820
10907
|
0 && (module.exports = {
|
|
10821
10908
|
ArrowTool,
|