@fieldnotes/core 0.51.0 → 0.52.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 +24 -3
- package/dist/index.cjs +150 -5
- 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 +150 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,9 +164,30 @@ const blob = await viewport.exportImage({
|
|
|
164
164
|
});
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
Remote images are requested with anonymous CORS using their original URLs; failures are omitted from
|
|
168
|
+
the result and reported through `onAssetError` when supplied.
|
|
169
|
+
|
|
170
|
+
Application-owned HTML embeds require an explicit rasterization hook. Return a ready canvas-compatible
|
|
171
|
+
image source; Field Notes applies the element's size, rotation, paint order, and layer opacity in both
|
|
172
|
+
PNG and SVG exports:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const options = {
|
|
176
|
+
htmlTimeoutMs: 10_000,
|
|
177
|
+
renderHtml: async (element) => {
|
|
178
|
+
const node = document.querySelector(`[data-element-id="${element.id}"]`);
|
|
179
|
+
return node ? rasterizeToCanvas(node) : null; // application or library implementation
|
|
180
|
+
},
|
|
181
|
+
onHtmlError: ({ elementId, reason }) => {
|
|
182
|
+
console.warn(`Could not export HTML element ${elementId}: ${reason}`);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const png = await viewport.exportImage(options);
|
|
187
|
+
const svg = await viewport.exportSVG(options);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Without `renderHtml`, embeds remain omitted and can be observed through `onHtmlError`.
|
|
170
191
|
|
|
171
192
|
## Performance Monitoring
|
|
172
193
|
|
package/dist/index.cjs
CHANGED
|
@@ -5442,6 +5442,67 @@ function renderTextOnCanvas(ctx, text) {
|
|
|
5442
5442
|
ctx.restore();
|
|
5443
5443
|
}
|
|
5444
5444
|
|
|
5445
|
+
// src/canvas/html-export.ts
|
|
5446
|
+
var DEFAULT_HTML_TIMEOUT_MS = 1e4;
|
|
5447
|
+
var TIMEOUT = /* @__PURE__ */ Symbol("html-export-timeout");
|
|
5448
|
+
function validateHtmlExportOptions(options) {
|
|
5449
|
+
const timeoutMs = options.htmlTimeoutMs ?? DEFAULT_HTML_TIMEOUT_MS;
|
|
5450
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
5451
|
+
throw new RangeError("htmlTimeoutMs must be a finite number greater than 0");
|
|
5452
|
+
}
|
|
5453
|
+
return timeoutMs;
|
|
5454
|
+
}
|
|
5455
|
+
async function renderHtmlElements(elements, options) {
|
|
5456
|
+
const timeoutMs = validateHtmlExportOptions(options);
|
|
5457
|
+
const sources = /* @__PURE__ */ new Map();
|
|
5458
|
+
await Promise.all(
|
|
5459
|
+
elements.map(async (element) => {
|
|
5460
|
+
if (!options.renderHtml) {
|
|
5461
|
+
options.onHtmlError?.({
|
|
5462
|
+
elementId: element.id,
|
|
5463
|
+
htmlType: element.htmlType,
|
|
5464
|
+
reason: "unsupported"
|
|
5465
|
+
});
|
|
5466
|
+
return;
|
|
5467
|
+
}
|
|
5468
|
+
let timer;
|
|
5469
|
+
try {
|
|
5470
|
+
const timeout = new Promise((resolve) => {
|
|
5471
|
+
timer = setTimeout(() => resolve(TIMEOUT), timeoutMs);
|
|
5472
|
+
});
|
|
5473
|
+
const result = await Promise.race([Promise.resolve(options.renderHtml(element)), timeout]);
|
|
5474
|
+
if (result === TIMEOUT) {
|
|
5475
|
+
options.onHtmlError?.({
|
|
5476
|
+
elementId: element.id,
|
|
5477
|
+
htmlType: element.htmlType,
|
|
5478
|
+
reason: "timeout"
|
|
5479
|
+
});
|
|
5480
|
+
return;
|
|
5481
|
+
}
|
|
5482
|
+
if (!result) {
|
|
5483
|
+
options.onHtmlError?.({
|
|
5484
|
+
elementId: element.id,
|
|
5485
|
+
htmlType: element.htmlType,
|
|
5486
|
+
reason: "render"
|
|
5487
|
+
});
|
|
5488
|
+
return;
|
|
5489
|
+
}
|
|
5490
|
+
sources.set(element.id, result);
|
|
5491
|
+
} catch (cause) {
|
|
5492
|
+
options.onHtmlError?.({
|
|
5493
|
+
elementId: element.id,
|
|
5494
|
+
htmlType: element.htmlType,
|
|
5495
|
+
reason: "render",
|
|
5496
|
+
cause
|
|
5497
|
+
});
|
|
5498
|
+
} finally {
|
|
5499
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
5500
|
+
}
|
|
5501
|
+
})
|
|
5502
|
+
);
|
|
5503
|
+
return sources;
|
|
5504
|
+
}
|
|
5505
|
+
|
|
5445
5506
|
// src/canvas/export-image.ts
|
|
5446
5507
|
var DEFAULT_IMAGE_TIMEOUT_MS = 1e4;
|
|
5447
5508
|
var DEFAULT_MAX_DIMENSION = 16384;
|
|
@@ -5646,6 +5707,7 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5646
5707
|
const scale = positiveOption(options.scale, 2, "scale");
|
|
5647
5708
|
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
5648
5709
|
validateExportResourceOptions(options);
|
|
5710
|
+
validateHtmlExportOptions(options);
|
|
5649
5711
|
const background = options.background ?? "#ffffff";
|
|
5650
5712
|
const filter = options.filter;
|
|
5651
5713
|
const allElements = store.getAll();
|
|
@@ -5659,6 +5721,8 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5659
5721
|
const height = Math.ceil(bounds.h * scale);
|
|
5660
5722
|
assertExportSize(width, height, options);
|
|
5661
5723
|
const imageCache = await loadImages(visibleElements, options);
|
|
5724
|
+
const htmlElements = visibleElements.filter((el) => el.type === "html");
|
|
5725
|
+
const htmlSources = await renderHtmlElements(htmlElements, options);
|
|
5662
5726
|
const canvas = document.createElement("canvas");
|
|
5663
5727
|
canvas.width = width;
|
|
5664
5728
|
canvas.height = height;
|
|
@@ -5683,6 +5747,24 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5683
5747
|
return;
|
|
5684
5748
|
}
|
|
5685
5749
|
if (el.type === "html") {
|
|
5750
|
+
const source = htmlSources.get(el.id);
|
|
5751
|
+
if (!source) return;
|
|
5752
|
+
const b = getElementBounds(el);
|
|
5753
|
+
try {
|
|
5754
|
+
withRotation(
|
|
5755
|
+
target,
|
|
5756
|
+
el,
|
|
5757
|
+
b ? center(b) : el.position,
|
|
5758
|
+
() => target.drawImage(source, el.position.x, el.position.y, el.size.w, el.size.h)
|
|
5759
|
+
);
|
|
5760
|
+
} catch (cause) {
|
|
5761
|
+
options.onHtmlError?.({
|
|
5762
|
+
elementId: el.id,
|
|
5763
|
+
htmlType: el.htmlType,
|
|
5764
|
+
reason: "render",
|
|
5765
|
+
cause
|
|
5766
|
+
});
|
|
5767
|
+
}
|
|
5686
5768
|
return;
|
|
5687
5769
|
}
|
|
5688
5770
|
if (el.type === "image") {
|
|
@@ -5838,7 +5920,7 @@ function emitArrow(arrow, store) {
|
|
|
5838
5920
|
return out;
|
|
5839
5921
|
}
|
|
5840
5922
|
function emitImage(image, dataUri) {
|
|
5841
|
-
const href = dataUri ?? image.src;
|
|
5923
|
+
const href = dataUri ?? ("src" in image ? image.src : "");
|
|
5842
5924
|
if (!href) return "";
|
|
5843
5925
|
const { x, y } = image.position;
|
|
5844
5926
|
const { w, h } = image.size;
|
|
@@ -6042,6 +6124,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
6042
6124
|
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
6043
6125
|
const rasterScale = positiveOption(options.rasterScale, 2, "rasterScale");
|
|
6044
6126
|
validateExportResourceOptions(options);
|
|
6127
|
+
validateHtmlExportOptions(options);
|
|
6045
6128
|
const filter = options.filter;
|
|
6046
6129
|
const allElements = store.getAll();
|
|
6047
6130
|
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
@@ -6056,6 +6139,9 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
6056
6139
|
);
|
|
6057
6140
|
const imageCache = await loadImages(remoteImages, options);
|
|
6058
6141
|
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale, options);
|
|
6142
|
+
const htmlElements = visibleElements.filter((el) => el.type === "html");
|
|
6143
|
+
const htmlSources = await renderHtmlElements(htmlElements, options);
|
|
6144
|
+
const htmlDataUris = encodeHtmlElements(htmlElements, htmlSources, rasterScale, options);
|
|
6059
6145
|
const grids = visibleElements.filter((el) => el.type === "grid");
|
|
6060
6146
|
const firstGrid = grids[0];
|
|
6061
6147
|
let body = "";
|
|
@@ -6064,7 +6150,15 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
6064
6150
|
}
|
|
6065
6151
|
const layerBodies = /* @__PURE__ */ new Map();
|
|
6066
6152
|
for (const el of visibleElements) {
|
|
6067
|
-
const emitted = emitElement(
|
|
6153
|
+
const emitted = emitElement(
|
|
6154
|
+
el,
|
|
6155
|
+
imageDataUris,
|
|
6156
|
+
htmlDataUris,
|
|
6157
|
+
rasterScale,
|
|
6158
|
+
firstGrid,
|
|
6159
|
+
store,
|
|
6160
|
+
options
|
|
6161
|
+
);
|
|
6068
6162
|
layerBodies.set(el.layerId, (layerBodies.get(el.layerId) ?? "") + emitted);
|
|
6069
6163
|
}
|
|
6070
6164
|
for (const [layerId, emitted] of layerBodies) {
|
|
@@ -6078,7 +6172,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
6078
6172
|
}
|
|
6079
6173
|
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>`;
|
|
6080
6174
|
}
|
|
6081
|
-
function emitElement(el, imageDataUris, rasterScale, firstGrid, store, resourceOptions) {
|
|
6175
|
+
function emitElement(el, imageDataUris, htmlDataUris, rasterScale, firstGrid, store, resourceOptions) {
|
|
6082
6176
|
switch (el.type) {
|
|
6083
6177
|
case "stroke":
|
|
6084
6178
|
return withRotationSvg(el, emitStroke(el));
|
|
@@ -6097,11 +6191,62 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store, resourceO
|
|
|
6097
6191
|
case "grid":
|
|
6098
6192
|
return "";
|
|
6099
6193
|
case "html":
|
|
6100
|
-
return
|
|
6194
|
+
return withRotationSvg(el, emitImage(el, htmlDataUris.get(el.id)));
|
|
6101
6195
|
default:
|
|
6102
6196
|
return "";
|
|
6103
6197
|
}
|
|
6104
6198
|
}
|
|
6199
|
+
function encodeHtmlElements(elements, sources, rasterScale, options) {
|
|
6200
|
+
const encoded = /* @__PURE__ */ new Map();
|
|
6201
|
+
for (const element of elements) {
|
|
6202
|
+
const source = sources.get(element.id);
|
|
6203
|
+
if (!source) continue;
|
|
6204
|
+
const width = Math.max(1, Math.ceil(element.size.w * rasterScale));
|
|
6205
|
+
const height = Math.max(1, Math.ceil(element.size.h * rasterScale));
|
|
6206
|
+
assertExportSize(width, height, options);
|
|
6207
|
+
if (typeof document === "undefined") {
|
|
6208
|
+
options.onHtmlError?.({
|
|
6209
|
+
elementId: element.id,
|
|
6210
|
+
htmlType: element.htmlType,
|
|
6211
|
+
reason: "encode"
|
|
6212
|
+
});
|
|
6213
|
+
continue;
|
|
6214
|
+
}
|
|
6215
|
+
const canvas = document.createElement("canvas");
|
|
6216
|
+
canvas.width = width;
|
|
6217
|
+
canvas.height = height;
|
|
6218
|
+
const ctx = canvas.getContext("2d");
|
|
6219
|
+
if (!ctx) {
|
|
6220
|
+
options.onHtmlError?.({
|
|
6221
|
+
elementId: element.id,
|
|
6222
|
+
htmlType: element.htmlType,
|
|
6223
|
+
reason: "encode"
|
|
6224
|
+
});
|
|
6225
|
+
continue;
|
|
6226
|
+
}
|
|
6227
|
+
try {
|
|
6228
|
+
ctx.drawImage(source, 0, 0, width, height);
|
|
6229
|
+
const dataUri = canvas.toDataURL();
|
|
6230
|
+
if (dataUri.startsWith("data:")) {
|
|
6231
|
+
encoded.set(element.id, dataUri);
|
|
6232
|
+
} else {
|
|
6233
|
+
options.onHtmlError?.({
|
|
6234
|
+
elementId: element.id,
|
|
6235
|
+
htmlType: element.htmlType,
|
|
6236
|
+
reason: "encode"
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
} catch (cause) {
|
|
6240
|
+
options.onHtmlError?.({
|
|
6241
|
+
elementId: element.id,
|
|
6242
|
+
htmlType: element.htmlType,
|
|
6243
|
+
reason: "encode",
|
|
6244
|
+
cause
|
|
6245
|
+
});
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
return encoded;
|
|
6249
|
+
}
|
|
6105
6250
|
function encodeImages(elements, imageCache, rasterScale, resourceOptions) {
|
|
6106
6251
|
const out = /* @__PURE__ */ new Map();
|
|
6107
6252
|
for (const el of elements) {
|
|
@@ -10902,7 +11047,7 @@ var LaserTool = class {
|
|
|
10902
11047
|
};
|
|
10903
11048
|
|
|
10904
11049
|
// src/index.ts
|
|
10905
|
-
var VERSION = "0.
|
|
11050
|
+
var VERSION = "0.52.0";
|
|
10906
11051
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10907
11052
|
0 && (module.exports = {
|
|
10908
11053
|
ArrowTool,
|