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.
Files changed (118) hide show
  1. package/README.md +220 -11
  2. package/README.zh-CN.md +220 -11
  3. package/dist/assets/asset-manifest.d.cts +3 -0
  4. package/dist/assets/asset-manifest.d.ts +3 -0
  5. package/dist/assets/asset-pipeline.cjs +31 -17
  6. package/dist/assets/asset-pipeline.d.cts +5 -1
  7. package/dist/assets/asset-pipeline.d.ts +5 -1
  8. package/dist/assets/asset-pipeline.js +31 -17
  9. package/dist/assets/font-check.cjs +9 -1
  10. package/dist/assets/font-check.js +9 -1
  11. package/dist/assets/image-check.cjs +89 -6
  12. package/dist/assets/image-check.d.cts +17 -0
  13. package/dist/assets/image-check.d.ts +17 -0
  14. package/dist/assets/image-check.js +89 -7
  15. package/dist/commands/clipboard.cjs +141 -0
  16. package/dist/commands/clipboard.d.cts +47 -0
  17. package/dist/commands/clipboard.d.ts +47 -0
  18. package/dist/commands/clipboard.js +140 -0
  19. package/dist/commands/layers.cjs +97 -0
  20. package/dist/commands/layers.d.cts +39 -0
  21. package/dist/commands/layers.d.ts +39 -0
  22. package/dist/commands/layers.js +92 -0
  23. package/dist/document/document-format.d.cts +15 -0
  24. package/dist/document/document-format.d.ts +15 -0
  25. package/dist/document/validate-document.cjs +8 -0
  26. package/dist/document/validate-document.js +8 -0
  27. package/dist/engine/create-document-engine.cjs +196 -31
  28. package/dist/engine/create-document-engine.d.cts +44 -7
  29. package/dist/engine/create-document-engine.d.ts +44 -7
  30. package/dist/engine/create-document-engine.js +197 -32
  31. package/dist/engine/errors.d.cts +1 -1
  32. package/dist/engine/errors.d.ts +1 -1
  33. package/dist/export/batch-render.cjs +172 -0
  34. package/dist/export/batch-render.d.cts +43 -0
  35. package/dist/export/batch-render.d.ts +43 -0
  36. package/dist/export/batch-render.js +171 -0
  37. package/dist/export/export-options.cjs +21 -1
  38. package/dist/export/export-options.d.cts +29 -0
  39. package/dist/export/export-options.d.ts +29 -0
  40. package/dist/export/export-options.js +21 -2
  41. package/dist/export/preflight-export.cjs +11 -2
  42. package/dist/export/preflight-export.d.cts +1 -1
  43. package/dist/export/preflight-export.d.ts +1 -1
  44. package/dist/export/preflight-export.js +11 -2
  45. package/dist/export/render-export.cjs +57 -4
  46. package/dist/export/render-export.js +57 -5
  47. package/dist/export/svg/clip-masks.cjs +39 -0
  48. package/dist/export/svg/clip-masks.js +37 -0
  49. package/dist/export/svg/embed-assets.cjs +250 -0
  50. package/dist/export/svg/embed-assets.js +250 -0
  51. package/dist/export/svg/overrides.cjs +32 -0
  52. package/dist/export/svg/overrides.js +32 -0
  53. package/dist/export/svg/raster-object.cjs +82 -0
  54. package/dist/export/svg/raster-object.js +81 -0
  55. package/dist/export/svg/text-decorations.cjs +108 -0
  56. package/dist/export/svg/text-decorations.js +107 -0
  57. package/dist/export/svg/text-on-path.cjs +242 -0
  58. package/dist/export/svg/text-on-path.js +240 -0
  59. package/dist/fabric/fabric-adapter.cjs +49 -6
  60. package/dist/fabric/fabric-adapter.js +49 -6
  61. package/dist/fabric/object-registry.cjs +1 -1
  62. package/dist/fabric/object-registry.js +1 -1
  63. package/dist/fabric/page-state.cjs +69 -0
  64. package/dist/fabric/page-state.js +64 -0
  65. package/dist/history/apply-state.cjs +19 -2
  66. package/dist/history/apply-state.js +19 -2
  67. package/dist/history/create-history.cjs +53 -13
  68. package/dist/history/create-history.d.cts +8 -0
  69. package/dist/history/create-history.d.ts +8 -0
  70. package/dist/history/create-history.js +53 -13
  71. package/dist/history/snapshot.cjs +10 -1
  72. package/dist/history/snapshot.js +10 -2
  73. package/dist/import/svg-import.cjs +256 -0
  74. package/dist/import/svg-import.d.cts +50 -0
  75. package/dist/import/svg-import.d.ts +50 -0
  76. package/dist/import/svg-import.js +256 -0
  77. package/dist/index.cjs +12 -0
  78. package/dist/index.d.cts +10 -5
  79. package/dist/index.d.ts +10 -5
  80. package/dist/index.js +4 -1
  81. package/dist/migrations/migrate-document.cjs +2 -1
  82. package/dist/migrations/migrate-document.js +2 -1
  83. package/dist/pdf/export-pdf.cjs +381 -0
  84. package/dist/pdf/export-pdf.d.cts +73 -0
  85. package/dist/pdf/export-pdf.d.ts +73 -0
  86. package/dist/pdf/export-pdf.js +381 -0
  87. package/dist/pdf/fonts.cjs +115 -0
  88. package/dist/pdf/fonts.d.cts +12 -0
  89. package/dist/pdf/fonts.d.ts +12 -0
  90. package/dist/pdf/fonts.js +112 -0
  91. package/dist/pdf/page-layout.cjs +79 -0
  92. package/dist/pdf/page-layout.d.cts +51 -0
  93. package/dist/pdf/page-layout.d.ts +51 -0
  94. package/dist/pdf/page-layout.js +76 -0
  95. package/dist/pdf.cjs +7 -0
  96. package/dist/pdf.d.cts +4 -0
  97. package/dist/pdf.d.ts +4 -0
  98. package/dist/pdf.js +3 -0
  99. package/dist/react/use-layers.cjs +53 -0
  100. package/dist/react/use-layers.d.cts +10 -0
  101. package/dist/react/use-layers.d.ts +10 -0
  102. package/dist/react/use-layers.js +53 -0
  103. package/dist/react.cjs +2 -0
  104. package/dist/react.d.cts +3 -1
  105. package/dist/react.d.ts +3 -1
  106. package/dist/react.js +2 -1
  107. package/dist/recovery/recovery-controller.cjs +148 -29
  108. package/dist/recovery/recovery-controller.d.cts +8 -0
  109. package/dist/recovery/recovery-controller.d.ts +8 -0
  110. package/dist/recovery/recovery-controller.js +148 -29
  111. package/dist/security/content-limits.cjs +43 -3
  112. package/dist/security/content-limits.d.cts +8 -0
  113. package/dist/security/content-limits.d.ts +8 -0
  114. package/dist/security/content-limits.js +39 -4
  115. package/dist/util/concurrency.cjs +41 -0
  116. package/dist/util/concurrency.js +40 -0
  117. package/package.json +30 -3
  118. package/schema/document-v1.schema.json +5 -1
@@ -0,0 +1,381 @@
1
+ const require_errors = require("../engine/errors.cjs");
2
+ const require_object_ids = require("../fabric/object-ids.cjs");
3
+ const require_walk_objects = require("../fabric/walk-objects.cjs");
4
+ const require_overrides = require("../export/svg/overrides.cjs");
5
+ const require_text_on_path = require("../export/svg/text-on-path.cjs");
6
+ const require_raster_object = require("../export/svg/raster-object.cjs");
7
+ const require_text_decorations = require("../export/svg/text-decorations.cjs");
8
+ const require_render_export = require("../export/render-export.cjs");
9
+ const require_embed_assets = require("../export/svg/embed-assets.cjs");
10
+ const require_batch_render = require("../export/batch-render.cjs");
11
+ const require_fonts = require("./fonts.cjs");
12
+ const require_page_layout = require("./page-layout.cjs");
13
+ //#region src/pdf/export-pdf.ts
14
+ const RASTER_REASONS = {
15
+ shadow: "it has a shadow, which PDF vectors cannot draw",
16
+ blend: "it uses a blend mode, which PDF vectors cannot draw",
17
+ gradientStroke: "its outline is a gradient or pattern",
18
+ uniformStroke: "it keeps its outline width while scaled (strokeUniform)",
19
+ font: "its font has no TrueType file in fonts",
20
+ characters: "it uses characters the built-in PDF fonts do not have; add a TrueType font that has them",
21
+ mask: "it uses an inverted or nested clip path, which PDF vectors cannot draw"
22
+ };
23
+ function stop(signal) {
24
+ if (signal?.aborted) throw new require_errors.DocumentEngineError("EXPORT_ABORTED", "The PDF export was cancelled");
25
+ }
26
+ async function loadPeers(needsSvg) {
27
+ let jsPDF;
28
+ try {
29
+ jsPDF = (await import("jspdf")).jsPDF;
30
+ } catch (error) {
31
+ throw new require_errors.DocumentEngineError("PDF_UNAVAILABLE", "PDF export needs the jspdf package. Install it with: npm install jspdf svg2pdf.js", { cause: error });
32
+ }
33
+ if (needsSvg) try {
34
+ await import("svg2pdf.js");
35
+ } catch (error) {
36
+ throw new require_errors.DocumentEngineError("PDF_UNAVAILABLE", "Vector and hybrid PDF export need the svg2pdf.js package. Install it with: npm install svg2pdf.js, or use mode: \"raster\".", { cause: error });
37
+ }
38
+ return jsPDF;
39
+ }
40
+ function isEngine(source) {
41
+ return typeof source.toDocument === "function" && "canvas" in source;
42
+ }
43
+ function isCanvas(source) {
44
+ const candidate = source;
45
+ return typeof candidate.getObjects === "function" && typeof candidate.toCanvasElement === "function";
46
+ }
47
+ function idsOf(object) {
48
+ const ids = [];
49
+ require_walk_objects.walkObjects([object], (child) => {
50
+ const id = require_object_ids.readObjectId(child);
51
+ if (id !== void 0) ids.push(id);
52
+ });
53
+ return ids.slice(0, 1);
54
+ }
55
+ function familiesOf(object) {
56
+ const text = object;
57
+ const families = /* @__PURE__ */ new Set();
58
+ if (typeof text.fontFamily === "string") families.add(text.fontFamily);
59
+ for (const line of Object.values(text.styles ?? {})) for (const style of Object.values(line ?? {})) if (typeof style?.fontFamily === "string") families.add(style.fontFamily);
60
+ return [...families];
61
+ }
62
+ function resolvesTo(family, registered) {
63
+ const names = family.split(",").map((name) => name.trim().replace(/^['"]|['"]$/g, ""));
64
+ if (names.some((name) => registered.families.has(name.toLowerCase()))) return {
65
+ registered: true,
66
+ standard: false
67
+ };
68
+ return {
69
+ registered: false,
70
+ standard: names.some((name) => require_fonts.standardFamily(name) !== void 0)
71
+ };
72
+ }
73
+ /** Why an object cannot be drawn as PDF vectors, or undefined when it can. */
74
+ function rasterReason(object, registered, missingFonts) {
75
+ let reason;
76
+ require_walk_objects.walkObjects([object], (child) => {
77
+ if (reason) return;
78
+ if (child.shadow) reason = "shadow";
79
+ else if (child.clipPath?.inverted || child.clipPath?.clipPath) reason = "mask";
80
+ else if (child.globalCompositeOperation && child.globalCompositeOperation !== "source-over") reason = "blend";
81
+ else if (typeof child.stroke === "object" && child.stroke !== null && child.strokeWidth > 0) reason = "gradientStroke";
82
+ else if (child.strokeUniform && child.stroke && child.strokeWidth > 0) {
83
+ const { x: scaleX, y: scaleY } = child.getObjectScaling();
84
+ if (Math.abs(scaleX - 1) > 1e-6 || Math.abs(scaleY - 1) > 1e-6) reason = "uniformStroke";
85
+ }
86
+ const text = child.text;
87
+ if (!reason && typeof text === "string") {
88
+ const families = familiesOf(child).map((family) => resolvesTo(family, registered));
89
+ const allRegistered = families.every((family) => family.registered);
90
+ if (!allRegistered && /[^\u0000-ÿ]/.test(text)) reason = "characters";
91
+ else if (!allRegistered && missingFonts === "rasterize" && families.some((family) => !family.registered && !family.standard)) reason = "font";
92
+ }
93
+ });
94
+ return reason;
95
+ }
96
+ function quoteFamily(name) {
97
+ return /^[a-z-]+$/i.test(name) ? name : `"${name.replace(/"/g, "")}"`;
98
+ }
99
+ function resolveFamily(list, registered) {
100
+ const names = list.split(",").map((name) => name.trim().replace(/^['"]|['"]$/g, ""));
101
+ for (const name of names) {
102
+ const own = registered.families.get(name.toLowerCase());
103
+ if (own) return quoteFamily(own);
104
+ }
105
+ for (const name of names) {
106
+ const standard = require_fonts.standardFamily(name);
107
+ if (standard) return standard;
108
+ }
109
+ return "helvetica";
110
+ }
111
+ function weightOf(value) {
112
+ const number = Number(value);
113
+ return value === "bold" || value === "bolder" || Number.isFinite(number) && number >= 600 ? "bold" : "normal";
114
+ }
115
+ /**
116
+ * Points every font at a font jsPDF has, and rounds weights to normal or
117
+ * bold. svg2pdf matches family names exactly and otherwise falls back to
118
+ * Times, and it looks for weights such as 500 as styles no font has.
119
+ */
120
+ function normalizeFonts(root, registered) {
121
+ for (const element of [root, ...Array.from(root.getElementsByTagName("*"))]) {
122
+ const family = element.getAttribute("font-family");
123
+ if (family) element.setAttribute("font-family", resolveFamily(family, registered));
124
+ const weight = element.getAttribute("font-weight");
125
+ if (weight) element.setAttribute("font-weight", weightOf(weight));
126
+ const style = element.getAttribute("style");
127
+ if (style && /font-(family|weight)/.test(style)) element.setAttribute("style", style.replace(/font-family:\s*([^;]+)/g, (_, value) => `font-family: ${resolveFamily(value, registered)}`).replace(/font-weight:\s*([^;]+)/g, (_, value) => `font-weight: ${weightOf(value.trim())}`));
128
+ }
129
+ }
130
+ /** Warns once for each family and style the canvas uses that has no file of its own. */
131
+ function warnSubstitutedStyles(canvas, registered, page, warnings, warned) {
132
+ if (registered.substituted.length === 0) return;
133
+ const missing = new Set(registered.substituted.map(({ family, style }) => `${family.toLowerCase()}|${style}`));
134
+ require_walk_objects.walkObjects(canvas.getObjects(), (object) => {
135
+ const text = object;
136
+ if (typeof text.text !== "string") return;
137
+ const uses = [{
138
+ family: text.fontFamily,
139
+ weight: text.fontWeight,
140
+ style: text.fontStyle
141
+ }];
142
+ for (const line of Object.values(text.styles ?? {})) for (const style of Object.values(line ?? {})) uses.push({
143
+ family: style?.fontFamily ?? text.fontFamily,
144
+ weight: style?.fontWeight ?? text.fontWeight,
145
+ style: style?.fontStyle ?? text.fontStyle
146
+ });
147
+ for (const use of uses) {
148
+ const family = registered.families.get(String(use.family ?? "").toLowerCase());
149
+ if (!family) continue;
150
+ const style = require_fonts.jsPdfStyle(use.weight, use.style);
151
+ const key = `${family.toLowerCase()}|${style}`;
152
+ if (!missing.has(key) || warned.has(key)) continue;
153
+ warned.add(key);
154
+ warnings.push({
155
+ code: "PDF_FONT_SUBSTITUTED",
156
+ message: `No ${style} file was given for "${family}", so its ${style} text uses another file of the same family`,
157
+ page,
158
+ objectIds: idsOf(object),
159
+ family
160
+ });
161
+ }
162
+ });
163
+ }
164
+ function clipTo(context, draw) {
165
+ const clip = context.layout.clip;
166
+ if (!clip) return draw();
167
+ const { document } = context;
168
+ document.saveGraphicsState();
169
+ document.rect(clip.x, clip.y, clip.width, clip.height, null);
170
+ document.clip();
171
+ document.discardPath();
172
+ const finish = () => {
173
+ document.restoreGraphicsState();
174
+ };
175
+ const drawn = draw();
176
+ if (drawn instanceof Promise) return drawn.finally(finish);
177
+ finish();
178
+ }
179
+ async function drawRasterPage(canvas, context) {
180
+ const { layout, options } = context;
181
+ const multiplier = require_raster_object.safeMultiplier(layout.scale * (options.dpi ?? 300) / 72, canvas.getWidth(), canvas.getHeight(), options.limits);
182
+ const element = require_render_export.withExportView(canvas, options.background ?? "keep", "png", () => canvas.toCanvasElement(multiplier));
183
+ try {
184
+ await clipTo(context, () => {
185
+ context.document.addImage(element, "PNG", layout.x, layout.y, layout.width, layout.height, void 0, "FAST");
186
+ });
187
+ } finally {
188
+ element.width = 0;
189
+ element.height = 0;
190
+ }
191
+ }
192
+ async function drawVectorPage(canvas, context) {
193
+ const { layout, options, page, warnings, registered } = context;
194
+ const mode = options.mode ?? "hybrid";
195
+ const missingFonts = options.missingFonts ?? (mode === "hybrid" ? "rasterize" : "substitute");
196
+ const multiplier = layout.scale * (options.dpi ?? 300) / 72;
197
+ const width = canvas.getWidth();
198
+ const height = canvas.getHeight();
199
+ const svg = require_render_export.withExportView(canvas, options.background ?? "keep", "svg", () => {
200
+ const rasters = /* @__PURE__ */ new Map();
201
+ for (const object of canvas.getObjects()) {
202
+ const reason = rasterReason(object, registered, missingFonts);
203
+ if (!reason) continue;
204
+ if (mode === "hybrid") {
205
+ rasters.set(object, require_raster_object.rasterMarkup(canvas, object, multiplier, options.limits));
206
+ warnings.push({
207
+ code: "PDF_RASTERIZED",
208
+ message: `An object was drawn as a picture at ${options.dpi ?? 300} dpi because ${RASTER_REASONS[reason]}`,
209
+ page,
210
+ objectIds: idsOf(object)
211
+ });
212
+ } else warnings.push({
213
+ code: reason === "font" || reason === "characters" ? "PDF_FONT_SUBSTITUTED" : "PDF_UNSUPPORTED",
214
+ message: `An object may look different in the PDF because ${RASTER_REASONS[reason]}`,
215
+ page,
216
+ objectIds: idsOf(object)
217
+ });
218
+ }
219
+ const writerFor = (object) => {
220
+ const raster = rasters.get(object);
221
+ if (raster !== void 0) return () => raster;
222
+ if (require_text_on_path.isTextOnPath(object)) return (reviver) => require_text_on_path.textOnPathToSVG(object, reviver);
223
+ if (require_text_decorations.hasStraightDecorations(object)) return (reviver) => require_text_decorations.decoratedTextToSVG(object, reviver);
224
+ };
225
+ return require_overrides.withSvgOverrides(canvas, writerFor, () => canvas.toSVG({
226
+ viewBox: {
227
+ x: 0,
228
+ y: 0,
229
+ width,
230
+ height
231
+ },
232
+ width: String(width),
233
+ height: String(height)
234
+ })).result;
235
+ });
236
+ const embedded = await require_embed_assets.embedSvgAssets(require_render_export.repairFabricSvg(svg), canvas, {
237
+ textOnPath: "vector",
238
+ textDecorations: "shapes",
239
+ embedImages: true,
240
+ maxEmbeddedImageBytes: Number.MAX_SAFE_INTEGER,
241
+ embedFonts: {}
242
+ }, options.signal);
243
+ stop(options.signal);
244
+ for (const warning of embedded.warnings) if (warning.code === "IMAGE_NOT_EMBEDDED") warnings.push({
245
+ code: "IMAGE_NOT_EMBEDDED",
246
+ message: warning.message,
247
+ page,
248
+ objectIds: warning.objectIds,
249
+ url: warning.url
250
+ });
251
+ const root = new DOMParser().parseFromString(embedded.svg, "image/svg+xml").documentElement;
252
+ normalizeFonts(root, registered);
253
+ await clipTo(context, async () => {
254
+ await context.document.svg(root, {
255
+ x: layout.x,
256
+ y: layout.y,
257
+ width: layout.width,
258
+ height: layout.height
259
+ });
260
+ });
261
+ }
262
+ /**
263
+ * Exports one or more pages as a PDF. Each source becomes one page: an
264
+ * engine's canvas, a Fabric canvas, or a saved document, which is drawn on
265
+ * an off-screen canvas that is freed after its page.
266
+ */
267
+ async function exportPdf(sources, options = {}) {
268
+ const list = Array.isArray(sources) ? [...sources] : [sources];
269
+ if (list.length === 0) throw new require_errors.DocumentEngineError("INVALID_EXPORT_OPTIONS", "exportPdf needs at least one page");
270
+ const dpi = options.dpi ?? 300;
271
+ if (!(Number.isFinite(dpi) && dpi > 0 && dpi <= 1200)) throw new require_errors.DocumentEngineError("INVALID_EXPORT_OPTIONS", "dpi must be between 1 and 1200");
272
+ const mode = options.mode ?? "hybrid";
273
+ if (![
274
+ "vector",
275
+ "raster",
276
+ "hybrid"
277
+ ].includes(mode)) throw new require_errors.DocumentEngineError("INVALID_EXPORT_OPTIONS", "mode must be \"vector\", \"raster\" or \"hybrid\"");
278
+ if (typeof document === "undefined") throw new require_errors.DocumentEngineError("PDF_UNAVAILABLE", "PDF export needs a browser DOM");
279
+ stop(options.signal);
280
+ const jsPDF = await loadPeers(mode !== "raster");
281
+ const fonts = await require_fonts.loadFonts(options.fonts ?? [], options.signal);
282
+ stop(options.signal);
283
+ let pdf;
284
+ let registered = {
285
+ families: /* @__PURE__ */ new Map(),
286
+ substituted: []
287
+ };
288
+ const warnings = [];
289
+ const warnedStyles = /* @__PURE__ */ new Set();
290
+ let renderer;
291
+ try {
292
+ for (let index = 0; index < list.length; index += 1) {
293
+ stop(options.signal);
294
+ const source = list[index];
295
+ let canvas;
296
+ if (isEngine(source)) canvas = source.canvas;
297
+ else if (isCanvas(source)) canvas = source;
298
+ else {
299
+ renderer ?? (renderer = await createRenderer(options));
300
+ await renderer.engine.loadDocument(source, {
301
+ signal: options.signal,
302
+ discardUnsavedChanges: true
303
+ });
304
+ canvas = renderer.canvas;
305
+ }
306
+ const layout = require_page_layout.layoutPage({
307
+ width: canvas.getWidth(),
308
+ height: canvas.getHeight()
309
+ }, options);
310
+ const format = [layout.pageWidth, layout.pageHeight];
311
+ if (!pdf) {
312
+ pdf = new jsPDF({
313
+ unit: "pt",
314
+ format,
315
+ orientation: layout.orientation,
316
+ compress: true,
317
+ putOnlyUsedFonts: true
318
+ });
319
+ registered = require_fonts.registerFonts(pdf, fonts);
320
+ } else pdf.addPage(format, layout.orientation);
321
+ warnSubstitutedStyles(canvas, registered, index + 1, warnings, warnedStyles);
322
+ const context = {
323
+ document: pdf,
324
+ layout,
325
+ options,
326
+ registered,
327
+ page: index + 1,
328
+ warnings
329
+ };
330
+ if (mode === "raster") await drawRasterPage(canvas, context);
331
+ else await drawVectorPage(canvas, context);
332
+ if (renderer && canvas === renderer.canvas) require_batch_render.disposeObjects(renderer.canvas);
333
+ }
334
+ stop(options.signal);
335
+ const { metadata } = options;
336
+ pdf.setProperties({
337
+ creator: metadata?.creator ?? "fabricjs-document-engine",
338
+ ...metadata
339
+ });
340
+ return {
341
+ blob: pdf.output("blob"),
342
+ pageCount: list.length,
343
+ warnings
344
+ };
345
+ } catch (error) {
346
+ if (require_errors.isDocumentEngineError(error)) throw error;
347
+ if (options.signal?.aborted) throw new require_errors.DocumentEngineError("EXPORT_ABORTED", "The PDF export was cancelled", { cause: error });
348
+ const reason = error instanceof Error ? error.message : String(error);
349
+ throw new require_errors.DocumentEngineError("PDF_FAILED", `The PDF could not be made: ${reason}`, { cause: error });
350
+ } finally {
351
+ await renderer?.release();
352
+ }
353
+ }
354
+ async function createRenderer(options) {
355
+ const [{ StaticCanvas }, { createDocumentEngine }] = await Promise.all([import("fabric"), Promise.resolve().then(() => require("../engine/create-document-engine.cjs"))]);
356
+ const element = document.createElement("canvas");
357
+ const canvas = new StaticCanvas(element, {
358
+ renderOnAddRemove: false,
359
+ enableRetinaScaling: false
360
+ });
361
+ const engine = createDocumentEngine({
362
+ canvas,
363
+ customObjects: options.customObjects,
364
+ assets: options.assets,
365
+ limits: options.limits,
366
+ history: { limit: 1 }
367
+ });
368
+ return {
369
+ canvas,
370
+ engine,
371
+ release: async () => {
372
+ require_batch_render.disposeObjects(canvas);
373
+ engine.destroy();
374
+ await canvas.dispose();
375
+ element.width = 0;
376
+ element.height = 0;
377
+ }
378
+ };
379
+ }
380
+ //#endregion
381
+ exports.exportPdf = exportPdf;
@@ -0,0 +1,73 @@
1
+ import { FabricDocument } from "../document/document-format.cjs";
2
+ import { AssetOptions } from "../assets/asset-pipeline.cjs";
3
+ import { ExportBackground } from "../export/export-options.cjs";
4
+ import { ContentLimits } from "../security/content-limits.cjs";
5
+ import { CustomObjectDefinition } from "../fabric/object-registry.cjs";
6
+ import { DocumentEngine } from "../engine/create-document-engine.cjs";
7
+ import { PdfFont } from "./fonts.cjs";
8
+ import { PageLayoutOptions } from "./page-layout.cjs";
9
+ import { StaticCanvas } from "fabric";
10
+ //#region src/pdf/export-pdf.d.ts
11
+ /**
12
+ * Exports canvases and documents as PDF (fabric.js #5906), with jsPDF and
13
+ * svg2pdf.js as optional peer dependencies.
14
+ *
15
+ * - `vector` draws every object as PDF vectors and text, so text can be
16
+ * selected and the file prints sharply at any size.
17
+ * - `raster` draws each page as one picture at `dpi`.
18
+ * - `hybrid` (the default) draws everything as vectors except what svg2pdf
19
+ * cannot draw faithfully (shadows, blend modes, gradient outlines,
20
+ * non-scaling outlines on scaled objects, and text in a font with no file),
21
+ * which becomes a picture of just that object, in its place.
22
+ */
23
+ export type PdfMode = "vector" | "raster" | "hybrid";
24
+ /** An engine, a Fabric canvas, or a saved document or plain Fabric JSON. */
25
+ export type PdfSource = DocumentEngine | StaticCanvas | FabricDocument | Record<string, unknown>;
26
+ export interface PdfExportOptions extends PageLayoutOptions {
27
+ mode?: PdfMode;
28
+ /** Resolution of everything drawn as a picture. Default 300. */
29
+ dpi?: number;
30
+ /** TrueType files for the fonts the canvas uses. */
31
+ fonts?: PdfFont[];
32
+ /**
33
+ * Text in a font with no file in `fonts`. `rasterize` (the default in
34
+ * hybrid mode) draws it as a picture so it looks right; `substitute` keeps
35
+ * it as text in the closest built-in PDF font.
36
+ */
37
+ missingFonts?: "rasterize" | "substitute";
38
+ background?: ExportBackground;
39
+ metadata?: {
40
+ title?: string;
41
+ author?: string;
42
+ subject?: string;
43
+ keywords?: string;
44
+ creator?: string;
45
+ };
46
+ signal?: AbortSignal;
47
+ /** For documents: custom object classes, asset options and limits, as for the engine. */
48
+ customObjects?: CustomObjectDefinition[];
49
+ assets?: AssetOptions;
50
+ limits?: ContentLimits;
51
+ }
52
+ export type PdfWarningCode = "PDF_RASTERIZED" | "PDF_UNSUPPORTED" | "PDF_FONT_SUBSTITUTED" | "IMAGE_NOT_EMBEDDED";
53
+ export interface PdfWarning {
54
+ code: PdfWarningCode;
55
+ message: string;
56
+ /** The page, counted from 1. */
57
+ page: number;
58
+ objectIds: string[];
59
+ family?: string;
60
+ url?: string;
61
+ }
62
+ export interface PdfExportResult {
63
+ blob: Blob;
64
+ pageCount: number;
65
+ warnings: PdfWarning[];
66
+ }
67
+ /**
68
+ * Exports one or more pages as a PDF. Each source becomes one page: an
69
+ * engine's canvas, a Fabric canvas, or a saved document, which is drawn on
70
+ * an off-screen canvas that is freed after its page.
71
+ */
72
+ export declare function exportPdf(sources: PdfSource | readonly PdfSource[], options?: PdfExportOptions): Promise<PdfExportResult>;
73
+ //#endregion
@@ -0,0 +1,73 @@
1
+ import { FabricDocument } from "../document/document-format.js";
2
+ import { AssetOptions } from "../assets/asset-pipeline.js";
3
+ import { ExportBackground } from "../export/export-options.js";
4
+ import { ContentLimits } from "../security/content-limits.js";
5
+ import { CustomObjectDefinition } from "../fabric/object-registry.js";
6
+ import { DocumentEngine } from "../engine/create-document-engine.js";
7
+ import { PdfFont } from "./fonts.js";
8
+ import { PageLayoutOptions } from "./page-layout.js";
9
+ import { StaticCanvas } from "fabric";
10
+ //#region src/pdf/export-pdf.d.ts
11
+ /**
12
+ * Exports canvases and documents as PDF (fabric.js #5906), with jsPDF and
13
+ * svg2pdf.js as optional peer dependencies.
14
+ *
15
+ * - `vector` draws every object as PDF vectors and text, so text can be
16
+ * selected and the file prints sharply at any size.
17
+ * - `raster` draws each page as one picture at `dpi`.
18
+ * - `hybrid` (the default) draws everything as vectors except what svg2pdf
19
+ * cannot draw faithfully (shadows, blend modes, gradient outlines,
20
+ * non-scaling outlines on scaled objects, and text in a font with no file),
21
+ * which becomes a picture of just that object, in its place.
22
+ */
23
+ export type PdfMode = "vector" | "raster" | "hybrid";
24
+ /** An engine, a Fabric canvas, or a saved document or plain Fabric JSON. */
25
+ export type PdfSource = DocumentEngine | StaticCanvas | FabricDocument | Record<string, unknown>;
26
+ export interface PdfExportOptions extends PageLayoutOptions {
27
+ mode?: PdfMode;
28
+ /** Resolution of everything drawn as a picture. Default 300. */
29
+ dpi?: number;
30
+ /** TrueType files for the fonts the canvas uses. */
31
+ fonts?: PdfFont[];
32
+ /**
33
+ * Text in a font with no file in `fonts`. `rasterize` (the default in
34
+ * hybrid mode) draws it as a picture so it looks right; `substitute` keeps
35
+ * it as text in the closest built-in PDF font.
36
+ */
37
+ missingFonts?: "rasterize" | "substitute";
38
+ background?: ExportBackground;
39
+ metadata?: {
40
+ title?: string;
41
+ author?: string;
42
+ subject?: string;
43
+ keywords?: string;
44
+ creator?: string;
45
+ };
46
+ signal?: AbortSignal;
47
+ /** For documents: custom object classes, asset options and limits, as for the engine. */
48
+ customObjects?: CustomObjectDefinition[];
49
+ assets?: AssetOptions;
50
+ limits?: ContentLimits;
51
+ }
52
+ export type PdfWarningCode = "PDF_RASTERIZED" | "PDF_UNSUPPORTED" | "PDF_FONT_SUBSTITUTED" | "IMAGE_NOT_EMBEDDED";
53
+ export interface PdfWarning {
54
+ code: PdfWarningCode;
55
+ message: string;
56
+ /** The page, counted from 1. */
57
+ page: number;
58
+ objectIds: string[];
59
+ family?: string;
60
+ url?: string;
61
+ }
62
+ export interface PdfExportResult {
63
+ blob: Blob;
64
+ pageCount: number;
65
+ warnings: PdfWarning[];
66
+ }
67
+ /**
68
+ * Exports one or more pages as a PDF. Each source becomes one page: an
69
+ * engine's canvas, a Fabric canvas, or a saved document, which is drawn on
70
+ * an off-screen canvas that is freed after its page.
71
+ */
72
+ export declare function exportPdf(sources: PdfSource | readonly PdfSource[], options?: PdfExportOptions): Promise<PdfExportResult>;
73
+ //#endregion