@openpresentation/opf-render 0.13.1 → 0.15.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 +123 -86
- package/dist/chart-options.js +26 -21
- package/dist/charts.js +206 -123
- 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 +45 -40
- 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 +25 -31
- package/dist/script-fonts.js +6 -17
- package/dist/svg.d.ts +39 -38
- package/dist/svg.js +394 -376
- 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/preview-fonts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BrowserFontsHandle } from "./fonts-browser.js";
|
|
2
2
|
|
|
3
3
|
/** One face of the self-hosted font root: where it sits under the root, and its pinned hash. */
|
|
4
4
|
export interface PreviewBaseFace { family: string; weight: number; italic: boolean; file: string; sha256: string; license: string }
|
|
@@ -16,8 +16,8 @@ export declare function previewFontLayout(manifest?: unknown, lazyFiles?: readon
|
|
|
16
16
|
export declare function previewBaseFaces(manifest?: unknown): PreviewBaseFace[];
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* A browser
|
|
19
|
+
* A browser fonts handle (`loadFonts`) over a self-hosted font root: `<root>/base/<package>/<file>`, `<root>/lazy/fonts/<family>/<file>` and
|
|
20
20
|
* `<root>/scripts/<package>/<file>`. It starts with Roboto Regular and loads other faces on demand, hash-verified, only when a
|
|
21
|
-
* deck draws them.
|
|
21
|
+
* deck draws them. Handles are shared per root on a page.
|
|
22
22
|
*/
|
|
23
|
-
export declare function loadPreviewFonts(root: string, options?: { document?: Document; fetch?: typeof fetch; signal?: AbortSignal; registryOptions?: Record<string, unknown> }): Promise<
|
|
23
|
+
export declare function loadPreviewFonts(root: string, options?: { document?: Document; fetch?: typeof fetch; signal?: AbortSignal; registryOptions?: Record<string, unknown> }): Promise<BrowserFontsHandle>;
|
package/dist/preview-fonts.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// `copyPreviewFonts` (the `/fonts-node` side of this layout, `preview-fonts-node.js`) fills it from the installed packages.
|
|
9
9
|
// Every file is verified against its pinned SHA-256 by the registry before use, and only the faces a deck draws are fetched.
|
|
10
10
|
import { BUNDLED_FONT_MANIFEST } from "./font-manifest.js";
|
|
11
|
-
import { lazyFontList,
|
|
11
|
+
import { lazyFontList, loadFonts, splitStartupFaces } from "./fonts-browser.js";
|
|
12
12
|
|
|
13
13
|
/** The directory a package's files sit in under its kind: a vendored family's own name, or the npm package name without its scope. */
|
|
14
14
|
export function previewFontDirectory(pkg) {
|
|
@@ -47,9 +47,9 @@ const absolute = (root, document) => {
|
|
|
47
47
|
const shared = new Map();
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* A browser
|
|
51
|
-
* only when a deck draws it, face by face: vendored faces from `<root>/lazy/`, script faces from `<root>/scripts/`, the
|
|
52
|
-
* eager faces as the registry's extra lazy faces. One
|
|
50
|
+
* A browser fonts handle (`loadFonts`) over a font root. It starts with Roboto Regular (the renderer's fallback family) and loads
|
|
51
|
+
* the rest only when a deck draws it, face by face: vendored faces from `<root>/lazy/`, script faces from `<root>/scripts/`, the
|
|
52
|
+
* other eager faces as the registry's extra lazy faces. One handle is shared by every deck on the page that names the same root.
|
|
53
53
|
*/
|
|
54
54
|
export function loadPreviewFonts(root, options = {}) {
|
|
55
55
|
if (typeof root !== "string" || !root) return Promise.reject(new TypeError("loadPreviewFonts needs the URL of the font root."));
|
|
@@ -59,7 +59,8 @@ export function loadPreviewFonts(root, options = {}) {
|
|
|
59
59
|
const faces = previewBaseFaces();
|
|
60
60
|
const { startup, rest } = splitStartupFaces(faces, { startup: (face) => face.family === "Roboto" && face.weight === 400 && !face.italic });
|
|
61
61
|
const entry = (face) => ({ url: base + face.file, family: face.family, weight: face.weight, italic: face.italic, license: face.license, sha256: face.sha256 });
|
|
62
|
-
const promise =
|
|
62
|
+
const promise = loadFonts({
|
|
63
|
+
faces: startup.map(entry),
|
|
63
64
|
substitutionPolicy: "visual",
|
|
64
65
|
fallbackFamily: "Roboto",
|
|
65
66
|
scriptBaseUrl: `${base}scripts/`,
|
package/dist/raster.js
CHANGED
|
@@ -10,11 +10,24 @@ const DEFAULT_RASTER_BACKGROUND = "#FFFFFF";
|
|
|
10
10
|
const DEFAULT_PDF_MODE = "vector";
|
|
11
11
|
let bundledFontFilesCache=null;
|
|
12
12
|
|
|
13
|
+
// The faces a raster or PDF conversion draws with, from the fonts handle (`loadFonts()` from `/fonts-node`): its `fontFiles`, and
|
|
14
|
+
// whether the bundled base faces are added (`useBundledFonts`, default true; a Node handle already holds its own, so it says false) and
|
|
15
|
+
// whether system fonts load (`loadSystemFonts`, default false). Without a handle the bundled base faces draw.
|
|
16
|
+
function fontSettings(options) {
|
|
17
|
+
const fonts = options.fonts ?? {};
|
|
18
|
+
return { fontFiles: stringArray(fonts.fontFiles), useBundledFonts: fonts.useBundledFonts !== false, loadSystemFonts: fonts.loadSystemFonts === true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert one SVG (the output of `renderSvg` or `renderSlideSvg`) to PNG bytes. `fonts` is the handle `loadFonts()` returns: the raster draws
|
|
23
|
+
* with its font files, so the preview and the PNG use the same faces. Without it the bundled base faces draw.
|
|
24
|
+
*/
|
|
13
25
|
export async function svgToPng(svg, options = {}) {
|
|
14
26
|
const rendered = await rasterizeSvg(svg, options);
|
|
15
27
|
return rendered.png;
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
/** Convert SVG slides (the output of `renderSvg`) to a PDF, one page each. `fonts` is as for `svgToPng`; `mode` is `"vector"` (default) or `"raster"`. */
|
|
18
31
|
export async function svgToPdf(svgs, options = {}) {
|
|
19
32
|
const pdfInputs = normalizeSvgList(svgs);
|
|
20
33
|
if (!pdfInputs.length) {
|
|
@@ -49,13 +62,14 @@ export async function svgToPdf(svgs, options = {}) {
|
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
async function vectorPdf(inputs, options) {
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
const settings = fontSettings(options);
|
|
66
|
+
if (settings.loadSystemFonts) {
|
|
67
|
+
throw new OPFRenderError("pdf-system-fonts-unsupported", 'Vector PDF output embeds only the font files you supply: fonts.loadSystemFonts is not supported with mode "vector". Pass a fonts handle with fontFiles (and fontDirs), or use mode "raster".', { option: "fonts.loadSystemFonts" });
|
|
54
68
|
}
|
|
55
69
|
const scale = positiveNumber(options.rasterFallbackScale, "rasterFallbackScale", 2);
|
|
56
70
|
const fontFiles = [
|
|
57
|
-
...(
|
|
58
|
-
...
|
|
71
|
+
...(settings.useBundledFonts ? await bundledFontFiles() : []),
|
|
72
|
+
...settings.fontFiles
|
|
59
73
|
];
|
|
60
74
|
const svgs = [];
|
|
61
75
|
for (const input of inputs) svgs.push(await prepareRasterImages(normalizeSvgInput(input)));
|
|
@@ -100,12 +114,13 @@ async function rasterizeSvg(svgInput, options) {
|
|
|
100
114
|
const { Resvg } = await loadResvg();
|
|
101
115
|
const scale = positiveNumber(options.scale, "scale", DEFAULT_RASTER_SCALE);
|
|
102
116
|
// FF-45: resvg draws no COLRv1 or OT-SVG colour glyphs; the colour faces stay out of its font list (see color-fonts.js).
|
|
117
|
+
const settings = fontSettings(options);
|
|
103
118
|
const fontFiles = rasterFontFiles([
|
|
104
|
-
...(
|
|
105
|
-
...
|
|
119
|
+
...(settings.useBundledFonts ? await bundledFontFiles() : []),
|
|
120
|
+
...settings.fontFiles
|
|
106
121
|
]);
|
|
107
122
|
const font = {
|
|
108
|
-
loadSystemFonts:
|
|
123
|
+
loadSystemFonts: settings.loadSystemFonts,
|
|
109
124
|
fontFiles,
|
|
110
125
|
fontDirs: stringArray(options.fontDirs),
|
|
111
126
|
defaultFontFamily: options.defaultFontFamily ?? "Roboto",
|
|
@@ -178,8 +193,8 @@ async function bundledFontFiles() {
|
|
|
178
193
|
}
|
|
179
194
|
|
|
180
195
|
async function resolveBundledFontFiles() {
|
|
181
|
-
const {
|
|
182
|
-
return (await
|
|
196
|
+
const {loadFonts} = await import('./fonts-node.js');
|
|
197
|
+
return (await loadFonts()).fontFiles;
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
function normalizeSvgList(value) {
|
package/dist/script-font-pack.js
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
// stand in for proprietary script fonts in previews. The packages are optional
|
|
3
3
|
// peers, so the renderer install stays small; hosts install only what they use.
|
|
4
4
|
import { BUNDLED_FONT_MANIFEST } from "./font-manifest.js";
|
|
5
|
-
import { OPFFontError } from "./
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
// Optional core exports are read from the namespace so an older published core still loads.
|
|
9
|
-
import * as opfCore from "@openpresentation/opf";
|
|
5
|
+
import { OPFFontError } from "./font-registry.js";
|
|
6
|
+
import { SCRIPT_FONT_REPLACEMENTS, glyphFallbackFamilies, scriptOfCharacter, scriptsOfText } from "./script-fonts.js";
|
|
7
|
+
import { resolveScriptFonts } from "@openpresentation/opf/composition";
|
|
10
8
|
import { presentationFamilies } from "./lazy-fonts.js";
|
|
11
9
|
import { SYMBOL_SCRIPT, isSymbolEncodedFamily } from "./symbol-fonts.js";
|
|
12
10
|
|
|
@@ -33,7 +31,7 @@ export function scriptFontPackages(scripts) {
|
|
|
33
31
|
* Browser entries for the script pack. `baseUrl` is where the host serves the
|
|
34
32
|
* installed `@expo-google-fonts/*` packages (for example a copy of
|
|
35
33
|
* `node_modules/@expo-google-fonts`). Each entry carries the reviewed SHA-256,
|
|
36
|
-
* which `
|
|
34
|
+
* which `loadFonts` (browser) verifies before use.
|
|
37
35
|
*/
|
|
38
36
|
export function scriptFontEntries(scripts, {baseUrl}) {
|
|
39
37
|
return scriptPackageEntries(scriptFontPackages(scripts), {baseUrl});
|
|
@@ -55,17 +53,17 @@ export function scriptPackageEntries(packages, {baseUrl}) {
|
|
|
55
53
|
// Fields a preview never draws (checked against what the renderer and core composition draw).
|
|
56
54
|
// Everywhere: the schema URL, embedded assets, catalog records, speaker notes, ids, image alt text and sources,
|
|
57
55
|
// links, metadata and typed `extensions` passthrough. On the presentation: name (aria-label fallback), description,
|
|
58
|
-
// filename, author,
|
|
59
|
-
// On a slide: beat. The organization (name) and a slide's section are drawn only as
|
|
60
|
-
// furniture (`organization: true` / `section: true` in a design), so they count
|
|
61
|
-
// them. Everything else is drawn text: titles, text blocks, list items, runs, table cells, chart labels,
|
|
56
|
+
// filename, author, audience, purpose, tone, takeaway, duration, tags, narrative, language and version.
|
|
57
|
+
// On a slide: beat. The organization (name), the speaker (name and title) and a slide's section are drawn only as
|
|
58
|
+
// generated header/footer furniture (`organization: true` / `speaker: true` / `section: true` in a design), so they count
|
|
59
|
+
// only when a design asks for them; built-in variables copy their values into slide strings before this runs. Everything else is drawn text: titles, text blocks, list items, runs, table cells, chart labels,
|
|
62
60
|
// categories and series names, code, quotes, metrics, timelines, furniture text.
|
|
63
61
|
const UNDRAWN_KEYS = Object.freeze(["$schema", "assets", "catalogs", "notes", "id", "alt", "altText", "src", "url", "href", "link", "metadata", "extensions"]);
|
|
64
|
-
const UNDRAWN_ROOT_KEYS = Object.freeze(["name", "description", "filename", "author", "authors", "creator", "
|
|
62
|
+
const UNDRAWN_ROOT_KEYS = Object.freeze(["name", "description", "filename", "author", "authors", "creator", "audience", "purpose", "tone", "takeaway", "duration", "tags", "narrative", "language", "license", "version", "keywords"]);
|
|
65
63
|
const UNDRAWN_SLIDE_KEYS = Object.freeze(["beat"]);
|
|
66
64
|
const UNDRAWN_VALUE = /^(?:https?|data|blob|pkg|file|mailto):\S*$/i;
|
|
67
65
|
|
|
68
|
-
/** True when a design in the presentation turns on generated `field` furniture (`organization` or `section`). */
|
|
66
|
+
/** True when a design in the presentation turns on generated `field` furniture (`organization`, `speaker` or `section`). */
|
|
69
67
|
function furnitureUses(presentation, field) {
|
|
70
68
|
const designs = [presentation?.design, ...(Array.isArray(presentation?.slides) ? presentation.slides.map(slide => slide?.design) : [])];
|
|
71
69
|
const pattern = new RegExp(`"${field}"\\s*:\\s*true`);
|
|
@@ -76,6 +74,7 @@ function furnitureUses(presentation, field) {
|
|
|
76
74
|
export function* drawnStrings(presentation) {
|
|
77
75
|
const undrawn = new Set(UNDRAWN_KEYS), undrawnRoot = new Set(UNDRAWN_ROOT_KEYS), undrawnSlide = new Set(UNDRAWN_SLIDE_KEYS);
|
|
78
76
|
if (!furnitureUses(presentation, "organization")) undrawnRoot.add("organization");
|
|
77
|
+
if (!furnitureUses(presentation, "speaker")) undrawnRoot.add("speaker");
|
|
79
78
|
if (!furnitureUses(presentation, "section")) undrawnSlide.add("section");
|
|
80
79
|
function* visit(item, level) {
|
|
81
80
|
if (typeof item === "string") { if (!UNDRAWN_VALUE.test(item)) yield item; }
|
|
@@ -92,21 +91,17 @@ export function* drawnStrings(presentation) {
|
|
|
92
91
|
|
|
93
92
|
/**
|
|
94
93
|
* The script profile of a presentation: core `resolveScriptFonts` output, exactly what the renderer plans
|
|
95
|
-
* with. When the
|
|
96
|
-
*
|
|
94
|
+
* with. When it throws, the renderer ignores the document language, so the profile is empty and Han text is
|
|
95
|
+
* Simplified Chinese, as drawn. `renderOptions.catalogs` (the host's registered catalogs) resolve as they do for the renderer.
|
|
97
96
|
*/
|
|
98
|
-
export function presentationScriptProfile(presentation) {
|
|
99
|
-
|
|
100
|
-
try { return opfCore.resolveScriptFonts(presentation); } catch { /* the renderer falls back the same way */ }
|
|
101
|
-
}
|
|
97
|
+
export function presentationScriptProfile(presentation, renderOptions) {
|
|
98
|
+
try { return resolveScriptFonts(presentation, renderOptions?.catalogs !== undefined ? { catalogs: renderOptions.catalogs } : {}); } catch { /* the renderer falls back the same way */ }
|
|
102
99
|
return {};
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
const servedScripts = () => new Set(BUNDLED_FONT_MANIFEST.packages.filter(item => item.pack === "scripts").flatMap(item => item.scripts));
|
|
106
103
|
const cjkKeys = ["Jpan", "Hans", "Hant", "Kore"];
|
|
107
104
|
const cjkCharacters = new Set(["Hani", "Hira", "Kana", "Hang", "Bopo"]);
|
|
108
|
-
/** True when the renderer has per-character glyph fallback (FF-19 glyphFallbackFamilies). */
|
|
109
|
-
const hasGlyphFallback = () => typeof scriptFontModule.glyphFallbackFamilies === "function";
|
|
110
105
|
|
|
111
106
|
/**
|
|
112
107
|
* ISO 15924 script whose pinned face a family resolves to: a proprietary script font (Yu Gothic: Jpan), a pinned face itself
|
|
@@ -116,7 +111,7 @@ function scriptOfFamily(family) {
|
|
|
116
111
|
const name = String(family ?? "").toLowerCase();
|
|
117
112
|
if (!name) return undefined;
|
|
118
113
|
if (isSymbolEncodedFamily(name)) return SYMBOL_SCRIPT;
|
|
119
|
-
const rule =
|
|
114
|
+
const rule = SCRIPT_FONT_REPLACEMENTS.find(item => item.requestedFamily.toLowerCase() === name);
|
|
120
115
|
if (rule) return rule.script;
|
|
121
116
|
return BUNDLED_FONT_MANIFEST.packages.find(item => item.pack === "scripts" && item.faces.some(face => face.family.toLowerCase() === name))?.scripts[0];
|
|
122
117
|
}
|
|
@@ -128,9 +123,9 @@ function scriptOfFamily(family) {
|
|
|
128
123
|
* scheme (`slide.design`), so each such slide is resolved too. `han` is the CJK script the scheme names, which tells
|
|
129
124
|
* Han-only text apart when the language does not (a Yu Gothic deck whose language is English).
|
|
130
125
|
*
|
|
131
|
-
* `renderOptions` are the host's `renderSvg` options
|
|
132
|
-
*
|
|
133
|
-
*
|
|
126
|
+
* `renderOptions` are the host's `renderSvg` options: their `catalogs` (core `Catalog[]`) go to core's `resolveScriptFonts`
|
|
127
|
+
* unchanged, and the families the renderer resolves per slide count too, so a scheme that exists only in the host's catalogs
|
|
128
|
+
* and names a script font still loads that font.
|
|
134
129
|
*/
|
|
135
130
|
function designScripts(presentation, profile, renderOptions) {
|
|
136
131
|
const scripts = new Set();
|
|
@@ -144,10 +139,10 @@ function designScripts(presentation, profile, renderOptions) {
|
|
|
144
139
|
}
|
|
145
140
|
};
|
|
146
141
|
note(profile);
|
|
147
|
-
if (
|
|
142
|
+
if (Array.isArray(presentation?.slides)) {
|
|
148
143
|
presentation.slides.forEach((slide, slideIndex) => {
|
|
149
144
|
if (!slide || typeof slide !== "object" || !slide.design || typeof slide.design !== "object") return;
|
|
150
|
-
try { note(
|
|
145
|
+
try { note(resolveScriptFonts(presentation, { slideIndex, ...(renderOptions?.catalogs !== undefined ? { catalogs: renderOptions.catalogs } : {}) })); } catch { /* the renderer reports it when it draws the slide */ }
|
|
151
146
|
});
|
|
152
147
|
}
|
|
153
148
|
if (renderOptions?.catalogs) {
|
|
@@ -177,7 +172,8 @@ function* namedFontFamilies(value, key) {
|
|
|
177
172
|
* `renderSvg` options) matter only for font schemes the host supplies in `renderOptions.catalogs`, which then resolve
|
|
178
173
|
* like the renderer resolves them; a document that then does not resolve throws what `renderSvg` throws for it.
|
|
179
174
|
*/
|
|
180
|
-
export function analyzePresentationScripts(presentation, profile
|
|
175
|
+
export function analyzePresentationScripts(presentation, profile, renderOptions) {
|
|
176
|
+
profile ??= presentationScriptProfile(presentation, renderOptions);
|
|
181
177
|
const scripts = new Set(), cjk = new Set();
|
|
182
178
|
const design = designScripts(presentation, profile, renderOptions);
|
|
183
179
|
// Han-only text in a deck whose language is not CJK draws with the scheme's own CJK face when the scheme names one.
|
|
@@ -230,13 +226,12 @@ export function autoScriptSelection(presentation, options) {
|
|
|
230
226
|
* renderer reports `missing-glyph` for them.
|
|
231
227
|
*/
|
|
232
228
|
export function nextFallbackPackage(analysis, { covers, loaded }) {
|
|
233
|
-
if (!hasGlyphFallback()) return undefined;
|
|
234
229
|
const packages = BUNDLED_FONT_MANIFEST.packages.filter(item => item.pack === "scripts" && item.scripts.some(script => cjkKeys.includes(script)));
|
|
235
230
|
const primary = new Set([...analysis.detected, ...(analysis.design ?? [])].filter(script => cjkKeys.includes(script))).size;
|
|
236
231
|
if (packages.filter(item => loaded.has(item.name)).length >= primary + 1) return undefined;
|
|
237
232
|
for (const character of analysis.cjk) {
|
|
238
233
|
if (covers(character)) continue;
|
|
239
|
-
for (const family of
|
|
234
|
+
for (const family of glyphFallbackFamilies(character, analysis.profile, analysis.profile?.serif === true)) {
|
|
240
235
|
const pkg = packages.find(item => !loaded.has(item.name) && item.faces.some(face => face.family === family));
|
|
241
236
|
if (pkg) return pkg;
|
|
242
237
|
}
|
|
@@ -244,8 +239,7 @@ export function nextFallbackPackage(analysis, { covers, loaded }) {
|
|
|
244
239
|
return undefined;
|
|
245
240
|
}
|
|
246
241
|
|
|
247
|
-
/** Drawn Han, kana or Hangul characters (at most `limit`) that no loaded script face covers.
|
|
242
|
+
/** Drawn Han, kana or Hangul characters (at most `limit`) that no loaded script face covers. */
|
|
248
243
|
export function uncoveredCjkCharacters(analysis, { covers, limit = 16 }) {
|
|
249
|
-
if (!hasGlyphFallback()) return [];
|
|
250
244
|
return [...analysis.cjk].filter(character => !covers(character)).slice(0, limit);
|
|
251
245
|
}
|
package/dist/script-fonts.js
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
// is reported as a note. `glyphFallback: "none"` (strict mode) keeps the exact
|
|
17
17
|
// faces and lets a missing glyph raise `missing-glyph`.
|
|
18
18
|
|
|
19
|
-
import { fontPolicyFor } from "
|
|
20
|
-
import {
|
|
19
|
+
import { fontPolicyFor } from "@openpresentation/opf/font-policy";
|
|
20
|
+
import { scriptFontRole } from "@openpresentation/opf/composition";
|
|
21
|
+
import { SYMBOL_PLACEHOLDER, SYMBOL_SCRIPT, mapSymbolAdvances, symbolEncodingFamily, symbolPreviewFaces } from "./symbol-fonts.js";
|
|
22
|
+
export { scriptFontRole };
|
|
21
23
|
|
|
22
24
|
const freeze = value => { if (value && typeof value === "object") { for (const child of Object.values(value)) freeze(child); Object.freeze(value); } return value; };
|
|
23
25
|
|
|
@@ -171,23 +173,10 @@ export function adjustedFontSize(size, adjust) {
|
|
|
171
173
|
export const EMOJI_FONT_FAMILIES = SCRIPT_FONT_REPLACEMENTS.find(rule => rule.script === "Zsye").substitutes;
|
|
172
174
|
const emojiFaces = new Set(EMOJI_FONT_FAMILIES);
|
|
173
175
|
|
|
174
|
-
const eastAsianScripts = new Set(["Hani", "Hans", "Hant", "Hanb", "Jpan", "Kore", "Hang", "Jamo", "Hira", "Kana", "Hrkt", "Bopo", "Yiii"]);
|
|
175
|
-
const complexScripts = new Set([
|
|
176
|
-
"Arab", "Hebr", "Syrc", "Thaa", "Nkoo", "Adlm", "Rohg", "Mand", "Samr",
|
|
177
|
-
"Deva", "Beng", "Guru", "Gujr", "Orya", "Taml", "Telu", "Knda", "Mlym", "Sinh",
|
|
178
|
-
"Thai", "Laoo", "Tibt", "Mymr", "Khmr", "Mong", "Bali", "Java", "Lana", "Tale", "Talu", "Cakm", "Olck",
|
|
179
|
-
]);
|
|
180
176
|
const rtlScripts = new Set(["Arab", "Hebr", "Syrc", "Thaa", "Nkoo", "Adlm", "Rohg", "Mand", "Samr"]);
|
|
181
177
|
/** Scripts the latin slot covers directly; never split from the surrounding Latin run. */
|
|
182
178
|
const latinGroup = new Set(["Latn", "Cyrl", "Grek", "Zyyy", "Zinh", "Zzzz"]);
|
|
183
179
|
|
|
184
|
-
/** OOXML script slot for an ISO 15924 script code (the same table as core `scriptFontRole`). */
|
|
185
|
-
export function scriptFontRole(script) {
|
|
186
|
-
if (eastAsianScripts.has(script)) return "eastAsian";
|
|
187
|
-
if (complexScripts.has(script)) return "complexScript";
|
|
188
|
-
return "latin";
|
|
189
|
-
}
|
|
190
|
-
|
|
191
180
|
const detected = ["Latn", "Cyrl", "Grek", "Hani", "Hira", "Kana", "Hang", "Bopo", "Yiii", "Arab", "Hebr", "Syrc", "Thaa", "Nkoo", "Adlm", "Rohg", "Mand", "Samr",
|
|
192
181
|
"Deva", "Beng", "Guru", "Gujr", "Orya", "Taml", "Telu", "Knda", "Mlym", "Sinh", "Thai", "Laoo", "Tibt", "Mymr", "Khmr", "Mong", "Bali", "Java", "Lana",
|
|
193
182
|
"Tale", "Talu", "Cakm", "Olck", "Armn", "Geor", "Ethi", "Cher", "Tfng", "Vaii", "Zinh", "Zyyy"].map(code => [code, new RegExp(String.raw`^\p{Script=${code}}$`, "u")]);
|
|
@@ -564,7 +553,7 @@ export function createScriptFonts(profile = {}, measurement, options = {}) {
|
|
|
564
553
|
const hex = item.code.toString(16).toUpperCase().padStart(2, "0");
|
|
565
554
|
if (!entry.codes.includes(hex) && entry.codes.length < 32) { entry.codes.push(hex); if (entry.characters.length < 16) entry.characters.push(item.source); }
|
|
566
555
|
};
|
|
567
|
-
for (const item of
|
|
556
|
+
for (const item of mapSymbolAdvances(encoding, text)) {
|
|
568
557
|
if (item.code === null) { foreign += item.source; continue; }
|
|
569
558
|
flushForeign();
|
|
570
559
|
let drawn = item.unicode, family = null, placeholder = false;
|
|
@@ -600,7 +589,7 @@ export function createScriptFonts(profile = {}, measurement, options = {}) {
|
|
|
600
589
|
text = String(text ?? "");
|
|
601
590
|
// A symbol-encoded family (resolved styles carry `symbolEncoding`; an unresolved style names the family itself) is mapped code by
|
|
602
591
|
// code; `symbolEncoding: null` is the planner's own request for ordinary runs (characters that are not codes).
|
|
603
|
-
const encoding = style.symbolEncoding === null ? undefined : style.symbolEncoding ??
|
|
592
|
+
const encoding = style.symbolEncoding === null ? undefined : style.symbolEncoding ?? symbolEncodingFamily(style.fontFamily);
|
|
604
593
|
if (encoding) return text ? planSymbols(text, style, encoding) : [{text, family: style.fontFamily, own: true}];
|
|
605
594
|
// An emoji face (Noto Color Emoji for a Segoe UI Emoji run) draws emoji-presentation clusters only: its Latin text and digits
|
|
606
595
|
// are planned per cluster below, where they take a text face (FF-45).
|
package/dist/svg.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RenderFonts, ScriptFonts } from "./fonts.js";
|
|
2
|
+
import type { Catalog, Finding, UnresolvedReferenceDiagnostic } from "@openpresentation/opf";
|
|
2
3
|
import type { SlideComposition, LayoutDiagnostic, TextMeasurement } from "@openpresentation/opf/composition";
|
|
3
4
|
export declare const packageName = "@openpresentation/opf-render";
|
|
4
5
|
|
|
@@ -16,19 +17,12 @@ export declare const runtimePolicy: Readonly<{
|
|
|
16
17
|
deterministicLocalExecution: true;
|
|
17
18
|
}>;
|
|
18
19
|
|
|
20
|
+
/** What draws when nothing resolves: core's engine defaults (OPF 0.15 ships no catalog records), and the chart type a chart without `type` previews as. */
|
|
19
21
|
export declare const engineDefaults: Readonly<{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
narrative: "classic-story";
|
|
25
|
-
tone: "formal";
|
|
26
|
-
audience: "executives";
|
|
27
|
-
fontScheme: Readonly<{
|
|
28
|
-
pptx: Readonly<{ latin: "aptos"; ea: "microsoft-yahei"; cs: "nirmala-ui" }>;
|
|
29
|
-
google: Readonly<{ latin: "roboto"; ea: "noto-sans-sc"; cs: "noto-sans" }>;
|
|
30
|
-
}>;
|
|
31
|
-
chartTypes: readonly ["stacked-column-3x", "stacked-area-3x", "line-with-markers-3x"];
|
|
22
|
+
theme: typeof import("@openpresentation/opf/composition").ENGINE_DEFAULT_THEME;
|
|
23
|
+
colorScheme: typeof import("@openpresentation/opf/composition").ENGINE_DEFAULT_COLOR_SCHEME;
|
|
24
|
+
fontScheme: typeof import("@openpresentation/opf/composition").ENGINE_DEFAULT_FONT_SCHEME;
|
|
25
|
+
chartType: string;
|
|
32
26
|
}>;
|
|
33
27
|
|
|
34
28
|
export type RenderDiagnostic = LayoutDiagnostic | {
|
|
@@ -41,17 +35,10 @@ export type RenderDiagnostic = LayoutDiagnostic | {
|
|
|
41
35
|
scripts: string[];
|
|
42
36
|
characters: string[];
|
|
43
37
|
} | {
|
|
44
|
-
code: "unsupported-pattern" | "variable-example-used" | "
|
|
38
|
+
code: "unsupported-pattern" | "variable-example-used" | "variable-builtin-missing" | "date-needs-value" | "language-preview-unresolved";
|
|
45
39
|
path: string;
|
|
46
40
|
message: string;
|
|
47
|
-
} | {
|
|
48
|
-
/** A font-scheme id matched no record; the default font scheme (`aptos`) was used as the base. */
|
|
49
|
-
code: "unresolved-font-scheme";
|
|
50
|
-
path: string;
|
|
51
|
-
message: string;
|
|
52
|
-
id: string;
|
|
53
|
-
fallback: string;
|
|
54
|
-
} | {
|
|
41
|
+
} | UnresolvedReferenceDiagnostic | {
|
|
55
42
|
code: "unresolved-asset";
|
|
56
43
|
path: string;
|
|
57
44
|
message: string;
|
|
@@ -63,12 +50,18 @@ export type RenderDiagnostic = LayoutDiagnostic | {
|
|
|
63
50
|
};
|
|
64
51
|
|
|
65
52
|
export interface RenderSvgOptions {
|
|
66
|
-
|
|
53
|
+
/** Leave out slides marked `hidden: true`, as the player does. Default false: one SVG per slide. With true the result holds only the visible slides, in order, so an index no longer names the slide at that index. Read by the deck-level `renderSvg`; `renderSlideSvg` draws the slide it is given. */
|
|
54
|
+
skipHidden?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The fonts the deck is laid out and drawn with: the handle `loadFonts()` returns (from `@openpresentation/opf-render/fonts-node` or
|
|
57
|
+
* `/fonts-browser`), or any object with a `textMeasurement` and, to embed faces in each SVG, an `embeddedFonts` list. Without it layout uses
|
|
58
|
+
* core's portable width estimate and the SVG names the design fonts without embedding them.
|
|
59
|
+
*/
|
|
60
|
+
fonts?: RenderFonts;
|
|
67
61
|
/** `"chain"` (default): a character the resolved face lacks is drawn with the first bundled face that has it, and reported as `font-glyph-fallback`. `"none"`: exact faces, a missing glyph raises `missing-glyph`. */
|
|
68
62
|
glyphFallback?: "chain" | "none";
|
|
69
63
|
/** Unscaled reference-pixel clearance around supplied vector text outlines; default 1. */
|
|
70
64
|
textRasterPadding?: number;
|
|
71
|
-
embeddedFonts?: EmbeddedFont[];
|
|
72
65
|
strictAssets?: boolean;
|
|
73
66
|
imageResolver?: (src: string | undefined, context: { asset: unknown; path: string }) => string | null | undefined;
|
|
74
67
|
onDiagnostic?: (diagnostic: RenderDiagnostic) => void;
|
|
@@ -77,34 +70,38 @@ export interface RenderSvgOptions {
|
|
|
77
70
|
/**
|
|
78
71
|
* Values for the deck's template variables, keyed by variable id (core `resolveVariables`). A deck that uses
|
|
79
72
|
* content variables, or is marked `template: true`, is resolved to a concrete deck first. A template previews with
|
|
80
|
-
* each unfilled variable's example (reported as `variable-example-used`); a normal deck with an unfilled required
|
|
73
|
+
* each unfilled variable's example (reported as `variable-example-used`; a built-in such as `{{speaker.name}}` with no source value is reported as `variable-builtin-missing`); a normal deck with an unfilled required
|
|
81
74
|
* variable throws `unfilled-variables`. `false` draws the document as authored, with `{{id}}` tokens and
|
|
82
75
|
* `var:id` references visible and nothing resolved: the view an editor uses while the template itself is edited.
|
|
83
76
|
*/
|
|
84
77
|
variables?: Record<string, unknown> | false;
|
|
85
|
-
slideIndex?: number;
|
|
86
78
|
/**
|
|
87
79
|
* Today's calendar date (ISO YYYY-MM-DD) for `date: true` header/footer furniture. The renderer
|
|
88
80
|
* never reads a clock; without it a current date is reported as unresolved content.
|
|
89
81
|
*/
|
|
90
82
|
date?: string;
|
|
91
83
|
trace?: boolean;
|
|
92
|
-
catalogs?: Record<string, { records?: unknown[] } | unknown[]>;
|
|
93
84
|
/**
|
|
94
|
-
*
|
|
95
|
-
* `
|
|
96
|
-
*
|
|
85
|
+
* Catalogs the host registered (core `Catalog[]`), passed unchanged to core resolution: a reference the document does not
|
|
86
|
+
* embed resolves against the catalog whose `source` its group names, and the first entry is the default catalog for bare ids
|
|
87
|
+
* when the document omits `catalogs.default`. The renderer bundles and fetches no catalog; register the gallery snapshot with
|
|
88
|
+
* `import { defaultCatalog } from "@openpresentation/opf/catalog"` and `catalogs: [defaultCatalog]`. Omitted: only embedded
|
|
89
|
+
* records resolve, and anything else draws with core's engine defaults (reported as `unresolved-reference`).
|
|
90
|
+
* A malformed value throws core's `OPFCatalogsOptionError` (`code: "invalid-catalogs"`); a reference with an undeclared prefix
|
|
91
|
+
* (`foo:id` without `catalogs.foo`) fails the boundary check (`invalid-opf`, finding `opf/undeclared-catalog`).
|
|
97
92
|
*/
|
|
98
|
-
|
|
93
|
+
catalogs?: readonly Catalog[];
|
|
94
|
+
/** Fail with `unresolved-reference` (an OPFRenderError whose `details.diagnostics` list the references) instead of falling back when a reference resolves nowhere. Default false. */
|
|
95
|
+
strictReferences?: boolean;
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
export interface SvgToPngOptions {
|
|
99
|
+
/** The fonts the conversion draws with (Node): see `SvgToPngOptions` in the main entry. */
|
|
100
|
+
fonts?: RenderFonts;
|
|
102
101
|
scale?: number;
|
|
103
102
|
background?: string;
|
|
104
103
|
dpi?: number;
|
|
105
|
-
|
|
106
|
-
loadSystemFonts?: boolean;
|
|
107
|
-
fontFiles?: string[];
|
|
104
|
+
/** Extra directories of font files to draw with. */
|
|
108
105
|
fontDirs?: string[];
|
|
109
106
|
defaultFontFamily?: string;
|
|
110
107
|
sansSerifFamily?: string;
|
|
@@ -128,7 +125,8 @@ export interface ResolvedSlide {
|
|
|
128
125
|
index: number;
|
|
129
126
|
path: string;
|
|
130
127
|
slide: unknown;
|
|
131
|
-
layout
|
|
128
|
+
/** The layout record the slide names; absent when it names none or a reference that resolves nowhere (the slide then composes automatically). */
|
|
129
|
+
layout?: unknown;
|
|
132
130
|
design: {
|
|
133
131
|
theme: unknown;
|
|
134
132
|
colorScheme: unknown;
|
|
@@ -157,15 +155,18 @@ export interface RenderBinding {
|
|
|
157
155
|
export declare class OPFRenderError extends Error {
|
|
158
156
|
readonly code: string;
|
|
159
157
|
readonly details: Record<string, unknown>;
|
|
160
|
-
|
|
158
|
+
/** `invalid-opf`: the error findings of core `validate(presentation, { only: ["format"] })` (also `details.findings`; `details.report` is the whole report). */
|
|
159
|
+
readonly findings?: Finding[];
|
|
161
160
|
readonly path?: string;
|
|
162
161
|
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
export declare function resolvePresentation(input: unknown, options?: RenderSvgOptions): ResolvedPresentation;
|
|
166
165
|
|
|
167
|
-
|
|
166
|
+
/** The SVG of every slide of the deck, in order. */
|
|
167
|
+
export declare function renderSvg(input: unknown, options?: RenderSvgOptions): string[];
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
/** The SVG of one slide of the deck (`index` is zero-based; out of range throws `slide-index-out-of-range`). */
|
|
170
|
+
export declare function renderSlideSvg(input: unknown, index: number, options?: RenderSvgOptions): string;
|
|
170
171
|
|
|
171
172
|
|