@fieldnotes/core 0.50.7 → 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 +244 -68
- 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 +244 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -239,6 +239,17 @@ function sanitizeAttributes(el, tag) {
|
|
|
239
239
|
|
|
240
240
|
// src/core/state-serializer.ts
|
|
241
241
|
var CURRENT_VERSION = 2;
|
|
242
|
+
var ELEMENT_TYPES = [
|
|
243
|
+
"stroke",
|
|
244
|
+
"note",
|
|
245
|
+
"arrow",
|
|
246
|
+
"image",
|
|
247
|
+
"html",
|
|
248
|
+
"text",
|
|
249
|
+
"shape",
|
|
250
|
+
"grid",
|
|
251
|
+
"template"
|
|
252
|
+
];
|
|
242
253
|
function exportState(elements, camera, layers = [], activeLayerId) {
|
|
243
254
|
const state = {
|
|
244
255
|
version: CURRENT_VERSION,
|
|
@@ -264,37 +275,42 @@ function parseState(json) {
|
|
|
264
275
|
return data;
|
|
265
276
|
}
|
|
266
277
|
function validateState(data) {
|
|
267
|
-
if (!data
|
|
278
|
+
if (!isRecord(data)) {
|
|
268
279
|
throw new Error("Invalid state: expected an object");
|
|
269
280
|
}
|
|
270
281
|
const obj = data;
|
|
271
|
-
if (
|
|
282
|
+
if (!Number.isInteger(obj["version"]) || obj["version"] < 1) {
|
|
272
283
|
throw new Error("Invalid state: missing or invalid version");
|
|
273
284
|
}
|
|
274
|
-
if (
|
|
285
|
+
if (obj["version"] > CURRENT_VERSION) {
|
|
286
|
+
throw new Error(`Invalid state: unsupported version ${String(obj["version"])}`);
|
|
287
|
+
}
|
|
288
|
+
if (!isRecord(obj["camera"])) {
|
|
275
289
|
throw new Error("Invalid state: missing camera");
|
|
276
290
|
}
|
|
277
291
|
const cam = obj["camera"];
|
|
278
|
-
if (!cam["position"]
|
|
292
|
+
if (!isRecord(cam["position"])) {
|
|
279
293
|
throw new Error("Invalid state: missing camera.position");
|
|
280
294
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
throw new Error("Invalid state: camera.position must have x and y numbers");
|
|
295
|
+
if (!isPoint(cam["position"])) {
|
|
296
|
+
throw new Error("Invalid state: camera.position must have finite x and y numbers");
|
|
284
297
|
}
|
|
285
|
-
if (
|
|
286
|
-
throw new Error("Invalid state:
|
|
298
|
+
if (!isFiniteNumber(cam["zoom"]) || cam["zoom"] <= 0) {
|
|
299
|
+
throw new Error("Invalid state: camera.zoom must be a positive finite number");
|
|
287
300
|
}
|
|
288
301
|
if (!Array.isArray(obj["elements"])) {
|
|
289
302
|
throw new Error("Invalid state: elements must be an array");
|
|
290
303
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
migrateElement(el);
|
|
304
|
+
if (obj["layers"] !== void 0 && !Array.isArray(obj["layers"])) {
|
|
305
|
+
throw new Error("Invalid state: layers must be an array");
|
|
294
306
|
}
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
307
|
+
const elements = obj["elements"];
|
|
308
|
+
const hasLayers = Array.isArray(obj["layers"]) && obj["layers"].length > 0;
|
|
309
|
+
for (const el of elements) {
|
|
310
|
+
if (!isRecord(el)) throw new Error("Invalid element: expected an object");
|
|
311
|
+
migrateElement(el, !hasLayers);
|
|
312
|
+
}
|
|
313
|
+
if (!hasLayers) {
|
|
298
314
|
obj["layers"] = [
|
|
299
315
|
{
|
|
300
316
|
id: "default-layer",
|
|
@@ -306,31 +322,73 @@ function validateState(data) {
|
|
|
306
322
|
}
|
|
307
323
|
];
|
|
308
324
|
}
|
|
325
|
+
const layers = obj["layers"];
|
|
326
|
+
const layerIds = /* @__PURE__ */ new Set();
|
|
327
|
+
for (const layer of layers) {
|
|
328
|
+
validateLayer(layer);
|
|
329
|
+
if (layerIds.has(layer.id)) throw new Error(`Invalid state: duplicate layer id "${layer.id}"`);
|
|
330
|
+
layerIds.add(layer.id);
|
|
331
|
+
}
|
|
332
|
+
const elementIds = /* @__PURE__ */ new Set();
|
|
333
|
+
for (const el of elements) {
|
|
334
|
+
validateElement(el);
|
|
335
|
+
if (elementIds.has(el.id)) throw new Error(`Invalid state: duplicate element id "${el.id}"`);
|
|
336
|
+
elementIds.add(el.id);
|
|
337
|
+
if (!layerIds.has(el.layerId)) {
|
|
338
|
+
throw new Error(`Invalid element "${el.id}": unknown layerId "${el.layerId}"`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (obj["activeLayerId"] !== void 0) {
|
|
342
|
+
if (typeof obj["activeLayerId"] !== "string" || !layerIds.has(obj["activeLayerId"])) {
|
|
343
|
+
throw new Error("Invalid state: activeLayerId must reference an existing layer");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
cleanBindings(elements);
|
|
309
347
|
}
|
|
310
|
-
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
311
|
-
"stroke",
|
|
312
|
-
"note",
|
|
313
|
-
"arrow",
|
|
314
|
-
"image",
|
|
315
|
-
"html",
|
|
316
|
-
"text",
|
|
317
|
-
"shape",
|
|
318
|
-
"grid",
|
|
319
|
-
"template"
|
|
320
|
-
]);
|
|
321
348
|
function validateElement(el) {
|
|
322
|
-
if (!el
|
|
349
|
+
if (!isRecord(el)) {
|
|
323
350
|
throw new Error("Invalid element: expected an object");
|
|
324
351
|
}
|
|
325
|
-
|
|
326
|
-
if (typeof obj["id"] !== "string") {
|
|
352
|
+
if (typeof el["id"] !== "string" || el["id"].length === 0) {
|
|
327
353
|
throw new Error("Invalid element: missing id");
|
|
328
354
|
}
|
|
329
|
-
if (
|
|
330
|
-
throw new Error(`Invalid element: unknown type "${String(
|
|
355
|
+
if (!isEnum(el["type"], ELEMENT_TYPES)) {
|
|
356
|
+
throw new Error(`Invalid element: unknown type "${String(el["type"])}"`);
|
|
357
|
+
}
|
|
358
|
+
if (!isFiniteNumber(el["zIndex"])) {
|
|
359
|
+
throw new Error(`Invalid element "${el["id"]}": missing or invalid zIndex`);
|
|
360
|
+
}
|
|
361
|
+
if (!isPoint(el["position"]) || typeof el["locked"] !== "boolean" || typeof el["layerId"] !== "string" || !isOptional(el["groupId"], isString) || !isOptional(el["rotation"], isFiniteNumber)) {
|
|
362
|
+
throw new Error(`Invalid element "${el["id"]}": invalid base fields or geometry`);
|
|
363
|
+
}
|
|
364
|
+
const valid = validateTypeFields(el, el["type"]);
|
|
365
|
+
if (!valid) throw new Error(`Invalid element "${el["id"]}": malformed ${el["type"]} data`);
|
|
366
|
+
}
|
|
367
|
+
function validateTypeFields(el, type) {
|
|
368
|
+
switch (type) {
|
|
369
|
+
case "stroke":
|
|
370
|
+
return Array.isArray(el["points"]) && el["points"].every(isStrokePoint) && isString(el["color"]) && isFiniteNumber(el["width"]) && isFiniteNumber(el["opacity"]) && isOptionalEnum(el["blendMode"], ["multiply"]);
|
|
371
|
+
case "note":
|
|
372
|
+
return isSize(el["size"]) && isString(el["text"]) && isString(el["backgroundColor"]) && isString(el["textColor"]) && isOptional(el["fontSize"], isFiniteNumber);
|
|
373
|
+
case "arrow":
|
|
374
|
+
return isPoint(el["from"]) && isPoint(el["to"]) && isFiniteNumber(el["bend"]) && isString(el["color"]) && isFiniteNumber(el["width"]) && isOptional(el["fromBinding"], isBinding) && isOptional(el["toBinding"], isBinding) && isOptional(el["cachedControlPoint"], isPoint) && isOptional(el["label"], isString) && isOptionalEnum(el["strokeStyle"], ["solid", "dashed", "dotted"]);
|
|
375
|
+
case "image":
|
|
376
|
+
return isSize(el["size"]) && isString(el["src"]);
|
|
377
|
+
case "html":
|
|
378
|
+
return isSize(el["size"]) && isOptional(el["domId"], isString) && isOptional(el["interactive"], isBoolean) && isOptional(el["htmlType"], isString) && isOptional(el["data"], isRecord);
|
|
379
|
+
case "text":
|
|
380
|
+
return isSize(el["size"]) && isString(el["text"]) && isFiniteNumber(el["fontSize"]) && isString(el["color"]) && isEnum(el["textAlign"], ["left", "center", "right"]);
|
|
381
|
+
case "shape":
|
|
382
|
+
return isEnum(el["shape"], ["rectangle", "ellipse", "line"]) && isSize(el["size"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isString(el["fillColor"]) && isOptional(el["flip"], isBoolean);
|
|
383
|
+
case "grid":
|
|
384
|
+
return isEnum(el["gridType"], ["square", "hex"]) && isEnum(el["hexOrientation"], ["pointy", "flat"]) && isFiniteNumber(el["cellSize"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isFiniteNumber(el["opacity"]);
|
|
385
|
+
case "template":
|
|
386
|
+
return isEnum(el["templateShape"], ["circle", "cone", "line", "square", "rectangle"]) && isFiniteNumber(el["radius"]) && isFiniteNumber(el["angle"]) && isOptional(el["width"], isFiniteNumber) && isString(el["fillColor"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isFiniteNumber(el["opacity"]) && isOptional(el["feetPerCell"], isFiniteNumber) && isOptional(el["radiusFeet"], isFiniteNumber) && isOptionalEnum(el["renderStyle"], ["cells", "geometric"]);
|
|
331
387
|
}
|
|
332
|
-
|
|
333
|
-
|
|
388
|
+
}
|
|
389
|
+
function validateLayer(layer) {
|
|
390
|
+
if (!isRecord(layer) || typeof layer["id"] !== "string" || layer["id"].length === 0 || typeof layer["name"] !== "string" || typeof layer["visible"] !== "boolean" || typeof layer["locked"] !== "boolean" || !isFiniteNumber(layer["order"]) || !isFiniteNumber(layer["opacity"]) || layer["opacity"] < 0 || layer["opacity"] > 1) {
|
|
391
|
+
throw new Error("Invalid state: malformed layer");
|
|
334
392
|
}
|
|
335
393
|
}
|
|
336
394
|
function cleanBindings(elements) {
|
|
@@ -347,30 +405,63 @@ function cleanBindings(elements) {
|
|
|
347
405
|
}
|
|
348
406
|
}
|
|
349
407
|
}
|
|
350
|
-
function migrateElement(obj) {
|
|
351
|
-
if (
|
|
408
|
+
function migrateElement(obj, useDefaultLayer) {
|
|
409
|
+
if (obj["layerId"] === void 0 || useDefaultLayer && obj["layerId"] === "") {
|
|
352
410
|
obj["layerId"] = "default-layer";
|
|
353
411
|
}
|
|
354
|
-
if (obj["type"] === "arrow" &&
|
|
412
|
+
if (obj["type"] === "arrow" && obj["bend"] === void 0) {
|
|
355
413
|
obj["bend"] = 0;
|
|
356
414
|
}
|
|
357
415
|
if (obj["type"] === "stroke" && Array.isArray(obj["points"])) {
|
|
358
416
|
for (const pt of obj["points"]) {
|
|
359
|
-
if (
|
|
417
|
+
if (pt["pressure"] === void 0) {
|
|
360
418
|
pt["pressure"] = 0.5;
|
|
361
419
|
}
|
|
362
420
|
}
|
|
363
421
|
}
|
|
364
|
-
if (obj["type"] === "shape" &&
|
|
422
|
+
if (obj["type"] === "shape" && obj["shape"] === void 0) {
|
|
365
423
|
obj["shape"] = "rectangle";
|
|
366
424
|
}
|
|
367
|
-
if (obj["type"] === "note" &&
|
|
425
|
+
if (obj["type"] === "note" && obj["textColor"] === void 0) {
|
|
368
426
|
obj["textColor"] = "#000000";
|
|
369
427
|
}
|
|
370
428
|
if ((obj["type"] === "note" || obj["type"] === "text") && typeof obj["text"] === "string") {
|
|
371
429
|
obj["text"] = sanitizeNoteHtml(obj["text"]);
|
|
372
430
|
}
|
|
373
431
|
}
|
|
432
|
+
function isRecord(value) {
|
|
433
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
434
|
+
}
|
|
435
|
+
function isFiniteNumber(value) {
|
|
436
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
437
|
+
}
|
|
438
|
+
function isString(value) {
|
|
439
|
+
return typeof value === "string";
|
|
440
|
+
}
|
|
441
|
+
function isBoolean(value) {
|
|
442
|
+
return typeof value === "boolean";
|
|
443
|
+
}
|
|
444
|
+
function isOptional(value, validate) {
|
|
445
|
+
return value === void 0 || validate(value);
|
|
446
|
+
}
|
|
447
|
+
function isEnum(value, values) {
|
|
448
|
+
return typeof value === "string" && values.includes(value);
|
|
449
|
+
}
|
|
450
|
+
function isOptionalEnum(value, values) {
|
|
451
|
+
return value === void 0 || isEnum(value, values);
|
|
452
|
+
}
|
|
453
|
+
function isPoint(value) {
|
|
454
|
+
return isRecord(value) && isFiniteNumber(value["x"]) && isFiniteNumber(value["y"]);
|
|
455
|
+
}
|
|
456
|
+
function isSize(value) {
|
|
457
|
+
return isRecord(value) && isFiniteNumber(value["w"]) && isFiniteNumber(value["h"]);
|
|
458
|
+
}
|
|
459
|
+
function isStrokePoint(value) {
|
|
460
|
+
return isRecord(value) && isPoint(value) && isFiniteNumber(value["pressure"]);
|
|
461
|
+
}
|
|
462
|
+
function isBinding(value) {
|
|
463
|
+
return isRecord(value) && typeof value["elementId"] === "string";
|
|
464
|
+
}
|
|
374
465
|
|
|
375
466
|
// src/core/storage/local-storage-adapter.ts
|
|
376
467
|
var LocalStorageAdapter = class {
|
|
@@ -5352,6 +5443,9 @@ function renderTextOnCanvas(ctx, text) {
|
|
|
5352
5443
|
}
|
|
5353
5444
|
|
|
5354
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;
|
|
5355
5449
|
var center = (b) => ({ x: b.x + b.w / 2, y: b.y + b.h / 2 });
|
|
5356
5450
|
function getStrokeBounds(el) {
|
|
5357
5451
|
if (el.type !== "stroke") return null;
|
|
@@ -5461,12 +5555,53 @@ function renderGridForBounds(ctx, grid, bounds) {
|
|
|
5461
5555
|
);
|
|
5462
5556
|
}
|
|
5463
5557
|
}
|
|
5464
|
-
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 = {}) {
|
|
5465
5595
|
const imageElements = elements.filter(
|
|
5466
5596
|
(el) => el.type === "image" && "src" in el
|
|
5467
5597
|
);
|
|
5468
5598
|
const cache3 = /* @__PURE__ */ new Map();
|
|
5469
5599
|
if (imageElements.length === 0) return Promise.resolve(cache3);
|
|
5600
|
+
const timeoutMs = positiveOption(
|
|
5601
|
+
options.imageTimeoutMs,
|
|
5602
|
+
DEFAULT_IMAGE_TIMEOUT_MS,
|
|
5603
|
+
"imageTimeoutMs"
|
|
5604
|
+
);
|
|
5470
5605
|
return new Promise((resolve) => {
|
|
5471
5606
|
let remaining = imageElements.length;
|
|
5472
5607
|
const done = () => {
|
|
@@ -5476,19 +5611,41 @@ function loadImages(elements) {
|
|
|
5476
5611
|
for (const el of imageElements) {
|
|
5477
5612
|
const img = new Image();
|
|
5478
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
|
+
};
|
|
5479
5631
|
img.onload = () => {
|
|
5632
|
+
if (!settle()) return;
|
|
5480
5633
|
cache3.set(el.id, img);
|
|
5481
5634
|
done();
|
|
5482
5635
|
};
|
|
5483
|
-
img.onerror =
|
|
5484
|
-
|
|
5485
|
-
|
|
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;
|
|
5486
5642
|
}
|
|
5487
5643
|
});
|
|
5488
5644
|
}
|
|
5489
5645
|
async function exportImage(store, options = {}, layerManager) {
|
|
5490
|
-
const scale = options.scale
|
|
5491
|
-
const padding = options.padding
|
|
5646
|
+
const scale = positiveOption(options.scale, 2, "scale");
|
|
5647
|
+
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
5648
|
+
validateExportResourceOptions(options);
|
|
5492
5649
|
const background = options.background ?? "#ffffff";
|
|
5493
5650
|
const filter = options.filter;
|
|
5494
5651
|
const allElements = store.getAll();
|
|
@@ -5498,10 +5655,13 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5498
5655
|
}
|
|
5499
5656
|
const bounds = computeBounds(visibleElements, padding);
|
|
5500
5657
|
if (!bounds) return null;
|
|
5501
|
-
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);
|
|
5502
5662
|
const canvas = document.createElement("canvas");
|
|
5503
|
-
canvas.width =
|
|
5504
|
-
canvas.height =
|
|
5663
|
+
canvas.width = width;
|
|
5664
|
+
canvas.height = height;
|
|
5505
5665
|
const ctx = canvas.getContext("2d");
|
|
5506
5666
|
if (!ctx) return null;
|
|
5507
5667
|
ctx.scale(scale, scale);
|
|
@@ -5684,14 +5844,17 @@ function emitImage(image, dataUri) {
|
|
|
5684
5844
|
const { w, h } = image.size;
|
|
5685
5845
|
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5686
5846
|
}
|
|
5687
|
-
function emitText(text, rasterScale) {
|
|
5847
|
+
function emitText(text, rasterScale, resourceOptions) {
|
|
5688
5848
|
if (!text.text) return "";
|
|
5689
5849
|
const { x, y } = text.position;
|
|
5690
5850
|
const { w, h } = text.size;
|
|
5691
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);
|
|
5692
5855
|
const canvas = document.createElement("canvas");
|
|
5693
|
-
canvas.width =
|
|
5694
|
-
canvas.height =
|
|
5856
|
+
canvas.width = width;
|
|
5857
|
+
canvas.height = height;
|
|
5695
5858
|
const ctx = canvas.getContext("2d");
|
|
5696
5859
|
if (!ctx) return "";
|
|
5697
5860
|
ctx.scale(rasterScale, rasterScale);
|
|
@@ -5706,13 +5869,16 @@ function emitText(text, rasterScale) {
|
|
|
5706
5869
|
if (!dataUri || !dataUri.startsWith("data:")) return "";
|
|
5707
5870
|
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5708
5871
|
}
|
|
5709
|
-
function emitNote(note, rasterScale) {
|
|
5872
|
+
function emitNote(note, rasterScale, resourceOptions) {
|
|
5710
5873
|
const { x, y } = note.position;
|
|
5711
5874
|
const { w, h } = note.size;
|
|
5712
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);
|
|
5713
5879
|
const canvas = document.createElement("canvas");
|
|
5714
|
-
canvas.width =
|
|
5715
|
-
canvas.height =
|
|
5880
|
+
canvas.width = width;
|
|
5881
|
+
canvas.height = height;
|
|
5716
5882
|
const ctx = canvas.getContext("2d");
|
|
5717
5883
|
if (!ctx) return emitNotePlaceholder(note);
|
|
5718
5884
|
ctx.scale(rasterScale, rasterScale);
|
|
@@ -5873,8 +6039,9 @@ function emitHexTemplate(t, grid) {
|
|
|
5873
6039
|
return `<path d="${d}" fill="${esc(t.fillColor)}" stroke="${esc(t.strokeColor)}" stroke-width="${n(t.strokeWidth)}" opacity="${n(t.opacity)}" />`;
|
|
5874
6040
|
}
|
|
5875
6041
|
async function exportSvg(store, options = {}, layerManager) {
|
|
5876
|
-
const padding = options.padding
|
|
5877
|
-
const rasterScale = options.rasterScale
|
|
6042
|
+
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
6043
|
+
const rasterScale = positiveOption(options.rasterScale, 2, "rasterScale");
|
|
6044
|
+
validateExportResourceOptions(options);
|
|
5878
6045
|
const filter = options.filter;
|
|
5879
6046
|
const allElements = store.getAll();
|
|
5880
6047
|
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
@@ -5883,11 +6050,12 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5883
6050
|
if (!bounds) {
|
|
5884
6051
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
5885
6052
|
}
|
|
6053
|
+
assertExportSize(Math.ceil(bounds.w), Math.ceil(bounds.h), options);
|
|
5886
6054
|
const remoteImages = visibleElements.filter(
|
|
5887
6055
|
(el) => el.type === "image" && !el.src.startsWith("data:")
|
|
5888
6056
|
);
|
|
5889
|
-
const imageCache = await loadImages(remoteImages);
|
|
5890
|
-
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale);
|
|
6057
|
+
const imageCache = await loadImages(remoteImages, options);
|
|
6058
|
+
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale, options);
|
|
5891
6059
|
const grids = visibleElements.filter((el) => el.type === "grid");
|
|
5892
6060
|
const firstGrid = grids[0];
|
|
5893
6061
|
let body = "";
|
|
@@ -5896,7 +6064,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5896
6064
|
}
|
|
5897
6065
|
const layerBodies = /* @__PURE__ */ new Map();
|
|
5898
6066
|
for (const el of visibleElements) {
|
|
5899
|
-
const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store);
|
|
6067
|
+
const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store, options);
|
|
5900
6068
|
layerBodies.set(el.layerId, (layerBodies.get(el.layerId) ?? "") + emitted);
|
|
5901
6069
|
}
|
|
5902
6070
|
for (const [layerId, emitted] of layerBodies) {
|
|
@@ -5910,7 +6078,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5910
6078
|
}
|
|
5911
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>`;
|
|
5912
6080
|
}
|
|
5913
|
-
function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
6081
|
+
function emitElement(el, imageDataUris, rasterScale, firstGrid, store, resourceOptions) {
|
|
5914
6082
|
switch (el.type) {
|
|
5915
6083
|
case "stroke":
|
|
5916
6084
|
return withRotationSvg(el, emitStroke(el));
|
|
@@ -5921,9 +6089,9 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
5921
6089
|
case "image":
|
|
5922
6090
|
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
5923
6091
|
case "text":
|
|
5924
|
-
return withRotationSvg(el, emitText(el, rasterScale));
|
|
6092
|
+
return withRotationSvg(el, emitText(el, rasterScale, resourceOptions));
|
|
5925
6093
|
case "note":
|
|
5926
|
-
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
6094
|
+
return withRotationSvg(el, emitNote(el, rasterScale, resourceOptions));
|
|
5927
6095
|
case "template":
|
|
5928
6096
|
return emitTemplate(el, firstGrid);
|
|
5929
6097
|
case "grid":
|
|
@@ -5934,7 +6102,7 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
5934
6102
|
return "";
|
|
5935
6103
|
}
|
|
5936
6104
|
}
|
|
5937
|
-
function encodeImages(elements, imageCache, rasterScale) {
|
|
6105
|
+
function encodeImages(elements, imageCache, rasterScale, resourceOptions) {
|
|
5938
6106
|
const out = /* @__PURE__ */ new Map();
|
|
5939
6107
|
for (const el of elements) {
|
|
5940
6108
|
if (el.type !== "image") continue;
|
|
@@ -5944,16 +6112,24 @@ function encodeImages(elements, imageCache, rasterScale) {
|
|
|
5944
6112
|
}
|
|
5945
6113
|
const img = imageCache.get(el.id);
|
|
5946
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);
|
|
5947
6118
|
const canvas = document.createElement("canvas");
|
|
5948
|
-
canvas.width =
|
|
5949
|
-
canvas.height =
|
|
6119
|
+
canvas.width = width;
|
|
6120
|
+
canvas.height = height;
|
|
5950
6121
|
const ctx = canvas.getContext("2d");
|
|
5951
|
-
if (!ctx)
|
|
6122
|
+
if (!ctx) {
|
|
6123
|
+
resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
5952
6126
|
try {
|
|
5953
6127
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
5954
6128
|
const uri = canvas.toDataURL();
|
|
5955
6129
|
if (uri.startsWith("data:")) out.set(el.id, uri);
|
|
5956
|
-
|
|
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 });
|
|
5957
6133
|
}
|
|
5958
6134
|
}
|
|
5959
6135
|
return out;
|
|
@@ -10726,7 +10902,7 @@ var LaserTool = class {
|
|
|
10726
10902
|
};
|
|
10727
10903
|
|
|
10728
10904
|
// src/index.ts
|
|
10729
|
-
var VERSION = "0.
|
|
10905
|
+
var VERSION = "0.51.0";
|
|
10730
10906
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10731
10907
|
0 && (module.exports = {
|
|
10732
10908
|
ArrowTool,
|