fabricjs-document-engine 1.0.2 → 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.
- package/README.md +220 -11
- package/README.zh-CN.md +220 -11
- package/dist/assets/asset-manifest.d.cts +3 -0
- package/dist/assets/asset-manifest.d.ts +3 -0
- package/dist/assets/asset-pipeline.cjs +31 -17
- package/dist/assets/asset-pipeline.d.cts +5 -1
- package/dist/assets/asset-pipeline.d.ts +5 -1
- package/dist/assets/asset-pipeline.js +31 -17
- package/dist/assets/font-check.cjs +9 -1
- package/dist/assets/font-check.js +9 -1
- package/dist/assets/image-check.cjs +89 -6
- package/dist/assets/image-check.d.cts +17 -0
- package/dist/assets/image-check.d.ts +17 -0
- package/dist/assets/image-check.js +89 -7
- package/dist/commands/clipboard.cjs +141 -0
- package/dist/commands/clipboard.d.cts +47 -0
- package/dist/commands/clipboard.d.ts +47 -0
- package/dist/commands/clipboard.js +140 -0
- package/dist/commands/layers.cjs +97 -0
- package/dist/commands/layers.d.cts +39 -0
- package/dist/commands/layers.d.ts +39 -0
- package/dist/commands/layers.js +92 -0
- package/dist/document/document-format.d.cts +15 -0
- package/dist/document/document-format.d.ts +15 -0
- package/dist/document/validate-document.cjs +8 -0
- package/dist/document/validate-document.js +8 -0
- package/dist/engine/create-document-engine.cjs +196 -31
- package/dist/engine/create-document-engine.d.cts +44 -7
- package/dist/engine/create-document-engine.d.ts +44 -7
- package/dist/engine/create-document-engine.js +197 -32
- package/dist/engine/errors.d.cts +1 -1
- package/dist/engine/errors.d.ts +1 -1
- package/dist/export/batch-render.cjs +172 -0
- package/dist/export/batch-render.d.cts +43 -0
- package/dist/export/batch-render.d.ts +43 -0
- package/dist/export/batch-render.js +171 -0
- package/dist/export/export-options.cjs +21 -1
- package/dist/export/export-options.d.cts +29 -0
- package/dist/export/export-options.d.ts +29 -0
- package/dist/export/export-options.js +21 -2
- package/dist/export/preflight-export.cjs +11 -2
- package/dist/export/preflight-export.d.cts +1 -1
- package/dist/export/preflight-export.d.ts +1 -1
- package/dist/export/preflight-export.js +11 -2
- package/dist/export/render-export.cjs +57 -4
- package/dist/export/render-export.js +57 -5
- package/dist/export/svg/clip-masks.cjs +39 -0
- package/dist/export/svg/clip-masks.js +37 -0
- package/dist/export/svg/embed-assets.cjs +250 -0
- package/dist/export/svg/embed-assets.js +250 -0
- package/dist/export/svg/overrides.cjs +32 -0
- package/dist/export/svg/overrides.js +32 -0
- package/dist/export/svg/raster-object.cjs +82 -0
- package/dist/export/svg/raster-object.js +81 -0
- package/dist/export/svg/text-decorations.cjs +108 -0
- package/dist/export/svg/text-decorations.js +107 -0
- package/dist/export/svg/text-on-path.cjs +242 -0
- package/dist/export/svg/text-on-path.js +240 -0
- package/dist/fabric/fabric-adapter.cjs +49 -6
- package/dist/fabric/fabric-adapter.js +49 -6
- package/dist/fabric/object-registry.cjs +1 -1
- package/dist/fabric/object-registry.js +1 -1
- package/dist/fabric/page-state.cjs +69 -0
- package/dist/fabric/page-state.js +64 -0
- package/dist/history/apply-state.cjs +19 -2
- package/dist/history/apply-state.js +19 -2
- package/dist/history/create-history.cjs +53 -13
- package/dist/history/create-history.d.cts +8 -0
- package/dist/history/create-history.d.ts +8 -0
- package/dist/history/create-history.js +53 -13
- package/dist/history/snapshot.cjs +10 -1
- package/dist/history/snapshot.js +10 -2
- package/dist/import/svg-import.cjs +256 -0
- package/dist/import/svg-import.d.cts +50 -0
- package/dist/import/svg-import.d.ts +50 -0
- package/dist/import/svg-import.js +256 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +4 -1
- package/dist/migrations/migrate-document.cjs +2 -1
- package/dist/migrations/migrate-document.js +2 -1
- package/dist/pdf/export-pdf.cjs +381 -0
- package/dist/pdf/export-pdf.d.cts +73 -0
- package/dist/pdf/export-pdf.d.ts +73 -0
- package/dist/pdf/export-pdf.js +381 -0
- package/dist/pdf/fonts.cjs +115 -0
- package/dist/pdf/fonts.d.cts +12 -0
- package/dist/pdf/fonts.d.ts +12 -0
- package/dist/pdf/fonts.js +112 -0
- package/dist/pdf/page-layout.cjs +79 -0
- package/dist/pdf/page-layout.d.cts +51 -0
- package/dist/pdf/page-layout.d.ts +51 -0
- package/dist/pdf/page-layout.js +76 -0
- package/dist/pdf.cjs +7 -0
- package/dist/pdf.d.cts +4 -0
- package/dist/pdf.d.ts +4 -0
- package/dist/pdf.js +3 -0
- package/dist/react/use-layers.cjs +53 -0
- package/dist/react/use-layers.d.cts +10 -0
- package/dist/react/use-layers.d.ts +10 -0
- package/dist/react/use-layers.js +53 -0
- package/dist/react.cjs +2 -0
- package/dist/react.d.cts +3 -1
- package/dist/react.d.ts +3 -1
- package/dist/react.js +2 -1
- package/dist/recovery/recovery-controller.cjs +148 -29
- package/dist/recovery/recovery-controller.d.cts +8 -0
- package/dist/recovery/recovery-controller.d.ts +8 -0
- package/dist/recovery/recovery-controller.js +148 -29
- package/dist/security/content-limits.cjs +43 -3
- package/dist/security/content-limits.d.cts +8 -0
- package/dist/security/content-limits.d.ts +8 -0
- package/dist/security/content-limits.js +39 -4
- package/dist/util/concurrency.cjs +41 -0
- package/dist/util/concurrency.js +40 -0
- package/package.json +30 -3
- package/schema/document-v1.schema.json +5 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildAssetManifest } from "../assets/asset-manifest.js";
|
|
2
|
+
import { documentObjects, pickPageFields } from "../fabric/page-state.js";
|
|
2
3
|
import { DocumentEngineError, isDocumentEngineError } from "./errors.js";
|
|
3
|
-
import { secureDocument } from "../security/content-limits.js";
|
|
4
|
+
import { canvasSizeProblem, isSafeImageUrl, secureDocument } from "../security/content-limits.js";
|
|
4
5
|
import { isSameUrl } from "../assets/image-check.js";
|
|
5
6
|
import { inspectAssets, prepareAssetsForLoad, prepareAssetsForSave } from "../assets/asset-pipeline.js";
|
|
6
7
|
import { resolveExportArea } from "../export/export-area.js";
|
|
@@ -9,6 +10,8 @@ import { readObjectId, writeObjectId } from "../fabric/object-ids.js";
|
|
|
9
10
|
import { collectSerializedTypes, walkObjects } from "../fabric/walk-objects.js";
|
|
10
11
|
import { preflightExport } from "../export/preflight-export.js";
|
|
11
12
|
import { mimeTypes, renderRaster, renderSvg, withExportView } from "../export/render-export.js";
|
|
13
|
+
import { embedSvgAssets } from "../export/svg/embed-assets.js";
|
|
14
|
+
import { readSvg } from "../import/svg-import.js";
|
|
12
15
|
import "../document/document-format.js";
|
|
13
16
|
import { createId } from "../document/ids.js";
|
|
14
17
|
import { createDocumentInfo } from "../document/create-document.js";
|
|
@@ -36,11 +39,21 @@ function createDocumentEngine(options) {
|
|
|
36
39
|
const registry = createObjectRegistry(options.customObjects);
|
|
37
40
|
const events = createEventEmitter();
|
|
38
41
|
const objectsById = /* @__PURE__ */ new Map();
|
|
39
|
-
const assetOptions =
|
|
42
|
+
const assetOptions = {
|
|
43
|
+
...options.assets,
|
|
44
|
+
maxImagePixels: options.limits?.maxImagePixels ?? 67108864
|
|
45
|
+
};
|
|
40
46
|
const uploadedUrls = /* @__PURE__ */ new Map();
|
|
41
47
|
let documentInfo = createDocumentInfo(options.document);
|
|
42
48
|
let activeLoad = null;
|
|
43
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 DocumentEngineError("DOCUMENT_CHANGED", `${what} was dropped because another document was opened while it ran`);
|
|
56
|
+
}
|
|
44
57
|
function ensureUsable() {
|
|
45
58
|
if (destroyed) throw new DocumentEngineError("ENGINE_DESTROYED", "This document engine was destroyed");
|
|
46
59
|
}
|
|
@@ -65,21 +78,41 @@ function createDocumentEngine(options) {
|
|
|
65
78
|
objectsById.clear();
|
|
66
79
|
canvas.getObjects().forEach(indexObjectTree);
|
|
67
80
|
}
|
|
68
|
-
const handleObjectAdded = ({ target }) =>
|
|
69
|
-
|
|
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
|
+
};
|
|
70
92
|
canvas.on("object:added", handleObjectAdded);
|
|
71
93
|
canvas.on("object:removed", handleObjectRemoved);
|
|
94
|
+
canvas.on("object:modified", countMutation);
|
|
95
|
+
canvas.on("text:changed", countMutation);
|
|
72
96
|
rebuildIndex();
|
|
73
97
|
const history = createHistory({
|
|
74
98
|
canvas,
|
|
75
99
|
limit: options.history?.limit,
|
|
76
100
|
maxBytes: options.history?.maxBytes,
|
|
77
|
-
|
|
101
|
+
serializeState: () => {
|
|
78
102
|
rebuildIndex();
|
|
79
|
-
|
|
103
|
+
const serialized = serializeCanvas(canvas, registry.propertiesToInclude());
|
|
104
|
+
return {
|
|
105
|
+
objects: serialized.objects,
|
|
106
|
+
page: {
|
|
107
|
+
width: canvas.getWidth(),
|
|
108
|
+
height: canvas.getHeight(),
|
|
109
|
+
...pickPageFields(serialized)
|
|
110
|
+
}
|
|
111
|
+
};
|
|
80
112
|
},
|
|
81
113
|
onChange: (state) => events.emit("history:change", state),
|
|
82
|
-
onContentChange: () => noteContentChange()
|
|
114
|
+
onContentChange: () => noteContentChange(),
|
|
115
|
+
onLiveChange: () => noteContentChange()
|
|
83
116
|
});
|
|
84
117
|
const saving = createSaveController({
|
|
85
118
|
getStorage: () => requireStorage(),
|
|
@@ -114,6 +147,7 @@ function createDocumentEngine(options) {
|
|
|
114
147
|
}
|
|
115
148
|
}) : void 0;
|
|
116
149
|
function noteContentChange() {
|
|
150
|
+
mutations += 1;
|
|
117
151
|
saving.noteContentChange();
|
|
118
152
|
recovery?.schedule();
|
|
119
153
|
}
|
|
@@ -136,15 +170,21 @@ function createDocumentEngine(options) {
|
|
|
136
170
|
height: canvas.getHeight()
|
|
137
171
|
},
|
|
138
172
|
objects: serialized.objects,
|
|
139
|
-
assets: buildAssetManifest(
|
|
173
|
+
assets: buildAssetManifest(documentObjects({
|
|
174
|
+
objects: serialized.objects,
|
|
175
|
+
canvas: serialized
|
|
176
|
+
})),
|
|
140
177
|
metadata: { ...documentInfo.metadata }
|
|
141
178
|
};
|
|
142
179
|
if (serialized.version !== void 0) document.fabricVersion = serialized.version;
|
|
143
|
-
|
|
180
|
+
Object.assign(document.canvas, pickPageFields(serialized));
|
|
144
181
|
return document;
|
|
145
182
|
}
|
|
146
|
-
function toLoadError(error,
|
|
147
|
-
if (signal.aborted)
|
|
183
|
+
function toLoadError(error, controller) {
|
|
184
|
+
if (controller.signal.aborted) {
|
|
185
|
+
const reason = cancelledByCaller.has(controller) ? "Loading was cancelled" : destroyed ? "Loading stopped because the engine was destroyed" : "Loading stopped because a newer load started";
|
|
186
|
+
return new DocumentEngineError("LOAD_ABORTED", reason, { cause: error });
|
|
187
|
+
}
|
|
148
188
|
if (isDocumentEngineError(error)) return error;
|
|
149
189
|
const reason = error instanceof Error ? error.message : String(error);
|
|
150
190
|
return new DocumentEngineError("LOAD_FAILED", `Fabric could not load the document: ${reason}`, { cause: error });
|
|
@@ -156,7 +196,7 @@ function createDocumentEngine(options) {
|
|
|
156
196
|
throw new DocumentEngineError(issues[0].code, `The document is not valid: ${summary}`, { issues });
|
|
157
197
|
}
|
|
158
198
|
const document = input;
|
|
159
|
-
const unknownTypes = registry.findUnknownTypes(collectSerializedTypes(document
|
|
199
|
+
const unknownTypes = registry.findUnknownTypes(collectSerializedTypes(documentObjects(document)));
|
|
160
200
|
if (unknownTypes.length > 0) throw new 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 });
|
|
161
201
|
return document;
|
|
162
202
|
}
|
|
@@ -167,19 +207,50 @@ function createDocumentEngine(options) {
|
|
|
167
207
|
if (!storage || discardUnsavedChanges || !saving.state().isDirty) return;
|
|
168
208
|
throw new DocumentEngineError("UNSAVED_CHANGES", "The current document has unsaved changes. Save first, or pass { discardUnsavedChanges: true } to replace it anyway.");
|
|
169
209
|
}
|
|
170
|
-
|
|
210
|
+
const cancelledByCaller = /* @__PURE__ */ new WeakSet();
|
|
211
|
+
const unlinkCallerSignal = /* @__PURE__ */ new WeakMap();
|
|
212
|
+
function startLoad(callerSignal) {
|
|
171
213
|
activeLoad?.abort();
|
|
172
214
|
const controller = new AbortController();
|
|
173
215
|
activeLoad = controller;
|
|
216
|
+
if (callerSignal) {
|
|
217
|
+
const cancel = () => {
|
|
218
|
+
cancelledByCaller.add(controller);
|
|
219
|
+
controller.abort();
|
|
220
|
+
};
|
|
221
|
+
if (callerSignal.aborted) cancel();
|
|
222
|
+
else {
|
|
223
|
+
callerSignal.addEventListener("abort", cancel, { once: true });
|
|
224
|
+
unlinkCallerSignal.set(controller, () => callerSignal.removeEventListener("abort", cancel));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
174
227
|
return controller;
|
|
175
228
|
}
|
|
229
|
+
function finishLoad(controller) {
|
|
230
|
+
unlinkCallerSignal.get(controller)?.();
|
|
231
|
+
unlinkCallerSignal.delete(controller);
|
|
232
|
+
}
|
|
176
233
|
async function loadMigratedDocument(input, loadOptions, importDetails, startedLoad) {
|
|
177
234
|
ensureUsable();
|
|
178
235
|
protectUnsavedChanges(loadOptions.discardUnsavedChanges);
|
|
179
|
-
const controller = startedLoad ?? startLoad();
|
|
236
|
+
const controller = startedLoad ?? startLoad(loadOptions.signal);
|
|
237
|
+
const mutationsAtStart = mutations;
|
|
180
238
|
const documentId = describeDocumentId(input);
|
|
239
|
+
const report = (stage, done, total) => {
|
|
240
|
+
if (controller.signal.aborted) return;
|
|
241
|
+
const progress = {
|
|
242
|
+
documentId,
|
|
243
|
+
stage,
|
|
244
|
+
done,
|
|
245
|
+
total
|
|
246
|
+
};
|
|
247
|
+
loadOptions.onProgress?.(progress);
|
|
248
|
+
events.emit("load:progress", progress);
|
|
249
|
+
};
|
|
181
250
|
events.emit("load:start", { documentId });
|
|
182
251
|
try {
|
|
252
|
+
if (controller.signal.aborted) throw new Error("aborted");
|
|
253
|
+
report("prepare", 0, 1);
|
|
183
254
|
await recovery?.markLoadStarted(documentId);
|
|
184
255
|
if (controller.signal.aborted) throw new Error("aborted");
|
|
185
256
|
const { document: migrated, migratedFrom } = migrateDocument(secureDocument(input, options.limits), {
|
|
@@ -188,13 +259,21 @@ function createDocumentEngine(options) {
|
|
|
188
259
|
...importDetails
|
|
189
260
|
});
|
|
190
261
|
const checked = checkDocument(migrated);
|
|
191
|
-
|
|
262
|
+
report("prepare", 1, 1);
|
|
263
|
+
const { document, warnings } = await prepareAssetsForLoad(checked, assetOptions, controller.signal, options.limits?.isAllowedUrl, (done, total) => report("images", done, total));
|
|
192
264
|
if (controller.signal.aborted) throw new Error("aborted");
|
|
193
265
|
await history.withoutRecording(() => loadIntoCanvas(canvas, {
|
|
194
266
|
version: document.fabricVersion,
|
|
195
|
-
|
|
267
|
+
...pickPageFields(document.canvas),
|
|
196
268
|
objects: document.objects
|
|
197
|
-
},
|
|
269
|
+
}, {
|
|
270
|
+
signal: controller.signal,
|
|
271
|
+
onObjects: (done, total) => report("objects", done, total),
|
|
272
|
+
beforeSwap: () => {
|
|
273
|
+
if (mutations !== mutationsAtStart && !loadOptions.discardUnsavedChanges) throw new 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
|
+
}
|
|
276
|
+
}));
|
|
198
277
|
if (controller.signal.aborted) throw new Error("aborted");
|
|
199
278
|
if (loadOptions.restoreCanvasSize ?? true) canvas.setDimensions({
|
|
200
279
|
width: document.canvas.width,
|
|
@@ -211,6 +290,7 @@ function createDocumentEngine(options) {
|
|
|
211
290
|
recovery?.cancel();
|
|
212
291
|
saving.startSession(document.revision ?? 0);
|
|
213
292
|
canvas.requestRenderAll();
|
|
293
|
+
report("done", 1, 1);
|
|
214
294
|
if (warnings.length > 0) events.emit("assets:warning", { warnings });
|
|
215
295
|
events.emit("document:change", { documentId: document.id });
|
|
216
296
|
events.emit("load:success", {
|
|
@@ -220,10 +300,11 @@ function createDocumentEngine(options) {
|
|
|
220
300
|
});
|
|
221
301
|
return document;
|
|
222
302
|
} catch (error) {
|
|
223
|
-
const engineError = toLoadError(error, controller
|
|
303
|
+
const engineError = toLoadError(error, controller);
|
|
224
304
|
events.emit("load:error", { error: engineError });
|
|
225
305
|
throw engineError;
|
|
226
306
|
} finally {
|
|
307
|
+
finishLoad(controller);
|
|
227
308
|
if (activeLoad === controller) {
|
|
228
309
|
activeLoad = null;
|
|
229
310
|
await recovery?.markLoadFinished();
|
|
@@ -244,6 +325,43 @@ function createDocumentEngine(options) {
|
|
|
244
325
|
metadata
|
|
245
326
|
});
|
|
246
327
|
}
|
|
328
|
+
function setPage(changes, label = "Change page") {
|
|
329
|
+
ensureUsable();
|
|
330
|
+
const problem = canvasSizeProblem(changes.width ?? canvas.getWidth(), changes.height ?? canvas.getHeight(), options.limits);
|
|
331
|
+
if (problem) throw new 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
|
+
}
|
|
349
|
+
async function importSvg(svg, importOptions = {}) {
|
|
350
|
+
ensureUsable();
|
|
351
|
+
const startedIn = session;
|
|
352
|
+
const result = await readSvg(svg, importOptions, options.limits);
|
|
353
|
+
try {
|
|
354
|
+
ensureSameSession(startedIn, "The SVG import");
|
|
355
|
+
} catch (error) {
|
|
356
|
+
result.objects.forEach((object) => object.dispose?.());
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
if (result.objects.length > 0) {
|
|
360
|
+
history.transaction("Import SVG", () => canvas.add(...result.objects));
|
|
361
|
+
canvas.requestRenderAll();
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
247
365
|
let savesSinceAutomaticVersion = 0;
|
|
248
366
|
let lastVersionTime = 0;
|
|
249
367
|
function nextVersionTimestamp() {
|
|
@@ -322,15 +440,17 @@ function createDocumentEngine(options) {
|
|
|
322
440
|
ensureUsable();
|
|
323
441
|
const source = requireStorage();
|
|
324
442
|
protectUnsavedChanges(loadOptions?.discardUnsavedChanges);
|
|
325
|
-
const controller = startLoad();
|
|
443
|
+
const controller = startLoad(loadOptions?.signal);
|
|
326
444
|
let stored;
|
|
327
445
|
try {
|
|
446
|
+
if (controller.signal.aborted) throw new Error("aborted");
|
|
328
447
|
stored = await source.loadDocument(documentId);
|
|
329
448
|
if (controller.signal.aborted) throw new Error("aborted");
|
|
330
449
|
} catch (error) {
|
|
450
|
+
finishLoad(controller);
|
|
331
451
|
if (activeLoad === controller) activeLoad = null;
|
|
332
452
|
if (controller.signal.aborted) {
|
|
333
|
-
const aborted = toLoadError(error, controller
|
|
453
|
+
const aborted = toLoadError(error, controller);
|
|
334
454
|
events.emit("load:error", { error: aborted });
|
|
335
455
|
throw aborted;
|
|
336
456
|
}
|
|
@@ -355,6 +475,7 @@ function createDocumentEngine(options) {
|
|
|
355
475
|
protectUnsavedChanges(newOptions.discardUnsavedChanges);
|
|
356
476
|
activeLoad?.abort();
|
|
357
477
|
history.withoutRecording(() => canvas.clear());
|
|
478
|
+
session += 1;
|
|
358
479
|
objectsById.clear();
|
|
359
480
|
documentInfo = createDocumentInfo(newOptions);
|
|
360
481
|
uploadedUrls.clear();
|
|
@@ -381,10 +502,12 @@ function createDocumentEngine(options) {
|
|
|
381
502
|
if (isReplaceableImage(object) && isSameUrl(object.getSrc(), oldUrl)) images.push(object);
|
|
382
503
|
});
|
|
383
504
|
if (images.length === 0) return 0;
|
|
505
|
+
const startedIn = session;
|
|
384
506
|
await history.transaction("Replace image", () => Promise.all(images.map(async (image) => {
|
|
385
507
|
const displayedWidth = image.width * image.scaleX;
|
|
386
508
|
const displayedHeight = image.height * image.scaleY;
|
|
387
509
|
await image.setSrc(newUrl, { crossOrigin: image.crossOrigin ?? null });
|
|
510
|
+
ensureSameSession(startedIn, "Replacing the image");
|
|
388
511
|
image.set({
|
|
389
512
|
scaleX: displayedWidth / image.width,
|
|
390
513
|
scaleY: displayedHeight / image.height
|
|
@@ -400,8 +523,8 @@ function createDocumentEngine(options) {
|
|
|
400
523
|
}
|
|
401
524
|
async function checkBeforeExport(exportOptions) {
|
|
402
525
|
ensureUsable();
|
|
403
|
-
const { format } = normalizeExportOptions(exportOptions);
|
|
404
|
-
return preflightExport(canvas, format, format === "json" ? [] : fontsOnCanvas(), assetOptions);
|
|
526
|
+
const { format, svg } = normalizeExportOptions(exportOptions);
|
|
527
|
+
return preflightExport(canvas, format, format === "json" ? [] : fontsOnCanvas(), assetOptions, svg);
|
|
405
528
|
}
|
|
406
529
|
function describeProblems(check) {
|
|
407
530
|
return check.problems.map((problem) => problem.message).join("; ");
|
|
@@ -430,24 +553,58 @@ function createDocumentEngine(options) {
|
|
|
430
553
|
if (!check.ok) throw new DocumentEngineError("EXPORT_BLOCKED", `The export cannot run: ${describeProblems(check)}`, { problems: check.problems });
|
|
431
554
|
const format = settings.format;
|
|
432
555
|
try {
|
|
556
|
+
const warnings = [...check.warnings];
|
|
433
557
|
const rendered = withExportView(canvas, settings.background, format, () => {
|
|
434
558
|
const area = resolveExportArea(canvas, settings.area, settings.padding);
|
|
559
|
+
if (format !== "svg") {
|
|
560
|
+
const problem = 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 DocumentEngineError("EXPORT_BLOCKED", message, { problems: [{
|
|
564
|
+
code: "TOO_LARGE",
|
|
565
|
+
message,
|
|
566
|
+
objectIds: []
|
|
567
|
+
}] });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
435
570
|
return {
|
|
436
571
|
area,
|
|
437
|
-
content: format === "svg" ? Promise.resolve(
|
|
572
|
+
content: format === "svg" ? Promise.resolve(renderSvg(canvas, area, settings.scale, settings.svg, options.limits)) : renderRaster(canvas, area, format, settings.scale, settings.quality)
|
|
438
573
|
};
|
|
439
574
|
});
|
|
440
|
-
|
|
575
|
+
let blob;
|
|
576
|
+
if (format === "svg") {
|
|
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 embedSvgAssets(svg, canvas, settings.svg, settings.signal, options.limits?.isAllowedUrl ?? isSafeImageUrl);
|
|
584
|
+
stopIfCancelled();
|
|
585
|
+
if (settings.svg.embedImages === "require" && embedded.problems.length > 0) {
|
|
586
|
+
const problems = embedded.warnings.filter((warning) => warning.code === "IMAGE_NOT_EMBEDDED").map((warning) => ({
|
|
587
|
+
code: "IMAGE_NOT_EMBEDDED",
|
|
588
|
+
message: warning.message,
|
|
589
|
+
url: warning.url,
|
|
590
|
+
objectIds: warning.objectIds
|
|
591
|
+
}));
|
|
592
|
+
throw new DocumentEngineError("EXPORT_BLOCKED", `The export cannot run: ${embedded.problems.join("; ")}`, { problems });
|
|
593
|
+
}
|
|
594
|
+
warnings.push(...embedded.warnings);
|
|
595
|
+
blob = new Blob([embedded.svg], { type: mimeTypes.svg });
|
|
596
|
+
} else blob = await rendered.content;
|
|
441
597
|
return {
|
|
442
598
|
format,
|
|
443
599
|
mimeType: blob.type || mimeTypes[format],
|
|
444
600
|
blob,
|
|
445
601
|
width: Math.round(rendered.area.width * settings.scale),
|
|
446
602
|
height: Math.round(rendered.area.height * settings.scale),
|
|
447
|
-
warnings
|
|
603
|
+
warnings
|
|
448
604
|
};
|
|
449
605
|
} catch (error) {
|
|
450
606
|
if (isDocumentEngineError(error)) throw error;
|
|
607
|
+
if (settings.signal?.aborted) throw new DocumentEngineError("EXPORT_ABORTED", "The export was cancelled", { cause: error });
|
|
451
608
|
if (error?.name === "SecurityError") {
|
|
452
609
|
const problem = {
|
|
453
610
|
code: "CROSS_ORIGIN_IMAGE",
|
|
@@ -485,12 +642,14 @@ function createDocumentEngine(options) {
|
|
|
485
642
|
if (!recovery) throw new DocumentEngineError("RECOVERY_MISSING", "Pass recovery: { store } to createDocumentEngine to use recovery");
|
|
486
643
|
return recovery;
|
|
487
644
|
}
|
|
488
|
-
async function restoreRecovery(documentId,
|
|
645
|
+
async function restoreRecovery(documentId, restoreOptions = {}) {
|
|
489
646
|
const source = requireRecovery();
|
|
490
647
|
const id = documentId ?? documentInfo.id;
|
|
491
|
-
const
|
|
648
|
+
const { sessionId: branch, ...loadOptions } = restoreOptions;
|
|
649
|
+
const record = await source.read(id, branch);
|
|
492
650
|
if (!record) throw new DocumentEngineError("RECOVERY_NOT_FOUND", `There is no recovery copy for document "${id}"`);
|
|
493
651
|
const loaded = await loadDocument(await restoreRecordedFiles(record), loadOptions);
|
|
652
|
+
source.adopt(loaded.id, record.sessionId);
|
|
494
653
|
noteContentChange();
|
|
495
654
|
events.emit("recovery:restored", { document: loaded });
|
|
496
655
|
return loaded;
|
|
@@ -511,12 +670,15 @@ function createDocumentEngine(options) {
|
|
|
511
670
|
const { disposed } = canvas;
|
|
512
671
|
if (recovery && saving.state().isDirty && !disposed) recovery.writeNow();
|
|
513
672
|
destroyed = true;
|
|
673
|
+
session += 1;
|
|
514
674
|
activeLoad?.abort();
|
|
515
675
|
history.destroy();
|
|
516
676
|
saving.destroy();
|
|
517
677
|
recovery?.destroy();
|
|
518
678
|
canvas.off("object:added", handleObjectAdded);
|
|
519
679
|
canvas.off("object:removed", handleObjectRemoved);
|
|
680
|
+
canvas.off("object:modified", countMutation);
|
|
681
|
+
canvas.off("text:changed", countMutation);
|
|
520
682
|
objectsById.clear();
|
|
521
683
|
events.clear();
|
|
522
684
|
}
|
|
@@ -524,7 +686,8 @@ function createDocumentEngine(options) {
|
|
|
524
686
|
canvas,
|
|
525
687
|
getDocumentInfo: () => ({
|
|
526
688
|
...documentInfo,
|
|
527
|
-
metadata: { ...documentInfo.metadata }
|
|
689
|
+
metadata: { ...documentInfo.metadata },
|
|
690
|
+
session
|
|
528
691
|
}),
|
|
529
692
|
updateMetadata(changes) {
|
|
530
693
|
ensureUsable();
|
|
@@ -542,6 +705,7 @@ function createDocumentEngine(options) {
|
|
|
542
705
|
loadDocument,
|
|
543
706
|
load,
|
|
544
707
|
importFabricJson,
|
|
708
|
+
importSvg,
|
|
545
709
|
createVersion: (name) => storeVersion(name ?? `Version ${(/* @__PURE__ */ new Date()).toLocaleString()}`, "named"),
|
|
546
710
|
listVersions: async (documentId) => requireVersions().listVersions(documentId ?? documentInfo.id),
|
|
547
711
|
restoreVersion,
|
|
@@ -564,14 +728,15 @@ function createDocumentEngine(options) {
|
|
|
564
728
|
preflightExport: checkBeforeExport,
|
|
565
729
|
flushRecovery: async () => requireRecovery().flush(),
|
|
566
730
|
getRecoverableDocuments: async () => requireRecovery().list(),
|
|
567
|
-
getRecovery: async (documentId) => requireRecovery().read(documentId ?? documentInfo.id),
|
|
731
|
+
getRecovery: async (documentId, sessionId) => requireRecovery().read(documentId ?? documentInfo.id, sessionId),
|
|
568
732
|
restoreRecovery,
|
|
569
|
-
discardRecovery: async (documentId) => requireRecovery().
|
|
733
|
+
discardRecovery: async (documentId, sessionId) => requireRecovery().discard(documentId ?? documentInfo.id, sessionId),
|
|
570
734
|
getInterruptedLoad: async () => requireRecovery().interruptedLoad(),
|
|
571
|
-
transaction(label, work) {
|
|
735
|
+
transaction(label, work, transactionOptions) {
|
|
572
736
|
ensureUsable();
|
|
573
|
-
return history.transaction(label, work);
|
|
737
|
+
return history.transaction(label, work, transactionOptions);
|
|
574
738
|
},
|
|
739
|
+
setPage,
|
|
575
740
|
commit(label = "Edit") {
|
|
576
741
|
ensureUsable();
|
|
577
742
|
return history.commit(label);
|
package/dist/engine/errors.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FontAsset, ImageAsset } from "../assets/asset-manifest.cjs";
|
|
2
2
|
import { ExportProblem } from "../export/preflight-export.cjs";
|
|
3
3
|
//#region src/engine/errors.d.ts
|
|
4
|
-
export type DocumentErrorCode = "INVALID_DOCUMENT" | "UNSAFE_DOCUMENT" | "UNSUPPORTED_SCHEMA" | "UNKNOWN_OBJECT_TYPE" | "INVALID_CUSTOM_OBJECT" | "LOAD_ABORTED" | "LOAD_FAILED" | "MISSING_ASSETS" | "MISSING_FONTS" | "ASSET_UPLOAD_FAILED" | "STORAGE_MISSING" | "UNSAVED_CHANGES" | "SAVE_FAILED" | "SAVE_CONFLICT" | "SAVE_CANCELLED" | "DOCUMENT_NOT_FOUND" | "HISTORY_FAILED" | "RECOVERY_MISSING" | "RECOVERY_NOT_FOUND" | "RECOVERY_FAILED" | "MIGRATION_FAILED" | "VERSIONS_UNSUPPORTED" | "VERSION_NOT_FOUND" | "VERSION_FAILED" | "INVALID_EXPORT_OPTIONS" | "EXPORT_BLOCKED" | "EXPORT_FAILED" | "EXPORT_ABORTED" | "ENGINE_DESTROYED";
|
|
4
|
+
export type DocumentErrorCode = "INVALID_DOCUMENT" | "UNSAFE_DOCUMENT" | "UNSUPPORTED_SCHEMA" | "UNKNOWN_OBJECT_TYPE" | "INVALID_CUSTOM_OBJECT" | "LOAD_ABORTED" | "LOAD_FAILED" | "MISSING_ASSETS" | "MISSING_FONTS" | "ASSET_UPLOAD_FAILED" | "STORAGE_MISSING" | "UNSAVED_CHANGES" | "SAVE_FAILED" | "SAVE_CONFLICT" | "SAVE_CANCELLED" | "DOCUMENT_NOT_FOUND" | "HISTORY_FAILED" | "RECOVERY_MISSING" | "RECOVERY_NOT_FOUND" | "RECOVERY_FAILED" | "MIGRATION_FAILED" | "VERSIONS_UNSUPPORTED" | "VERSION_NOT_FOUND" | "VERSION_FAILED" | "INVALID_EXPORT_OPTIONS" | "EXPORT_BLOCKED" | "EXPORT_FAILED" | "EXPORT_ABORTED" | "SVG_IMPORT_FAILED" | "LOAD_CONFLICT" | "DOCUMENT_CHANGED" | "PDF_UNAVAILABLE" | "PDF_FAILED" | "ENGINE_DESTROYED";
|
|
5
5
|
export interface DocumentIssue {
|
|
6
6
|
code: DocumentErrorCode;
|
|
7
7
|
path: string;
|
package/dist/engine/errors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FontAsset, ImageAsset } from "../assets/asset-manifest.js";
|
|
2
2
|
import { ExportProblem } from "../export/preflight-export.js";
|
|
3
3
|
//#region src/engine/errors.d.ts
|
|
4
|
-
export type DocumentErrorCode = "INVALID_DOCUMENT" | "UNSAFE_DOCUMENT" | "UNSUPPORTED_SCHEMA" | "UNKNOWN_OBJECT_TYPE" | "INVALID_CUSTOM_OBJECT" | "LOAD_ABORTED" | "LOAD_FAILED" | "MISSING_ASSETS" | "MISSING_FONTS" | "ASSET_UPLOAD_FAILED" | "STORAGE_MISSING" | "UNSAVED_CHANGES" | "SAVE_FAILED" | "SAVE_CONFLICT" | "SAVE_CANCELLED" | "DOCUMENT_NOT_FOUND" | "HISTORY_FAILED" | "RECOVERY_MISSING" | "RECOVERY_NOT_FOUND" | "RECOVERY_FAILED" | "MIGRATION_FAILED" | "VERSIONS_UNSUPPORTED" | "VERSION_NOT_FOUND" | "VERSION_FAILED" | "INVALID_EXPORT_OPTIONS" | "EXPORT_BLOCKED" | "EXPORT_FAILED" | "EXPORT_ABORTED" | "ENGINE_DESTROYED";
|
|
4
|
+
export type DocumentErrorCode = "INVALID_DOCUMENT" | "UNSAFE_DOCUMENT" | "UNSUPPORTED_SCHEMA" | "UNKNOWN_OBJECT_TYPE" | "INVALID_CUSTOM_OBJECT" | "LOAD_ABORTED" | "LOAD_FAILED" | "MISSING_ASSETS" | "MISSING_FONTS" | "ASSET_UPLOAD_FAILED" | "STORAGE_MISSING" | "UNSAVED_CHANGES" | "SAVE_FAILED" | "SAVE_CONFLICT" | "SAVE_CANCELLED" | "DOCUMENT_NOT_FOUND" | "HISTORY_FAILED" | "RECOVERY_MISSING" | "RECOVERY_NOT_FOUND" | "RECOVERY_FAILED" | "MIGRATION_FAILED" | "VERSIONS_UNSUPPORTED" | "VERSION_NOT_FOUND" | "VERSION_FAILED" | "INVALID_EXPORT_OPTIONS" | "EXPORT_BLOCKED" | "EXPORT_FAILED" | "EXPORT_ABORTED" | "SVG_IMPORT_FAILED" | "LOAD_CONFLICT" | "DOCUMENT_CHANGED" | "PDF_UNAVAILABLE" | "PDF_FAILED" | "ENGINE_DESTROYED";
|
|
5
5
|
export interface DocumentIssue {
|
|
6
6
|
code: DocumentErrorCode;
|
|
7
7
|
path: string;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const require_errors = require("../engine/errors.cjs");
|
|
2
|
+
const require_walk_objects = require("../fabric/walk-objects.cjs");
|
|
3
|
+
const require_create_document_engine = require("../engine/create-document-engine.cjs");
|
|
4
|
+
let fabric = require("fabric");
|
|
5
|
+
//#region src/export/batch-render.ts
|
|
6
|
+
/**
|
|
7
|
+
* Releases a canvas's pixels now. Fabric's `dispose` drops its references,
|
|
8
|
+
* but in browsers the memory behind a canvas comes back only much later,
|
|
9
|
+
* when the garbage collector gets to it.
|
|
10
|
+
*/
|
|
11
|
+
function releaseCanvas(value) {
|
|
12
|
+
if (typeof HTMLCanvasElement !== "undefined" && value instanceof HTMLCanvasElement) {
|
|
13
|
+
value.width = 0;
|
|
14
|
+
value.height = 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function disposeObject(object) {
|
|
18
|
+
const owned = object;
|
|
19
|
+
for (const key of [
|
|
20
|
+
"_cacheCanvas",
|
|
21
|
+
"_filteredEl",
|
|
22
|
+
"_element",
|
|
23
|
+
"_originalElement"
|
|
24
|
+
]) releaseCanvas(owned[key]);
|
|
25
|
+
object.dispose?.();
|
|
26
|
+
}
|
|
27
|
+
/** Frees the objects on a canvas: their cache canvases and image elements. */
|
|
28
|
+
function disposeObjects(canvas) {
|
|
29
|
+
const objects = canvas.getObjects();
|
|
30
|
+
canvas.remove(...objects);
|
|
31
|
+
require_walk_objects.walkObjects(objects, (object) => {
|
|
32
|
+
disposeObject(object);
|
|
33
|
+
if (object.clipPath) disposeObject(object.clipPath);
|
|
34
|
+
});
|
|
35
|
+
for (const key of ["backgroundImage", "overlayImage"]) {
|
|
36
|
+
const image = canvas[key];
|
|
37
|
+
if (image) {
|
|
38
|
+
disposeObject(image);
|
|
39
|
+
canvas[key] = void 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function createSlot(options) {
|
|
44
|
+
if (typeof document === "undefined") throw new require_errors.DocumentEngineError("EXPORT_FAILED", "Rendering documents needs a browser DOM");
|
|
45
|
+
const element = document.createElement("canvas");
|
|
46
|
+
const canvas = new fabric.StaticCanvas(element, {
|
|
47
|
+
renderOnAddRemove: false,
|
|
48
|
+
enableRetinaScaling: false
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
element,
|
|
52
|
+
canvas,
|
|
53
|
+
engine: require_create_document_engine.createDocumentEngine({
|
|
54
|
+
canvas,
|
|
55
|
+
customObjects: options.customObjects,
|
|
56
|
+
assets: options.assets,
|
|
57
|
+
limits: options.limits,
|
|
58
|
+
history: { limit: 1 }
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function releaseSlot(slot) {
|
|
63
|
+
disposeObjects(slot.canvas);
|
|
64
|
+
slot.engine.destroy();
|
|
65
|
+
await slot.canvas.dispose();
|
|
66
|
+
slot.element.width = 0;
|
|
67
|
+
slot.element.height = 0;
|
|
68
|
+
}
|
|
69
|
+
function toEngineError(error) {
|
|
70
|
+
if (require_errors.isDocumentEngineError(error)) return error;
|
|
71
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
72
|
+
return new require_errors.DocumentEngineError("EXPORT_FAILED", `The document could not be rendered: ${reason}`, { cause: error });
|
|
73
|
+
}
|
|
74
|
+
function documentIdOf(value) {
|
|
75
|
+
const id = value?.id;
|
|
76
|
+
return typeof id === "string" ? id : void 0;
|
|
77
|
+
}
|
|
78
|
+
async function renderOne(slot, input, options, signal) {
|
|
79
|
+
const { concurrency: _concurrency, onProgress: _onProgress, customObjects: _custom, assets: _assets, limits: _limits, ...exportOptions } = options;
|
|
80
|
+
try {
|
|
81
|
+
await slot.engine.loadDocument(input, {
|
|
82
|
+
signal,
|
|
83
|
+
discardUnsavedChanges: true
|
|
84
|
+
});
|
|
85
|
+
return await slot.engine.export({
|
|
86
|
+
...exportOptions,
|
|
87
|
+
signal
|
|
88
|
+
});
|
|
89
|
+
} finally {
|
|
90
|
+
disposeObjects(slot.canvas);
|
|
91
|
+
slot.engine.clearHistory();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Renders each document and yields the results as they finish, so they can
|
|
96
|
+
* be uploaded or saved one by one instead of all being held in memory.
|
|
97
|
+
* Documents can be `FabricDocument`s or plain Fabric JSON, and can come from
|
|
98
|
+
* an async iterable, such as pages of a database query.
|
|
99
|
+
*/
|
|
100
|
+
async function* renderDocuments(documents, options) {
|
|
101
|
+
const concurrency = Math.max(1, Math.floor(options.concurrency ?? 2));
|
|
102
|
+
const total = Array.isArray(documents) ? documents.length : void 0;
|
|
103
|
+
const controller = new AbortController();
|
|
104
|
+
const stop = () => controller.abort();
|
|
105
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
106
|
+
if (options.signal?.aborted) stop();
|
|
107
|
+
const iterator = Symbol.asyncIterator in documents ? documents[Symbol.asyncIterator]() : documents[Symbol.iterator]();
|
|
108
|
+
const slots = [];
|
|
109
|
+
const idle = [];
|
|
110
|
+
const running = /* @__PURE__ */ new Map();
|
|
111
|
+
let nextIndex = 0;
|
|
112
|
+
let exhausted = false;
|
|
113
|
+
let done = 0;
|
|
114
|
+
async function take() {
|
|
115
|
+
if (exhausted) return void 0;
|
|
116
|
+
const next = await iterator.next();
|
|
117
|
+
if (next.done) {
|
|
118
|
+
exhausted = true;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
index: nextIndex++,
|
|
123
|
+
value: next.value
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function start(slot, job) {
|
|
127
|
+
const task = renderOne(slot, job.value, options, controller.signal).then((result) => ({
|
|
128
|
+
index: job.index,
|
|
129
|
+
documentId: documentIdOf(job.value),
|
|
130
|
+
result
|
|
131
|
+
}), (error) => ({
|
|
132
|
+
index: job.index,
|
|
133
|
+
documentId: documentIdOf(job.value),
|
|
134
|
+
error: toEngineError(error)
|
|
135
|
+
}));
|
|
136
|
+
running.set(job.index, task.finally(() => idle.push(slot)));
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
while (true) {
|
|
140
|
+
if (controller.signal.aborted) throw new require_errors.DocumentEngineError("EXPORT_ABORTED", "Rendering was cancelled");
|
|
141
|
+
while (running.size < concurrency && !exhausted) {
|
|
142
|
+
const job = await take();
|
|
143
|
+
if (!job) break;
|
|
144
|
+
let slot = idle.pop();
|
|
145
|
+
if (!slot) {
|
|
146
|
+
slot = createSlot(options);
|
|
147
|
+
slots.push(slot);
|
|
148
|
+
}
|
|
149
|
+
start(slot, job);
|
|
150
|
+
}
|
|
151
|
+
if (running.size === 0) break;
|
|
152
|
+
const finished = await Promise.race(running.values());
|
|
153
|
+
running.delete(finished.index);
|
|
154
|
+
done += 1;
|
|
155
|
+
options.onProgress?.({
|
|
156
|
+
done,
|
|
157
|
+
total
|
|
158
|
+
});
|
|
159
|
+
if (controller.signal.aborted) throw new require_errors.DocumentEngineError("EXPORT_ABORTED", "Rendering was cancelled");
|
|
160
|
+
yield finished;
|
|
161
|
+
}
|
|
162
|
+
} finally {
|
|
163
|
+
controller.abort();
|
|
164
|
+
options.signal?.removeEventListener("abort", stop);
|
|
165
|
+
await Promise.allSettled(running.values());
|
|
166
|
+
await Promise.all(slots.map(releaseSlot));
|
|
167
|
+
if (!exhausted) await iterator.return?.();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
exports.disposeObjects = disposeObjects;
|
|
172
|
+
exports.renderDocuments = renderDocuments;
|