@broadpaper/forme 0.3.0 → 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/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
  *
@@ -190,12 +262,13 @@ interface FormeEditorMeasurerOptions {
190
262
  /** The engine call, e.g. `renderSerializedDocWithLayout` from `@formepdf/core/browser`. */
191
263
  render: FormeRenderer;
192
264
  /**
193
- * Font files the engine should use, matching the families the document names.
265
+ * Fonts for the engine beyond the theme's own.
194
266
  *
195
- * May be a promise, because a browser host has to fetch the files and
196
- * `createMeasurer` has to return synchronously. Every measurement waits for it,
197
- * so nothing is ever measured — and cached — with the engine's standard faces
198
- * standing in for a font that was still arriving.
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.
199
272
  */
200
273
  fonts?: unknown[] | Promise<unknown[]>;
201
274
  /** Called for every distinct translation warning, once. */
@@ -219,6 +292,7 @@ declare class FormeEditorMeasurer {
219
292
  destroy(): void;
220
293
  private key;
221
294
  measure(sections: ResolvedNode[], width: number): Promise<GeometryMap>;
295
+ private warn;
222
296
  }
223
297
 
224
298
  /**
@@ -235,13 +309,6 @@ declare class FormeEditorMeasurer {
235
309
  * `renderPdf` returns Forme's own page geometry so callers can compare.
236
310
  */
237
311
 
238
- interface FormeFont {
239
- family: string;
240
- /** TTF/OTF bytes, a data: URI, or a filesystem path (Node only). */
241
- src: string | Uint8Array;
242
- weight?: number;
243
- style?: "normal" | "italic";
244
- }
245
312
  interface RenderFormePdfOptions {
246
313
  template: ReportTemplate | AnyVersionTemplate;
247
314
  registry: BlockRegistry;
@@ -259,8 +326,16 @@ interface RenderFormePdfOptions {
259
326
  creator?: string;
260
327
  lang?: string;
261
328
  };
262
- /** Fonts to embed. Without these Forme falls back to its standard faces. */
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
+ */
263
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;
264
339
  /** Emit a tagged (accessible) PDF. */
265
340
  tagged?: boolean;
266
341
  /** PDF/UA-1 conformance. Requires an embeddable font. */
@@ -431,4 +506,4 @@ interface PaginatedLayout {
431
506
  */
432
507
  declare function layoutPaginated(opts: LayoutPaginatedOptions): Promise<PaginatedLayout>;
433
508
 
434
- export { type Annotation, type EngineLoader, 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 TranslateContext, type Translated, UNBOUNDED_PAGE_HEIGHT, buildFormeDocument, geometryFromLayout, layoutPaginated, 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
@@ -14,16 +14,22 @@ import {
14
14
  pxToPt,
15
15
  renderPdf,
16
16
  renderPdfPaginated,
17
+ resolveFonts,
17
18
  resolveLicenseStatus,
18
19
  setEngineLoader,
20
+ setFontFileReader,
19
21
  translate,
20
22
  translateStyle,
21
23
  verifyLicense,
22
24
  vnodeToForme,
23
25
  watermarkNode
24
- } from "./chunk-2C3MAW2U.js";
26
+ } from "./chunk-QA4APTWM.js";
25
27
 
26
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
+ };
27
33
  var loadEngine = async () => {
28
34
  const mod = await import("@formepdf/core");
29
35
  return (doc) => mod.renderSerializedDocWithLayout(doc);
@@ -31,6 +37,7 @@ var loadEngine = async () => {
31
37
 
32
38
  // src/index.ts
33
39
  setEngineLoader(loadEngine);
40
+ setFontFileReader(readFontFile);
34
41
  export {
35
42
  BroadPaper,
36
43
  FormeEditorMeasurer,
@@ -47,8 +54,10 @@ export {
47
54
  pxToPt,
48
55
  renderPdf,
49
56
  renderPdfPaginated,
57
+ resolveFonts,
50
58
  resolveLicenseStatus,
51
59
  setEngineLoader,
60
+ setFontFileReader,
52
61
  translate,
53
62
  translateStyle,
54
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":";;;;;;;;;;;;;;;;;;;;;;;;;;AASO,IAAM,aAA2B,YAAY;AAClD,QAAM,MAAO,MAAM,OAAO,gBAAgB;AAG1C,SAAO,CAAC,QAAQ,IAAI,8BAA8B,GAAG;AACvD;;;ACNA,gBAAgB,UAAU;","names":[]}
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.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/core": "0.3.0",
54
- "@broadpaper/blocks": "0.3.0",
55
- "@broadpaper/license": "0.3.0"
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": {