fabricjs-document-engine 1.1.0 → 1.2.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 (70) hide show
  1. package/README.md +39 -1
  2. package/README.zh-CN.md +39 -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/document/document-format.d.cts +15 -0
  14. package/dist/document/document-format.d.ts +15 -0
  15. package/dist/document/validate-document.cjs +8 -0
  16. package/dist/document/validate-document.js +8 -0
  17. package/dist/engine/create-document-engine.cjs +115 -21
  18. package/dist/engine/create-document-engine.d.cts +24 -6
  19. package/dist/engine/create-document-engine.d.ts +24 -6
  20. package/dist/engine/create-document-engine.js +116 -22
  21. package/dist/engine/errors.d.cts +1 -1
  22. package/dist/engine/errors.d.ts +1 -1
  23. package/dist/export/export-options.cjs +3 -0
  24. package/dist/export/export-options.d.cts +6 -0
  25. package/dist/export/export-options.d.ts +6 -0
  26. package/dist/export/export-options.js +3 -0
  27. package/dist/export/preflight-export.d.cts +1 -1
  28. package/dist/export/preflight-export.d.ts +1 -1
  29. package/dist/export/render-export.cjs +42 -4
  30. package/dist/export/render-export.js +43 -5
  31. package/dist/export/svg/clip-masks.cjs +39 -0
  32. package/dist/export/svg/clip-masks.js +37 -0
  33. package/dist/export/svg/embed-assets.cjs +1 -0
  34. package/dist/export/svg/embed-assets.js +1 -0
  35. package/dist/export/svg/raster-object.cjs +82 -0
  36. package/dist/export/svg/raster-object.js +81 -0
  37. package/dist/{pdf → export/svg}/text-decorations.cjs +3 -3
  38. package/dist/{pdf → export/svg}/text-decorations.js +3 -3
  39. package/dist/export/svg/text-on-path.cjs +0 -10
  40. package/dist/export/svg/text-on-path.js +1 -10
  41. package/dist/fabric/fabric-adapter.cjs +5 -3
  42. package/dist/fabric/fabric-adapter.js +5 -3
  43. package/dist/fabric/page-state.cjs +69 -0
  44. package/dist/fabric/page-state.js +64 -0
  45. package/dist/history/apply-state.cjs +19 -2
  46. package/dist/history/apply-state.js +19 -2
  47. package/dist/history/create-history.cjs +53 -13
  48. package/dist/history/create-history.d.cts +8 -0
  49. package/dist/history/create-history.d.ts +8 -0
  50. package/dist/history/create-history.js +53 -13
  51. package/dist/history/snapshot.cjs +10 -1
  52. package/dist/history/snapshot.js +10 -2
  53. package/dist/index.d.cts +3 -3
  54. package/dist/index.d.ts +3 -3
  55. package/dist/migrations/migrate-document.cjs +2 -1
  56. package/dist/migrations/migrate-document.js +2 -1
  57. package/dist/pdf/export-pdf.cjs +8 -67
  58. package/dist/pdf/export-pdf.js +8 -67
  59. package/dist/recovery/recovery-controller.cjs +148 -29
  60. package/dist/recovery/recovery-controller.d.cts +8 -0
  61. package/dist/recovery/recovery-controller.d.ts +8 -0
  62. package/dist/recovery/recovery-controller.js +148 -29
  63. package/dist/security/content-limits.cjs +43 -3
  64. package/dist/security/content-limits.d.cts +8 -0
  65. package/dist/security/content-limits.d.ts +8 -0
  66. package/dist/security/content-limits.js +39 -4
  67. package/dist/util/concurrency.cjs +16 -3
  68. package/dist/util/concurrency.js +16 -3
  69. package/package.json +1 -1
  70. package/schema/document-v1.schema.json +5 -1
@@ -1,4 +1,5 @@
1
1
  const require_asset_manifest = require("../assets/asset-manifest.cjs");
2
+ const require_page_state = require("../fabric/page-state.cjs");
2
3
  const require_errors = require("./errors.cjs");
3
4
  const require_content_limits = require("../security/content-limits.cjs");
4
5
  const require_image_check = require("../assets/image-check.cjs");
@@ -38,11 +39,21 @@ function createDocumentEngine(options) {
38
39
  const registry = require_object_registry.createObjectRegistry(options.customObjects);
39
40
  const events = require_event_emitter.createEventEmitter();
40
41
  const objectsById = /* @__PURE__ */ new Map();
41
- const assetOptions = options.assets ?? {};
42
+ const assetOptions = {
43
+ ...options.assets,
44
+ maxImagePixels: options.limits?.maxImagePixels ?? 67108864
45
+ };
42
46
  const uploadedUrls = /* @__PURE__ */ new Map();
43
47
  let documentInfo = require_create_document.createDocumentInfo(options.document);
44
48
  let activeLoad = null;
45
49
  let destroyed = false;
50
+ let mutations = 0;
51
+ let session = 0;
52
+ /** Throws when async work started under `startedIn` would now change another document. */
53
+ function ensureSameSession(startedIn, what) {
54
+ ensureUsable();
55
+ if (session !== startedIn) throw new require_errors.DocumentEngineError("DOCUMENT_CHANGED", `${what} was dropped because another document was opened while it ran`);
56
+ }
46
57
  function ensureUsable() {
47
58
  if (destroyed) throw new require_errors.DocumentEngineError("ENGINE_DESTROYED", "This document engine was destroyed");
48
59
  }
@@ -67,21 +78,41 @@ function createDocumentEngine(options) {
67
78
  objectsById.clear();
68
79
  canvas.getObjects().forEach(indexObjectTree);
69
80
  }
70
- const handleObjectAdded = ({ target }) => indexObjectTree(target);
71
- const handleObjectRemoved = ({ target }) => removeObjectTreeFromIndex(target);
81
+ const handleObjectAdded = ({ target }) => {
82
+ mutations += 1;
83
+ indexObjectTree(target);
84
+ };
85
+ const handleObjectRemoved = ({ target }) => {
86
+ mutations += 1;
87
+ removeObjectTreeFromIndex(target);
88
+ };
89
+ const countMutation = () => {
90
+ mutations += 1;
91
+ };
72
92
  canvas.on("object:added", handleObjectAdded);
73
93
  canvas.on("object:removed", handleObjectRemoved);
94
+ canvas.on("object:modified", countMutation);
95
+ canvas.on("text:changed", countMutation);
74
96
  rebuildIndex();
75
97
  const history = require_create_history.createHistory({
76
98
  canvas,
77
99
  limit: options.history?.limit,
78
100
  maxBytes: options.history?.maxBytes,
79
- serializeObjects: () => {
101
+ serializeState: () => {
80
102
  rebuildIndex();
81
- return require_fabric_adapter.serializeCanvas(canvas, registry.propertiesToInclude()).objects;
103
+ const serialized = require_fabric_adapter.serializeCanvas(canvas, registry.propertiesToInclude());
104
+ return {
105
+ objects: serialized.objects,
106
+ page: {
107
+ width: canvas.getWidth(),
108
+ height: canvas.getHeight(),
109
+ ...require_page_state.pickPageFields(serialized)
110
+ }
111
+ };
82
112
  },
83
113
  onChange: (state) => events.emit("history:change", state),
84
- onContentChange: () => noteContentChange()
114
+ onContentChange: () => noteContentChange(),
115
+ onLiveChange: () => noteContentChange()
85
116
  });
86
117
  const saving = require_save_controller.createSaveController({
87
118
  getStorage: () => requireStorage(),
@@ -116,6 +147,7 @@ function createDocumentEngine(options) {
116
147
  }
117
148
  }) : void 0;
118
149
  function noteContentChange() {
150
+ mutations += 1;
119
151
  saving.noteContentChange();
120
152
  recovery?.schedule();
121
153
  }
@@ -138,11 +170,14 @@ function createDocumentEngine(options) {
138
170
  height: canvas.getHeight()
139
171
  },
140
172
  objects: serialized.objects,
141
- assets: require_asset_manifest.buildAssetManifest(serialized.objects),
173
+ assets: require_asset_manifest.buildAssetManifest(require_page_state.documentObjects({
174
+ objects: serialized.objects,
175
+ canvas: serialized
176
+ })),
142
177
  metadata: { ...documentInfo.metadata }
143
178
  };
144
179
  if (serialized.version !== void 0) document.fabricVersion = serialized.version;
145
- if (serialized.background !== void 0) document.canvas.background = serialized.background;
180
+ Object.assign(document.canvas, require_page_state.pickPageFields(serialized));
146
181
  return document;
147
182
  }
148
183
  function toLoadError(error, controller) {
@@ -161,7 +196,7 @@ function createDocumentEngine(options) {
161
196
  throw new require_errors.DocumentEngineError(issues[0].code, `The document is not valid: ${summary}`, { issues });
162
197
  }
163
198
  const document = input;
164
- const unknownTypes = registry.findUnknownTypes(require_walk_objects.collectSerializedTypes(document.objects));
199
+ const unknownTypes = registry.findUnknownTypes(require_walk_objects.collectSerializedTypes(require_page_state.documentObjects(document)));
165
200
  if (unknownTypes.length > 0) throw new require_errors.DocumentEngineError("UNKNOWN_OBJECT_TYPE", `The document uses object types that are not registered: ${unknownTypes.join(", ")}. Register them with customObjects or engine.registerObject before loading.`, { unknownTypes });
166
201
  return document;
167
202
  }
@@ -199,6 +234,7 @@ function createDocumentEngine(options) {
199
234
  ensureUsable();
200
235
  protectUnsavedChanges(loadOptions.discardUnsavedChanges);
201
236
  const controller = startedLoad ?? startLoad(loadOptions.signal);
237
+ const mutationsAtStart = mutations;
202
238
  const documentId = describeDocumentId(input);
203
239
  const report = (stage, done, total) => {
204
240
  if (controller.signal.aborted) return;
@@ -228,11 +264,15 @@ function createDocumentEngine(options) {
228
264
  if (controller.signal.aborted) throw new Error("aborted");
229
265
  await history.withoutRecording(() => require_fabric_adapter.loadIntoCanvas(canvas, {
230
266
  version: document.fabricVersion,
231
- background: document.canvas.background,
267
+ ...require_page_state.pickPageFields(document.canvas),
232
268
  objects: document.objects
233
269
  }, {
234
270
  signal: controller.signal,
235
- onObjects: (done, total) => report("objects", done, total)
271
+ onObjects: (done, total) => report("objects", done, total),
272
+ beforeSwap: () => {
273
+ if (mutations !== mutationsAtStart && !loadOptions.discardUnsavedChanges) throw new require_errors.DocumentEngineError("LOAD_CONFLICT", "The canvas was edited while the document loaded, so the load was stopped to keep the edits. Pass { discardUnsavedChanges: true } to replace them.");
274
+ session += 1;
275
+ }
236
276
  }));
237
277
  if (controller.signal.aborted) throw new Error("aborted");
238
278
  if (loadOptions.restoreCanvasSize ?? true) canvas.setDimensions({
@@ -285,10 +325,37 @@ function createDocumentEngine(options) {
285
325
  metadata
286
326
  });
287
327
  }
328
+ function setPage(changes, label = "Change page") {
329
+ ensureUsable();
330
+ const problem = require_content_limits.canvasSizeProblem(changes.width ?? canvas.getWidth(), changes.height ?? canvas.getHeight(), options.limits);
331
+ if (problem) throw new require_errors.DocumentEngineError("UNSAFE_DOCUMENT", `The page was not resized: it would be ${problem}`);
332
+ history.transaction(label, () => {
333
+ const width = changes.width ?? canvas.getWidth();
334
+ const height = changes.height ?? canvas.getHeight();
335
+ if (width !== canvas.getWidth() || height !== canvas.getHeight()) canvas.setDimensions({
336
+ width,
337
+ height
338
+ });
339
+ const live = {};
340
+ if (changes.background !== void 0) live.backgroundColor = changes.background ?? "";
341
+ if (changes.backgroundImage !== void 0) live.backgroundImage = changes.backgroundImage ?? void 0;
342
+ if (changes.overlay !== void 0) live.overlayColor = changes.overlay ?? "";
343
+ if (changes.overlayImage !== void 0) live.overlayImage = changes.overlayImage ?? void 0;
344
+ if (changes.clipPath !== void 0) live.clipPath = changes.clipPath ?? void 0;
345
+ canvas.set(live);
346
+ });
347
+ canvas.requestRenderAll();
348
+ }
288
349
  async function importSvg(svg, importOptions = {}) {
289
350
  ensureUsable();
351
+ const startedIn = session;
290
352
  const result = await require_svg_import.readSvg(svg, importOptions, options.limits);
291
- ensureUsable();
353
+ try {
354
+ ensureSameSession(startedIn, "The SVG import");
355
+ } catch (error) {
356
+ result.objects.forEach((object) => object.dispose?.());
357
+ throw error;
358
+ }
292
359
  if (result.objects.length > 0) {
293
360
  history.transaction("Import SVG", () => canvas.add(...result.objects));
294
361
  canvas.requestRenderAll();
@@ -408,6 +475,7 @@ function createDocumentEngine(options) {
408
475
  protectUnsavedChanges(newOptions.discardUnsavedChanges);
409
476
  activeLoad?.abort();
410
477
  history.withoutRecording(() => canvas.clear());
478
+ session += 1;
411
479
  objectsById.clear();
412
480
  documentInfo = require_create_document.createDocumentInfo(newOptions);
413
481
  uploadedUrls.clear();
@@ -434,10 +502,12 @@ function createDocumentEngine(options) {
434
502
  if (isReplaceableImage(object) && require_image_check.isSameUrl(object.getSrc(), oldUrl)) images.push(object);
435
503
  });
436
504
  if (images.length === 0) return 0;
505
+ const startedIn = session;
437
506
  await history.transaction("Replace image", () => Promise.all(images.map(async (image) => {
438
507
  const displayedWidth = image.width * image.scaleX;
439
508
  const displayedHeight = image.height * image.scaleY;
440
509
  await image.setSrc(newUrl, { crossOrigin: image.crossOrigin ?? null });
510
+ ensureSameSession(startedIn, "Replacing the image");
441
511
  image.set({
442
512
  scaleX: displayedWidth / image.width,
443
513
  scaleY: displayedHeight / image.height
@@ -486,14 +556,31 @@ function createDocumentEngine(options) {
486
556
  const warnings = [...check.warnings];
487
557
  const rendered = require_render_export.withExportView(canvas, settings.background, format, () => {
488
558
  const area = require_export_area.resolveExportArea(canvas, settings.area, settings.padding);
559
+ if (format !== "svg") {
560
+ const problem = require_content_limits.canvasSizeProblem(area.width * settings.scale, area.height * settings.scale, options.limits);
561
+ if (problem) {
562
+ const message = `The export would be ${problem}. Lower the scale or export a smaller area.`;
563
+ throw new require_errors.DocumentEngineError("EXPORT_BLOCKED", message, { problems: [{
564
+ code: "TOO_LARGE",
565
+ message,
566
+ objectIds: []
567
+ }] });
568
+ }
569
+ }
489
570
  return {
490
571
  area,
491
- content: format === "svg" ? Promise.resolve(require_render_export.renderSvg(canvas, area, settings.scale, settings.svg)) : require_render_export.renderRaster(canvas, area, format, settings.scale, settings.quality)
572
+ content: format === "svg" ? Promise.resolve(require_render_export.renderSvg(canvas, area, settings.scale, settings.svg, options.limits)) : require_render_export.renderRaster(canvas, area, format, settings.scale, settings.quality)
492
573
  };
493
574
  });
494
575
  let blob;
495
576
  if (format === "svg") {
496
- const embedded = await require_embed_assets.embedSvgAssets(await rendered.content, canvas, settings.svg, settings.signal, options.limits?.isAllowedUrl ?? require_content_limits.isSafeImageUrl);
577
+ const { svg, rasterizedObjectIds } = await rendered.content;
578
+ if (rasterizedObjectIds.length > 0) warnings.push({
579
+ code: "CLIP_PATH_RASTERIZED",
580
+ message: "Objects whose clip path has a clip path of its own were drawn as pictures: Fabric cannot write nested clip paths as SVG.",
581
+ objectIds: rasterizedObjectIds
582
+ });
583
+ const embedded = await require_embed_assets.embedSvgAssets(svg, canvas, settings.svg, settings.signal, options.limits?.isAllowedUrl ?? require_content_limits.isSafeImageUrl);
497
584
  stopIfCancelled();
498
585
  if (settings.svg.embedImages === "require" && embedded.problems.length > 0) {
499
586
  const problems = embedded.warnings.filter((warning) => warning.code === "IMAGE_NOT_EMBEDDED").map((warning) => ({
@@ -555,12 +642,14 @@ function createDocumentEngine(options) {
555
642
  if (!recovery) throw new require_errors.DocumentEngineError("RECOVERY_MISSING", "Pass recovery: { store } to createDocumentEngine to use recovery");
556
643
  return recovery;
557
644
  }
558
- async function restoreRecovery(documentId, loadOptions) {
645
+ async function restoreRecovery(documentId, restoreOptions = {}) {
559
646
  const source = requireRecovery();
560
647
  const id = documentId ?? documentInfo.id;
561
- const record = await source.read(id);
648
+ const { sessionId: branch, ...loadOptions } = restoreOptions;
649
+ const record = await source.read(id, branch);
562
650
  if (!record) throw new require_errors.DocumentEngineError("RECOVERY_NOT_FOUND", `There is no recovery copy for document "${id}"`);
563
651
  const loaded = await loadDocument(await require_recovery_controller.restoreRecordedFiles(record), loadOptions);
652
+ source.adopt(loaded.id, record.sessionId);
564
653
  noteContentChange();
565
654
  events.emit("recovery:restored", { document: loaded });
566
655
  return loaded;
@@ -581,12 +670,15 @@ function createDocumentEngine(options) {
581
670
  const { disposed } = canvas;
582
671
  if (recovery && saving.state().isDirty && !disposed) recovery.writeNow();
583
672
  destroyed = true;
673
+ session += 1;
584
674
  activeLoad?.abort();
585
675
  history.destroy();
586
676
  saving.destroy();
587
677
  recovery?.destroy();
588
678
  canvas.off("object:added", handleObjectAdded);
589
679
  canvas.off("object:removed", handleObjectRemoved);
680
+ canvas.off("object:modified", countMutation);
681
+ canvas.off("text:changed", countMutation);
590
682
  objectsById.clear();
591
683
  events.clear();
592
684
  }
@@ -594,7 +686,8 @@ function createDocumentEngine(options) {
594
686
  canvas,
595
687
  getDocumentInfo: () => ({
596
688
  ...documentInfo,
597
- metadata: { ...documentInfo.metadata }
689
+ metadata: { ...documentInfo.metadata },
690
+ session
598
691
  }),
599
692
  updateMetadata(changes) {
600
693
  ensureUsable();
@@ -635,14 +728,15 @@ function createDocumentEngine(options) {
635
728
  preflightExport: checkBeforeExport,
636
729
  flushRecovery: async () => requireRecovery().flush(),
637
730
  getRecoverableDocuments: async () => requireRecovery().list(),
638
- getRecovery: async (documentId) => requireRecovery().read(documentId ?? documentInfo.id),
731
+ getRecovery: async (documentId, sessionId) => requireRecovery().read(documentId ?? documentInfo.id, sessionId),
639
732
  restoreRecovery,
640
- discardRecovery: async (documentId) => requireRecovery().remove(documentId ?? documentInfo.id),
733
+ discardRecovery: async (documentId, sessionId) => requireRecovery().discard(documentId ?? documentInfo.id, sessionId),
641
734
  getInterruptedLoad: async () => requireRecovery().interruptedLoad(),
642
- transaction(label, work) {
735
+ transaction(label, work, transactionOptions) {
643
736
  ensureUsable();
644
- return history.transaction(label, work);
737
+ return history.transaction(label, work, transactionOptions);
645
738
  },
739
+ setPage,
646
740
  commit(label = "Edit") {
647
741
  ensureUsable();
648
742
  return history.commit(label);
@@ -7,7 +7,7 @@ import { ContentLimits } from "../security/content-limits.cjs";
7
7
  import { SvgImportOptions, SvgImportResult } from "../import/svg-import.cjs";
8
8
  import { NewDocumentOptions } from "../document/create-document.cjs";
9
9
  import { CustomObjectDefinition } from "../fabric/object-registry.cjs";
10
- import { HistoryOptions, HistoryState } from "../history/create-history.cjs";
10
+ import { HistoryOptions, HistoryState, TransactionOptions } from "../history/create-history.cjs";
11
11
  import { VersionOptions, VersionSummary } from "../versions/document-version.cjs";
12
12
  import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "../recovery/recovery-controller.cjs";
13
13
  import { AutosaveOptions } from "../save/autosave-scheduler.cjs";
@@ -16,7 +16,7 @@ import { DocumentEngineError } from "./errors.cjs";
16
16
  import { DocumentStorage } from "../storage/storage-contract.cjs";
17
17
  import { SaveOptions, SaveRetryEvent, SaveState } from "../save/save-controller.cjs";
18
18
  import { Unsubscribe } from "./event-emitter.cjs";
19
- import { FabricObject, StaticCanvas } from "fabric";
19
+ import { FabricObject, StaticCanvas, TFiller } from "fabric";
20
20
  //#region src/engine/create-document-engine.d.ts
21
21
  export interface DocumentEngineOptions {
22
22
  canvas: StaticCanvas;
@@ -51,6 +51,23 @@ export interface LoadProgress {
51
51
  done: number;
52
52
  total: number;
53
53
  }
54
+ /**
55
+ * Page settings for `setPage`. Images and the mask are live Fabric objects;
56
+ * `null` removes one.
57
+ */
58
+ export interface PageChanges {
59
+ width?: number;
60
+ height?: number;
61
+ background?: string | TFiller | null;
62
+ backgroundImage?: FabricObject | null;
63
+ overlay?: string | TFiller | null;
64
+ overlayImage?: FabricObject | null;
65
+ clipPath?: FabricObject | null;
66
+ }
67
+ export interface RestoreRecoveryOptions extends LoadOptions {
68
+ /** Restore the copy one session wrote. By default the newest copy of the document is used. */
69
+ sessionId?: string;
70
+ }
54
71
  export interface NewDocumentRequest extends NewDocumentOptions {
55
72
  discardUnsavedChanges?: boolean;
56
73
  }
@@ -147,7 +164,8 @@ export interface DocumentEngine {
147
164
  isDirty(): boolean;
148
165
  getSaveState(): SaveState;
149
166
  registerObject(definition: CustomObjectDefinition): void;
150
- transaction<Result>(label: string, work: () => Result): Result;
167
+ transaction<Result>(label: string, work: () => Result, options?: TransactionOptions): Result;
168
+ setPage(changes: PageChanges, label?: string): void;
151
169
  commit(label?: string): boolean;
152
170
  undo(): Promise<boolean>;
153
171
  redo(): Promise<boolean>;
@@ -166,9 +184,9 @@ export interface DocumentEngine {
166
184
  preflightExport(options: ExportOptions): Promise<ExportPreflight>;
167
185
  flushRecovery(): Promise<void>;
168
186
  getRecoverableDocuments(): Promise<RecoveryRecord[]>;
169
- getRecovery(documentId?: string): Promise<RecoveryRecord | undefined>;
170
- restoreRecovery(documentId?: string, options?: LoadOptions): Promise<FabricDocument>;
171
- discardRecovery(documentId?: string): Promise<void>;
187
+ getRecovery(documentId?: string, sessionId?: string): Promise<RecoveryRecord | undefined>;
188
+ restoreRecovery(documentId?: string, options?: RestoreRecoveryOptions): Promise<FabricDocument>;
189
+ discardRecovery(documentId?: string, sessionId?: string): Promise<void>;
172
190
  getInterruptedLoad(): Promise<InterruptedLoad | undefined>;
173
191
  on<Name extends keyof DocumentEngineEvents>(name: Name, handler: (payload: DocumentEngineEvents[Name]) => void): Unsubscribe;
174
192
  destroy(): void;
@@ -7,7 +7,7 @@ import { ContentLimits } from "../security/content-limits.js";
7
7
  import { SvgImportOptions, SvgImportResult } from "../import/svg-import.js";
8
8
  import { NewDocumentOptions } from "../document/create-document.js";
9
9
  import { CustomObjectDefinition } from "../fabric/object-registry.js";
10
- import { HistoryOptions, HistoryState } from "../history/create-history.js";
10
+ import { HistoryOptions, HistoryState, TransactionOptions } from "../history/create-history.js";
11
11
  import { VersionOptions, VersionSummary } from "../versions/document-version.js";
12
12
  import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "../recovery/recovery-controller.js";
13
13
  import { AutosaveOptions } from "../save/autosave-scheduler.js";
@@ -16,7 +16,7 @@ import { DocumentEngineError } from "./errors.js";
16
16
  import { DocumentStorage } from "../storage/storage-contract.js";
17
17
  import { SaveOptions, SaveRetryEvent, SaveState } from "../save/save-controller.js";
18
18
  import { Unsubscribe } from "./event-emitter.js";
19
- import { FabricObject, StaticCanvas } from "fabric";
19
+ import { FabricObject, StaticCanvas, TFiller } from "fabric";
20
20
  //#region src/engine/create-document-engine.d.ts
21
21
  export interface DocumentEngineOptions {
22
22
  canvas: StaticCanvas;
@@ -51,6 +51,23 @@ export interface LoadProgress {
51
51
  done: number;
52
52
  total: number;
53
53
  }
54
+ /**
55
+ * Page settings for `setPage`. Images and the mask are live Fabric objects;
56
+ * `null` removes one.
57
+ */
58
+ export interface PageChanges {
59
+ width?: number;
60
+ height?: number;
61
+ background?: string | TFiller | null;
62
+ backgroundImage?: FabricObject | null;
63
+ overlay?: string | TFiller | null;
64
+ overlayImage?: FabricObject | null;
65
+ clipPath?: FabricObject | null;
66
+ }
67
+ export interface RestoreRecoveryOptions extends LoadOptions {
68
+ /** Restore the copy one session wrote. By default the newest copy of the document is used. */
69
+ sessionId?: string;
70
+ }
54
71
  export interface NewDocumentRequest extends NewDocumentOptions {
55
72
  discardUnsavedChanges?: boolean;
56
73
  }
@@ -147,7 +164,8 @@ export interface DocumentEngine {
147
164
  isDirty(): boolean;
148
165
  getSaveState(): SaveState;
149
166
  registerObject(definition: CustomObjectDefinition): void;
150
- transaction<Result>(label: string, work: () => Result): Result;
167
+ transaction<Result>(label: string, work: () => Result, options?: TransactionOptions): Result;
168
+ setPage(changes: PageChanges, label?: string): void;
151
169
  commit(label?: string): boolean;
152
170
  undo(): Promise<boolean>;
153
171
  redo(): Promise<boolean>;
@@ -166,9 +184,9 @@ export interface DocumentEngine {
166
184
  preflightExport(options: ExportOptions): Promise<ExportPreflight>;
167
185
  flushRecovery(): Promise<void>;
168
186
  getRecoverableDocuments(): Promise<RecoveryRecord[]>;
169
- getRecovery(documentId?: string): Promise<RecoveryRecord | undefined>;
170
- restoreRecovery(documentId?: string, options?: LoadOptions): Promise<FabricDocument>;
171
- discardRecovery(documentId?: string): Promise<void>;
187
+ getRecovery(documentId?: string, sessionId?: string): Promise<RecoveryRecord | undefined>;
188
+ restoreRecovery(documentId?: string, options?: RestoreRecoveryOptions): Promise<FabricDocument>;
189
+ discardRecovery(documentId?: string, sessionId?: string): Promise<void>;
172
190
  getInterruptedLoad(): Promise<InterruptedLoad | undefined>;
173
191
  on<Name extends keyof DocumentEngineEvents>(name: Name, handler: (payload: DocumentEngineEvents[Name]) => void): Unsubscribe;
174
192
  destroy(): void;