@openpresentation/opf-render 0.11.1 → 0.11.2
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 +2 -2
- package/dist/fonts.js +6 -1
- package/dist/script-font-pack.js +48 -6
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Aptos previews with Intos.** The Office pack includes Intos, Intos Display, Intos Narrow and Intos Serif (16 faces, about 12 MB, vendored under `fonts/intos/`, SIL OFL 1.1, pinned to upstream commit fef9315c14da9e4b23b4c3cac8e718998d4e4736 and per-file SHA-256, unmodified from the upstream files). They are the metric-compatible previews for Aptos, Aptos Display, Aptos Narrow and Aptos Serif, the default `aptos` font scheme: 0.000% mean and maximum width difference against Aptos 2.01 in all four styles, with equal vertical metrics. The letter shapes are Intos's own, not Aptos's. The exported PPTX still names Aptos; no Aptos file is bundled or embedded. A registry built without these faces (the base pack) has no Aptos substitute: visual policy falls back to Roboto or Carlito. Like the open families, Intos is `embed: "used"`: it is not in `registry.embeddedFonts` (the 33 eager npm faces), `prepareNodeFonts().options.embeddedFonts` supplies it, and an SVG embeds only the Intos faces its text draws. `registry.lazyFonts` lists the 51 vendored faces (35 open, 16 Intos) with package-relative file and sha256, and `loadBrowserFontRegistry(faces, {lazyFontsBaseUrl})` loads them on demand: `await registry.ensureLazyFonts(presentation)` fetches and verifies only the files the document's font families resolve to (the default Aptos scheme: Intos and Intos Display, 8 files, 5.9 MB), then adds them to the document and the registry together so measurement and painting never disagree. Hosts copy the vendored directories next to the page.
|
|
4
4
|
|
|
5
|
-
Version 0.11.1 adds the open replacement font families the policy routes to and complete Red Hat styles (no API change). Version 0.11.0 requires `@openpresentation/opf` ^0.11.2 (cover centering, Intos Aptos previews, lazy font loading, automatic script fonts and glyph fallback); use it with PPTX 0.11.0 and editor 0.10.0 so preview and export resolve the same core. Version 0.10.0 required `@openpresentation/opf` ^0.11.1 (font policy table, script fonts and right-to-left paragraphs, socials, slide-image treatments, formatted slide-number and date furniture with the host `date` option, per-item alignment). See the changelog for the intentional preview-output changes.
|
|
5
|
+
Version 0.11.2 makes `scripts: 'auto'` load the script faces a font scheme names and adds `details.loadedFaceHasGlyph` to `missing-glyph` (no export change). Version 0.11.1 adds the open replacement font families the policy routes to and complete Red Hat styles (no API change). Version 0.11.0 requires `@openpresentation/opf` ^0.11.2 (cover centering, Intos Aptos previews, lazy font loading, automatic script fonts and glyph fallback); use it with PPTX 0.11.0 and editor 0.10.0 so preview and export resolve the same core. Version 0.10.0 required `@openpresentation/opf` ^0.11.1 (font policy table, script fonts and right-to-left paragraphs, socials, slide-image treatments, formatted slide-number and date furniture with the host `date` option, per-item alignment). See the changelog for the intentional preview-output changes.
|
|
6
6
|
|
|
7
7
|
Version 0.9.0 required `@openpresentation/opf` ^0.11.0 and resolved content ColorRef / `variables` through core `resolveColorRef()`. Authored `#RRGGBB` paint stays authored. It retains the existing rendering APIs.
|
|
8
8
|
|
|
@@ -176,7 +176,7 @@ const png = await svgToPng(slides[0], options);
|
|
|
176
176
|
|
|
177
177
|
### Load only the script faces a document uses (`scripts: 'auto'`)
|
|
178
178
|
|
|
179
|
-
Hosts do not need to detect scripts themselves. `scripts: 'auto'` reads the presentation's drawn text and loads only the faces for the scripts it uses: a Latin, Greek or Cyrillic deck loads none, a Japanese deck loads Noto Sans JP, an Arabic deck the three Arabic packages. Text decides, so a deck whose `language` is `ja` but whose text is Latin still loads nothing. The document language only tells Han text apart (through core `resolveScriptFonts`, the same profile the renderer plans with; a core without it treats Han as Simplified Chinese, as the renderer does). Only drawn text counts (titles, text, list items, runs, table cells, chart labels, code, quotes, metrics, furniture); ids, image alt text and sources, URLs, assets, catalogs, speaker notes, `extensions`, a slide's `beat` and the deck's own name, description, filename, author, speaker, audience, purpose, tone, takeaway, duration, tags and narrative are ignored. The organization and a slide's `section` count only when a design turns on that generated header or footer field. Detection itemizes text exactly as drawing does, so with an East Asian language the curly quotes, dashes and ellipsis count as East Asian. `detectPresentationScripts(presentation)` and `autoScriptSelection(presentation)` (from `/fonts-node` and `/fonts-browser`) return the decision.
|
|
179
|
+
Hosts do not need to detect scripts themselves. `scripts: 'auto'` reads the presentation's drawn text and loads only the faces for the scripts it uses: a Latin, Greek or Cyrillic deck loads none, a Japanese deck loads Noto Sans JP, an Arabic deck the three Arabic packages. Text decides, so a deck whose `language` is `ja` but whose text is Latin still loads nothing. The one exception is a font scheme that names a script font (Yu Gothic, Meiryo, Malgun Gothic, Microsoft YaHei, Arabic Typesetting, Mangal or a pinned Noto family, on the deck or on a slide's `design`): its Latin text previews with that font's open replacement, so its face loads whatever the text, and Han-only text in such a deck draws with the scheme's CJK face. `detected` still lists only what the text draws; `scripts` is what to load. The document language only tells Han text apart (through core `resolveScriptFonts`, the same profile the renderer plans with; a core without it treats Han as Simplified Chinese, as the renderer does). Only drawn text counts (titles, text, list items, runs, table cells, chart labels, code, quotes, metrics, furniture); ids, image alt text and sources, URLs, assets, catalogs, speaker notes, `extensions`, a slide's `beat` and the deck's own name, description, filename, author, speaker, audience, purpose, tone, takeaway, duration, tags and narrative are ignored. The organization and a slide's `section` count only when a design turns on that generated header or footer field. Detection itemizes text exactly as drawing does, so with an East Asian language the curly quotes, dashes and ellipsis count as East Asian. `detectPresentationScripts(presentation)` and `autoScriptSelection(presentation)` (from `/fonts-node` and `/fonts-browser`) return the decision.
|
|
180
180
|
|
|
181
181
|
```js
|
|
182
182
|
// Node: pass the presentation with scripts: 'auto'. Auto never fails on a package that is not installed
|
package/dist/fonts.js
CHANGED
|
@@ -187,7 +187,12 @@ export function createFontRegistry(entries, options = {}) {
|
|
|
187
187
|
if (value===undefined) {
|
|
188
188
|
if (options.strictGlyphs!==false) for(const character of text) {
|
|
189
189
|
if (/\p{Default_Ignorable_Code_Point}/u.test(character)) continue;
|
|
190
|
-
|
|
190
|
+
const code=character.codePointAt(0);
|
|
191
|
+
if (!face.font.hasGlyphForCodePoint(code)) {
|
|
192
|
+
// No loaded face has it: the script faces for this text are not loaded (glyph fallback needs a face to fall back to).
|
|
193
|
+
const loadedElsewhere=faces.some(other=>other!==face&&other.font.hasGlyphForCodePoint(code));
|
|
194
|
+
throw new OPFFontError("missing-glyph", `Font '${face.family}' cannot display U+${code.toString(16).toUpperCase()}.${loadedElsewhere?"":" No loaded font has it, so glyph fallback has no face to fall back to; if it is a script character, load that script's font (prepareNodeFonts scripts: 'auto' with the presentation, or registry.ensureScripts(presentation) in a browser)."}`, {path:style.path,fontFamily:face.family,character,...(loadedElsewhere?{}:{loadedFaceHasGlyph:false})});
|
|
195
|
+
}
|
|
191
196
|
}
|
|
192
197
|
}
|
|
193
198
|
if(value===undefined||includeOutline&&!Object.hasOwn(value,'outline')) {
|
package/dist/script-font-pack.js
CHANGED
|
@@ -106,18 +106,58 @@ const cjkCharacters = new Set(["Hani", "Hira", "Kana", "Hang", "Bopo"]);
|
|
|
106
106
|
/** True when the renderer has per-character glyph fallback (FF-19 glyphFallbackFamilies). */
|
|
107
107
|
const hasGlyphFallback = () => typeof scriptFontModule.glyphFallbackFamilies === "function";
|
|
108
108
|
|
|
109
|
+
/** ISO 15924 script whose pinned face a family resolves to: a proprietary script font (Yu Gothic: Jpan) or a pinned face itself (Noto Sans JP: Jpan). */
|
|
110
|
+
function scriptOfFamily(family) {
|
|
111
|
+
const name = String(family ?? "").toLowerCase();
|
|
112
|
+
if (!name) return undefined;
|
|
113
|
+
const rule = scriptFontModule.SCRIPT_FONT_REPLACEMENTS.find(item => item.requestedFamily.toLowerCase() === name);
|
|
114
|
+
if (rule) return rule.script;
|
|
115
|
+
return BUNDLED_FONT_MANIFEST.packages.find(item => item.pack === "scripts" && item.faces.some(face => face.family.toLowerCase() === name))?.scripts[0];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The scripts whose faces the deck's font schemes need in order to resolve at all. A scheme that names a script font
|
|
120
|
+
* (Yu Gothic, Malgun Gothic, Arabic Typesetting or a pinned Noto family) previews its Latin text with that font's
|
|
121
|
+
* open replacement, so the face is needed even when no drawn character has the script. A slide may override the
|
|
122
|
+
* scheme (`slide.design`), so each such slide is resolved too. `han` is the CJK script the scheme names, which tells
|
|
123
|
+
* Han-only text apart when the language does not (a Yu Gothic deck whose language is English).
|
|
124
|
+
*/
|
|
125
|
+
function designScripts(presentation, profile) {
|
|
126
|
+
const scripts = new Set();
|
|
127
|
+
let han;
|
|
128
|
+
const note = resolved => {
|
|
129
|
+
for (const family of [resolved?.heading?.latin, resolved?.body?.latin]) {
|
|
130
|
+
const script = scriptOfFamily(family);
|
|
131
|
+
if (!script) continue;
|
|
132
|
+
scripts.add(script);
|
|
133
|
+
if (!han && cjkKeys.includes(script)) han = script;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
note(profile);
|
|
137
|
+
if (typeof opfCore.resolveScriptFonts === "function" && Array.isArray(presentation?.slides)) {
|
|
138
|
+
presentation.slides.forEach((slide, slideIndex) => {
|
|
139
|
+
if (!slide || typeof slide !== "object" || !slide.design || typeof slide.design !== "object") return;
|
|
140
|
+
try { note(opfCore.resolveScriptFonts(presentation, { slideIndex })); } catch { /* the renderer reports it when it draws the slide */ }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return { scripts: [...scripts].sort(), han };
|
|
144
|
+
}
|
|
145
|
+
|
|
109
146
|
/**
|
|
110
147
|
* What a preview of the presentation draws: its script profile, the scripts of its text (the renderer's own
|
|
111
|
-
* itemization, so language-dependent punctuation counts),
|
|
112
|
-
* have to draw with another CJK face.
|
|
148
|
+
* itemization, so language-dependent punctuation counts), the scripts its font schemes need (`design`, for example
|
|
149
|
+
* Jpan for a Yu Gothic scheme) and the CJK characters that a glyph fallback might have to draw with another CJK face.
|
|
113
150
|
*/
|
|
114
151
|
export function analyzePresentationScripts(presentation, profile = presentationScriptProfile(presentation)) {
|
|
115
152
|
const scripts = new Set(), cjk = new Set();
|
|
153
|
+
const design = designScripts(presentation, profile);
|
|
154
|
+
// Han-only text in a deck whose language is not CJK draws with the scheme's own CJK face when the scheme names one.
|
|
155
|
+
const itemizing = design.han && !cjkKeys.includes(profile?.script) ? { ...profile, script: design.han } : profile;
|
|
116
156
|
for (const text of drawnStrings(presentation)) {
|
|
117
|
-
for (const script of scriptsOfText(text,
|
|
157
|
+
for (const script of scriptsOfText(text, itemizing)) scripts.add(script);
|
|
118
158
|
for (const character of text) if (character.codePointAt(0) > 0x2E7F && cjkCharacters.has(scriptOfCharacter(character))) cjk.add(character);
|
|
119
159
|
}
|
|
120
|
-
return { profile, detected: [...scripts].sort(), cjk };
|
|
160
|
+
return { profile, detected: [...scripts].sort(), design: design.scripts, cjk };
|
|
121
161
|
}
|
|
122
162
|
|
|
123
163
|
/**
|
|
@@ -135,7 +175,9 @@ export function detectPresentationScripts(presentation, { profile } = {}) {
|
|
|
135
175
|
/** The scripts of an analysis that a pinned pack serves (`scripts`, the input for `scriptFontPackages`) and those it does not. */
|
|
136
176
|
export function scriptSelectionOf(analysis) {
|
|
137
177
|
const served = servedScripts();
|
|
138
|
-
|
|
178
|
+
// `scripts` are the faces to load: the text's scripts and the ones its font schemes need; `detected` stays what the text draws.
|
|
179
|
+
const needed = [...new Set([...analysis.detected, ...(analysis.design ?? [])])].sort();
|
|
180
|
+
return { detected: analysis.detected, scripts: needed.filter(script => served.has(script)), unavailable: analysis.detected.filter(script => !served.has(script)) };
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
/**
|
|
@@ -160,7 +202,7 @@ export function autoScriptSelection(presentation, options) {
|
|
|
160
202
|
export function nextFallbackPackage(analysis, { covers, loaded }) {
|
|
161
203
|
if (!hasGlyphFallback()) return undefined;
|
|
162
204
|
const packages = BUNDLED_FONT_MANIFEST.packages.filter(item => item.pack === "scripts" && item.scripts.some(script => cjkKeys.includes(script)));
|
|
163
|
-
const primary = analysis.detected.filter(script => cjkKeys.includes(script)).
|
|
205
|
+
const primary = new Set([...analysis.detected, ...(analysis.design ?? [])].filter(script => cjkKeys.includes(script))).size;
|
|
164
206
|
if (packages.filter(item => loaded.has(item.name)).length >= primary + 1) return undefined;
|
|
165
207
|
for (const character of analysis.cjk) {
|
|
166
208
|
if (covers(character)) continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-render",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Deterministic local OPF renderer for SVG, PNG, and PDF output.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"sideEffects": false,
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "node scripts/build.mjs",
|
|
59
|
-
"typecheck": "node --check src/index.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check test/golden.mjs && node --check src/fonts.js && node --check src/script-fonts.js && node --check src/script-font-pack.js && node --check test/script-fonts.mjs && node --check test/glyph-fallback.mjs && node --check test/script-fonts-browser.mjs && node --check test/script-fonts-auto.mjs && node --check test/script-fonts-auto-robust.mjs && node --check test/script-fonts-auto-browser.mjs && node --check src/fonts-node.js && node --check src/font-compatibility.js && node --check test/office-fonts.mjs && node --check test/font-policy.mjs && node --check test/open-fonts.mjs && node --check test/aptos-preview.mjs && node --check test/aptos-preview-browser.mjs && node --check test/lazy-fonts.mjs && node --check test/lazy-fonts-browser.mjs && node --check src/font-policy.js && node --check scripts/update-font-policy.mjs && node --check src/fonts-browser.js && node --check test/fonts-browser.mjs && node --check src/svg.js && node --check src/raster.js && node --check src/raster-images.js && node --check test/webp.mjs && node --check test/jpeg-orientation.mjs && node --check scripts/build-browser-check.mjs && node --check test/jpeg-browser.js && node --check test/rich-table.mjs && node --check test/rich-tab.mjs && node --check test/rich-tab-browser.mjs && node --check test/rich-tab-estimated-browser.mjs && node --check test/styled-table.mjs && node --check test/color-references.mjs && node --check test/default-font-scheme.mjs && node --check test/chart-colors.mjs && node --check test/chart-axis.mjs && node --check test/image-placeholders.mjs && node --check test/image-placeholders-browser.mjs && node --check test/accepted-text.mjs && node --check test/accepted-text-fixtures.mjs && node --check test/accepted-text-browser.mjs && node --check test/rich-spacing-browser.mjs && node --check test/rich-flow-browser.mjs && node --check test/rich-flow-review-sheets.mjs && node --check test/furniture.mjs && node --check test/socials.mjs && node --check test/slide-image.mjs && node --check test/slide-image-treatments.mjs && node --check test/item-alignment.mjs && node --check test/cover-centering.mjs && node --check scripts/font-license.mjs && node --check test/font-licenses.mjs && node --check scripts/check-font-hotlinks.mjs && node --check scripts/check-font-hotlinks.test.mjs",
|
|
59
|
+
"typecheck": "node --check src/index.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check test/golden.mjs && node --check src/fonts.js && node --check src/script-fonts.js && node --check src/script-font-pack.js && node --check test/script-fonts.mjs && node --check test/glyph-fallback.mjs && node --check test/script-fonts-browser.mjs && node --check test/script-fonts-auto.mjs && node --check test/script-fonts-auto-robust.mjs && node --check test/script-fonts-auto-browser.mjs && node --check src/fonts-node.js && node --check src/font-compatibility.js && node --check test/office-fonts.mjs && node --check test/font-policy.mjs && node --check test/open-fonts.mjs && node --check test/aptos-preview.mjs && node --check test/aptos-preview-browser.mjs && node --check test/lazy-fonts.mjs && node --check test/lazy-fonts-browser.mjs && node --check test/lazy-face-fallback.mjs && node --check test/lazy-face-fallback-browser.mjs && node --check src/font-policy.js && node --check scripts/update-font-policy.mjs && node --check src/fonts-browser.js && node --check test/fonts-browser.mjs && node --check src/svg.js && node --check src/raster.js && node --check src/raster-images.js && node --check test/webp.mjs && node --check test/jpeg-orientation.mjs && node --check scripts/build-browser-check.mjs && node --check test/jpeg-browser.js && node --check test/rich-table.mjs && node --check test/rich-tab.mjs && node --check test/rich-tab-browser.mjs && node --check test/rich-tab-estimated-browser.mjs && node --check test/styled-table.mjs && node --check test/color-references.mjs && node --check test/default-font-scheme.mjs && node --check test/chart-colors.mjs && node --check test/chart-axis.mjs && node --check test/image-placeholders.mjs && node --check test/image-placeholders-browser.mjs && node --check test/accepted-text.mjs && node --check test/accepted-text-fixtures.mjs && node --check test/accepted-text-browser.mjs && node --check test/rich-spacing-browser.mjs && node --check test/rich-flow-browser.mjs && node --check test/rich-flow-review-sheets.mjs && node --check test/furniture.mjs && node --check test/socials.mjs && node --check test/slide-image.mjs && node --check test/slide-image-treatments.mjs && node --check test/item-alignment.mjs && node --check test/cover-centering.mjs && node --check scripts/font-license.mjs && node --check test/font-licenses.mjs && node --check scripts/check-font-hotlinks.mjs && node --check scripts/check-font-hotlinks.test.mjs",
|
|
60
60
|
"validate": "node scripts/validate-package.mjs",
|
|
61
|
-
"test": "npm run build && npm run check:fonts && node test/furniture.mjs && node test/socials.mjs && node test/font-outlines.mjs && node test/accepted-text.mjs && node test/image-placeholders.mjs && node test/slide-image.mjs && node test/slide-image-treatments.mjs && node test/chart-colors.mjs && node test/chart-axis.mjs && node test/smoke.mjs && node test/webp.mjs && node test/jpeg-orientation.mjs && node test/fonts.mjs && node test/office-fonts.mjs && node test/font-policy.mjs && node test/open-fonts.mjs && node test/aptos-preview.mjs && node test/lazy-fonts.mjs && node test/timeline.mjs && node test/quote-footer.mjs && node test/shared-quote.mjs && node test/rich-trace.mjs && node test/rich-table.mjs && node test/rich-tab.mjs && node test/color-references.mjs && node test/default-font-scheme.mjs && node test/golden-gate.mjs && node test/golden.mjs && node test/fonts-browser.mjs && node test/script-fonts.mjs && node test/glyph-fallback.mjs && node test/script-fonts-auto.mjs && node test/script-fonts-auto-robust.mjs && node test/design-preview.mjs && node test/item-alignment.mjs && node test/cover-centering.mjs && npm run build:browser-check && npm run test:styled-table",
|
|
61
|
+
"test": "npm run build && npm run check:fonts && node test/furniture.mjs && node test/socials.mjs && node test/font-outlines.mjs && node test/accepted-text.mjs && node test/image-placeholders.mjs && node test/slide-image.mjs && node test/slide-image-treatments.mjs && node test/chart-colors.mjs && node test/chart-axis.mjs && node test/smoke.mjs && node test/webp.mjs && node test/jpeg-orientation.mjs && node test/fonts.mjs && node test/office-fonts.mjs && node test/font-policy.mjs && node test/open-fonts.mjs && node test/aptos-preview.mjs && node test/lazy-fonts.mjs && node test/lazy-face-fallback.mjs && node test/timeline.mjs && node test/quote-footer.mjs && node test/shared-quote.mjs && node test/rich-trace.mjs && node test/rich-table.mjs && node test/rich-tab.mjs && node test/color-references.mjs && node test/default-font-scheme.mjs && node test/golden-gate.mjs && node test/golden.mjs && node test/fonts-browser.mjs && node test/script-fonts.mjs && node test/glyph-fallback.mjs && node test/script-fonts-auto.mjs && node test/script-fonts-auto-robust.mjs && node test/design-preview.mjs && node test/item-alignment.mjs && node test/cover-centering.mjs && npm run build:browser-check && npm run test:styled-table",
|
|
62
62
|
"check:fonts": "node test/font-licenses.mjs && npm run check:font-hotlinks",
|
|
63
63
|
"check:font-hotlinks": "node --test scripts/check-font-hotlinks.test.mjs && node scripts/check-font-hotlinks.mjs",
|
|
64
64
|
"test:golden": "npm run build && node test/golden.mjs",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"test:font-variants": "node test/font-variants.mjs",
|
|
78
78
|
"test:font-variants-browser": "node test/font-variants-browser.mjs",
|
|
79
79
|
"test:aptos-preview-browser": "node test/aptos-preview-browser.mjs artifacts/aptos-preview-browser.json",
|
|
80
|
+
"test:lazy-face-fallback-browser": "node test/lazy-face-fallback-browser.mjs artifacts/lazy-face-fallback",
|
|
80
81
|
"test:lazy-fonts-browser": "node test/lazy-fonts-browser.mjs artifacts/lazy-fonts",
|
|
81
82
|
"test:script-fonts-browser": "node test/script-fonts-browser.mjs artifacts/script-fonts-browser.json",
|
|
82
83
|
"test:script-fonts-auto-browser": "node test/script-fonts-auto-browser.mjs artifacts/script-fonts-auto",
|