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
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Save, load, undo and redo for an existing [Fabric.js](https://fabricjs.com) canv
|
|
|
11
11
|
|
|
12
12
|
You keep your own canvas, toolbar and UI. This package sits beside them and turns what is on the canvas into a document you can save, reopen and keep editing. It works with Fabric 6 and 7, in React, Next.js, Vue, Svelte or plain JavaScript.
|
|
13
13
|
|
|
14
|
+
It also covers the jobs around the document: copy and paste, layer order, importing SVG files, and exporting images, SVG and PDF that look like the canvas.
|
|
15
|
+
|
|
14
16
|
## Why this exists
|
|
15
17
|
|
|
16
18
|
Anyone who has shipped a fabricjs editor has hit the same walls. Fabric.js serialization and drawing work well, but a document needs more than that:
|
|
@@ -19,8 +21,9 @@ Anyone who has shipped a fabricjs editor has hit the same walls. Fabric.js seria
|
|
|
19
21
|
- **Custom properties vanish.** `toJSON` and `loadFromJSON` drop fields Fabric does not know about, unless you list them on every call ([fabric.js#10887](https://github.com/fabricjs/fabric.js/issues/10887)).
|
|
20
22
|
- **Objects cannot be found again.** After loading, you cannot get an object by id, because Fabric gives objects no stable id. Group children have none at all.
|
|
21
23
|
- **Saves race each other.** An older request can finish last and overwrite newer edits, or a second tab can save over the first.
|
|
24
|
+
- **Export stops at the canvas.** There is no PDF export ([fabric.js#5906](https://github.com/fabricjs/fabric.js/issues/5906)), curved text exports to SVG in the wrong place ([fabric.js#6958](https://github.com/fabricjs/fabric.js/issues/6958)), and an exported SVG shows empty boxes once its image links stop working ([fabric.js#1980](https://github.com/fabricjs/fabric.js/issues/1980)).
|
|
22
25
|
|
|
23
|
-
This package handles those
|
|
26
|
+
This package handles those problems and the ones behind them: missing images, fonts that fail to load, crashed tabs, old file formats, large documents that freeze the page, and SVG files that land in the wrong place when imported.
|
|
24
27
|
|
|
25
28
|
## Install
|
|
26
29
|
|
|
@@ -30,7 +33,7 @@ Install from npm, together with Fabric:
|
|
|
30
33
|
npm install fabricjs-document-engine fabric
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
The package is written in TypeScript and ships its own types. It has no runtime dependencies. `fabric` is a peer dependency,
|
|
36
|
+
The package is written in TypeScript and ships its own types. It has no runtime dependencies. `fabric` is a peer dependency, React is needed only for the hooks, and `jspdf` and `svg2pdf.js` only for PDF export.
|
|
34
37
|
|
|
35
38
|
## Quick start
|
|
36
39
|
|
|
@@ -61,13 +64,19 @@ That is the whole setup for a first test. localStorage is fine here; in a real a
|
|
|
61
64
|
- **Custom objects.** Register your own Fabric classes and the extra properties they need to keep.
|
|
62
65
|
- **Safe saving.** It tracks unsaved changes and can autosave. Only one save runs at a time, so a slow older save can never overwrite newer work. Revision checks catch another tab or device saving the same document, and failed saves are retried with backoff.
|
|
63
66
|
- **Your storage.** Plug in any backend with two functions, or use the built-in memory and localStorage adapters. No hosted service is needed.
|
|
64
|
-
- **Assets and fonts.** Documents record the images and fonts they need. When a document is opened, every image and font is checked first. You get the exact list of what is missing, can offer replacements, and tab-only images are uploaded when you save.
|
|
67
|
+
- **Assets and fonts.** Documents record the images and fonts they need. When a document is opened, every image and font is checked first. You get the exact list of what is missing and why (not found, server error, CORS, timeout or a broken file), can offer replacements, and tab-only images are uploaded when you save.
|
|
65
68
|
- **Recovery.** Unsaved work is copied to IndexedDB while the user edits, and again at the moment the tab is closed or refreshed. After a crash or refresh you can offer to restore it, including images that only existed in the old tab.
|
|
66
|
-
- **Export.** PNG, JPEG, WebP, SVG and editable JSON. You choose the area, scale and background. A preflight check means an export either succeeds or tells you exactly which image or font prevents it.
|
|
69
|
+
- **Export.** PNG, JPEG, WebP, SVG, PDF and editable JSON. You choose the area, scale and background. A preflight check means an export either succeeds or tells you exactly which image or font prevents it.
|
|
70
|
+
- **SVG that matches the canvas.** Text on a path keeps its place, its background and its underline in the SVG. Images and fonts can be embedded, so the file opens in Illustrator or on another computer.
|
|
71
|
+
- **PDF with real text.** Pages in A4, Letter or the canvas size, with margins, several pages per file, and text that stays selectable. Only shadows, blend modes and similar effects become pictures.
|
|
72
|
+
- **SVG import.** SVG files open where their viewBox puts them, even with elements outside it, and scripts and outside links are removed first.
|
|
73
|
+
- **Rendering in bulk.** Thumbnails or exports for hundreds of saved documents in one tab, on a few reused canvases that are freed after each document.
|
|
67
74
|
- **Versions and migration.** Keep named versions, restore any of them as a new revision, and open plain Fabric JSON or documents saved by older versions of this package.
|
|
75
|
+
- **Large documents.** Objects are created in chunks so the page stays responsive. Loads report progress and can be cancelled with an `AbortSignal`, and a cancelled load leaves the canvas as it was.
|
|
76
|
+
- **Copy, paste and layers.** A clipboard that keeps group transforms and custom properties and gives every pasted object a new id, plus bring-to-front and send-to-back commands that keep a pinned background in place. Each is one undo step.
|
|
68
77
|
- **Undo and redo.** One user action is one undo step. Transactions group several code changes into one labelled step, and ids survive undo and redo.
|
|
69
78
|
- **React ready, framework free.** Hooks for React, and a small state store for any other framework.
|
|
70
|
-
- **Hardened.** Imported documents are cleaned and size-limited, undo history has a memory budget, and every feature is tested in Chromium, Firefox and WebKit.
|
|
79
|
+
- **Hardened.** Imported documents and SVG files are cleaned and size-limited, undo history has a memory budget, and every feature is tested on Fabric 6 and 7 in Chromium, Firefox and WebKit. Exports are checked pixel by pixel against the canvas.
|
|
71
80
|
|
|
72
81
|
## React, Next.js, Vue and Svelte
|
|
73
82
|
|
|
@@ -164,6 +173,26 @@ const storage: DocumentStorage = {
|
|
|
164
173
|
- Throw an error with `retryable: false` for failures that retrying cannot fix. Every other error is retried.
|
|
165
174
|
- Pass `signal` to `fetch`. The engine aborts it when another document is opened.
|
|
166
175
|
|
|
176
|
+
## Load progress and cancelling
|
|
177
|
+
|
|
178
|
+
Large Fabric.js documents with thousands of objects can take a while to open. The engine creates objects 100 at a time and gives the page a turn between chunks, so the page stays responsive. Show progress, and let the user cancel:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
const controller = new AbortController();
|
|
182
|
+
cancelButton.onclick = () => controller.abort();
|
|
183
|
+
|
|
184
|
+
await engine.load('big-floor-plan', {
|
|
185
|
+
signal: controller.signal,
|
|
186
|
+
onProgress: ({ stage, done, total }) => {
|
|
187
|
+
// stage is 'prepare', 'images', 'objects' or 'done'
|
|
188
|
+
progressBar.value = total > 0 ? done / total : 0;
|
|
189
|
+
progressLabel.textContent = stage;
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Cancelling rejects with `LOAD_ABORTED` and leaves the canvas showing what it showed before. A failed load does the same: every object is created before the canvas is cleared. The `load:progress` event carries the same progress for code that is not the caller.
|
|
195
|
+
|
|
167
196
|
## Autosave and save conflicts
|
|
168
197
|
|
|
169
198
|
Fabric.js autosave is one option. The rest of this section is about what happens when saves go wrong, because that is where editors lose work.
|
|
@@ -228,8 +257,25 @@ engine.on('assets:warning', ({ warnings }) => warnings.forEach((warning) => cons
|
|
|
228
257
|
|
|
229
258
|
1. `resolveUrl` can rewrite each stored URL, for example to sign it or to map asset ids to a CDN.
|
|
230
259
|
2. `loadFont` runs for each font variant. Then the engine checks that the font really renders, rather than silently falling back to a default.
|
|
231
|
-
3.
|
|
232
|
-
4. If images are still missing, loading fails with `MISSING_ASSETS`, and `error.missingAssets` lists each `{ url, objectIds }`. The canvas is not touched.
|
|
260
|
+
3. Images load six at a time (`maxConcurrentImages`), and each one has 30 seconds (`imageTimeout`). If any are missing, `replaceMissingImage` can supply a replacement URL for each one. Return `null` to leave it missing.
|
|
261
|
+
4. If images are still missing, loading fails with `MISSING_ASSETS`, and `error.missingAssets` lists each `{ url, objectIds, failure }`. The canvas is not touched.
|
|
262
|
+
|
|
263
|
+
`failure.reason` tells you why an image failed, so you can show the right message:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
try {
|
|
267
|
+
await engine.load('poster-42');
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (isDocumentEngineError(error) && error.code === 'MISSING_ASSETS') {
|
|
270
|
+
for (const { url, objectIds, failure } of error.missingAssets) {
|
|
271
|
+
// NOT_FOUND, HTTP_ERROR, CORS, NETWORK, TIMEOUT, DECODE or ABORTED
|
|
272
|
+
console.warn(failure?.reason, failure?.status, url, objectIds);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Browsers hide some details on purpose. When an image from another site fails without CORS headers, the reason is `CORS` if the image asked for `crossOrigin`, and `NETWORK` otherwise.
|
|
233
279
|
|
|
234
280
|
Fabric.js fonts that are not available produce a `FONT_UNAVAILABLE` warning, and the text uses a fallback font. Set `requireFonts: true` to fail with `MISSING_FONTS` instead. Warnings are also delivered with `load:success` as `{ document, warnings }`.
|
|
235
281
|
|
|
@@ -276,12 +322,35 @@ downloadExport(result, 'poster.png');
|
|
|
276
322
|
| `padding` | Extra space around `content` or `selection` | `0` |
|
|
277
323
|
| `background` | `'keep'`, `'transparent'` or any CSS color | `'keep'` |
|
|
278
324
|
| `signal` | An `AbortSignal` to cancel | |
|
|
325
|
+
| `svg` | `{ textOnPath?, embedImages?, maxEmbeddedImageBytes?, embedFonts? }` for SVG exports | |
|
|
279
326
|
|
|
280
327
|
- The current zoom and pan do not matter. Exports always use document coordinates, and the view is restored afterwards.
|
|
281
328
|
- A JPEG has no transparency, so an empty or transparent background becomes white instead of black.
|
|
282
329
|
- The export never changes the canvas, the history or the unsaved state.
|
|
283
330
|
- A JSON export is the same portable document a save produces, including uploaded images when `assets.upload` is set.
|
|
284
|
-
-
|
|
331
|
+
- For PDF, see [Export a PDF](#export-a-pdf) below.
|
|
332
|
+
|
|
333
|
+
### SVG that opens anywhere
|
|
334
|
+
|
|
335
|
+
A Fabric.js SVG export links to images by URL. Open the file in Illustrator, on another computer or after a signed URL expires, and the images are empty boxes. Embed them, and the fonts too:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const result = await engine.export({
|
|
339
|
+
format: 'svg',
|
|
340
|
+
svg: {
|
|
341
|
+
embedImages: true, // or 'require' to block the export when one cannot be embedded
|
|
342
|
+
embedFonts: { 'Brand Sans': '/fonts/brand-sans.woff2' },
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
An image from another site that does not allow CORS cannot be read by the page. With `embedImages: true` it stays a link and you get an `IMAGE_NOT_EMBEDDED` warning that names the objects. Fonts can be a URL or the file's bytes, and fonts set on single letters are embedded too.
|
|
348
|
+
|
|
349
|
+
### Curved text in SVG
|
|
350
|
+
|
|
351
|
+
Fabric.js text on a path (`text.path`) does not export to SVG the way it looks on the canvas: `pathAlign` is ignored, raised letters move the wrong way, and text backgrounds and underlines are drawn straight. When the text contains a space, Fabric 6 and 7 even write invalid XML that browsers and Illustrator refuse to open.
|
|
352
|
+
|
|
353
|
+
SVG exports write each letter where the canvas draws it, with its background and underline in the same place, so curved text looks the same in the SVG. Every SVG reader understands the output, and it stays editable text. Pass `svg: { textOnPath: 'fabric' }` to keep Fabric's own output instead.
|
|
285
354
|
|
|
286
355
|
### Preflight and errors
|
|
287
356
|
|
|
@@ -298,6 +367,68 @@ const check = await engine.preflightExport({ format: 'png' });
|
|
|
298
367
|
if (!check.ok) showProblems(check.problems);
|
|
299
368
|
```
|
|
300
369
|
|
|
370
|
+
## Export a PDF
|
|
371
|
+
|
|
372
|
+
Fabric.js has no PDF export, and the usual recipe, a screenshot pasted into jsPDF, gives blurry pages with no selectable text. `exportPdf` draws the canvas as real PDF vectors and text instead. Install the two optional libraries first:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npm install jspdf svg2pdf.js
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { downloadExport } from 'fabricjs-document-engine';
|
|
380
|
+
import { exportPdf } from 'fabricjs-document-engine/pdf';
|
|
381
|
+
|
|
382
|
+
const { blob, warnings } = await exportPdf(engine, {
|
|
383
|
+
page: 'A4', // 'A3', 'A5', 'Letter', 'Legal', 'Tabloid', 'canvas' or [width, height] in points
|
|
384
|
+
margin: 36, // half an inch
|
|
385
|
+
fonts: [
|
|
386
|
+
{ family: 'Inter', source: '/fonts/Inter-Regular.ttf' },
|
|
387
|
+
{ family: 'Inter', source: '/fonts/Inter-Bold.ttf', weight: 'bold' },
|
|
388
|
+
],
|
|
389
|
+
metadata: { title: 'Spring poster' },
|
|
390
|
+
});
|
|
391
|
+
downloadExport({ blob, format: 'pdf' }, 'poster.pdf');
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- **Text stays text.** Text in the fonts you pass, and in Arial, Helvetica, Times and Courier, can be selected and searched in the PDF. Fonts must be TrueType (.ttf) files, which most font sites offer next to the web formats.
|
|
395
|
+
- **Hybrid by default.** Everything is drawn as vectors, except what PDF vectors cannot show: shadows, blend modes, gradient outlines, outlines that keep their width while scaled, and text in a font with no file. Each of those is drawn as a 300 dpi picture of just that object, in its place, and `warnings` names it. Use `mode: 'vector'` for vectors only, or `mode: 'raster'` for one picture per page.
|
|
396
|
+
- **Curved text and underlines** come out as on the canvas, using the same fixes as the SVG export.
|
|
397
|
+
- **Several pages.** Pass an array of engines, Fabric canvases or saved documents to get one page each. Saved documents are drawn on an off-screen canvas that is freed after its page.
|
|
398
|
+
|
|
399
|
+
## Render many documents
|
|
400
|
+
|
|
401
|
+
Making thumbnails or PDFs for hundreds of saved designs in one browser tab runs out of memory with a canvas per design, because browsers free canvas memory late. `renderDocuments` reuses a few off-screen canvases and frees every object and cache canvas after each document:
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
import { renderDocuments } from 'fabricjs-document-engine';
|
|
405
|
+
|
|
406
|
+
for await (const { documentId, result, error } of renderDocuments(savedDocuments, { format: 'png', scale: 0.5, concurrency: 2 })) {
|
|
407
|
+
if (result) await uploadThumbnail(documentId, result.blob);
|
|
408
|
+
else console.warn(documentId, error?.message);
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Results arrive as each document finishes, so they can be uploaded one by one. A broken document reports its own `error` and the rest still render. `documents` can be an async iterable, such as pages of a database query, and a `signal` stops the batch.
|
|
413
|
+
|
|
414
|
+
## Import an SVG file
|
|
415
|
+
|
|
416
|
+
Fabric's usual SVG import, `loadSVGFromString` with `util.groupSVGElements`, sizes the group to what is drawn. An element outside the SVG's viewBox, or a hidden one, then moves and resizes the whole artwork. `importSvg` keeps the SVG's own frame:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
const { objects, viewport, warnings } = await engine.importSvg(svgText, {
|
|
420
|
+
left: 40,
|
|
421
|
+
top: 40,
|
|
422
|
+
fit: { width: 300, height: 200 }, // optional: scale into a box
|
|
423
|
+
offscreen: 'clip', // or 'keep' (default) or 'drop'
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
- Elements land where the SVG puts them, after `viewBox` and `preserveAspectRatio`.
|
|
428
|
+
- The result is one group with a fixed layout the size of the viewport, or separate objects with `as: 'objects'`. Either way it is one undo step, and every object gets an id.
|
|
429
|
+
- Scripts, event handlers, `foreignObject`, links to other files and image addresses that `limits.isAllowedUrl` refuses are removed, and `warnings` says what was removed.
|
|
430
|
+
- Size limits apply as for documents, so a huge or deeply nested SVG is refused with `UNSAFE_DOCUMENT`.
|
|
431
|
+
|
|
301
432
|
## Version history
|
|
302
433
|
|
|
303
434
|
```ts
|
|
@@ -425,6 +556,73 @@ engine.on('history:change', ({ canUndo, canRedo, undoLabel, redoLabel }) => {
|
|
|
425
556
|
|
|
426
557
|
The [undo and redo guide](https://fabricjs-document-engine.jscrate.dev/docs/guides/undo-redo) has a live demo and covers text editing in more detail.
|
|
427
558
|
|
|
559
|
+
## Copy, paste and layer order
|
|
560
|
+
|
|
561
|
+
Copy and paste in Fabric.js usually means `object.clone()`, which copies the id and can place objects from a moved selection or a group in the wrong spot. The clipboard copies objects where they really are on the canvas, keeps custom properties, and gives every pasted object, group child and clip path a new id:
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
import { createClipboard } from 'fabricjs-document-engine';
|
|
565
|
+
|
|
566
|
+
const clipboard = createClipboard(engine);
|
|
567
|
+
|
|
568
|
+
clipboard.copy(); // the selection, or pass objects
|
|
569
|
+
await clipboard.paste(); // one undo step, 10 units further each time
|
|
570
|
+
clipboard.cut(); // one undo step; the next paste lands in place
|
|
571
|
+
await clipboard.paste({ target: otherEngine });
|
|
572
|
+
|
|
573
|
+
// Share between tabs through the system clipboard
|
|
574
|
+
await navigator.clipboard.writeText(JSON.stringify(clipboard.read()));
|
|
575
|
+
clipboard.write(JSON.parse(await navigator.clipboard.readText()));
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Content passed to `write` is checked like a loaded document, so pasted JSON cannot carry unsafe image addresses.
|
|
579
|
+
|
|
580
|
+
Layer commands move objects, or the selection, and record one undo step. Several selected objects keep their order. A pinned object, such as a background, never moves:
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import { bringForward, bringToFront, getLayers, sendBackward, sendToBack } from 'fabricjs-document-engine';
|
|
584
|
+
|
|
585
|
+
const keepBackground = { pinned: (object) => object.name === 'background' };
|
|
586
|
+
|
|
587
|
+
bringToFront(engine);
|
|
588
|
+
sendToBack(engine, undefined, keepBackground); // stops just above the background
|
|
589
|
+
bringForward(engine, [logo]);
|
|
590
|
+
|
|
591
|
+
getLayers(engine); // [{ id, type, name, index, visible, locked }], top first
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
The engine saves each object's `name`, so a layers panel keeps its labels. In React, `useLayers(engine)` from `fabricjs-document-engine/react` returns the same list and updates on every change.
|
|
595
|
+
|
|
596
|
+
## Edits that are never lost
|
|
597
|
+
|
|
598
|
+
These guarantees hold however the user and your code interleave work:
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
// Page settings are one undo step and count as unsaved work
|
|
602
|
+
engine.setPage({ width: 1080, height: 1080, background: '#fff8e7' }, 'Square post');
|
|
603
|
+
|
|
604
|
+
// All or nothing: a failure leaves the canvas as it was
|
|
605
|
+
await engine.transaction('Apply template', async () => {
|
|
606
|
+
await addTemplateObjects(engine.canvas);
|
|
607
|
+
}, { rollback: true });
|
|
608
|
+
|
|
609
|
+
// A load refuses to overwrite edits made while it ran
|
|
610
|
+
try {
|
|
611
|
+
await engine.load('poster-42');
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (isDocumentEngineError(error) && error.code === 'LOAD_CONFLICT') askBeforeReplacing();
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
- **The whole page is saved.** Background images, overlays and a mask on the canvas (`canvas.backgroundImage`, `overlayImage`, `clipPath`) are saved, reopened, checked for missing images and kept in versions and recovery copies.
|
|
618
|
+
- **Page changes are undoable.** `setPage` changes the size, background, overlay or mask as one undo step, and changes made to the canvas inside a `transaction` count too.
|
|
619
|
+
- **Typing counts at once.** Each keystroke marks the document unsaved and feeds autosave and recovery, while the whole edit stays one undo step.
|
|
620
|
+
- **Loads keep edits.** If the canvas is edited while a document loads, the load stops with `LOAD_CONFLICT` and the edits stay, unless you pass `discardUnsavedChanges: true`.
|
|
621
|
+
- **Async work stays in its document.** An SVG import, image replacement or paste that finishes after another document was opened is dropped with `DOCUMENT_CHANGED`, instead of landing in the wrong document.
|
|
622
|
+
- **Each tab has its own recovery copy.** Two tabs editing the same document no longer overwrite each other's copy, and a save removes only the copy it covers.
|
|
623
|
+
- **Sizes are checked before drawing.** Pages, exports and images over the browser's canvas limits are refused before any canvas is made, so a huge file cannot crash the tab. Limits are set with `limits`.
|
|
624
|
+
- **Rollback when you want it.** `transaction(label, work, { rollback: true })` undoes everything `work` changed when it fails.
|
|
625
|
+
|
|
428
626
|
## Document format
|
|
429
627
|
|
|
430
628
|
```ts
|
|
@@ -435,7 +633,15 @@ interface FabricDocument {
|
|
|
435
633
|
updatedAt: string;
|
|
436
634
|
revision?: number;
|
|
437
635
|
fabricVersion?: string;
|
|
438
|
-
canvas: {
|
|
636
|
+
canvas: {
|
|
637
|
+
width: number;
|
|
638
|
+
height: number;
|
|
639
|
+
background?: unknown; // color, gradient or pattern
|
|
640
|
+
backgroundImage?: object; // Fabric image behind every object
|
|
641
|
+
overlay?: unknown; // color drawn over every object
|
|
642
|
+
overlayImage?: object; // Fabric image over every object
|
|
643
|
+
clipPath?: object; // mask for the whole canvas
|
|
644
|
+
};
|
|
439
645
|
objects: SerializedFabricObject[];
|
|
440
646
|
assets?: {
|
|
441
647
|
images: Array<{ url: string; objectIds: string[] }>;
|
|
@@ -455,7 +661,7 @@ import schema from 'fabricjs-document-engine/schema/document-v1.json';
|
|
|
455
661
|
|
|
456
662
|
## Where it fits
|
|
457
663
|
|
|
458
|
-
Use it when you are building a Fabric.js canvas editor: a design editor, an image editor, a floor planner, a label or certificate builder. Fabric still does the drawing, selection and serialization. This package adds the document layer on top: ids, history, saving, loading, assets,
|
|
664
|
+
Use it when you are building a Fabric.js canvas editor: a design editor, an image editor, a floor planner, a label or certificate builder. Fabric still does the drawing, selection and serialization. This package adds the document layer on top: ids, history, saving, loading, assets, recovery, copy and paste, layer order, SVG import, and image, SVG and PDF export.
|
|
459
665
|
|
|
460
666
|
If you are comparing a canvas editor JS library or an undo redo JavaScript library, note the scope. It does not draw a toolbar, and it does not do real-time collaboration. The [comparison page](https://fabricjs-document-engine.jscrate.dev/docs/overview/comparison) sets it next to `fabric-history`, `fabricjs-react` and hand-written `toJSON`.
|
|
461
667
|
|
|
@@ -466,7 +672,8 @@ If you are comparing a canvas editor JS library or an undo redo JavaScript libra
|
|
|
466
672
|
| Fabric | Fabric.js 6 and Fabric.js 7 (peer `^6.0.0 \|\| ^7.0.0`); plain JSON from Fabric 5 opens through migration |
|
|
467
673
|
| Browsers | Full suite passes in Chromium, Firefox and WebKit |
|
|
468
674
|
| React | 18 and 19, optional |
|
|
469
|
-
| Node | 18 or later, for server-side import, validation and migration |
|
|
675
|
+
| Node | 18 or later, for server-side import, validation and migration. PDF export and `renderDocuments` need a browser |
|
|
676
|
+
| PDF | Optional peers `jspdf` 4 and `svg2pdf.js` 2.7 or later, only for `fabricjs-document-engine/pdf` |
|
|
470
677
|
| Modules | ESM and CommonJS, with TypeScript types |
|
|
471
678
|
|
|
472
679
|
Tested versions and performance numbers (5,000 objects, every step under 50 ms except load) are in [docs/compatibility.md](https://github.com/re-sohail/fabricjs-document-engine/blob/main/docs/compatibility.md).
|
|
@@ -492,6 +699,8 @@ Documents often come from users, so the engine treats them as untrusted:
|
|
|
492
699
|
- Keys named `__proto__`, `constructor` or `prototype` are removed before Fabric sees them. Fabric copies every key onto the object it creates, so these keys could otherwise change an object's prototype.
|
|
493
700
|
- Image addresses are checked after `assets.resolveUrl`, before anything is fetched. `http:`, `https:`, `blob:`, relative addresses and `data:image/...` are allowed. `javascript:`, `file:` and non-image `data:` addresses are refused with `UNSAFE_DOCUMENT`.
|
|
494
701
|
- A document with more than 50,000 objects, or nested more than 100 levels deep, is refused before loading, so a hostile file cannot freeze the tab.
|
|
702
|
+
- SVG files passed to `importSvg` lose scripts, event handlers, `foreignObject` and links to other files before Fabric parses them, and the same size limits apply.
|
|
703
|
+
- JSON written into the clipboard with `clipboard.write` is checked like a document, so pasted content cannot bring in unsafe image addresses.
|
|
495
704
|
|
|
496
705
|
```ts
|
|
497
706
|
createDocumentEngine({
|