@broadpaper/forme 0.2.1 → 0.4.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 +12 -9
- package/dist/{chunk-2PT5WRKQ.js → chunk-QA4APTWM.js} +532 -89
- package/dist/chunk-QA4APTWM.js.map +1 -0
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.js +15 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +152 -12
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-2PT5WRKQ.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -172,6 +172,78 @@ declare function vnodeToForme(v: VNode, ctx: TranslateContext): FormeNode | null
|
|
|
172
172
|
|
|
173
173
|
declare function watermarkNode(spec: WatermarkSpec): FormeNode;
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* The theme's fonts, made into fonts the engine can embed.
|
|
177
|
+
*
|
|
178
|
+
* A host declares a font once, in `theme.fonts`. The browser has always read that
|
|
179
|
+
* declaration — `@font-face` rules on the canvas, the designer's font pickers, the
|
|
180
|
+
* validator, the Chromium backend — and this engine did not: it took a separate
|
|
181
|
+
* `fonts` option, so a font declared only in the theme was drawn on screen and
|
|
182
|
+
* measured and printed in the standard faces. Nothing said so, and the symptom was
|
|
183
|
+
* a canvas whose sections overlapped and a PDF whose lines broke somewhere else.
|
|
184
|
+
*
|
|
185
|
+
* Three things stand between a `@font-face` declaration and the engine.
|
|
186
|
+
*
|
|
187
|
+
* **Format.** The engine parses TrueType and OpenType and nothing else; there is no
|
|
188
|
+
* WOFF decoder in it. A face often lists a WOFF2 for the browser beside a TTF, so
|
|
189
|
+
* the first source the engine can read is the one it gets, and a WOFF is skipped by
|
|
190
|
+
* its name before anything is downloaded. What is downloaded is checked by its
|
|
191
|
+
* first four bytes, not its extension. A face with nothing readable is a warning,
|
|
192
|
+
* because the alternative is exactly the silence described above.
|
|
193
|
+
*
|
|
194
|
+
* **Where it comes from.** A theme is the host's own code in the SDK and a JSON body
|
|
195
|
+
* from a stranger in the render service, so the service narrows `FontSourcePolicy`
|
|
196
|
+
* to its allowed hosts and to no files at all. The SDK default is to trust the
|
|
197
|
+
* theme, as it trusts the template.
|
|
198
|
+
*
|
|
199
|
+
* **Doing it once.** The engine resolves a font by rewriting its entry in place,
|
|
200
|
+
* and re-reads anything that is not a `data:` URI on every render. Every source is
|
|
201
|
+
* therefore resolved here, once per address, to a `data:` URI the engine passes
|
|
202
|
+
* straight through.
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
interface FormeFont {
|
|
206
|
+
family: string;
|
|
207
|
+
/** TTF/OTF bytes, a data: URI, or a filesystem path (Node only). */
|
|
208
|
+
src: string | Uint8Array;
|
|
209
|
+
weight?: number;
|
|
210
|
+
style?: "normal" | "italic";
|
|
211
|
+
}
|
|
212
|
+
/** Where the theme's fonts may be read from. */
|
|
213
|
+
interface FontSourcePolicy {
|
|
214
|
+
/** Hosts an `http(s)` font may be fetched from; `"*"` for any. Unset means any. */
|
|
215
|
+
allowedHosts?: string[];
|
|
216
|
+
/**
|
|
217
|
+
* Whether a source that is neither `data:` nor `http(s)` may be read — a file in
|
|
218
|
+
* Node, an address relative to the page in a browser. Default true.
|
|
219
|
+
*/
|
|
220
|
+
local?: boolean;
|
|
221
|
+
}
|
|
222
|
+
/** Reads a source that is neither `data:` nor `http(s)`. Installed by each entry point. */
|
|
223
|
+
type FontFileReader = (src: string) => Promise<Uint8Array>;
|
|
224
|
+
/**
|
|
225
|
+
* Installs the reader for local sources. `src/index.ts` reads files and
|
|
226
|
+
* `src/index.browser.ts` fetches relative to the page, and neither bundle contains
|
|
227
|
+
* the other's — the same arrangement as `setEngineLoader`.
|
|
228
|
+
*/
|
|
229
|
+
declare function setFontFileReader(reader: FontFileReader): void;
|
|
230
|
+
interface ResolvedFonts {
|
|
231
|
+
fonts: FormeFont[];
|
|
232
|
+
warnings: string[];
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The fonts to hand the engine: the caller's own `fonts` first, exactly as given,
|
|
236
|
+
* then every face in the theme the engine can read.
|
|
237
|
+
*
|
|
238
|
+
* The first entry for a family, weight and style wins, so an explicit font — or a
|
|
239
|
+
* service's own — is never displaced by a theme naming the same face.
|
|
240
|
+
*/
|
|
241
|
+
declare function resolveFonts(input: {
|
|
242
|
+
fonts?: FormeFont[];
|
|
243
|
+
theme?: Partial<Theme>;
|
|
244
|
+
sources?: FontSourcePolicy;
|
|
245
|
+
}): Promise<ResolvedFonts>;
|
|
246
|
+
|
|
175
247
|
/**
|
|
176
248
|
* Forme as the editor's measurer.
|
|
177
249
|
*
|
|
@@ -189,8 +261,16 @@ declare function watermarkNode(spec: WatermarkSpec): FormeNode;
|
|
|
189
261
|
interface FormeEditorMeasurerOptions {
|
|
190
262
|
/** The engine call, e.g. `renderSerializedDocWithLayout` from `@formepdf/core/browser`. */
|
|
191
263
|
render: FormeRenderer;
|
|
192
|
-
/**
|
|
193
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Fonts for the engine beyond the theme's own.
|
|
266
|
+
*
|
|
267
|
+
* Usually unnecessary: the faces in `theme.fonts` are read and given to the
|
|
268
|
+
* engine without being named here. May be a promise, because a browser host has
|
|
269
|
+
* to fetch the files and `createMeasurer` has to return synchronously. Every
|
|
270
|
+
* measurement waits for it, so nothing is ever measured — and cached — with the
|
|
271
|
+
* engine's standard faces standing in for a font that was still arriving.
|
|
272
|
+
*/
|
|
273
|
+
fonts?: unknown[] | Promise<unknown[]>;
|
|
194
274
|
/** Called for every distinct translation warning, once. */
|
|
195
275
|
onWarning?(message: string): void;
|
|
196
276
|
}
|
|
@@ -212,6 +292,7 @@ declare class FormeEditorMeasurer {
|
|
|
212
292
|
destroy(): void;
|
|
213
293
|
private key;
|
|
214
294
|
measure(sections: ResolvedNode[], width: number): Promise<GeometryMap>;
|
|
295
|
+
private warn;
|
|
215
296
|
}
|
|
216
297
|
|
|
217
298
|
/**
|
|
@@ -228,13 +309,6 @@ declare class FormeEditorMeasurer {
|
|
|
228
309
|
* `renderPdf` returns Forme's own page geometry so callers can compare.
|
|
229
310
|
*/
|
|
230
311
|
|
|
231
|
-
interface FormeFont {
|
|
232
|
-
family: string;
|
|
233
|
-
/** TTF/OTF bytes, a data: URI, or a filesystem path (Node only). */
|
|
234
|
-
src: string | Uint8Array;
|
|
235
|
-
weight?: number;
|
|
236
|
-
style?: "normal" | "italic";
|
|
237
|
-
}
|
|
238
312
|
interface RenderFormePdfOptions {
|
|
239
313
|
template: ReportTemplate | AnyVersionTemplate;
|
|
240
314
|
registry: BlockRegistry;
|
|
@@ -252,14 +326,37 @@ interface RenderFormePdfOptions {
|
|
|
252
326
|
creator?: string;
|
|
253
327
|
lang?: string;
|
|
254
328
|
};
|
|
255
|
-
/**
|
|
329
|
+
/**
|
|
330
|
+
* Fonts handed to the engine as they are, before the theme's.
|
|
331
|
+
*
|
|
332
|
+
* Most hosts need none: every face in `theme.fonts` with a TrueType or OpenType
|
|
333
|
+
* source is embedded without being named twice. This is for a font the theme
|
|
334
|
+
* does not declare — one a service embeds in every render, say.
|
|
335
|
+
*/
|
|
256
336
|
fonts?: FormeFont[];
|
|
337
|
+
/** Where the theme's fonts may be read from. Unset, anywhere: a host's own theme is trusted. */
|
|
338
|
+
fontSources?: FontSourcePolicy;
|
|
257
339
|
/** Emit a tagged (accessible) PDF. */
|
|
258
340
|
tagged?: boolean;
|
|
259
341
|
/** PDF/UA-1 conformance. Requires an embeddable font. */
|
|
260
342
|
pdfUa?: boolean;
|
|
261
343
|
/** PDF/A conformance level. */
|
|
262
344
|
pdfA?: "2b" | "2u" | "2a" | "3b" | "3u" | "3a";
|
|
345
|
+
/**
|
|
346
|
+
* An extra mark to draw behind every page, on top of whatever the licence
|
|
347
|
+
* decides.
|
|
348
|
+
*
|
|
349
|
+
* Additive and never a replacement: the licence mark is decided by
|
|
350
|
+
* `watermarkFor` and this cannot remove it, which is the property that keeps
|
|
351
|
+
* "a caller cannot reach a PDF without passing the licence check" true. It
|
|
352
|
+
* exists for a host whose own commercial model wants a mark — BroadPaper Cloud
|
|
353
|
+
* marks the documents its free plan produces — and for the ordinary "DRAFT"
|
|
354
|
+
* stamp a business document sometimes needs.
|
|
355
|
+
*
|
|
356
|
+
* Remember that an unstyled Forme watermark draws solid black at full opacity;
|
|
357
|
+
* a spec without a colour and an opacity makes the document unreadable.
|
|
358
|
+
*/
|
|
359
|
+
watermark?: WatermarkSpec | null;
|
|
263
360
|
/**
|
|
264
361
|
* A licence for this render only, overriding whatever `configure` was given.
|
|
265
362
|
*
|
|
@@ -364,6 +461,49 @@ interface RenderPaginatedResult extends RenderFormePdfResult {
|
|
|
364
461
|
* engine at the same width, so the second cannot re-break what the first
|
|
365
462
|
* measured.
|
|
366
463
|
*/
|
|
367
|
-
declare function renderPdfPaginated(opts:
|
|
464
|
+
declare function renderPdfPaginated(opts: LayoutPaginatedOptions): Promise<RenderPaginatedResult>;
|
|
465
|
+
/** A table longer than this many body rows is measured a batch at a time. */
|
|
466
|
+
declare const MEASURE_BATCH_ROWS = 1000;
|
|
467
|
+
interface LayoutPaginatedOptions extends RenderFormePdfOptions {
|
|
468
|
+
/**
|
|
469
|
+
* Body rows per measuring pass for a long table. Only a test should change it:
|
|
470
|
+
* a small number exercises the batching on a table short enough to compare with
|
|
471
|
+
* measuring it whole.
|
|
472
|
+
*/
|
|
473
|
+
measureBatchRows?: number;
|
|
474
|
+
}
|
|
475
|
+
/** A document laid out and paginated, and not yet drawn. */
|
|
476
|
+
interface PaginatedLayout {
|
|
477
|
+
/** The pages BroadPaper's own paginator decided. */
|
|
478
|
+
paged: PagedDocument;
|
|
479
|
+
/** Geometry measured by the engine, keyed by section. */
|
|
480
|
+
geometry: GeometryMap;
|
|
481
|
+
/**
|
|
482
|
+
* Draws pages `[from, to)` — the whole document when no range is given — as one
|
|
483
|
+
* PDF. Pages keep their numbers in the whole document, so page 251 of a split
|
|
484
|
+
* document still says "251 of 1,300".
|
|
485
|
+
*/
|
|
486
|
+
draw(range?: {
|
|
487
|
+
from: number;
|
|
488
|
+
to: number;
|
|
489
|
+
}): Promise<{
|
|
490
|
+
pdf: Uint8Array;
|
|
491
|
+
pages: number;
|
|
492
|
+
layout: {
|
|
493
|
+
pages: FormePageInfo[];
|
|
494
|
+
} | null;
|
|
495
|
+
warnings: string[];
|
|
496
|
+
}>;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Resolves, measures and paginates without drawing.
|
|
500
|
+
*
|
|
501
|
+
* Separate from drawing for two reasons a long document made plain. The page
|
|
502
|
+
* count is known here, before the more expensive half of the work, so a caller
|
|
503
|
+
* with a ceiling can refuse or split first rather than after; and a document of
|
|
504
|
+
* a thousand pages can be drawn as several files from one layout, every one of
|
|
505
|
+
* them agreeing about where the page breaks fall and what the totals are.
|
|
506
|
+
*/
|
|
507
|
+
declare function layoutPaginated(opts: LayoutPaginatedOptions): Promise<PaginatedLayout>;
|
|
368
508
|
|
|
369
|
-
export { type Annotation, type EngineLoader, FormeEditorMeasurer, type FormeEditorMeasurerOptions, type FormeFont, FormeMeasurer, type FormeMeasurerOptions, type FormeNode, type FormePageInfo, type FormeRenderer, type LayoutElement, type LayoutPage, PX_TO_PT, type RenderFormePdfOptions, type RenderFormePdfResult, type RenderPaginatedResult, type TranslateContext, type Translated, UNBOUNDED_PAGE_HEIGHT, buildFormeDocument, geometryFromLayout, parseDeclarations, pxToPt, renderPdf, renderPdfPaginated, setEngineLoader, translate, translateStyle, vnodeToForme, watermarkNode };
|
|
509
|
+
export { type Annotation, type EngineLoader, type FontFileReader, type FontSourcePolicy, FormeEditorMeasurer, type FormeEditorMeasurerOptions, type FormeFont, FormeMeasurer, type FormeMeasurerOptions, type FormeNode, type FormePageInfo, type FormeRenderer, type LayoutElement, type LayoutPage, type LayoutPaginatedOptions, MEASURE_BATCH_ROWS, PX_TO_PT, type PaginatedLayout, type RenderFormePdfOptions, type RenderFormePdfResult, type RenderPaginatedResult, type ResolvedFonts, type TranslateContext, type Translated, UNBOUNDED_PAGE_HEIGHT, buildFormeDocument, geometryFromLayout, layoutPaginated, parseDeclarations, pxToPt, renderPdf, renderPdfPaginated, resolveFonts, setEngineLoader, setFontFileReader, translate, translateStyle, vnodeToForme, watermarkNode };
|
package/dist/index.js
CHANGED
|
@@ -2,26 +2,34 @@ import {
|
|
|
2
2
|
BroadPaper,
|
|
3
3
|
FormeEditorMeasurer,
|
|
4
4
|
FormeMeasurer,
|
|
5
|
+
MEASURE_BATCH_ROWS,
|
|
5
6
|
PX_TO_PT,
|
|
6
7
|
UNBOUNDED_PAGE_HEIGHT,
|
|
7
8
|
buildFormeDocument,
|
|
8
9
|
configure,
|
|
9
10
|
geometryFromLayout,
|
|
11
|
+
layoutPaginated,
|
|
10
12
|
parseDeclarations,
|
|
11
13
|
parseLicense,
|
|
12
14
|
pxToPt,
|
|
13
15
|
renderPdf,
|
|
14
16
|
renderPdfPaginated,
|
|
17
|
+
resolveFonts,
|
|
15
18
|
resolveLicenseStatus,
|
|
16
19
|
setEngineLoader,
|
|
20
|
+
setFontFileReader,
|
|
17
21
|
translate,
|
|
18
22
|
translateStyle,
|
|
19
23
|
verifyLicense,
|
|
20
24
|
vnodeToForme,
|
|
21
25
|
watermarkNode
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-QA4APTWM.js";
|
|
23
27
|
|
|
24
28
|
// src/engine.node.ts
|
|
29
|
+
var readFontFile = async (path) => {
|
|
30
|
+
const { readFile } = await import("fs/promises");
|
|
31
|
+
return new Uint8Array(await readFile(path));
|
|
32
|
+
};
|
|
25
33
|
var loadEngine = async () => {
|
|
26
34
|
const mod = await import("@formepdf/core");
|
|
27
35
|
return (doc) => mod.renderSerializedDocWithLayout(doc);
|
|
@@ -29,22 +37,27 @@ var loadEngine = async () => {
|
|
|
29
37
|
|
|
30
38
|
// src/index.ts
|
|
31
39
|
setEngineLoader(loadEngine);
|
|
40
|
+
setFontFileReader(readFontFile);
|
|
32
41
|
export {
|
|
33
42
|
BroadPaper,
|
|
34
43
|
FormeEditorMeasurer,
|
|
35
44
|
FormeMeasurer,
|
|
45
|
+
MEASURE_BATCH_ROWS,
|
|
36
46
|
PX_TO_PT,
|
|
37
47
|
UNBOUNDED_PAGE_HEIGHT,
|
|
38
48
|
buildFormeDocument,
|
|
39
49
|
configure,
|
|
40
50
|
geometryFromLayout,
|
|
51
|
+
layoutPaginated,
|
|
41
52
|
parseDeclarations,
|
|
42
53
|
parseLicense,
|
|
43
54
|
pxToPt,
|
|
44
55
|
renderPdf,
|
|
45
56
|
renderPdfPaginated,
|
|
57
|
+
resolveFonts,
|
|
46
58
|
resolveLicenseStatus,
|
|
47
59
|
setEngineLoader,
|
|
60
|
+
setFontFileReader,
|
|
48
61
|
translate,
|
|
49
62
|
translateStyle,
|
|
50
63
|
verifyLicense,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/engine.node.ts","../src/index.ts"],"sourcesContent":["/**\n * The engine as Node, Bun, Deno and edge runtimes get it.\n *\n * `@formepdf/core`'s own conditional exports pick the right build for the\n * runtime resolving it, so the bare specifier is the correct one here — this\n * module is only ever in a graph that is not a browser's.\n */\nimport type { EngineLoader } from \"./api.js\";\n\nexport const loadEngine: EngineLoader = async () => {\n const mod = (await import(\"@formepdf/core\")) as unknown as {\n renderSerializedDocWithLayout(d: Record<string, unknown>): Promise<{ pdf: Uint8Array; layout: unknown; warnings: string[] }>;\n };\n return (doc) => mod.renderSerializedDocWithLayout(doc);\n};\n","/**\n * @broadpaper/forme — the browserless PDF backend, for Node and every other\n * non-browser runtime. Browser bundlers resolve `./index.browser.ts` instead,\n * through the `browser` condition in this package's exports.\n */\nimport { setEngineLoader } from \"./api.js\";\nimport { loadEngine } from \"./engine.node.js\";\n\nsetEngineLoader(loadEngine);\n\nexport * from \"./api.js\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/engine.node.ts","../src/index.ts"],"sourcesContent":["/**\n * The engine as Node, Bun, Deno and edge runtimes get it.\n *\n * `@formepdf/core`'s own conditional exports pick the right build for the\n * runtime resolving it, so the bare specifier is the correct one here — this\n * module is only ever in a graph that is not a browser's.\n */\nimport type { EngineLoader } from \"./api.js\";\nimport type { FontFileReader } from \"./fonts.js\";\n\n/** A theme font that is not a data: URI or a web address is a file, as the engine itself reads one. */\nexport const readFontFile: FontFileReader = async (path) => {\n const { readFile } = await import(\"node:fs/promises\");\n return new Uint8Array(await readFile(path));\n};\n\nexport const loadEngine: EngineLoader = async () => {\n const mod = (await import(\"@formepdf/core\")) as unknown as {\n renderSerializedDocWithLayout(d: Record<string, unknown>): Promise<{ pdf: Uint8Array; layout: unknown; warnings: string[] }>;\n };\n return (doc) => mod.renderSerializedDocWithLayout(doc);\n};\n","/**\n * @broadpaper/forme — the browserless PDF backend, for Node and every other\n * non-browser runtime. Browser bundlers resolve `./index.browser.ts` instead,\n * through the `browser` condition in this package's exports.\n */\nimport { setEngineLoader, setFontFileReader } from \"./api.js\";\nimport { loadEngine, readFontFile } from \"./engine.node.js\";\n\nsetEngineLoader(loadEngine);\nsetFontFileReader(readFontFile);\n\nexport * from \"./api.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWO,IAAM,eAA+B,OAAO,SAAS;AAC1D,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,aAAkB;AACpD,SAAO,IAAI,WAAW,MAAM,SAAS,IAAI,CAAC;AAC5C;AAEO,IAAM,aAA2B,YAAY;AAClD,QAAM,MAAO,MAAM,OAAO,gBAAgB;AAG1C,SAAO,CAAC,QAAQ,IAAI,8BAA8B,GAAG;AACvD;;;ACbA,gBAAgB,UAAU;AAC1B,kBAAkB,YAAY;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broadpaper/forme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Browserless PDF backend for BroadPaper, built on the Forme (Rust/WASM) engine. Runs in Node, edge runtimes and the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pdf",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@formepdf/core": "^0.20.0",
|
|
52
52
|
"@formepdf/shared": "^0.20.0",
|
|
53
|
-
"@broadpaper/
|
|
54
|
-
"@broadpaper/
|
|
55
|
-
"@broadpaper/
|
|
53
|
+
"@broadpaper/core": "0.4.0",
|
|
54
|
+
"@broadpaper/license": "0.4.0",
|
|
55
|
+
"@broadpaper/blocks": "0.4.0"
|
|
56
56
|
},
|
|
57
57
|
"browser": "./dist/index.browser.js",
|
|
58
58
|
"scripts": {
|