@openpresentation/opf-render 0.13.1 → 0.14.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 +106 -76
- package/dist/chart-options.js +26 -21
- package/dist/charts.js +204 -117
- package/dist/deck-runtime.js +21 -34
- package/dist/element.d.ts +11 -6
- package/dist/element.js +28 -18
- package/dist/export-browser.d.ts +7 -1
- package/dist/export-browser.js +7 -6
- package/dist/font-compatibility.js +4 -4
- package/dist/font-registry.js +324 -0
- package/dist/fonts-browser.d.ts +25 -3
- package/dist/fonts-browser.js +54 -3
- package/dist/fonts-node.d.ts +32 -16
- package/dist/fonts-node.js +110 -50
- package/dist/fonts.d.ts +48 -27
- package/dist/fonts.js +7 -320
- package/dist/index.d.ts +36 -16
- package/dist/lazy-font-list.js +1 -1
- package/dist/lazy-fonts.js +7 -7
- package/dist/pdf-fonts.js +1 -1
- package/dist/player.d.ts +4 -4
- package/dist/player.js +5 -5
- package/dist/preview-fonts.d.ts +4 -4
- package/dist/preview-fonts.js +6 -5
- package/dist/raster.js +24 -9
- package/dist/script-font-pack.js +19 -26
- package/dist/script-fonts.js +6 -17
- package/dist/svg.d.ts +30 -14
- package/dist/svg.js +286 -266
- package/dist/symbol-fonts.js +22 -55
- package/package.json +2 -2
- package/dist/font-policy.js +0 -2734
- package/dist/symbol-encodings.js +0 -7
package/dist/deck-runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// RR-28: the part of the slideshow player and the <opf-deck> element that has no DOM of its own. It reads an OPF document,
|
|
2
|
-
// decides which slides play (hidden ones do not), renders each slide once with the renderer's own SVG output (
|
|
2
|
+
// decides which slides play (hidden ones do not), renders each slide once with the renderer's own SVG output (renderSlideSvg:
|
|
3
3
|
// there is no second layout engine) behind a face-level font gate, and synchronizes windows over a BroadcastChannel.
|
|
4
4
|
// Nothing here fetches anything but the font files the host's font root serves.
|
|
5
|
-
import {
|
|
5
|
+
import { renderSlideSvg } from "./svg.js";
|
|
6
6
|
|
|
7
7
|
/** Error raised for a deck that cannot be read, rendered or loaded. */
|
|
8
8
|
export class DeckError extends Error {
|
|
@@ -173,38 +173,24 @@ export function localIsoDate(now = new Date()) {
|
|
|
173
173
|
|
|
174
174
|
// --- font gate -----------------------------------------------------------------------------------------------------
|
|
175
175
|
|
|
176
|
-
const MAX_FONT_ROUNDS = 4;
|
|
177
|
-
|
|
178
176
|
/**
|
|
179
|
-
* The gate every render goes through: nothing draws a document before the faces it needs are loaded. `
|
|
180
|
-
*
|
|
181
|
-
* script faces the document draws are fetched, each hash-verified by the registry.
|
|
177
|
+
* The gate every render goes through: nothing draws a document before the faces it needs are loaded. `fonts` is a browser
|
|
178
|
+
* fonts handle (`loadFonts` from `/fonts-browser`); one without `pending` and `ensure` gates nothing. Face level: only the
|
|
179
|
+
* vendored and script faces the document draws are fetched, each hash-verified by the registry behind the handle.
|
|
182
180
|
*/
|
|
183
|
-
export function createFontGate(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
try { scripts = registry?.pendingScripts?.(document, options) ?? []; } catch { /* the renderer reports the document itself */ }
|
|
187
|
-
try { lazy = (registry?.pendingLazyFonts?.(document, options) ?? []).map((face) => face?.file ?? String(face)); } catch { /* likewise */ }
|
|
188
|
-
return { lazy, scripts };
|
|
189
|
-
};
|
|
181
|
+
export function createFontGate(fonts) {
|
|
182
|
+
// A document that does not resolve has nothing to load here: the renderer reports it when it draws.
|
|
183
|
+
const pending = (document, options) => { try { return fonts?.pending?.(document, options) ?? []; } catch { return []; } };
|
|
190
184
|
return {
|
|
191
|
-
pending
|
|
185
|
+
pending,
|
|
192
186
|
async ensure(document, { signal, renderOptions } = {}) {
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
for (let round = 0; round < MAX_FONT_ROUNDS; round++) {
|
|
196
|
-
const { lazy, scripts } = sources(document, renderOptions);
|
|
197
|
-
if (!lazy.length && !scripts.length) return;
|
|
198
|
-
signal?.throwIfAborted?.();
|
|
199
|
-
if (lazy.length) await registry.ensureLazyFonts?.(document, call);
|
|
200
|
-
if (scripts.length) await registry.ensureScripts?.(document, call);
|
|
201
|
-
}
|
|
202
|
-
} catch (cause) {
|
|
187
|
+
if (!pending(document, renderOptions).length) return;
|
|
188
|
+
try { await fonts.ensure(document, { ...renderOptions, signal }); } catch (cause) {
|
|
203
189
|
if (signal?.aborted) throw cause;
|
|
204
190
|
throw new DeckError("fonts-unavailable", `Fonts for this deck could not be loaded: ${cause?.message ?? cause}`, { cause });
|
|
205
191
|
}
|
|
206
|
-
const left =
|
|
207
|
-
if (left.
|
|
192
|
+
const left = pending(document, renderOptions);
|
|
193
|
+
if (left.length) throw new DeckError("fonts-unavailable", `Fonts for this deck did not finish loading: ${left.join(", ")}.`);
|
|
208
194
|
},
|
|
209
195
|
};
|
|
210
196
|
}
|
|
@@ -213,9 +199,9 @@ export function createFontGate(registry) {
|
|
|
213
199
|
|
|
214
200
|
/**
|
|
215
201
|
* One deck's rendering, shared by the element, the full-screen player and the speaker view: font loading once, each slide
|
|
216
|
-
* rendered once by `
|
|
202
|
+
* rendered once by `renderSlideSvg` and kept, so moving between slides never draws twice and every surface shows the same markup.
|
|
217
203
|
*
|
|
218
|
-
* `fonts` is a browser
|
|
204
|
+
* `fonts` is a browser fonts handle (or nothing: layout then uses estimated widths and the visitor's system sans-serif).
|
|
219
205
|
*/
|
|
220
206
|
export function createDeckStore({ document, fonts, renderOptions = {}, date } = {}) {
|
|
221
207
|
const deck = parseDeckDocument(document);
|
|
@@ -224,7 +210,8 @@ export function createDeckStore({ document, fonts, renderOptions = {}, date } =
|
|
|
224
210
|
const diagnostics = [];
|
|
225
211
|
const options = () => ({
|
|
226
212
|
...renderOptions,
|
|
227
|
-
|
|
213
|
+
// Inline slides use the page's own faces (the registry added them to the document), so only the measurement is passed: nothing is embedded.
|
|
214
|
+
...(fonts?.textMeasurement ? { fonts: { textMeasurement: fonts.textMeasurement } } : {}),
|
|
228
215
|
...(renderOptions.date === undefined && date !== undefined ? { date } : {}),
|
|
229
216
|
onDiagnostic(diagnostic) { diagnostics.push(diagnostic); renderOptions.onDiagnostic?.(diagnostic); },
|
|
230
217
|
});
|
|
@@ -243,12 +230,12 @@ export function createDeckStore({ document, fonts, renderOptions = {}, date } =
|
|
|
243
230
|
let svg = cache.get(index);
|
|
244
231
|
if (svg === undefined) {
|
|
245
232
|
try {
|
|
246
|
-
svg =
|
|
233
|
+
svg = renderSlideSvg(deck, index, { ...options(), validate: !validated });
|
|
247
234
|
} catch (cause) {
|
|
248
235
|
if (cause instanceof DeckError) throw cause;
|
|
249
|
-
// A document the schema rejects says where: the first
|
|
250
|
-
const
|
|
251
|
-
const where =
|
|
236
|
+
// A document the schema rejects says where: the first finding's path and message.
|
|
237
|
+
const finding = cause?.code === "invalid-opf" ? cause.findings?.[0] : undefined;
|
|
238
|
+
const where = finding ? ` (${finding.path || "/"}: ${finding.message})` : "";
|
|
252
239
|
throw new DeckError(cause?.code === "invalid-opf" ? "invalid-document" : "render-failed", `${cause?.message ?? "The slide could not be drawn."}${where}`, { cause });
|
|
253
240
|
}
|
|
254
241
|
validated = true;
|
package/dist/element.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BrowserFontsHandle } from "./fonts-browser.js";
|
|
2
|
+
import type { RenderFonts } from "./fonts.js";
|
|
2
3
|
import type { RenderSvgOptions } from "./svg.js";
|
|
3
4
|
import type { PlayerSession, PresentOptions, PlayerSlideChange } from "./player.js";
|
|
4
5
|
export { DeckError } from "./player.js";
|
|
@@ -32,10 +33,13 @@ export interface OpfDeckElement extends HTMLElement {
|
|
|
32
33
|
/** The 1-based number of the showing slide in the sequence that plays; set it to navigate. */
|
|
33
34
|
slide: number;
|
|
34
35
|
readonly total: number;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The fonts the deck draws with: the URL of the self-hosted font root (the `fonts` attribute), or a browser fonts handle the page
|
|
38
|
+
* already has (`loadFonts` from `/fonts-browser`), which wins over the attribute. The getter returns the handle when one is set, else the
|
|
39
|
+
* attribute's URL (an empty string without either).
|
|
40
|
+
*/
|
|
41
|
+
fonts: string | BrowserFontsHandle;
|
|
42
|
+
/** Extra `renderSlideSvg` options (catalogs, imageResolver, date, ...). Set before the deck loads, or call `reload()`. */
|
|
39
43
|
renderOptions: Partial<RenderSvgOptions>;
|
|
40
44
|
includeHidden: boolean;
|
|
41
45
|
thumbnails: boolean;
|
|
@@ -65,7 +69,8 @@ export interface RenderDeckHtmlOptions {
|
|
|
65
69
|
includeHidden?: boolean;
|
|
66
70
|
src?: string;
|
|
67
71
|
slide?: number;
|
|
68
|
-
fonts
|
|
72
|
+
/** A string is the font root URL written to the tag's `fonts` attribute; an object (a `loadFonts()` handle, or any `RenderFonts`) draws the markup with those fonts and writes no attribute. */
|
|
73
|
+
fonts?: string | RenderFonts;
|
|
69
74
|
label?: string;
|
|
70
75
|
thumbnails?: boolean;
|
|
71
76
|
present?: boolean;
|
package/dist/element.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
prepareSlideSvg, sanitizeSvgTree, slideInfo, slideLabel, svgDimensions, plainText,
|
|
7
7
|
} from "./deck-runtime.js";
|
|
8
8
|
import { loadPreviewFonts } from "./preview-fonts.js";
|
|
9
|
-
import {
|
|
9
|
+
import { renderSlideSvg } from "./svg.js";
|
|
10
10
|
|
|
11
11
|
export { DeckError } from "./deck-runtime.js";
|
|
12
12
|
export { loadPreviewFonts, previewBaseFaces, previewFontLayout } from "./preview-fonts.js";
|
|
@@ -74,7 +74,7 @@ function createElementClass() {
|
|
|
74
74
|
return class OpfDeckElement extends Base {
|
|
75
75
|
static get observedAttributes() { return ["src", "slide", "fonts", "thumbnails", "controls", "present", "include-hidden", "label", "keyboard"]; }
|
|
76
76
|
|
|
77
|
-
#root; #parts = {}; #built = false; #store; #cursor; #deck; #generation = 0; #abort; #documentValue; #
|
|
77
|
+
#root; #parts = {}; #built = false; #store; #cursor; #deck; #generation = 0; #abort; #documentValue; #fonts; #renderOptions = {};
|
|
78
78
|
#readyPromise = Promise.resolve(); #state = "idle"; #error; #observer; #thumbsBuilt = false; #pointer; #session; #announce = false;
|
|
79
79
|
#slideAttribute; #sheet;
|
|
80
80
|
|
|
@@ -124,13 +124,20 @@ function createElementClass() {
|
|
|
124
124
|
/** Number of slides in the sequence that plays. */
|
|
125
125
|
get total() { return this.#cursor?.total ?? 0; }
|
|
126
126
|
|
|
127
|
-
/**
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
get
|
|
133
|
-
set
|
|
127
|
+
/**
|
|
128
|
+
* The fonts the deck draws with: the URL of the self-hosted font root (the `fonts` attribute; see the package README), or a
|
|
129
|
+
* browser fonts handle the page already has (`loadFonts` from `/fonts-browser`), which wins over the attribute. Without either,
|
|
130
|
+
* layout uses estimated widths and system fonts.
|
|
131
|
+
*/
|
|
132
|
+
get fonts() { return this.#fonts ?? this.getAttribute("fonts") ?? ""; }
|
|
133
|
+
set fonts(value) {
|
|
134
|
+
if (value && typeof value === "object") { this.#fonts = value; if (this.isConnected && this.#built) this.#start(); return; }
|
|
135
|
+
const had = this.#fonts !== undefined, before = this.getAttribute("fonts");
|
|
136
|
+
this.#fonts = undefined;
|
|
137
|
+
if (value) this.setAttribute("fonts", String(value)); else this.removeAttribute("fonts");
|
|
138
|
+
// Dropping a handle changes what draws even when the attribute keeps its value.
|
|
139
|
+
if (had && before === this.getAttribute("fonts") && this.isConnected && this.#built) this.#start();
|
|
140
|
+
}
|
|
134
141
|
|
|
135
142
|
/** Extra `renderSvg` options (catalogs, imageResolver, date, ...). Set before the deck loads, or call `reload()`. */
|
|
136
143
|
get renderOptions() { return this.#renderOptions; }
|
|
@@ -165,7 +172,7 @@ function createElementClass() {
|
|
|
165
172
|
if (!this.#store || !this.#cursor) throw new DeckError("not-ready", "The deck is not loaded yet.");
|
|
166
173
|
const { present } = await import("./player.js");
|
|
167
174
|
const session = await present(this.#deck, {
|
|
168
|
-
store: this.#store, fonts: this.#
|
|
175
|
+
store: this.#store, fonts: this.#fonts ?? this.#resolvedFonts, renderOptions: this.#renderOptions,
|
|
169
176
|
includeHidden: this.includeHidden, startSlide: this.#cursor.number, returnFocus: this.#parts.viewport, ...options,
|
|
170
177
|
});
|
|
171
178
|
this.#session = session;
|
|
@@ -182,7 +189,7 @@ function createElementClass() {
|
|
|
182
189
|
|
|
183
190
|
// --- loading ----------------------------------------------------------------------------------------------------
|
|
184
191
|
|
|
185
|
-
#
|
|
192
|
+
#resolvedFonts;
|
|
186
193
|
|
|
187
194
|
#start() {
|
|
188
195
|
this.#abort?.abort();
|
|
@@ -202,22 +209,22 @@ function createElementClass() {
|
|
|
202
209
|
const deck = await this.#readDocument(signal);
|
|
203
210
|
if (stale()) return;
|
|
204
211
|
parseDeckDocument(deck);
|
|
205
|
-
let registry = this.#
|
|
206
|
-
const root = this.fonts;
|
|
212
|
+
let registry = this.#fonts;
|
|
213
|
+
const root = this.getAttribute("fonts");
|
|
207
214
|
if (!registry && root) {
|
|
208
215
|
try { registry = await loadPreviewFonts(root, { signal }); } catch (cause) {
|
|
209
216
|
if (stale()) return;
|
|
210
217
|
this.#report(new DeckError("fonts-unavailable", `The fonts at ${root} could not be loaded; the deck draws with estimated layout and system fonts.`, { cause }), false);
|
|
211
218
|
}
|
|
212
219
|
}
|
|
213
|
-
this.#
|
|
220
|
+
this.#resolvedFonts = registry;
|
|
214
221
|
const date = this.#renderOptions.date ?? localIsoDate();
|
|
215
222
|
let store = createDeckStore({ document: deck, fonts: registry, renderOptions: this.#renderOptions, date });
|
|
216
223
|
try { await store.ready(signal); } catch (cause) {
|
|
217
224
|
if (stale()) return;
|
|
218
225
|
if (!registry) throw cause;
|
|
219
226
|
this.#report(cause, false);
|
|
220
|
-
this.#
|
|
227
|
+
this.#resolvedFonts = undefined;
|
|
221
228
|
store = createDeckStore({ document: deck, renderOptions: this.#renderOptions, date });
|
|
222
229
|
}
|
|
223
230
|
if (stale()) return;
|
|
@@ -491,7 +498,9 @@ const escapeHtml = (value) => String(value).replace(/&/g, "&").replace(/</g,
|
|
|
491
498
|
/**
|
|
492
499
|
* Server-side markup for a deck: the slides as inline SVG inside the tag, which a page that has not loaded JavaScript (or a
|
|
493
500
|
* crawler, or a reader) shows as it is, and which the element replaces with its own shadow DOM when it upgrades. Pure and
|
|
494
|
-
* synchronous; layout uses estimated widths unless you pass `
|
|
501
|
+
* synchronous; layout uses estimated widths unless you pass a fonts handle as `fonts` (an object: `loadFonts()` from
|
|
502
|
+
* `/fonts-browser`, or one with a `textMeasurement` and `embeddedFonts`). A `fonts` string is the font root URL written to the
|
|
503
|
+
* element's `fonts` attribute.
|
|
495
504
|
*
|
|
496
505
|
* `slides`: `"first"` (default: the first slide plus a list of titles), `"all"` or a list of 1-based slide numbers.
|
|
497
506
|
* `embed` puts the document in an `application/opf+json` script child, so the element upgrades without a request.
|
|
@@ -502,13 +511,14 @@ export function renderDeckHtml(input, options = {}) {
|
|
|
502
511
|
const sequence = presentableIndexes(deck, { includeHidden: options.includeHidden });
|
|
503
512
|
const wanted = options.slides ?? "first";
|
|
504
513
|
const shown = wanted === "all" ? sequence.map((_, position) => position) : wanted === "first" ? [0] : wanted.map((number) => number - 1).filter((position) => position >= 0 && position < sequence.length);
|
|
505
|
-
const
|
|
514
|
+
const handle = options.fonts !== null && typeof options.fonts === "object" ? options.fonts : undefined;
|
|
515
|
+
const attributes = { src: options.src, slide: options.slide, fonts: handle ? undefined : options.fonts, label: options.label, ...options.attributes };
|
|
506
516
|
const attributeText = Object.entries(attributes).filter(([, value]) => value !== undefined && value !== false).map(([name, value]) => (value === true ? ` ${name}` : ` ${name}="${escapeHtml(value)}"`)).join("")
|
|
507
517
|
+ (options.thumbnails ? " thumbnails" : "") + (options.present ? " present" : "") + (options.includeHidden ? " include-hidden" : "");
|
|
508
518
|
const date = options.renderOptions?.date ?? options.date;
|
|
509
519
|
const figures = shown.map((position) => {
|
|
510
520
|
const index = sequence[position];
|
|
511
|
-
const svg =
|
|
521
|
+
const svg = renderSlideSvg(deck, index, { ...options.renderOptions, ...(handle ? { fonts: handle } : {}), ...(date ? { date } : {}) });
|
|
512
522
|
const { width, height } = svgDimensions(svg);
|
|
513
523
|
const prepared = prepareSlideSvg(svg).replace(/^<svg\b/, `<svg style="display:block;width:100%;height:auto;aspect-ratio:${width} / ${height}"`);
|
|
514
524
|
const label = slideLabel(slideInfo(deck, index), position + 1, sequence.length);
|
package/dist/export-browser.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PdfDiagnostic, PdfMetadata, SvgToPdfOptions } from "./index.js";
|
|
2
|
+
import type { FontsHandle } from "./fonts.js";
|
|
2
3
|
export type { PdfDiagnostic, PdfMetadata } from "./index.js";
|
|
3
4
|
|
|
4
5
|
export interface BrowserSvgToPngOptions {
|
|
@@ -12,7 +13,12 @@ export interface BrowserSvgToPngOptions {
|
|
|
12
13
|
export interface BrowserSvgToPdfOptions extends Pick<SvgToPdfOptions, "mode" | "background" | "serifFamily" | "metadata" | "tagged" | "compress" | "strict" | "rasterFallbackScale" | "defaultFontFamily" | "sansSerifFamily" | "monospaceFamily" | "onDiagnostic"> {
|
|
13
14
|
/** Raster mode: pixel density of each page image (default 2). */
|
|
14
15
|
scale?: number;
|
|
15
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The browser fonts handle (`loadFonts` from `/fonts-browser`): every face it holds can be embedded in the PDF, including script faces
|
|
18
|
+
* a standalone SVG does not carry. Faces an SVG embeds as `@font-face` data (the `embeddedFonts` of the handle `renderSvg` was given) need no entry.
|
|
19
|
+
*/
|
|
20
|
+
fonts?: FontsHandle;
|
|
21
|
+
/** Extra font faces as bytes (TrueType outlines) when there is no handle. */
|
|
16
22
|
fontData?: Array<{ data: Uint8Array; family?: string }>;
|
|
17
23
|
/** Checked between pages; an abort rejects with the signal's reason (an `AbortError`). */
|
|
18
24
|
signal?: AbortSignal;
|
package/dist/export-browser.js
CHANGED
|
@@ -5,8 +5,9 @@ import { OPFRenderError, packageName } from "./svg.js";
|
|
|
5
5
|
// has the same two names for a page. It uses the same vector PDF converter as Node (fontkit, bidi-js and pako, all pure
|
|
6
6
|
// JavaScript) over the SVG the preview draws, so the text stays real text in the same embedded subsets. What differs from
|
|
7
7
|
// Node is only how pixels are made: a canvas decodes pictures and draws PNGs. No system font is read (a browser has no way
|
|
8
|
-
// to hand one over) and nothing is fetched: faces come from the SVG's own @font-face data (
|
|
9
|
-
// from `
|
|
8
|
+
// to hand one over) and nothing is fetched: faces come from the SVG's own @font-face data (the `embeddedFonts` of the fonts handle
|
|
9
|
+
// `renderSvg` was given), from the faces the `fonts` handle holds (`loadFonts` from `/fonts-browser`, so script faces the SVG does not
|
|
10
|
+
// embed reach the PDF too), or from `fontData`.
|
|
10
11
|
|
|
11
12
|
const DEFAULT_DIMENSIONS = { width: 1280, height: 720 };
|
|
12
13
|
const MAX_PIXELS = 40_000_000;
|
|
@@ -31,21 +32,21 @@ export async function svgToPng(svg, options = {}) {
|
|
|
31
32
|
* Convert SVG slides to a PDF, one slide per page (1 SVG pixel is 1 PDF point, as in Node). `mode: "vector"` (default) writes
|
|
32
33
|
* real text, paths, gradients and images; `"raster"` draws each slide as an image (`scale`, default 2). Takes the options of
|
|
33
34
|
* the Node `svgToPdf` that make sense here (`metadata`, `tagged`, `strict`, `compress`, `onDiagnostic`, the generic family
|
|
34
|
-
* names, `rasterFallbackScale`)
|
|
35
|
-
*
|
|
35
|
+
* names, `rasterFallbackScale`), `fonts` (the handle `loadFonts()` returns: every face it holds can be embedded), `fontData`
|
|
36
|
+
* (`[{ data, family? }]`, extra face bytes without a handle), `signal` (an AbortSignal, checked between pages) and
|
|
37
|
+
* `onProgress({ page, pages })`.
|
|
36
38
|
*/
|
|
37
39
|
export async function svgToPdf(svgs, options = {}) {
|
|
38
40
|
const inputs = (Array.isArray(svgs) ? svgs : [svgs]).map(svgText);
|
|
39
41
|
if (!inputs.length) throw new OPFRenderError("empty-pdf", "svgToPdf requires at least one SVG slide.");
|
|
40
42
|
const mode = options.mode ?? "vector";
|
|
41
43
|
if (mode !== "vector" && mode !== "raster") throw new OPFRenderError("invalid-conversion-option", 'mode must be "vector" or "raster".', { option: "mode", value: mode });
|
|
42
|
-
if (options.loadSystemFonts === true) throw new OPFRenderError("pdf-system-fonts-unsupported", "A browser cannot hand system fonts to the PDF export: supply faces as @font-face data or `fontData`.", { option: "loadSystemFonts" });
|
|
43
44
|
if (mode === "raster") return rasterPdf(inputs, options);
|
|
44
45
|
const scale = positive(options.rasterFallbackScale, "rasterFallbackScale", 2);
|
|
45
46
|
const fontCss = fontFaceCss(inputs);
|
|
46
47
|
return svgsToVectorPdf(inputs, {
|
|
47
48
|
fontFiles: [], fontDirs: [],
|
|
48
|
-
fontData: Array.isArray(options.fontData) ? options.fontData : [],
|
|
49
|
+
fontData: [...(options.fonts?.registry?.exportFaces?.() ?? []), ...(Array.isArray(options.fontData) ? options.fontData : [])],
|
|
49
50
|
imageCodec: canvasImageCodec(),
|
|
50
51
|
defaultFontFamily: options.defaultFontFamily ?? "Roboto",
|
|
51
52
|
sansSerifFamily: options.sansSerifFamily ?? options.defaultFontFamily ?? "Roboto",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { FONT_POLICY } from "
|
|
1
|
+
import { FONT_POLICY } from "@openpresentation/opf/font-policy";
|
|
2
2
|
/** Curated substitution policy. Metric means upstream intent, not universal pixel identity. */
|
|
3
3
|
const metric = (requestedFamily, substitutes, source, note = "Standard regular, bold, italic and bold italic styles; verify coverage and font versions.") => ({requestedFamily, substitutes, compatibility:"metric", weights:[400,700], source, note});
|
|
4
4
|
const visual = (requestedFamily, substitutes, note = "Approximate appearance; measure again and expect reflow.") => ({requestedFamily, substitutes, compatibility:"visual", note});
|
|
5
|
-
// Rules before FF-31. Families that the OPF font policy table (
|
|
5
|
+
// Rules before FF-31. Families that the OPF font policy table (core `FONT_POLICY`) lists take their
|
|
6
6
|
// rule from the table below; the remaining legacy rules keep working for other families.
|
|
7
7
|
const LEGACY = [
|
|
8
8
|
metric("Calibri",["Carlito"],"https://github.com/googlefonts/carlito"),
|
|
@@ -58,7 +58,7 @@ const describe = replacement => replacement.measured
|
|
|
58
58
|
* preview never needs a download. Replacements drive measurement and drawing only; exporters keep
|
|
59
59
|
* writing the chosen family.
|
|
60
60
|
*/
|
|
61
|
-
const FROM_POLICY = FONT_POLICY.filter(row => row.replacement).map(row => ({
|
|
61
|
+
const FROM_POLICY = FONT_POLICY.families.filter(row => row.replacement).map(row => ({
|
|
62
62
|
requestedFamily: row.family,
|
|
63
63
|
substitutes: [row.replacement.family, ...(row.alternates ?? [])],
|
|
64
64
|
compatibility: row.replacement.compatibility,
|
|
@@ -80,7 +80,7 @@ export const FONT_COMPATIBILITY = Object.freeze([...FROM_POLICY, ...LEGACY.filte
|
|
|
80
80
|
// The row's metric claim is measured that way, so the renderer must shape (fonts.js) and draw (svg.js)
|
|
81
81
|
// the face that way too. Keyed by the replacement family: a Georgia deck is measured and drawn with
|
|
82
82
|
// Gelasio, and Gelasio requested by name is treated identically, so measurement and drawing never disagree.
|
|
83
|
-
const DISABLED_FEATURES = new Map(FONT_POLICY.filter(row => row.replacement?.disabledFeatures?.length).map(row => [row.replacement.family.toLowerCase(), Object.freeze([...row.replacement.disabledFeatures])]));
|
|
83
|
+
const DISABLED_FEATURES = new Map(FONT_POLICY.families.filter(row => row.replacement?.disabledFeatures?.length).map(row => [row.replacement.family.toLowerCase(), Object.freeze([...row.replacement.disabledFeatures])]));
|
|
84
84
|
/** OpenType feature tags to turn off for a preview face, from the font policy; undefined when none. */
|
|
85
85
|
export function disabledFeaturesFor(family) {
|
|
86
86
|
return typeof family === "string" ? DISABLED_FEATURES.get(family.trim().toLowerCase()) : undefined;
|