fabricjs-document-engine 1.0.2 → 1.1.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 +181 -10
- package/README.zh-CN.md +181 -10
- package/dist/assets/asset-manifest.d.cts +3 -0
- package/dist/assets/asset-manifest.d.ts +3 -0
- package/dist/assets/asset-pipeline.cjs +22 -10
- package/dist/assets/asset-pipeline.d.cts +5 -1
- package/dist/assets/asset-pipeline.d.ts +5 -1
- package/dist/assets/asset-pipeline.js +22 -10
- package/dist/assets/font-check.cjs +9 -1
- package/dist/assets/font-check.js +9 -1
- package/dist/assets/image-check.cjs +88 -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 +88 -7
- package/dist/commands/clipboard.cjs +136 -0
- package/dist/commands/clipboard.d.cts +47 -0
- package/dist/commands/clipboard.d.ts +47 -0
- package/dist/commands/clipboard.js +135 -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/engine/create-document-engine.cjs +85 -14
- package/dist/engine/create-document-engine.d.cts +20 -1
- package/dist/engine/create-document-engine.d.ts +20 -1
- package/dist/engine/create-document-engine.js +86 -15
- 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 +18 -1
- package/dist/export/export-options.d.cts +23 -0
- package/dist/export/export-options.d.ts +23 -0
- package/dist/export/export-options.js +18 -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 +18 -3
- package/dist/export/render-export.js +18 -4
- package/dist/export/svg/embed-assets.cjs +249 -0
- package/dist/export/svg/embed-assets.js +249 -0
- package/dist/export/svg/overrides.cjs +32 -0
- package/dist/export/svg/overrides.js +32 -0
- package/dist/export/svg/text-on-path.cjs +252 -0
- package/dist/export/svg/text-on-path.js +249 -0
- package/dist/fabric/fabric-adapter.cjs +46 -5
- package/dist/fabric/fabric-adapter.js +46 -5
- package/dist/fabric/object-registry.cjs +1 -1
- package/dist/fabric/object-registry.js +1 -1
- 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 +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -1
- package/dist/pdf/export-pdf.cjs +440 -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 +440 -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/text-decorations.cjs +108 -0
- package/dist/pdf/text-decorations.js +107 -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/util/concurrency.cjs +28 -0
- package/dist/util/concurrency.js +27 -0
- package/package.json +30 -3
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,43 @@ 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
|
+
|
|
428
596
|
## Document format
|
|
429
597
|
|
|
430
598
|
```ts
|
|
@@ -455,7 +623,7 @@ import schema from 'fabricjs-document-engine/schema/document-v1.json';
|
|
|
455
623
|
|
|
456
624
|
## Where it fits
|
|
457
625
|
|
|
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,
|
|
626
|
+
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
627
|
|
|
460
628
|
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
629
|
|
|
@@ -466,7 +634,8 @@ If you are comparing a canvas editor JS library or an undo redo JavaScript libra
|
|
|
466
634
|
| Fabric | Fabric.js 6 and Fabric.js 7 (peer `^6.0.0 \|\| ^7.0.0`); plain JSON from Fabric 5 opens through migration |
|
|
467
635
|
| Browsers | Full suite passes in Chromium, Firefox and WebKit |
|
|
468
636
|
| React | 18 and 19, optional |
|
|
469
|
-
| Node | 18 or later, for server-side import, validation and migration |
|
|
637
|
+
| Node | 18 or later, for server-side import, validation and migration. PDF export and `renderDocuments` need a browser |
|
|
638
|
+
| PDF | Optional peers `jspdf` 4 and `svg2pdf.js` 2.7 or later, only for `fabricjs-document-engine/pdf` |
|
|
470
639
|
| Modules | ESM and CommonJS, with TypeScript types |
|
|
471
640
|
|
|
472
641
|
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 +661,8 @@ Documents often come from users, so the engine treats them as untrusted:
|
|
|
492
661
|
- 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
662
|
- 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
663
|
- 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.
|
|
664
|
+
- 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.
|
|
665
|
+
- JSON written into the clipboard with `clipboard.write` is checked like a document, so pasted content cannot bring in unsafe image addresses.
|
|
495
666
|
|
|
496
667
|
```ts
|
|
497
668
|
createDocumentEngine({
|
package/README.zh-CN.md
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
画布、工具栏和界面都由你自己掌控。这个包在旁边工作,把画布上的内容变成一份可以保存、重新打开、继续编辑的文档。它支持 Fabric 6 和 7,可以用在 React、Next.js、Vue、Svelte 或原生 JavaScript 中。
|
|
13
13
|
|
|
14
|
+
它也负责文档周边的工作:复制和粘贴、图层顺序、导入 SVG 文件,以及导出和画布看起来一样的图片、SVG 和 PDF。
|
|
15
|
+
|
|
14
16
|
## 为什么需要它
|
|
15
17
|
|
|
16
18
|
做过 fabricjs 编辑器的人,基本都踩过同样的坑。Fabric.js 的序列化和绘制都做得很好,但一份文档需要的不止这些:
|
|
@@ -19,8 +21,9 @@
|
|
|
19
21
|
- **自定义属性会丢失。** `toJSON` 和 `loadFromJSON` 会丢掉 Fabric 不认识的字段,除非你每次调用都把它们列出来([fabric.js#10887](https://github.com/fabricjs/fabric.js/issues/10887))。
|
|
20
22
|
- **加载后找不到对象。** Fabric 不给对象分配稳定的 id,所以加载之后没法按 id 获取对象。编组里的子对象更是完全没有 id。
|
|
21
23
|
- **保存互相冲突。** 旧的请求可能最后才返回,覆盖掉新的改动;另一个标签页也可能覆盖这一个的保存。
|
|
24
|
+
- **导出能力有限。** 没有 PDF 导出([fabric.js#5906](https://github.com/fabricjs/fabric.js/issues/5906)),曲线文字导出成 SVG 后位置不对([fabric.js#6958](https://github.com/fabricjs/fabric.js/issues/6958)),导出的 SVG 一旦图片链接失效就只剩空框([fabric.js#1980](https://github.com/fabricjs/fabric.js/issues/1980))。
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
这个包解决这些问题,以及它们背后的问题:图片缺失、字体加载失败、标签页崩溃、旧的文件格式、大文档卡住页面,以及导入的 SVG 文件位置错乱。
|
|
24
27
|
|
|
25
28
|
## 安装
|
|
26
29
|
|
|
@@ -30,7 +33,7 @@
|
|
|
30
33
|
npm install fabricjs-document-engine fabric
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
这个包用 TypeScript 编写,自带类型定义。它没有运行时依赖。`fabric` 是 peer dependency,只有使用 hooks 时才需要 React
|
|
36
|
+
这个包用 TypeScript 编写,自带类型定义。它没有运行时依赖。`fabric` 是 peer dependency,只有使用 hooks 时才需要 React,只有导出 PDF 时才需要 `jspdf` 和 `svg2pdf.js`。
|
|
34
37
|
|
|
35
38
|
## 快速开始
|
|
36
39
|
|
|
@@ -61,13 +64,19 @@ await engine.loadDocument(JSON.parse(localStorage.getItem(document.id)!));
|
|
|
61
64
|
- **自定义对象。** 注册你自己的 Fabric 类,以及它们需要保留的额外属性。
|
|
62
65
|
- **安全保存。** 它会跟踪未保存的改动,也可以自动保存。同一时间只运行一次保存,所以慢的旧保存永远不会覆盖新的改动。修订号检查能发现另一个标签页或设备保存了同一份文档,失败的保存会按退避策略重试。
|
|
63
66
|
- **你自己的存储。** 用两个函数接入任意后端,或者使用内置的内存和 localStorage 适配器。不需要任何托管服务。
|
|
64
|
-
- **图片和字体。**
|
|
67
|
+
- **图片和字体。** 文档会记录它需要的图片和字体。打开文档时,会先检查每张图片和每种字体。你会拿到缺失内容的准确列表和原因(找不到、服务器错误、CORS、超时或文件损坏),可以提供替换,只存在于当前标签页的图片会在保存时上传。
|
|
65
68
|
- **恢复。** 用户编辑时,未保存的内容会被复制到 IndexedDB;关闭或刷新标签页的那一刻还会再复制一次。崩溃或刷新之后,你可以提示用户恢复,包括只存在于旧标签页中的图片。
|
|
66
|
-
- **导出。** 支持 PNG、JPEG、WebP、SVG 和可编辑的 JSON。区域、缩放和背景由你选择。导出前的预检意味着导出要么成功,要么准确告诉你是哪张图片或哪种字体导致失败。
|
|
69
|
+
- **导出。** 支持 PNG、JPEG、WebP、SVG、PDF 和可编辑的 JSON。区域、缩放和背景由你选择。导出前的预检意味着导出要么成功,要么准确告诉你是哪张图片或哪种字体导致失败。
|
|
70
|
+
- **和画布一致的 SVG。** 沿路径排列的文字在 SVG 中保持原来的位置、背景和下划线。图片和字体可以嵌入文件,所以在 Illustrator 或另一台电脑上也能打开。
|
|
71
|
+
- **带真实文字的 PDF。** 支持 A4、Letter 或画布大小的页面,可以设置页边距,一个文件可以有多页,文字仍然可以选中。只有阴影、混合模式等效果会变成图片。
|
|
72
|
+
- **导入 SVG。** SVG 文件按照它的 viewBox 放置,即使有元素在外面也不会错位;导入前会先移除脚本和外部链接。
|
|
73
|
+
- **批量渲染。** 在一个标签页里为几百份已保存的文档生成缩略图或导出文件,使用几个复用的画布,每份文档完成后都会释放。
|
|
67
74
|
- **版本和迁移。** 保存命名版本,把任意版本恢复为新的修订,还能打开纯 Fabric JSON 或旧版本这个包保存的文档。
|
|
75
|
+
- **大文档。** 对象分批创建,页面保持响应。加载会报告进度,并能用 `AbortSignal` 取消;取消后画布保持原样。
|
|
76
|
+
- **复制、粘贴和图层。** 剪贴板会保留编组的变换和自定义属性,并给每个粘贴出的对象一个新 id;置顶、置底等图层命令可以让固定的背景保持不动。每个操作都是一步撤销。
|
|
68
77
|
- **撤销和重做。** 用户的一次操作就是一步撤销。事务可以把代码里的多处改动合成一个带标签的步骤,撤销和重做后 id 保持不变。
|
|
69
78
|
- **支持 React,不绑定框架。** 为 React 提供 hooks,为其他框架提供一个小的状态 store。
|
|
70
|
-
- **经过加固。**
|
|
79
|
+
- **经过加固。** 导入的文档和 SVG 文件会被清理并限制大小,撤销历史有内存上限,每个功能都在 Fabric 6 和 7、Chromium、Firefox 和 WebKit 中测试过。导出结果会和画布逐像素比对。
|
|
71
80
|
|
|
72
81
|
## React、Next.js、Vue 和 Svelte
|
|
73
82
|
|
|
@@ -164,6 +173,26 @@ const storage: DocumentStorage = {
|
|
|
164
173
|
- 对于重试也无法解决的失败,抛出带 `retryable: false` 的错误。其他错误都会重试。
|
|
165
174
|
- 把 `signal` 传给 `fetch`。打开另一份文档时,引擎会中止它。
|
|
166
175
|
|
|
176
|
+
## 加载进度和取消
|
|
177
|
+
|
|
178
|
+
包含几千个对象的大型 Fabric.js 文档打开时可能需要一些时间。引擎每次创建 100 个对象,并在每批之间把控制权交还给页面,所以页面保持响应。你可以显示进度,并让用户取消:
|
|
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
|
+
取消会以 `LOAD_ABORTED` 拒绝,画布继续显示原来的内容。加载失败时也一样:所有对象都创建完成后才会清空画布。`load:progress` 事件也带有同样的进度,方便调用方以外的代码使用。
|
|
195
|
+
|
|
167
196
|
## 自动保存和保存冲突
|
|
168
197
|
|
|
169
198
|
Fabric.js 自动保存只是一个选项。本节主要讲保存出错时会发生什么,因为编辑器正是在这里丢失内容的。
|
|
@@ -228,8 +257,25 @@ engine.on('assets:warning', ({ warnings }) => warnings.forEach((warning) => cons
|
|
|
228
257
|
|
|
229
258
|
1. `resolveUrl` 可以改写每个存储的 URL,例如给它签名,或者把资源 id 映射到 CDN。
|
|
230
259
|
2. `loadFont` 对每个字体变体运行一次。之后引擎会检查字体是否真的能渲染,而不是悄悄回退到默认字体。
|
|
231
|
-
3.
|
|
232
|
-
4. 如果仍有图片缺失,加载会以 `MISSING_ASSETS` 失败,`error.missingAssets` 列出每个 `{ url, objectIds }`。画布不会被改动。
|
|
260
|
+
3. 图片每次加载六张(`maxConcurrentImages`),每张最多等 30 秒(`imageTimeout`)。如果有缺失,`replaceMissingImage` 可以为每一张提供替换 URL。返回 `null` 则保持缺失。
|
|
261
|
+
4. 如果仍有图片缺失,加载会以 `MISSING_ASSETS` 失败,`error.missingAssets` 列出每个 `{ url, objectIds, failure }`。画布不会被改动。
|
|
262
|
+
|
|
263
|
+
`failure.reason` 说明图片失败的原因,方便你显示合适的提示:
|
|
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
|
+
浏览器会有意隐藏一些细节。来自其他网站、没有 CORS 头的图片失败时,如果图片设置了 `crossOrigin`,原因是 `CORS`,否则是 `NETWORK`。
|
|
233
279
|
|
|
234
280
|
不可用的 Fabric.js 字体会产生 `FONT_UNAVAILABLE` 警告,文字使用后备字体。设置 `requireFonts: true` 则改为以 `MISSING_FONTS` 失败。警告也会随 `load:success` 以 `{ document, warnings }` 的形式传递。
|
|
235
281
|
|
|
@@ -276,12 +322,35 @@ downloadExport(result, 'poster.png');
|
|
|
276
322
|
| `padding` | `content` 或 `selection` 周围的额外空白 | `0` |
|
|
277
323
|
| `background` | `'keep'`、`'transparent'` 或任意 CSS 颜色 | `'keep'` |
|
|
278
324
|
| `signal` | 用于取消的 `AbortSignal` | |
|
|
325
|
+
| `svg` | SVG 导出的选项:`{ textOnPath?, embedImages?, maxEmbeddedImageBytes?, embedFonts? }` | |
|
|
279
326
|
|
|
280
327
|
- 当前的缩放和平移不影响结果。导出始终使用文档坐标,之后会恢复视图。
|
|
281
328
|
- JPEG 没有透明通道,所以空的或透明的背景会变成白色,而不是黑色。
|
|
282
329
|
- 导出不会改变画布、历史记录或未保存状态。
|
|
283
330
|
- JSON 导出就是保存时生成的那份可移植文档;设置了 `assets.upload` 时,也包括上传后的图片。
|
|
284
|
-
-
|
|
331
|
+
- PDF 导出见下文的[导出 PDF](#导出-pdf)。
|
|
332
|
+
|
|
333
|
+
### 在任何地方都能打开的 SVG
|
|
334
|
+
|
|
335
|
+
Fabric.js 导出的 SVG 通过 URL 链接图片。在 Illustrator 里、在另一台电脑上,或者签名 URL 过期之后打开,图片就成了空框。可以把图片和字体一起嵌入文件:
|
|
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
|
+
来自不允许 CORS 的其他网站的图片,页面无法读取。使用 `embedImages: true` 时它会保留为链接,并给出带有对象 id 的 `IMAGE_NOT_EMBEDDED` 警告。字体可以是 URL,也可以是文件字节;设置在单个字母上的字体也会被嵌入。
|
|
348
|
+
|
|
349
|
+
### SVG 中的曲线文字
|
|
350
|
+
|
|
351
|
+
Fabric.js 沿路径排列的文字(`text.path`)导出成 SVG 后,和画布上看起来不一样:`pathAlign` 被忽略,抬高的字母移向错误的方向,文字背景和下划线被画成直的。文字里有空格时,Fabric 6 和 7 甚至会写出无效的 XML,浏览器和 Illustrator 都打不开。
|
|
352
|
+
|
|
353
|
+
SVG 导出会把每个字母写在画布上绘制它的位置,背景和下划线也在同一个位置,所以曲线文字在 SVG 里看起来一样。所有 SVG 阅读器都能理解这种输出,文字也仍然可以编辑。传入 `svg: { textOnPath: 'fabric' }` 可以保留 Fabric 自己的输出。
|
|
285
354
|
|
|
286
355
|
### 预检和错误
|
|
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
|
+
## 导出 PDF
|
|
371
|
+
|
|
372
|
+
Fabric.js 本身没有 PDF 导出,常见的做法是把截图塞进 jsPDF,得到的页面模糊,文字也无法选中。`exportPdf` 把画布画成真正的 PDF 矢量和文字。先安装两个可选的库:
|
|
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
|
+
- **文字仍然是文字。** 使用你传入的字体,以及 Arial、Helvetica、Times 和 Courier 的文字,在 PDF 中可以选中和搜索。字体必须是 TrueType(.ttf)文件,大多数字体网站都会在网页格式之外提供它。
|
|
395
|
+
- **默认是混合模式。** 所有内容都画成矢量,只有 PDF 矢量无法表现的部分除外:阴影、混合模式、渐变描边、缩放时保持宽度的描边,以及没有字体文件的文字。这些对象会在原位置被画成 300 dpi 的图片,`warnings` 会指出是哪些对象。使用 `mode: 'vector'` 只输出矢量,使用 `mode: 'raster'` 则每页一张图片。
|
|
396
|
+
- **曲线文字和下划线**的效果和画布上一样,使用的是与 SVG 导出相同的修正。
|
|
397
|
+
- **多页。** 传入引擎、Fabric 画布或已保存文档组成的数组,每个生成一页。已保存的文档会画在离屏画布上,每页完成后释放。
|
|
398
|
+
|
|
399
|
+
## 批量渲染文档
|
|
400
|
+
|
|
401
|
+
在一个浏览器标签页里为几百份已保存的设计生成缩略图或 PDF,如果每份设计用一个画布,内存会耗尽,因为浏览器释放画布内存很慢。`renderDocuments` 复用几个离屏画布,并在每份文档之后释放所有对象和缓存画布:
|
|
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
|
+
每份文档完成后就会返回结果,所以可以逐个上传。损坏的文档会报告自己的 `error`,其余文档继续渲染。`documents` 可以是异步可迭代对象,例如数据库查询的分页结果;`signal` 可以停止整个批次。
|
|
413
|
+
|
|
414
|
+
## 导入 SVG 文件
|
|
415
|
+
|
|
416
|
+
Fabric 常用的 SVG 导入方式,即 `loadSVGFromString` 加 `util.groupSVGElements`,会按绘制的内容确定编组大小。SVG 的 viewBox 之外的元素,或者隐藏的元素,会让整幅图移动并改变大小。`importSvg` 保留 SVG 自己的画框:
|
|
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
|
+
- 元素落在 SVG 放置它们的位置,已经应用了 `viewBox` 和 `preserveAspectRatio`。
|
|
428
|
+
- 结果是一个固定布局、大小等于视口的编组;使用 `as: 'objects'` 时则是分开的对象。两种方式都只算一步撤销,每个对象都有 id。
|
|
429
|
+
- 脚本、事件处理器、`foreignObject`、指向其他文件的链接,以及 `limits.isAllowedUrl` 拒绝的图片地址都会被移除,`warnings` 会说明移除了什么。
|
|
430
|
+
- 大小限制和文档相同,过大或嵌套过深的 SVG 会以 `UNSAFE_DOCUMENT` 被拒绝。
|
|
431
|
+
|
|
301
432
|
## 版本历史
|
|
302
433
|
|
|
303
434
|
```ts
|
|
@@ -425,6 +556,43 @@ engine.on('history:change', ({ canUndo, canRedo, undoLabel, redoLabel }) => {
|
|
|
425
556
|
|
|
426
557
|
[撤销和重做指南](https://fabricjs-document-engine.jscrate.dev/zh/docs/guides/undo-redo)有在线示例,并更详细地介绍了文字编辑。
|
|
427
558
|
|
|
559
|
+
## 复制、粘贴和图层顺序
|
|
560
|
+
|
|
561
|
+
在 Fabric.js 里复制粘贴通常用 `object.clone()`,它会复制 id,还可能把移动过的选区或编组里的对象放错位置。这个剪贴板按对象在画布上的真实位置复制,保留自定义属性,并给每个粘贴出的对象、编组子对象和裁剪路径一个新 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
|
+
传给 `write` 的内容会像加载的文档一样经过检查,所以粘贴的 JSON 不能带入不安全的图片地址。
|
|
579
|
+
|
|
580
|
+
图层命令移动指定对象或当前选区,并记录一步撤销。多个选中的对象保持原有顺序。固定的对象(例如背景)永远不会移动:
|
|
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
|
+
引擎会保存每个对象的 `name`,所以图层面板的名称不会丢。在 React 中,`fabricjs-document-engine/react` 的 `useLayers(engine)` 返回同样的列表,并在每次改动后更新。
|
|
595
|
+
|
|
428
596
|
## 文档格式
|
|
429
597
|
|
|
430
598
|
```ts
|
|
@@ -455,7 +623,7 @@ import schema from 'fabricjs-document-engine/schema/document-v1.json';
|
|
|
455
623
|
|
|
456
624
|
## 适用场景
|
|
457
625
|
|
|
458
|
-
当你在做 Fabric.js 画布编辑器时使用它:设计编辑器、图片编辑器、户型图工具、标签或证书生成器。绘制、选择和序列化仍然由 Fabric 完成。这个包在上面加了一层文档能力:id
|
|
626
|
+
当你在做 Fabric.js 画布编辑器时使用它:设计编辑器、图片编辑器、户型图工具、标签或证书生成器。绘制、选择和序列化仍然由 Fabric 完成。这个包在上面加了一层文档能力:id、历史记录、保存、加载、资源、恢复、复制和粘贴、图层顺序、SVG 导入,以及图片、SVG 和 PDF 导出。
|
|
459
627
|
|
|
460
628
|
如果你在比较画布编辑器 JS 库或撤销重做 JavaScript 库,注意它的范围。它不画工具栏,也不做实时协作。[对比页面](https://fabricjs-document-engine.jscrate.dev/zh/docs/overview/comparison)把它和 `fabric-history`、`fabricjs-react` 以及手写的 `toJSON` 放在一起比较。
|
|
461
629
|
|
|
@@ -466,7 +634,8 @@ import schema from 'fabricjs-document-engine/schema/document-v1.json';
|
|
|
466
634
|
| Fabric | Fabric.js 6 和 Fabric.js 7(peer `^6.0.0 \|\| ^7.0.0`);Fabric 5 的纯 JSON 通过迁移打开 |
|
|
467
635
|
| 浏览器 | 完整测试在 Chromium、Firefox 和 WebKit 中通过 |
|
|
468
636
|
| React | 18 和 19,可选 |
|
|
469
|
-
| Node | 18
|
|
637
|
+
| Node | 18 或更高,用于服务端导入、校验和迁移。PDF 导出和 `renderDocuments` 需要浏览器 |
|
|
638
|
+
| PDF | 可选的 peer `jspdf` 4 和 `svg2pdf.js` 2.7 或更高,只用于 `fabricjs-document-engine/pdf` |
|
|
470
639
|
| 模块 | ESM 和 CommonJS,带 TypeScript 类型 |
|
|
471
640
|
|
|
472
641
|
测试过的版本和性能数据(5,000 个对象,除加载外每一步都在 50 ms 以内)见 [docs/compatibility.md](https://github.com/re-sohail/fabricjs-document-engine/blob/main/docs/compatibility.md)。
|
|
@@ -492,6 +661,8 @@ import schema from 'fabricjs-document-engine/schema/document-v1.json';
|
|
|
492
661
|
- 名为 `__proto__`、`constructor` 或 `prototype` 的键会在 Fabric 看到之前被删除。Fabric 会把每个键复制到它创建的对象上,否则这些键可能改变对象的原型。
|
|
493
662
|
- 图片地址在 `assets.resolveUrl` 之后、任何请求之前检查。允许 `http:`、`https:`、`blob:`、相对地址和 `data:image/...`。`javascript:`、`file:` 和非图片的 `data:` 地址会以 `UNSAFE_DOCUMENT` 拒绝。
|
|
494
663
|
- 超过 50,000 个对象、或嵌套超过 100 层的文档会在加载前被拒绝,恶意文件无法卡死标签页。
|
|
664
|
+
- 传给 `importSvg` 的 SVG 文件会在 Fabric 解析之前去掉脚本、事件处理器、`foreignObject` 和指向其他文件的链接,同样的大小限制也适用。
|
|
665
|
+
- 用 `clipboard.write` 写入剪贴板的 JSON 会像文档一样经过检查,所以粘贴的内容不能带入不安全的图片地址。
|
|
495
666
|
|
|
496
667
|
```ts
|
|
497
668
|
createDocumentEngine({
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { ImageLoadFailure } from "./image-check.cjs";
|
|
1
2
|
//#region src/assets/asset-manifest.d.ts
|
|
2
3
|
export interface ImageAsset {
|
|
3
4
|
url: string;
|
|
4
5
|
objectIds: string[];
|
|
6
|
+
/** Why the image could not be loaded. Set on images in `missingImages` and `missingAssets`. */
|
|
7
|
+
failure?: ImageLoadFailure;
|
|
5
8
|
}
|
|
6
9
|
export interface FontAsset {
|
|
7
10
|
family: string;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { ImageLoadFailure } from "./image-check.js";
|
|
1
2
|
//#region src/assets/asset-manifest.d.ts
|
|
2
3
|
export interface ImageAsset {
|
|
3
4
|
url: string;
|
|
4
5
|
objectIds: string[];
|
|
6
|
+
/** Why the image could not be loaded. Set on images in `missingImages` and `missingAssets`. */
|
|
7
|
+
failure?: ImageLoadFailure;
|
|
5
8
|
}
|
|
6
9
|
export interface FontAsset {
|
|
7
10
|
family: string;
|