fabricjs-document-engine 1.1.0 → 1.3.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.
Files changed (146) hide show
  1. package/README.md +70 -1
  2. package/README.zh-CN.md +71 -1
  3. package/dist/assets/asset-pipeline.cjs +10 -8
  4. package/dist/assets/asset-pipeline.d.cts +1 -1
  5. package/dist/assets/asset-pipeline.d.ts +1 -1
  6. package/dist/assets/asset-pipeline.js +10 -8
  7. package/dist/assets/image-check.cjs +4 -3
  8. package/dist/assets/image-check.d.cts +1 -1
  9. package/dist/assets/image-check.d.ts +1 -1
  10. package/dist/assets/image-check.js +4 -3
  11. package/dist/commands/clipboard.cjs +5 -0
  12. package/dist/commands/clipboard.js +5 -0
  13. package/dist/commands/text-edit.cjs +223 -0
  14. package/dist/commands/text-edit.d.cts +23 -0
  15. package/dist/commands/text-edit.d.ts +23 -0
  16. package/dist/commands/text-edit.js +218 -0
  17. package/dist/document/document-format.d.cts +15 -0
  18. package/dist/document/document-format.d.ts +15 -0
  19. package/dist/document/validate-document.cjs +8 -0
  20. package/dist/document/validate-document.js +8 -0
  21. package/dist/engine/create-document-engine.cjs +115 -21
  22. package/dist/engine/create-document-engine.d.cts +24 -6
  23. package/dist/engine/create-document-engine.d.ts +24 -6
  24. package/dist/engine/create-document-engine.js +116 -22
  25. package/dist/engine/errors.d.cts +1 -1
  26. package/dist/engine/errors.d.ts +1 -1
  27. package/dist/export/export-options.cjs +3 -0
  28. package/dist/export/export-options.d.cts +6 -0
  29. package/dist/export/export-options.d.ts +6 -0
  30. package/dist/export/export-options.js +3 -0
  31. package/dist/export/preflight-export.d.cts +1 -1
  32. package/dist/export/preflight-export.d.ts +1 -1
  33. package/dist/export/render-export.cjs +42 -4
  34. package/dist/export/render-export.js +43 -5
  35. package/dist/export/svg/clip-masks.cjs +39 -0
  36. package/dist/export/svg/clip-masks.js +37 -0
  37. package/dist/export/svg/embed-assets.cjs +1 -0
  38. package/dist/export/svg/embed-assets.js +1 -0
  39. package/dist/export/svg/raster-object.cjs +82 -0
  40. package/dist/export/svg/raster-object.js +81 -0
  41. package/dist/{pdf → export/svg}/text-decorations.cjs +3 -3
  42. package/dist/{pdf → export/svg}/text-decorations.js +3 -3
  43. package/dist/export/svg/text-on-path.cjs +0 -10
  44. package/dist/export/svg/text-on-path.js +1 -10
  45. package/dist/fabric/fabric-adapter.cjs +5 -3
  46. package/dist/fabric/fabric-adapter.js +5 -3
  47. package/dist/fabric/object-registry.cjs +7 -1
  48. package/dist/fabric/object-registry.js +7 -1
  49. package/dist/fabric/page-state.cjs +69 -0
  50. package/dist/fabric/page-state.js +64 -0
  51. package/dist/filters/filter-worker.cjs +281 -0
  52. package/dist/filters/filter-worker.d.cts +26 -0
  53. package/dist/filters/filter-worker.d.ts +26 -0
  54. package/dist/filters/filter-worker.js +281 -0
  55. package/dist/filters/pipeline.cjs +91 -0
  56. package/dist/filters/pipeline.d.cts +35 -0
  57. package/dist/filters/pipeline.d.ts +35 -0
  58. package/dist/filters/pipeline.js +87 -0
  59. package/dist/filters/worker-entry.cjs +78 -0
  60. package/dist/filters/worker-entry.d.cts +29 -0
  61. package/dist/filters/worker-entry.d.ts +29 -0
  62. package/dist/filters/worker-entry.js +78 -0
  63. package/dist/filters.cjs +9 -0
  64. package/dist/filters.d.cts +3 -0
  65. package/dist/filters.d.ts +3 -0
  66. package/dist/filters.js +3 -0
  67. package/dist/history/apply-state.cjs +19 -2
  68. package/dist/history/apply-state.js +19 -2
  69. package/dist/history/create-history.cjs +53 -13
  70. package/dist/history/create-history.d.cts +8 -0
  71. package/dist/history/create-history.d.ts +8 -0
  72. package/dist/history/create-history.js +53 -13
  73. package/dist/history/snapshot.cjs +10 -1
  74. package/dist/history/snapshot.js +10 -2
  75. package/dist/import/svg-import.cjs +102 -0
  76. package/dist/import/svg-import.d.cts +1 -0
  77. package/dist/import/svg-import.d.ts +1 -0
  78. package/dist/import/svg-import.js +103 -1
  79. package/dist/index.cjs +7 -0
  80. package/dist/index.d.cts +4 -3
  81. package/dist/index.d.ts +4 -3
  82. package/dist/index.js +2 -1
  83. package/dist/migrations/migrate-document.cjs +2 -1
  84. package/dist/migrations/migrate-document.js +2 -1
  85. package/dist/pdf/export-pdf.cjs +8 -67
  86. package/dist/pdf/export-pdf.js +8 -67
  87. package/dist/performance/batch.cjs +31 -0
  88. package/dist/performance/batch.d.cts +4 -0
  89. package/dist/performance/batch.d.ts +4 -0
  90. package/dist/performance/batch.js +31 -0
  91. package/dist/performance/dirty-regions.cjs +378 -0
  92. package/dist/performance/dirty-regions.d.cts +23 -0
  93. package/dist/performance/dirty-regions.d.ts +23 -0
  94. package/dist/performance/dirty-regions.js +378 -0
  95. package/dist/performance/monitor.cjs +60 -0
  96. package/dist/performance/monitor.d.cts +20 -0
  97. package/dist/performance/monitor.d.ts +20 -0
  98. package/dist/performance/monitor.js +60 -0
  99. package/dist/performance/spatial-index.cjs +78 -0
  100. package/dist/performance/spatial-index.d.cts +21 -0
  101. package/dist/performance/spatial-index.d.ts +21 -0
  102. package/dist/performance/spatial-index.js +78 -0
  103. package/dist/performance.cjs +9 -0
  104. package/dist/performance.d.cts +5 -0
  105. package/dist/performance.d.ts +5 -0
  106. package/dist/performance.js +5 -0
  107. package/dist/recovery/recovery-controller.cjs +148 -29
  108. package/dist/recovery/recovery-controller.d.cts +8 -0
  109. package/dist/recovery/recovery-controller.d.ts +8 -0
  110. package/dist/recovery/recovery-controller.js +148 -29
  111. package/dist/security/content-limits.cjs +43 -3
  112. package/dist/security/content-limits.d.cts +8 -0
  113. package/dist/security/content-limits.d.ts +8 -0
  114. package/dist/security/content-limits.js +39 -4
  115. package/dist/text/bounded-textbox.cjs +306 -0
  116. package/dist/text/bounded-textbox.d.cts +53 -0
  117. package/dist/text/bounded-textbox.d.ts +53 -0
  118. package/dist/text/bounded-textbox.js +306 -0
  119. package/dist/text/mobile-input.cjs +146 -0
  120. package/dist/text/mobile-input.d.cts +19 -0
  121. package/dist/text/mobile-input.d.ts +19 -0
  122. package/dist/text/mobile-input.js +145 -0
  123. package/dist/text/register.cjs +17 -0
  124. package/dist/text/register.d.cts +5 -0
  125. package/dist/text/register.d.ts +5 -0
  126. package/dist/text/register.js +16 -0
  127. package/dist/text/shaping.cjs +125 -0
  128. package/dist/text/shaping.d.cts +28 -0
  129. package/dist/text/shaping.d.ts +28 -0
  130. package/dist/text/shaping.js +121 -0
  131. package/dist/text/vertical-orientation.cjs +192 -0
  132. package/dist/text/vertical-orientation.d.cts +10 -0
  133. package/dist/text/vertical-orientation.d.ts +10 -0
  134. package/dist/text/vertical-orientation.js +190 -0
  135. package/dist/text/vertical-text.cjs +336 -0
  136. package/dist/text/vertical-text.d.cts +54 -0
  137. package/dist/text/vertical-text.d.ts +54 -0
  138. package/dist/text/vertical-text.js +336 -0
  139. package/dist/text.cjs +21 -0
  140. package/dist/text.d.cts +7 -0
  141. package/dist/text.d.ts +7 -0
  142. package/dist/text.js +7 -0
  143. package/dist/util/concurrency.cjs +16 -3
  144. package/dist/util/concurrency.js +16 -3
  145. package/package.json +31 -1
  146. package/schema/document-v1.schema.json +5 -1
@@ -0,0 +1,82 @@
1
+ require("../../security/content-limits.cjs");
2
+ const require_walk_objects = require("../../fabric/walk-objects.cjs");
3
+ //#region src/export/svg/raster-object.ts
4
+ /**
5
+ * The largest multiplier that keeps a `width × height` area within the
6
+ * canvas limits. A picture over the browser's limit would fail, so a large
7
+ * page is drawn at a lower resolution instead: O(1) arithmetic.
8
+ */
9
+ function safeMultiplier(multiplier, width, height, limits) {
10
+ const maxPixels = limits?.maxCanvasPixels ?? 67108864;
11
+ const maxSide = limits?.maxCanvasSide ?? 16384;
12
+ const byArea = Math.sqrt(maxPixels / Math.max(1, width * height));
13
+ const bySide = maxSide / Math.max(1, width, height);
14
+ return Math.min(multiplier, byArea, bySide);
15
+ }
16
+ function shadowPadding(object) {
17
+ let padding = 0;
18
+ require_walk_objects.walkObjects([object], (child) => {
19
+ const shadow = child.shadow;
20
+ if (shadow) padding = Math.max(padding, shadow.blur + Math.max(Math.abs(shadow.offsetX), Math.abs(shadow.offsetY)));
21
+ });
22
+ return padding + 2;
23
+ }
24
+ function usesBlendMode(object) {
25
+ let blends = false;
26
+ require_walk_objects.walkObjects([object], (child) => {
27
+ if (child.globalCompositeOperation && child.globalCompositeOperation !== "source-over") blends = true;
28
+ });
29
+ return blends;
30
+ }
31
+ /**
32
+ * An object as a picture placed where it sits on the canvas. Usually the
33
+ * object alone, so the vectors underneath show through. An object with a
34
+ * blend mode mixes with what lies below it, so its picture includes
35
+ * everything below it within its bounds, which covers those vectors exactly.
36
+ */
37
+ function rasterMarkup(canvas, object, multiplier, limits) {
38
+ object.setCoords();
39
+ const bounds = object.getBoundingRect();
40
+ const padding = shadowPadding(object);
41
+ const left = Math.max(0, Math.floor(bounds.left - padding));
42
+ const top = Math.max(0, Math.floor(bounds.top - padding));
43
+ const right = Math.min(canvas.getWidth(), Math.ceil(bounds.left + bounds.width + padding));
44
+ const bottom = Math.min(canvas.getHeight(), Math.ceil(bounds.top + bounds.height + padding));
45
+ if (right <= left || bottom <= top) return "";
46
+ const blends = usesBlendMode(object);
47
+ const stack = canvas.getObjects();
48
+ const position = stack.indexOf(object);
49
+ const saved = {
50
+ backgroundColor: canvas.backgroundColor,
51
+ backgroundImage: canvas.backgroundImage,
52
+ overlayColor: canvas.overlayColor,
53
+ overlayImage: canvas.overlayImage
54
+ };
55
+ if (!blends) {
56
+ canvas.backgroundColor = "";
57
+ canvas.backgroundImage = void 0;
58
+ }
59
+ canvas.overlayColor = "";
60
+ canvas.overlayImage = void 0;
61
+ let element;
62
+ try {
63
+ const filter = (candidate) => blends ? stack.indexOf(candidate) <= position : candidate === object;
64
+ const safe = safeMultiplier(multiplier, right - left, bottom - top, limits);
65
+ element = canvas.toCanvasElement(safe, {
66
+ left,
67
+ top,
68
+ width: right - left,
69
+ height: bottom - top,
70
+ filter
71
+ });
72
+ } finally {
73
+ Object.assign(canvas, saved);
74
+ }
75
+ const url = element.toDataURL("image/png");
76
+ element.width = 0;
77
+ element.height = 0;
78
+ return `<image x="${left}" y="${top}" width="${right - left}" height="${bottom - top}" preserveAspectRatio="none" xlink:href="${url}" />\n`;
79
+ }
80
+ //#endregion
81
+ exports.rasterMarkup = rasterMarkup;
82
+ exports.safeMultiplier = safeMultiplier;
@@ -0,0 +1,81 @@
1
+ import "../../security/content-limits.js";
2
+ import { walkObjects } from "../../fabric/walk-objects.js";
3
+ //#region src/export/svg/raster-object.ts
4
+ /**
5
+ * The largest multiplier that keeps a `width × height` area within the
6
+ * canvas limits. A picture over the browser's limit would fail, so a large
7
+ * page is drawn at a lower resolution instead: O(1) arithmetic.
8
+ */
9
+ function safeMultiplier(multiplier, width, height, limits) {
10
+ const maxPixels = limits?.maxCanvasPixels ?? 67108864;
11
+ const maxSide = limits?.maxCanvasSide ?? 16384;
12
+ const byArea = Math.sqrt(maxPixels / Math.max(1, width * height));
13
+ const bySide = maxSide / Math.max(1, width, height);
14
+ return Math.min(multiplier, byArea, bySide);
15
+ }
16
+ function shadowPadding(object) {
17
+ let padding = 0;
18
+ walkObjects([object], (child) => {
19
+ const shadow = child.shadow;
20
+ if (shadow) padding = Math.max(padding, shadow.blur + Math.max(Math.abs(shadow.offsetX), Math.abs(shadow.offsetY)));
21
+ });
22
+ return padding + 2;
23
+ }
24
+ function usesBlendMode(object) {
25
+ let blends = false;
26
+ walkObjects([object], (child) => {
27
+ if (child.globalCompositeOperation && child.globalCompositeOperation !== "source-over") blends = true;
28
+ });
29
+ return blends;
30
+ }
31
+ /**
32
+ * An object as a picture placed where it sits on the canvas. Usually the
33
+ * object alone, so the vectors underneath show through. An object with a
34
+ * blend mode mixes with what lies below it, so its picture includes
35
+ * everything below it within its bounds, which covers those vectors exactly.
36
+ */
37
+ function rasterMarkup(canvas, object, multiplier, limits) {
38
+ object.setCoords();
39
+ const bounds = object.getBoundingRect();
40
+ const padding = shadowPadding(object);
41
+ const left = Math.max(0, Math.floor(bounds.left - padding));
42
+ const top = Math.max(0, Math.floor(bounds.top - padding));
43
+ const right = Math.min(canvas.getWidth(), Math.ceil(bounds.left + bounds.width + padding));
44
+ const bottom = Math.min(canvas.getHeight(), Math.ceil(bounds.top + bounds.height + padding));
45
+ if (right <= left || bottom <= top) return "";
46
+ const blends = usesBlendMode(object);
47
+ const stack = canvas.getObjects();
48
+ const position = stack.indexOf(object);
49
+ const saved = {
50
+ backgroundColor: canvas.backgroundColor,
51
+ backgroundImage: canvas.backgroundImage,
52
+ overlayColor: canvas.overlayColor,
53
+ overlayImage: canvas.overlayImage
54
+ };
55
+ if (!blends) {
56
+ canvas.backgroundColor = "";
57
+ canvas.backgroundImage = void 0;
58
+ }
59
+ canvas.overlayColor = "";
60
+ canvas.overlayImage = void 0;
61
+ let element;
62
+ try {
63
+ const filter = (candidate) => blends ? stack.indexOf(candidate) <= position : candidate === object;
64
+ const safe = safeMultiplier(multiplier, right - left, bottom - top, limits);
65
+ element = canvas.toCanvasElement(safe, {
66
+ left,
67
+ top,
68
+ width: right - left,
69
+ height: bottom - top,
70
+ filter
71
+ });
72
+ } finally {
73
+ Object.assign(canvas, saved);
74
+ }
75
+ const url = element.toDataURL("image/png");
76
+ element.width = 0;
77
+ element.height = 0;
78
+ return `<image x="${left}" y="${top}" width="${right - left}" height="${bottom - top}" preserveAspectRatio="none" xlink:href="${url}" />\n`;
79
+ }
80
+ //#endregion
81
+ export { rasterMarkup, safeMultiplier };
@@ -1,4 +1,4 @@
1
- //#region src/pdf/text-decorations.ts
1
+ //#region src/export/svg/text-decorations.ts
2
2
  const DECORATIONS = [
3
3
  "underline",
4
4
  "overline",
@@ -16,7 +16,7 @@ function isText(object) {
16
16
  }
17
17
  function hasStraightDecorations(object) {
18
18
  if (!isText(object)) return false;
19
- return DECORATIONS.some((type) => object[type] || object.styleHas(type));
19
+ return DECORATIONS.some((type) => object[type] || object.styleHas(type)) || object.styleHas("deltaY");
20
20
  }
21
21
  function rect(x, y, width, height, color) {
22
22
  if (typeof color !== "string" || width <= 0 || height <= 0) return "";
@@ -90,7 +90,7 @@ function decorationRects(text, type) {
90
90
  /** SVG for straight text with its decorations as boxes instead of CSS. */
91
91
  function decoratedTextToSVG(object, reviver) {
92
92
  const text = object;
93
- const inner = text._toSVG().join("").replace(/\stext-decoration="[^"]*"/g, "").replace(/text-decoration:[^;"]*;?\s*/g, "");
93
+ const inner = text._toSVG().join("").replace(/\stext-decoration="[^"]*"/g, "").replace(/text-decoration:[^;"]*;?\s*/g, "").replace(/baseline-shift:[^;"]*;?\s*/g, "");
94
94
  const markup = [
95
95
  decorationRects(text, "underline"),
96
96
  inner,
@@ -1,4 +1,4 @@
1
- //#region src/pdf/text-decorations.ts
1
+ //#region src/export/svg/text-decorations.ts
2
2
  const DECORATIONS = [
3
3
  "underline",
4
4
  "overline",
@@ -16,7 +16,7 @@ function isText(object) {
16
16
  }
17
17
  function hasStraightDecorations(object) {
18
18
  if (!isText(object)) return false;
19
- return DECORATIONS.some((type) => object[type] || object.styleHas(type));
19
+ return DECORATIONS.some((type) => object[type] || object.styleHas(type)) || object.styleHas("deltaY");
20
20
  }
21
21
  function rect(x, y, width, height, color) {
22
22
  if (typeof color !== "string" || width <= 0 || height <= 0) return "";
@@ -90,7 +90,7 @@ function decorationRects(text, type) {
90
90
  /** SVG for straight text with its decorations as boxes instead of CSS. */
91
91
  function decoratedTextToSVG(object, reviver) {
92
92
  const text = object;
93
- const inner = text._toSVG().join("").replace(/\stext-decoration="[^"]*"/g, "").replace(/text-decoration:[^;"]*;?\s*/g, "");
93
+ const inner = text._toSVG().join("").replace(/\stext-decoration="[^"]*"/g, "").replace(/text-decoration:[^;"]*;?\s*/g, "").replace(/baseline-shift:[^;"]*;?\s*/g, "");
94
94
  const markup = [
95
95
  decorationRects(text, "underline"),
96
96
  inner,
@@ -1,5 +1,4 @@
1
1
  const require_walk_objects = require("../../fabric/walk-objects.cjs");
2
- const require_overrides = require("./overrides.cjs");
3
2
  let fabric = require("fabric");
4
3
  //#region src/export/svg/text-on-path.ts
5
4
  function isTextOnPath(object) {
@@ -230,14 +229,6 @@ function textOnPathToSVG(object, reviver) {
230
229
  withShadow: true
231
230
  });
232
231
  }
233
- /**
234
- * Runs `work` while every text on a path on the canvas, including text in
235
- * groups, writes the SVG from this module. Everything else keeps Fabric's
236
- * SVG. Returns how many objects were handled.
237
- */
238
- function withTextOnPathSVG(canvas, work) {
239
- return require_overrides.withSvgOverrides(canvas, (object) => isTextOnPath(object) ? (reviver) => textOnPathToSVG(object, reviver) : void 0, work);
240
- }
241
232
  function findTextOnPath(canvas) {
242
233
  const found = [];
243
234
  require_walk_objects.walkObjects(canvas.getObjects(), (object) => {
@@ -249,4 +240,3 @@ function findTextOnPath(canvas) {
249
240
  exports.findTextOnPath = findTextOnPath;
250
241
  exports.isTextOnPath = isTextOnPath;
251
242
  exports.textOnPathToSVG = textOnPathToSVG;
252
- exports.withTextOnPathSVG = withTextOnPathSVG;
@@ -1,5 +1,4 @@
1
1
  import { walkObjects } from "../../fabric/walk-objects.js";
2
- import { withSvgOverrides } from "./overrides.js";
3
2
  import { Color, util } from "fabric";
4
3
  //#region src/export/svg/text-on-path.ts
5
4
  function isTextOnPath(object) {
@@ -230,14 +229,6 @@ function textOnPathToSVG(object, reviver) {
230
229
  withShadow: true
231
230
  });
232
231
  }
233
- /**
234
- * Runs `work` while every text on a path on the canvas, including text in
235
- * groups, writes the SVG from this module. Everything else keeps Fabric's
236
- * SVG. Returns how many objects were handled.
237
- */
238
- function withTextOnPathSVG(canvas, work) {
239
- return withSvgOverrides(canvas, (object) => isTextOnPath(object) ? (reviver) => textOnPathToSVG(object, reviver) : void 0, work);
240
- }
241
232
  function findTextOnPath(canvas) {
242
233
  const found = [];
243
234
  walkObjects(canvas.getObjects(), (object) => {
@@ -246,4 +237,4 @@ function findTextOnPath(canvas) {
246
237
  return found;
247
238
  }
248
239
  //#endregion
249
- export { findTextOnPath, isTextOnPath, textOnPathToSVG, withTextOnPathSVG };
240
+ export { findTextOnPath, isTextOnPath, textOnPathToSVG };
@@ -1,3 +1,4 @@
1
+ const require_page_state = require("./page-state.cjs");
1
2
  const require_concurrency = require("../util/concurrency.cjs");
2
3
  const require_walk_objects = require("./walk-objects.cjs");
3
4
  let fabric = require("fabric");
@@ -25,7 +26,7 @@ function serializeCanvas(canvas, propertiesToInclude) {
25
26
  keepOriginExplicit(canvas.getObjects(), objects);
26
27
  return {
27
28
  version: serialized.version,
28
- background: serialized.background,
29
+ ...require_page_state.pickPageFields(serialized),
29
30
  objects
30
31
  };
31
32
  }
@@ -73,11 +74,11 @@ function abortError() {
73
74
  * freeze the page. Like Fabric, every object is created before the canvas is
74
75
  * cleared: a failure or cancel leaves the old content in place.
75
76
  */
76
- async function loadIntoCanvas(canvas, content, { signal, chunkSize = 100, onObjects }) {
77
+ async function loadIntoCanvas(canvas, content, { signal, chunkSize = 100, onObjects, beforeSwap }) {
77
78
  const total = content.objects.length;
78
79
  const size = Math.max(1, Math.floor(chunkSize));
79
80
  const created = [];
80
- const background = fabric.util.enlivenObjectEnlivables({ backgroundColor: content.background }, { signal });
81
+ const background = require_page_state.enlivenPage(content, signal);
81
82
  background.catch(() => void 0);
82
83
  try {
83
84
  onObjects?.(0, total);
@@ -93,6 +94,7 @@ async function loadIntoCanvas(canvas, content, { signal, chunkSize = 100, onObje
93
94
  }
94
95
  const enlivenedBackground = await background;
95
96
  if (signal.aborted) throw abortError();
97
+ beforeSwap?.();
96
98
  const renderOnAddRemove = canvas.renderOnAddRemove;
97
99
  canvas.renderOnAddRemove = false;
98
100
  try {
@@ -1,3 +1,4 @@
1
+ import { enlivenPage, pickPageFields } from "./page-state.js";
1
2
  import { yieldToEventLoop } from "../util/concurrency.js";
2
3
  import { childrenOf, walkObjects } from "./walk-objects.js";
3
4
  import { util } from "fabric";
@@ -25,7 +26,7 @@ function serializeCanvas(canvas, propertiesToInclude) {
25
26
  keepOriginExplicit(canvas.getObjects(), objects);
26
27
  return {
27
28
  version: serialized.version,
28
- background: serialized.background,
29
+ ...pickPageFields(serialized),
29
30
  objects
30
31
  };
31
32
  }
@@ -73,11 +74,11 @@ function abortError() {
73
74
  * freeze the page. Like Fabric, every object is created before the canvas is
74
75
  * cleared: a failure or cancel leaves the old content in place.
75
76
  */
76
- async function loadIntoCanvas(canvas, content, { signal, chunkSize = 100, onObjects }) {
77
+ async function loadIntoCanvas(canvas, content, { signal, chunkSize = 100, onObjects, beforeSwap }) {
77
78
  const total = content.objects.length;
78
79
  const size = Math.max(1, Math.floor(chunkSize));
79
80
  const created = [];
80
- const background = util.enlivenObjectEnlivables({ backgroundColor: content.background }, { signal });
81
+ const background = enlivenPage(content, signal);
81
82
  background.catch(() => void 0);
82
83
  try {
83
84
  onObjects?.(0, total);
@@ -93,6 +94,7 @@ async function loadIntoCanvas(canvas, content, { signal, chunkSize = 100, onObje
93
94
  }
94
95
  const enlivenedBackground = await background;
95
96
  if (signal.aborted) throw abortError();
97
+ beforeSwap?.();
96
98
  const renderOnAddRemove = canvas.renderOnAddRemove;
97
99
  canvas.renderOnAddRemove = false;
98
100
  try {
@@ -2,7 +2,13 @@ const require_errors = require("../engine/errors.cjs");
2
2
  let fabric = require("fabric");
3
3
  //#region src/fabric/object-registry.ts
4
4
  function createObjectRegistry(initialDefinitions = []) {
5
- const extraProperties = /* @__PURE__ */ new Set(["id", "name"]);
5
+ const extraProperties = /* @__PURE__ */ new Set([
6
+ "id",
7
+ "name",
8
+ "svgId",
9
+ "svgClass",
10
+ "svgData"
11
+ ]);
6
12
  function register(definition) {
7
13
  const { fabricClass } = definition;
8
14
  if (typeof fabricClass?.type !== "string" || fabricClass.type.length === 0) throw new require_errors.DocumentEngineError("INVALID_CUSTOM_OBJECT", "A custom object class needs a static \"type\" string, for example: static type = \"Sticker\"");
@@ -2,7 +2,13 @@ import { DocumentEngineError } from "../engine/errors.js";
2
2
  import { classRegistry } from "fabric";
3
3
  //#region src/fabric/object-registry.ts
4
4
  function createObjectRegistry(initialDefinitions = []) {
5
- const extraProperties = /* @__PURE__ */ new Set(["id", "name"]);
5
+ const extraProperties = /* @__PURE__ */ new Set([
6
+ "id",
7
+ "name",
8
+ "svgId",
9
+ "svgClass",
10
+ "svgData"
11
+ ]);
6
12
  function register(definition) {
7
13
  const { fabricClass } = definition;
8
14
  if (typeof fabricClass?.type !== "string" || fabricClass.type.length === 0) throw new DocumentEngineError("INVALID_CUSTOM_OBJECT", "A custom object class needs a static \"type\" string, for example: static type = \"Sticker\"");
@@ -0,0 +1,69 @@
1
+ let fabric = require("fabric");
2
+ //#region src/fabric/page-state.ts
3
+ /** The page fields saved with a document, besides its size. */
4
+ const PAGE_FIELDS = [
5
+ "background",
6
+ "backgroundImage",
7
+ "overlay",
8
+ "overlayImage",
9
+ "clipPath"
10
+ ];
11
+ /** Copies the page fields that are set from serialized canvas output. */
12
+ function pickPageFields(source) {
13
+ const picked = {};
14
+ for (const field of PAGE_FIELDS) if (source[field] !== void 0 && source[field] !== null) picked[field] = source[field];
15
+ return picked;
16
+ }
17
+ /** The canvas-level objects that can hold images or other objects: background and overlay images and the mask. */
18
+ function pageObjects(page) {
19
+ const objects = [];
20
+ for (const field of [
21
+ "backgroundImage",
22
+ "overlayImage",
23
+ "clipPath"
24
+ ]) {
25
+ const value = page[field];
26
+ if (typeof value === "object" && value !== null && typeof value.type === "string") objects.push(value);
27
+ }
28
+ return objects;
29
+ }
30
+ /**
31
+ * Creates the live values for `canvas.set`: Fabric objects for the images
32
+ * and mask, gradients and patterns for colors. Missing fields become
33
+ * `undefined`, which clears them on the canvas.
34
+ */
35
+ function enlivenPage(page, signal) {
36
+ return fabric.util.enlivenObjectEnlivables({
37
+ backgroundColor: page.background,
38
+ backgroundImage: page.backgroundImage,
39
+ overlayColor: page.overlay,
40
+ overlayImage: page.overlayImage,
41
+ clipPath: page.clipPath
42
+ }, signal ? { signal } : {});
43
+ }
44
+ /** Applies a page state: the size first, then the enlivened fields. */
45
+ async function applyPage(canvas, page) {
46
+ const live = await enlivenPage(page);
47
+ if (canvas.getWidth() !== page.width || canvas.getHeight() !== page.height) canvas.setDimensions({
48
+ width: page.width,
49
+ height: page.height
50
+ });
51
+ canvas.set(live);
52
+ canvas.requestRenderAll();
53
+ }
54
+ /**
55
+ * Every top-level object a document holds: the objects on the page, then its
56
+ * background image, overlay image and mask. Asset checks, address checks and
57
+ * type checks walk this list, so they cover the page as well.
58
+ */
59
+ function documentObjects(document) {
60
+ const page = document.canvas ? pageObjects(document.canvas) : [];
61
+ return page.length === 0 ? [...document.objects] : [...document.objects, ...page];
62
+ }
63
+ //#endregion
64
+ exports.PAGE_FIELDS = PAGE_FIELDS;
65
+ exports.applyPage = applyPage;
66
+ exports.documentObjects = documentObjects;
67
+ exports.enlivenPage = enlivenPage;
68
+ exports.pageObjects = pageObjects;
69
+ exports.pickPageFields = pickPageFields;
@@ -0,0 +1,64 @@
1
+ import { util } from "fabric";
2
+ //#region src/fabric/page-state.ts
3
+ /** The page fields saved with a document, besides its size. */
4
+ const PAGE_FIELDS = [
5
+ "background",
6
+ "backgroundImage",
7
+ "overlay",
8
+ "overlayImage",
9
+ "clipPath"
10
+ ];
11
+ /** Copies the page fields that are set from serialized canvas output. */
12
+ function pickPageFields(source) {
13
+ const picked = {};
14
+ for (const field of PAGE_FIELDS) if (source[field] !== void 0 && source[field] !== null) picked[field] = source[field];
15
+ return picked;
16
+ }
17
+ /** The canvas-level objects that can hold images or other objects: background and overlay images and the mask. */
18
+ function pageObjects(page) {
19
+ const objects = [];
20
+ for (const field of [
21
+ "backgroundImage",
22
+ "overlayImage",
23
+ "clipPath"
24
+ ]) {
25
+ const value = page[field];
26
+ if (typeof value === "object" && value !== null && typeof value.type === "string") objects.push(value);
27
+ }
28
+ return objects;
29
+ }
30
+ /**
31
+ * Creates the live values for `canvas.set`: Fabric objects for the images
32
+ * and mask, gradients and patterns for colors. Missing fields become
33
+ * `undefined`, which clears them on the canvas.
34
+ */
35
+ function enlivenPage(page, signal) {
36
+ return util.enlivenObjectEnlivables({
37
+ backgroundColor: page.background,
38
+ backgroundImage: page.backgroundImage,
39
+ overlayColor: page.overlay,
40
+ overlayImage: page.overlayImage,
41
+ clipPath: page.clipPath
42
+ }, signal ? { signal } : {});
43
+ }
44
+ /** Applies a page state: the size first, then the enlivened fields. */
45
+ async function applyPage(canvas, page) {
46
+ const live = await enlivenPage(page);
47
+ if (canvas.getWidth() !== page.width || canvas.getHeight() !== page.height) canvas.setDimensions({
48
+ width: page.width,
49
+ height: page.height
50
+ });
51
+ canvas.set(live);
52
+ canvas.requestRenderAll();
53
+ }
54
+ /**
55
+ * Every top-level object a document holds: the objects on the page, then its
56
+ * background image, overlay image and mask. Asset checks, address checks and
57
+ * type checks walk this list, so they cover the page as well.
58
+ */
59
+ function documentObjects(document) {
60
+ const page = document.canvas ? pageObjects(document.canvas) : [];
61
+ return page.length === 0 ? [...document.objects] : [...document.objects, ...page];
62
+ }
63
+ //#endregion
64
+ export { PAGE_FIELDS, applyPage, documentObjects, enlivenPage, pageObjects, pickPageFields };