@openpresentation/opf-render 0.17.0 → 0.18.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 +80 -64
- package/dist/deck-runtime.js +4 -4
- package/dist/drawn-faces.js +38 -0
- package/dist/element.d.ts +12 -9
- package/dist/element.js +33 -18
- package/dist/export-browser.d.ts +21 -11
- package/dist/export-browser.js +33 -20
- package/dist/font-fstype.js +70 -0
- package/dist/font-subset.js +173 -0
- package/dist/fonts-browser.d.ts +33 -22
- package/dist/fonts-browser.js +43 -15
- package/dist/fonts-node.d.ts +9 -9
- package/dist/fonts-node.js +26 -4
- package/dist/fonts.d.ts +10 -6
- package/dist/hb-shape.js +121 -0
- package/dist/index.d.ts +66 -33
- package/dist/index.js +1 -1
- package/dist/lazy-fonts.js +10 -10
- package/dist/pdf-fonts.js +1 -1
- package/dist/pdf-text.js +6 -3
- package/dist/pdf-vector.js +5 -2
- package/dist/pdf.d.ts +2 -2
- package/dist/pdf.js +3 -3
- package/dist/player.d.ts +3 -3
- package/dist/player.js +1 -1
- package/dist/png.d.ts +2 -2
- package/dist/png.js +2 -2
- package/dist/raster.js +33 -28
- package/dist/script-font-pack.js +5 -5
- package/dist/slide-sources.js +47 -0
- package/dist/svg.d.ts +39 -32
- package/dist/svg.js +74 -58
- package/dist/text-paths.js +82 -30
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RenderFonts, ScriptFonts } from "./fonts.js";
|
|
2
|
-
import type { Catalog, Finding, UnresolvedReferenceDiagnostic } from "@openpresentation/opf";
|
|
2
|
+
import type { Catalog, Finding, SlideSelection, UnresolvedReferenceDiagnostic } from "@openpresentation/opf";
|
|
3
|
+
export type { SlideSelection } from "@openpresentation/opf";
|
|
3
4
|
import type { SlideComposition, LayoutDiagnostic, TextMeasurement } from "@openpresentation/opf/composition";
|
|
4
5
|
export declare const packageName = "@openpresentation/opf-render";
|
|
5
6
|
|
|
@@ -49,8 +50,8 @@ export type RenderDiagnostic = LayoutDiagnostic | {
|
|
|
49
50
|
placeholder: "label" | "icon";
|
|
50
51
|
};
|
|
51
52
|
|
|
52
|
-
export interface
|
|
53
|
-
/** Leave out slides marked `hidden: true
|
|
53
|
+
export interface ToSvgOptions {
|
|
54
|
+
/** Leave out slides marked `hidden: true` when drawing the whole deck, as the player does. Default false: one SVG per slide. A slide number or selection draws exactly the slides it names. */
|
|
54
55
|
skipHidden?: boolean;
|
|
55
56
|
/**
|
|
56
57
|
* The fonts the deck is laid out and drawn with: the handle `loadFonts()` returns (from `@openpresentation/opf-render/fonts-node` or
|
|
@@ -58,18 +59,32 @@ export interface RenderSvgOptions {
|
|
|
58
59
|
* core's portable width estimate and the SVG names the design fonts without embedding them.
|
|
59
60
|
*/
|
|
60
61
|
fonts?: RenderFonts;
|
|
61
|
-
/** `false` writes no `@font-face` data into the SVG (RR-61): the fonts handle still measures and the SVG still names the families, for a host whose page already has the faces (a browser `loadFonts` handle adds them to the document). Default true: each SVG embeds the faces of `fonts.embeddedFonts` its own text draws. */
|
|
62
|
-
embedFonts?: boolean;
|
|
63
62
|
/**
|
|
64
|
-
*
|
|
63
|
+
* How the SVG draws text (RR-74). `"fonts"` (default): each SVG embeds, as `@font-face` data, the faces of `fonts.embeddedFonts` its
|
|
64
|
+
* own text draws (RR-61). `"system"`: no `@font-face` data; the fonts handle still measures and the SVG still names the families,
|
|
65
|
+
* for a host whose page already has the faces (a browser `loadFonts` handle adds them to the document).
|
|
66
|
+
*
|
|
67
|
+
* `"paths"`: every `<text>` drawn as glyph outlines (RR-64): `<use>` of outlines kept once per slide in `<defs>`, so the SVG needs
|
|
65
68
|
* no font and looks the same in every viewer (about 40 KB a slide over the core example decks, against 0.5 to 4 MB of embedded
|
|
66
69
|
* faces). Needs the fonts handle `loadFonts()` returns (`text-as-paths-needs-fonts` otherwise). The text is no longer text: it
|
|
67
|
-
* cannot be
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
70
|
+
* cannot be edited in place. The glyphs are shaped by HarfBuzz (the browser's shaper; the Node handle always, a browser handle
|
|
71
|
+
* given `shapeWasm`), are hidden from assistive technology, and each outlined line is also invisible real text pinned to the
|
|
72
|
+
* drawn width, so screen readers, selection, copy and find-in-page get the words. Each element keeps its `data-opf-*` trace.
|
|
73
|
+
* A run stays text, reported as `text-as-paths-fallback` with a `reason`, when its font is a colour or bitmap font
|
|
74
|
+
* (`"colour-font"`), its face's fsType restricts embedding (`"restricted"`), or it has no pinned width and HarfBuzz and the
|
|
75
|
+
* layout's measurement disagree on it (`"shaping"`). Use it for thumbnails, previews and portable SVG files, not for an editing
|
|
76
|
+
* surface or the vector PDF (which keeps real text from `<text>` SVG).
|
|
71
77
|
*/
|
|
72
|
-
|
|
78
|
+
text?: "fonts" | "system" | "paths";
|
|
79
|
+
/**
|
|
80
|
+
* `false` embeds whole faces (RR-65). By default, with a fonts handle that carries `subsets` (the Node `loadFonts` handle, a browser
|
|
81
|
+
* handle given `subsetWasm`), each face an SVG embeds is cut to the characters the slide draws, keeping every layout feature, so the
|
|
82
|
+
* browser draws it exactly like the whole face (over the core example decks a slide's SVG falls from 2.4 MB to 59 KB on average).
|
|
83
|
+
* Faces whose license does not allow a modified version under their own name (Carlito, Raleway, Lora, Playfair Display, PT Serif,
|
|
84
|
+
* Merriweather Sans, Source Sans 3), whose fsType forbids subsetting, or that a host supplied, are always embedded whole. Set it to
|
|
85
|
+
* false for an SVG whose text will be edited outside the renderer.
|
|
86
|
+
*/
|
|
87
|
+
subsetFonts?: boolean;
|
|
73
88
|
/** `"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`. */
|
|
74
89
|
glyphFallback?: "chain" | "none";
|
|
75
90
|
/** Unscaled reference-pixel clearance around supplied vector text outlines; default 1. */
|
|
@@ -106,18 +121,18 @@ export interface RenderSvgOptions {
|
|
|
106
121
|
strictReferences?: boolean;
|
|
107
122
|
}
|
|
108
123
|
|
|
109
|
-
|
|
124
|
+
/** The options of `toPng`: the conversion's own, and the `toSvg` options that draw a deck. */
|
|
125
|
+
export interface ToPngOptions extends Omit<ToSvgOptions, "fonts"> {
|
|
110
126
|
/**
|
|
111
|
-
* The fonts the
|
|
112
|
-
* `loadSystemFonts` say whether the bundled base faces are added, default true, and whether
|
|
113
|
-
*
|
|
127
|
+
* The fonts the deck is laid out and drawn with: the handle `loadFonts()` returns from `/fonts-node` (it measures the deck, and its
|
|
128
|
+
* `fontFiles` draw; `useBundledFonts` and `loadSystemFonts` say whether the bundled base faces are added, default true, and whether
|
|
129
|
+
* system fonts load, default false), so the preview and the PNG use the same faces. For SVG input it may instead be font folders
|
|
130
|
+
* (a path or a list of paths), whose faces are added to the bundled base faces. Without it the bundled base faces draw.
|
|
114
131
|
*/
|
|
115
|
-
fonts?: RenderFonts;
|
|
132
|
+
fonts?: RenderFonts | string | readonly string[];
|
|
116
133
|
scale?: number;
|
|
117
134
|
background?: string;
|
|
118
135
|
dpi?: number;
|
|
119
|
-
/** Extra directories of font files to draw with. */
|
|
120
|
-
fontDirs?: string[];
|
|
121
136
|
defaultFontFamily?: string;
|
|
122
137
|
sansSerifFamily?: string;
|
|
123
138
|
monospaceFamily?: string;
|
|
@@ -186,14 +201,14 @@ export interface PdfMetadata {
|
|
|
186
201
|
modificationDate?: Date | string;
|
|
187
202
|
}
|
|
188
203
|
|
|
189
|
-
export interface
|
|
204
|
+
export interface ToPdfOptions extends Omit<ToPngOptions, "onDiagnostic"> {
|
|
190
205
|
/**
|
|
191
|
-
* `
|
|
192
|
-
* with embedded font subsets (selectable, searchable, correct copy and paste); the fonts are
|
|
193
|
-
* `fontFiles` and the
|
|
206
|
+
* `true` draws each slide as an image (`scale`). Default false, a vector PDF: shapes, gradients and patterns as PDF vector graphics,
|
|
207
|
+
* pictures as images, text as real text with embedded font subsets (selectable, searchable, correct copy and paste); the fonts are
|
|
208
|
+
* the bundled files, the `fonts` handle's `fontFiles` and the font folders you supply, never system fonts.
|
|
194
209
|
*/
|
|
195
|
-
|
|
196
|
-
/**
|
|
210
|
+
raster?: boolean;
|
|
211
|
+
/** A vector PDF paints the page this colour first (default white, as a raster PDF composites on white); `"none"` leaves it unpainted. */
|
|
197
212
|
background?: string;
|
|
198
213
|
/** Vector only. Family drawn for the generic `serif`; default `defaultFontFamily`. */
|
|
199
214
|
serifFamily?: string;
|
|
@@ -207,8 +222,8 @@ export interface SvgToPdfOptions extends SvgToPngOptions {
|
|
|
207
222
|
strict?: boolean;
|
|
208
223
|
/** Vector only. Pixel density of the rare element rasterized because it has no vector form; default 2. */
|
|
209
224
|
rasterFallbackScale?: number;
|
|
210
|
-
/** Vector only
|
|
211
|
-
onDiagnostic?: (diagnostic: PdfDiagnostic) => void;
|
|
225
|
+
/** Vector only: font embedding reports, substitutions, fallbacks and unsupported features; given a deck, also the diagnostics of drawing it. */
|
|
226
|
+
onDiagnostic?: (diagnostic: PdfDiagnostic | RenderDiagnostic) => void;
|
|
212
227
|
/** Vector only. Checked between pages; an abort rejects with the signal's reason. */
|
|
213
228
|
signal?: AbortSignal;
|
|
214
229
|
/** Vector only. Called after each page is written. */
|
|
@@ -266,14 +281,32 @@ export declare class OPFRenderError extends Error {
|
|
|
266
281
|
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
267
282
|
}
|
|
268
283
|
|
|
269
|
-
export declare function resolvePresentation(input: unknown, options?:
|
|
284
|
+
export declare function resolvePresentation(input: unknown, options?: ToSvgOptions): ResolvedPresentation;
|
|
270
285
|
|
|
271
|
-
/**
|
|
272
|
-
export
|
|
286
|
+
/** An SVG slide the renderer drew: its text, or its bytes. */
|
|
287
|
+
export type SvgSlide = string | Uint8Array;
|
|
273
288
|
|
|
274
|
-
/**
|
|
275
|
-
|
|
289
|
+
/**
|
|
290
|
+
* The deck as SVG (RR-73). `toSvg(deck)`: every slide, in order (without the hidden ones under `skipHidden`). `toSvg(deck, 3)`: the
|
|
291
|
+
* third slide (slides count from 1; out of range throws `slide-out-of-range`). `toSvg(deck, "1-3")` or `toSvg(deck, [1, 3])`: the
|
|
292
|
+
* selected slides (a malformed selection throws `invalid-slide-selection`). The second argument may be the options instead.
|
|
293
|
+
*/
|
|
294
|
+
export declare function toSvg(input: unknown, options?: ToSvgOptions): string[];
|
|
295
|
+
export declare function toSvg(input: unknown, slide: number, options?: ToSvgOptions): string;
|
|
296
|
+
export declare function toSvg(input: unknown, slides: Exclude<SlideSelection, number>, options?: ToSvgOptions): string[];
|
|
276
297
|
|
|
277
|
-
|
|
298
|
+
/**
|
|
299
|
+
* PNG (RR-73) of a deck, drawn with `toSvg` (the slide arguments are `toSvg`'s: one PNG for a slide number, a list otherwise), or of
|
|
300
|
+
* SVG the renderer drew (one SVG gives one PNG, a list a list). A slide selection with SVG input throws `invalid-conversion-option`.
|
|
301
|
+
*/
|
|
302
|
+
export declare function toPng(svg: Uint8Array | `<${string}`, options?: ToPngOptions): Promise<Uint8Array>;
|
|
303
|
+
export declare function toPng(svgs: readonly SvgSlide[], options?: ToPngOptions): Promise<Uint8Array[]>;
|
|
304
|
+
export declare function toPng(deck: unknown, slide: number, options?: ToPngOptions): Promise<Uint8Array>;
|
|
305
|
+
export declare function toPng(deck: unknown, slides: Exclude<SlideSelection, number>, options?: ToPngOptions): Promise<Uint8Array[]>;
|
|
306
|
+
export declare function toPng(deck: object, options?: ToPngOptions): Promise<Uint8Array[]>;
|
|
307
|
+
/** A string is SVG when it starts with `<`, and the deck's JSON text otherwise. */
|
|
308
|
+
export declare function toPng(source: string, options?: ToPngOptions): Promise<Uint8Array | Uint8Array[]>;
|
|
278
309
|
|
|
279
|
-
|
|
310
|
+
/** One PDF (RR-73), a page per slide: of a deck drawn with `toSvg` (every slide, or a selection: `toPdf(deck, "2-4")`), or of SVG slides. */
|
|
311
|
+
export declare function toPdf(source: unknown, options?: ToPdfOptions): Promise<Uint8Array>;
|
|
312
|
+
export declare function toPdf(deck: unknown, slides: SlideSelection, options?: ToPdfOptions): Promise<Uint8Array>;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './svg.js';
|
|
2
|
-
export {
|
|
2
|
+
export {toPng,toPdf} from './raster.js';
|
package/dist/lazy-fonts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fontPolicyFor } from "@openpresentation/opf/font-policy";
|
|
2
2
|
import { pickFace } from "./font-registry.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolvePresentation, toSvg } from "./svg.js";
|
|
4
4
|
export { lazyFontEntries, lazyFontList } from "./lazy-font-list.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -17,7 +17,7 @@ const lc = value => String(value).trim().toLowerCase();
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Render options that decide how a document resolves (catalogs, validate, date, ...) are forwarded from the host's
|
|
20
|
-
* `
|
|
20
|
+
* `toSvg` options. The analysis supplies its own measurement and reports nothing: callbacks, the host's fonts handle
|
|
21
21
|
* (measurement and embedded fonts), asset resolution and tracing are dropped.
|
|
22
22
|
*/
|
|
23
23
|
const NOT_RESOLVE_OPTIONS = new Set(["fonts", "onDiagnostic", "trace", "glyphFallback", "imageResolver", "strictAssets", "signal"]);
|
|
@@ -27,10 +27,10 @@ function resolveOptionsOf(options) {
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* The font families a presentation's slides resolve (heading, body and code roles of every slide, including per-slide
|
|
30
|
-
* design overrides), as `
|
|
30
|
+
* design overrides), as `toSvg` resolves them with the same `options` (`catalogs`, ...). This is the role families,
|
|
31
31
|
* not what is drawn: use `presentationFaces` for that.
|
|
32
32
|
*
|
|
33
|
-
* A document that does not resolve throws the error `
|
|
33
|
+
* A document that does not resolve throws the error `toSvg` throws for it (an OPFRenderError such as
|
|
34
34
|
* `catalog-resolution-failed` for a layout id no catalog has, or `invalid-opf`), never an empty set: a document whose layout comes only from
|
|
35
35
|
* `options.catalogs` needs those catalogs here too.
|
|
36
36
|
*/
|
|
@@ -57,7 +57,7 @@ function registryView({ faces, policy = "none", aliases = new Map(), fallbackFam
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* The faces a preview of the presentation draws, as `[{family, weight, italic}]`, sorted by family, style and weight. It is
|
|
60
|
-
* the renderer's own layout and painting run with a recording measurement, so it follows exactly what `
|
|
60
|
+
* the renderer's own layout and painting run with a recording measurement, so it follows exactly what `toSvg` measures
|
|
61
61
|
* and draws: the heading and body roles of every slide that draws them, bold and italic runs of rich text, table and cell
|
|
62
62
|
* styles, header and footer furniture, chart labels, metrics, quotes and timelines, the code role only where code is
|
|
63
63
|
* drawn, per-slide design overrides, font scheme and theme resolution, and script slots (a script run is requested in its
|
|
@@ -73,8 +73,8 @@ function registryView({ faces, policy = "none", aliases = new Map(), fallbackFam
|
|
|
73
73
|
* family found. The result is the faces the registry paints, for example `Intos 700` for a 600-weight label of an Aptos
|
|
74
74
|
* deck. A style the registry cannot resolve is left as named (drawing reports it).
|
|
75
75
|
*
|
|
76
|
-
* `options` are the `
|
|
77
|
-
* does not resolve throws what `
|
|
76
|
+
* `options` are the `toSvg` options the document resolves with (`catalogs`, `date`, `validate`, ...). A document that
|
|
77
|
+
* does not resolve throws what `toSvg` throws for it.
|
|
78
78
|
* @param {unknown} presentation
|
|
79
79
|
* @param {object} [options]
|
|
80
80
|
* @param {{faces: Iterable<{family: string, weight: number, italic: boolean}>, policy?: "none"|"metric"|"visual", aliases?: ReadonlyMap<string, string>, fallbackFamily?: string}} [registry]
|
|
@@ -98,7 +98,7 @@ export function presentationFaces(presentation, options = {}, registry) {
|
|
|
98
98
|
measure(text, size, style) { if (text !== "") record(settle(style)); return String(text).length * size * RECORDED_EM; },
|
|
99
99
|
resolveStyle: settle,
|
|
100
100
|
};
|
|
101
|
-
|
|
101
|
+
toSvg(presentation, { ...resolveOptionsOf(options), fonts: { textMeasurement } });
|
|
102
102
|
return [...seen.values()].sort((a, b) => a.family < b.family ? -1 : a.family > b.family ? 1 : Number(a.italic) - Number(b.italic) || a.weight - b.weight);
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -106,8 +106,8 @@ export function presentationFaces(presentation, options = {}, registry) {
|
|
|
106
106
|
* The vendored faces (of `lazy`) a presentation draws that the registry does not hold: what a registry loads before it can
|
|
107
107
|
* paint the document as Node paints it with every vendored face loaded. `held` lists the registry's faces
|
|
108
108
|
* (`registry.describeFaces()`), `loaded` the lazy files already loaded; `policy`, `aliases` and `fallbackFamily` are the
|
|
109
|
-
* registry's. `renderOptions` are the `
|
|
110
|
-
* does not resolve throws what `
|
|
109
|
+
* registry's. `renderOptions` are the `toSvg` options the document resolves with (`catalogs`, ...), and a document that
|
|
110
|
+
* does not resolve throws what `toSvg` throws for it. Face level: a bold or italic run added by an edit adds just that face.
|
|
111
111
|
*/
|
|
112
112
|
export function lazyFacesNeeded(presentation, renderOptions, { lazy, held, loaded = new Set(), policy = "none", aliases = new Map(), fallbackFamily }) {
|
|
113
113
|
const heldFaces = [...held], heldKeys = new Set(heldFaces.map(faceKeyOf));
|
package/dist/pdf-fonts.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isTrueTypeOutlines, subsetTrueType } from "./pdf-subset.js";
|
|
|
4
4
|
import { hex, name, num, sha256, textString, utf16Hex } from "./pdf-writer.js";
|
|
5
5
|
|
|
6
6
|
// Font selection, shaping and embedding for the vector PDF export (RR-12). Fonts come only from the faces the
|
|
7
|
-
// caller supplies (the same bundled font files,
|
|
7
|
+
// caller supplies (the same bundled font files, fonts handle or font folders the PNG preview draws with, and the
|
|
8
8
|
// `@font-face` data of the SVG itself); nothing is read from the system and nothing is fetched. A face whose
|
|
9
9
|
// OS/2 embedding bits forbid embedding is never embedded.
|
|
10
10
|
|
package/dist/pdf-text.js
CHANGED
|
@@ -166,7 +166,7 @@ function buildRuns(chunk, env, styleIds, faceCache, linkIds, ownerIds) {
|
|
|
166
166
|
for (const char of chunk.chars) { levels.push(embedding.levels[unit]); unit += char.ch.length; }
|
|
167
167
|
const mirrored = bidi.getMirroredCharactersMap(text, embedding.levels);
|
|
168
168
|
const runs = [];
|
|
169
|
-
const chunkInfo = { logical: chunk.chars.filter((char) => !IGNORED.test(char.ch)).map((char) => char.ch).join("") };
|
|
169
|
+
const chunkInfo = { logical: chunk.chars.filter((char) => !IGNORED.test(char.ch)).map((char) => char.ch).join(""), rtl: baseDirection === "rtl" || /^[]/.test(text) };
|
|
170
170
|
let current = null;
|
|
171
171
|
unit = 0;
|
|
172
172
|
chunk.chars.forEach((char, index) => {
|
|
@@ -179,7 +179,8 @@ function buildRuns(chunk, env, styleIds, faceCache, linkIds, ownerIds) {
|
|
|
179
179
|
const owner = env.splitByOwner ? linkId(ownerIds, { node: char.owner }) : 0;
|
|
180
180
|
const key = `${resolved.styleId}|${resolved.face.id}|${level}|${char.scope?.id ?? 0}|${linkId(linkIds, char.link)}|${[...char.decoration].sort().join(",")}|${owner}`;
|
|
181
181
|
if (!current || current.key !== key) {
|
|
182
|
-
|
|
182
|
+
// `start`: the run's first character in logical order, so a reader can put runs back in reading order (RR-64).
|
|
183
|
+
current = { chunk: chunkInfo, start: index, key, face: resolved.face, style: char.style, level, scope: char.scope, link: char.link, decoration: char.decoration, owner: char.owner, chars: [], text: "", logical: "", weight: resolved.weight, italic: resolved.italic };
|
|
183
184
|
runs.push(current);
|
|
184
185
|
}
|
|
185
186
|
current.logical += char.ch;
|
|
@@ -244,7 +245,9 @@ function shapeRun(run, env) {
|
|
|
244
245
|
const size = parseFloat(style["font-size"]) || 16;
|
|
245
246
|
const direction = run.level % 2 === 1 ? "rtl" : "ltr";
|
|
246
247
|
const language = openTypeLanguage(style.lang ?? style["xml:lang"]);
|
|
247
|
-
|
|
248
|
+
// RR-64: outlined text is shaped by HarfBuzz (env.shape), the browser's shaper; the vector PDF keeps fontkit.
|
|
249
|
+
// HarfBuzz takes the BCP 47 tag (`lang`); fontkit the OpenType language system (`language`).
|
|
250
|
+
const glyphs = (env.shape ?? shape)(run.face, run.text, { features: featuresOf(style), language, lang: style.lang ?? style["xml:lang"], direction });
|
|
248
251
|
const scale = size / run.face.upem;
|
|
249
252
|
const letterSpacing = parseLength(style["letter-spacing"], { fontSize: size }) ?? 0;
|
|
250
253
|
const wordSpacing = parseLength(style["word-spacing"], { fontSize: size }) ?? 0;
|
package/dist/pdf-vector.js
CHANGED
|
@@ -89,7 +89,7 @@ class Converter {
|
|
|
89
89
|
this.reported.add(key);
|
|
90
90
|
this.unsupported(d.code, d.message, { fontFamily: d.fontFamily, codePoint: d.codePoint });
|
|
91
91
|
},
|
|
92
|
-
unavailable: (families) => this.error("pdf-font-unavailable", `No embeddable font face is available for ${families.length ? `'${families.join("', '")}'` : "the requested text"}.
|
|
92
|
+
unavailable: (families) => this.error("pdf-font-unavailable", `No embeddable font face is available for ${families.length ? `'${families.join("', '")}'` : "the requested text"}. Pass the fonts handle or font folders as fonts, or keep useBundledFonts enabled.`, {}),
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -744,6 +744,9 @@ class Converter {
|
|
|
744
744
|
run.fill = await this.paint(run.style.fill, run.style, ctx, box, "fill");
|
|
745
745
|
run.stroke = await this.paint(run.style.stroke, run.style, ctx, box, "stroke");
|
|
746
746
|
}
|
|
747
|
+
// Text with neither fill nor stroke draws nothing (an outlined slide's invisible readable layer, RR-64): it adds no font,
|
|
748
|
+
// no glyph and no structure to the PDF.
|
|
749
|
+
if (!runs.some((run) => run.fill || run.stroke)) return;
|
|
747
750
|
// Runs are drawn in visual (left to right) order. Right-to-left runs are marked /ReversedChars so extractors read
|
|
748
751
|
// their characters back to front, and a cluster whose glyph-to-Unicode map cannot give its text (a ligature, a
|
|
749
752
|
// letter split into base and dots, a mirrored bracket) carries /ActualText; a left-to-right run the map cannot
|
|
@@ -1225,7 +1228,7 @@ async function listFonts(directory, { readdir, path }) {
|
|
|
1225
1228
|
}
|
|
1226
1229
|
|
|
1227
1230
|
/**
|
|
1228
|
-
* Convert SVG slides to a vector PDF. `options` carries the resolved conversion settings (see
|
|
1231
|
+
* Convert SVG slides to a vector PDF. `options` carries the resolved conversion settings (see toPdf in raster.js).
|
|
1229
1232
|
*/
|
|
1230
1233
|
export async function svgsToVectorPdf(svgs, options) {
|
|
1231
1234
|
const converter = new Converter(options);
|
package/dist/pdf.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
1
|
+
export { toPdf } from "./index.js";
|
|
2
|
+
export type { ToPdfOptions, PdfDiagnostic, PdfMetadata } from "./index.js";
|
package/dist/pdf.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// RR-63: the PDF format entry (Node). The default vector `
|
|
2
|
-
// `sharp` for pictures and `@resvg/resvg-js` for the rare element it rasterizes, and `
|
|
1
|
+
// RR-63: the PDF format entry (Node). The default vector `toPdf` needs no converter for text and shapes; it loads the optional peer
|
|
2
|
+
// `sharp` for pictures and `@resvg/resvg-js` for the rare element it rasterizes, and `raster: true` also loads `pdf-lib`. A converter
|
|
3
3
|
// that is not installed rejects with `OPFRenderError` code `converter-missing`. For a page use `@openpresentation/opf-render/export-browser`.
|
|
4
|
-
export {
|
|
4
|
+
export { toPdf } from "./raster.js";
|
package/dist/player.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrowserFontsHandle } from "./fonts-browser.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ToSvgOptions } from "./svg.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Why a deck could not be loaded, drawn or presented. `code` is stable: `invalid-document`, `fetch-failed`, `fonts-unavailable`,
|
|
@@ -19,8 +19,8 @@ export type BlankMode = "none" | "black" | "white";
|
|
|
19
19
|
export interface PresentOptions {
|
|
20
20
|
/** The self-hosted font root URL (see `copyPreviewFonts`), or a browser fonts handle the page already has (`loadFonts` from `/fonts-browser`). Without it layout uses estimated widths and system fonts. */
|
|
21
21
|
fonts?: string | BrowserFontsHandle;
|
|
22
|
-
/** Extra `
|
|
23
|
-
renderOptions?: Partial<
|
|
22
|
+
/** Extra `toSvg` options (catalogs, imageResolver, date, ...). */
|
|
23
|
+
renderOptions?: Partial<ToSvgOptions>;
|
|
24
24
|
/** 1-based slide to start on, in the sequence that plays. Default 1. */
|
|
25
25
|
startSlide?: number;
|
|
26
26
|
/** Play hidden slides too. Default false. */
|
package/dist/player.js
CHANGED
|
@@ -632,7 +632,7 @@ async function readSource(source, signal) {
|
|
|
632
632
|
* player covers the page at once (and asks for full screen), then resolves with the running session when the first slide is
|
|
633
633
|
* drawn. Call it from a click or key handler: full screen and the speaker view both need a user gesture.
|
|
634
634
|
*
|
|
635
|
-
* Options: `fonts` (the self-hosted font root URL, or a browser fonts handle from `loadFonts`), `renderOptions` (extra `
|
|
635
|
+
* Options: `fonts` (the self-hosted font root URL, or a browser fonts handle from `loadFonts`), `renderOptions` (extra `toSvg` options),
|
|
636
636
|
* `startSlide` (1-based), `includeHidden`, `fullscreen` (default true), `presenterView` (open the speaker view too),
|
|
637
637
|
* `role: "presenter"` (this window is the speaker view: for a second tab or window of your page), `channel` (name, or `false`
|
|
638
638
|
* for no sync), `container`, `signal` and `now` (an injectable clock).
|
package/dist/png.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
1
|
+
export { toPng } from "./index.js";
|
|
2
|
+
export type { ToPngOptions } from "./index.js";
|
package/dist/png.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// RR-63: the PNG format entry (Node). `
|
|
1
|
+
// RR-63: the PNG format entry (Node). `toPng` loads the optional peers `@resvg/resvg-js` (and `sharp` for WebP and rotated JPEG
|
|
2
2
|
// pictures) on first use and rejects with `OPFRenderError` code `converter-missing` when they are not installed. For a page use
|
|
3
3
|
// `@openpresentation/opf-render/export-browser`.
|
|
4
|
-
export {
|
|
4
|
+
export { toPng } from "./raster.js";
|
package/dist/raster.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {prepareRasterImages} from './raster-images.js';
|
|
2
|
-
import {OPFRenderError,packageName} from './svg.js';
|
|
2
|
+
import {OPFRenderError,packageName,toSvg} from './svg.js';
|
|
3
|
+
import {slideArguments,slideSources} from './slide-sources.js';
|
|
3
4
|
import {separateLigatures} from './font-compatibility.js';
|
|
4
5
|
import {monochromeColorFonts,rasterFontFiles} from './color-fonts.js';
|
|
5
6
|
import {pinScriptClusters} from './raster-text.js';
|
|
@@ -7,38 +8,47 @@ import {loadConverter} from './converters.js';
|
|
|
7
8
|
const DEFAULT_DIMENSIONS = { width: 1280, height: 720 };
|
|
8
9
|
const DEFAULT_RASTER_SCALE = 1;
|
|
9
10
|
const DEFAULT_RASTER_BACKGROUND = "#FFFFFF";
|
|
10
|
-
// RR-12: PDF output is vector (selectable text, vector drawing) unless a caller asks for the raster-backed compatibility mode.
|
|
11
|
-
const DEFAULT_PDF_MODE = "vector";
|
|
12
11
|
let bundledFontFilesCache=null;
|
|
13
12
|
|
|
14
|
-
// The faces a raster or PDF conversion draws with
|
|
15
|
-
// whether the bundled base faces are added (`useBundledFonts`, default true; a Node handle already holds its own, so it
|
|
16
|
-
// whether system fonts load (`loadSystemFonts`, default false).
|
|
13
|
+
// The faces a raster or PDF conversion draws with (RR-74: one `fonts`). The fonts handle (`loadFonts()` from `/fonts-node`) gives its
|
|
14
|
+
// `fontFiles`, whether the bundled base faces are added (`useBundledFonts`, default true; a Node handle already holds its own, so it
|
|
15
|
+
// says false) and whether system fonts load (`loadSystemFonts`, default false). Font folders (a path or a list of paths) add every
|
|
16
|
+
// face in them to the bundled base faces. Without either the bundled base faces draw.
|
|
17
17
|
function fontSettings(options) {
|
|
18
18
|
const fonts = options.fonts ?? {};
|
|
19
|
-
return { fontFiles: stringArray(fonts.
|
|
19
|
+
if (typeof fonts === "string" || Array.isArray(fonts)) return { fontFiles: [], fontDirs: stringArray([fonts].flat()), useBundledFonts: true, loadSystemFonts: false };
|
|
20
|
+
return { fontFiles: stringArray(fonts.fontFiles), fontDirs: [], useBundledFonts: fonts.useBundledFonts !== false, loadSystemFonts: fonts.loadSystemFonts === true };
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* PNG (RR-73): a deck drawn with `toSvg` (`toPng(deck)` gives one PNG per slide, `toPng(deck, 3)` the third slide's PNG,
|
|
25
|
+
* `toPng(deck, "1-3")` those slides'), or SVG the renderer drew (one SVG gives one PNG, a list a list). `fonts` is the handle
|
|
26
|
+
* `loadFonts()` returns, so the preview and the PNG draw with the same faces, or font folders for SVG input. Without either the
|
|
27
|
+
* bundled base faces draw.
|
|
25
28
|
*/
|
|
26
|
-
export async function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
export async function toPng(source, slides, options) {
|
|
30
|
+
({ slides, options } = slideArguments(slides, options));
|
|
31
|
+
const { svgs, one } = slideSources(source, slides, options, toSvg);
|
|
32
|
+
const pngs = [];
|
|
33
|
+
for (const svg of svgs) pngs.push((await rasterizeSvg(svg, options)).png);
|
|
34
|
+
return one ? pngs[0] : pngs;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
/**
|
|
38
|
+
* One PDF (RR-73), a page per slide, of a deck drawn with `toSvg` (all slides, or a selection: `toPdf(deck, "2-4")`) or of SVG
|
|
39
|
+
* slides the renderer drew. Vector by default (selectable text in embedded subsets); `raster: true` draws each slide as an image.
|
|
40
|
+
* `fonts` is as for `toPng`.
|
|
41
|
+
*/
|
|
42
|
+
export async function toPdf(source, slides, options) {
|
|
43
|
+
({ slides, options } = slideArguments(slides, options));
|
|
44
|
+
const pdfInputs = slideSources(source, slides, options, toSvg).svgs;
|
|
34
45
|
if (!pdfInputs.length) {
|
|
35
|
-
throw new OPFRenderError("empty-pdf", "
|
|
46
|
+
throw new OPFRenderError("empty-pdf", "toPdf needs at least one slide.");
|
|
36
47
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
throw new OPFRenderError("invalid-conversion-option", 'mode must be "vector" or "raster".', { option: "mode", value: mode });
|
|
48
|
+
if (options.raster !== undefined && typeof options.raster !== "boolean") {
|
|
49
|
+
throw new OPFRenderError("invalid-conversion-option", "raster must be true or false.", { option: "raster", value: options.raster });
|
|
40
50
|
}
|
|
41
|
-
if (
|
|
51
|
+
if (options.raster !== true) return vectorPdf(pdfInputs, options);
|
|
42
52
|
|
|
43
53
|
const { PDFDocument } = await loadPdfLib();
|
|
44
54
|
const pdf = await PDFDocument.create({ updateMetadata: false });
|
|
@@ -65,7 +75,7 @@ export async function svgToPdf(svgs, options = {}) {
|
|
|
65
75
|
async function vectorPdf(inputs, options) {
|
|
66
76
|
const settings = fontSettings(options);
|
|
67
77
|
if (settings.loadSystemFonts) {
|
|
68
|
-
throw new OPFRenderError("pdf-system-fonts-unsupported", 'Vector PDF output embeds only the font files you supply: fonts.loadSystemFonts
|
|
78
|
+
throw new OPFRenderError("pdf-system-fonts-unsupported", 'Vector PDF output embeds only the font files you supply: so fonts.loadSystemFonts does not apply. Pass a fonts handle with fontFiles, or font folders, or use raster: true.', { option: "fonts.loadSystemFonts" });
|
|
69
79
|
}
|
|
70
80
|
const scale = positiveNumber(options.rasterFallbackScale, "rasterFallbackScale", 2);
|
|
71
81
|
const fontFiles = [
|
|
@@ -77,7 +87,7 @@ async function vectorPdf(inputs, options) {
|
|
|
77
87
|
const {svgsToVectorPdf} = await import("./pdf-vector.js");
|
|
78
88
|
return svgsToVectorPdf(svgs, {
|
|
79
89
|
fontFiles,
|
|
80
|
-
fontDirs:
|
|
90
|
+
fontDirs: settings.fontDirs,
|
|
81
91
|
defaultFontFamily: options.defaultFontFamily ?? "Roboto",
|
|
82
92
|
sansSerifFamily: options.sansSerifFamily ?? options.defaultFontFamily ?? "Roboto",
|
|
83
93
|
monospaceFamily: options.monospaceFamily ?? "Roboto Mono",
|
|
@@ -123,7 +133,7 @@ async function rasterizeSvg(svgInput, options) {
|
|
|
123
133
|
const font = {
|
|
124
134
|
loadSystemFonts: settings.loadSystemFonts,
|
|
125
135
|
fontFiles,
|
|
126
|
-
fontDirs:
|
|
136
|
+
fontDirs: settings.fontDirs,
|
|
127
137
|
defaultFontFamily: options.defaultFontFamily ?? "Roboto",
|
|
128
138
|
sansSerifFamily: options.sansSerifFamily ?? options.defaultFontFamily ?? "Roboto",
|
|
129
139
|
monospaceFamily: options.monospaceFamily ?? "Roboto Mono"
|
|
@@ -181,11 +191,6 @@ async function resolveBundledFontFiles() {
|
|
|
181
191
|
return (await loadFonts()).fontFiles;
|
|
182
192
|
}
|
|
183
193
|
|
|
184
|
-
function normalizeSvgList(value) {
|
|
185
|
-
if (Array.isArray(value)) return value;
|
|
186
|
-
return [value];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
194
|
function normalizeSvgInput(value) {
|
|
190
195
|
if (typeof value === "string") return value;
|
|
191
196
|
if (value instanceof Uint8Array) return new TextDecoder().decode(value);
|
package/dist/script-font-pack.js
CHANGED
|
@@ -65,7 +65,7 @@ const UNDRAWN_VALUE = /^(?:https?|data|blob|pkg|file|mailto):\S*$/i;
|
|
|
65
65
|
|
|
66
66
|
// The built-in variables that copy a document value into drawn text: the inline token and the whole-field reference
|
|
67
67
|
// (a logo or photo reference draws a picture, not text).
|
|
68
|
-
const ORGANIZATION_TOKEN = /\{\{\s*organization\.|var:organization\.(?!logo\b)/;
|
|
68
|
+
const ORGANIZATION_TOKEN = /\{\{\s*organization\.|var:organization\.(?!(?:[^.\s]+\.)?logo\b)/;
|
|
69
69
|
const SPEAKER_TOKEN = /\{\{\s*speaker\.|var:speaker\.(?!photo\b|image\b|avatar\b)/;
|
|
70
70
|
const SECTION_TOKEN = /\{\{\s*slide\.section\s*[|}]/;
|
|
71
71
|
|
|
@@ -130,7 +130,7 @@ function scriptOfFamily(family) {
|
|
|
130
130
|
* scheme (`slide.design`), so each such slide is resolved too. `han` is the CJK script the scheme names, which tells
|
|
131
131
|
* Han-only text apart when the language does not (a Yu Gothic deck whose language is English).
|
|
132
132
|
*
|
|
133
|
-
* `renderOptions` are the host's `
|
|
133
|
+
* `renderOptions` are the host's `toSvg` options: their `catalogs` (core `Catalog[]`) go to core's `resolveScriptFonts`
|
|
134
134
|
* unchanged, and the families the renderer resolves per slide count too, so a scheme that exists only in the host's catalogs
|
|
135
135
|
* and names a script font still loads that font.
|
|
136
136
|
*/
|
|
@@ -176,8 +176,8 @@ function* namedFontFamilies(value, key) {
|
|
|
176
176
|
* itemization, so language-dependent punctuation counts), the scripts its font schemes need (`design`, for example
|
|
177
177
|
* Jpan for a Yu Gothic scheme) and the CJK characters that a glyph fallback might have to draw with another CJK face.
|
|
178
178
|
* The text decides, so a document is analyzed without resolving its layouts and catalogs; `renderOptions` (the host's
|
|
179
|
-
* `
|
|
180
|
-
* like the renderer resolves them; a document that then does not resolve throws what `
|
|
179
|
+
* `toSvg` options) matter only for font schemes the host supplies in `renderOptions.catalogs`, which then resolve
|
|
180
|
+
* like the renderer resolves them; a document that then does not resolve throws what `toSvg` throws for it.
|
|
181
181
|
*/
|
|
182
182
|
export function analyzePresentationScripts(presentation, profile, renderOptions) {
|
|
183
183
|
profile ??= presentationScriptProfile(presentation, renderOptions);
|
|
@@ -198,7 +198,7 @@ export function analyzePresentationScripts(presentation, profile, renderOptions)
|
|
|
198
198
|
* (Japanese, Korean, Simplified or Traditional Chinese) and makes curly quotes, dashes and the ellipsis
|
|
199
199
|
* East Asian. Text the preview never draws (ids, alt text, sources, assets, catalogs, notes) is ignored.
|
|
200
200
|
* Greek and Cyrillic add nothing: the office registry always carries Noto Sans (the glyph-fallback face) and Roboto covers them.
|
|
201
|
-
* Pass `profile` to reuse a resolved profile. Other options are the host's `
|
|
201
|
+
* Pass `profile` to reuse a resolved profile. Other options are the host's `toSvg` options (`catalogs`, see `analyzePresentationScripts`).
|
|
202
202
|
*/
|
|
203
203
|
export function detectPresentationScripts(presentation, { profile, ...renderOptions } = {}) {
|
|
204
204
|
return analyzePresentationScripts(presentation, profile, renderOptions).detected;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// RR-73: what `toPng` and `toPdf` draw. They take a deck (an object, or JSON text) and draw it with `toSvg`, or SVG the renderer
|
|
2
|
+
// already drew (a string or bytes starting with `<`, or a list of them). The second argument is a slide selection for a deck (`3`
|
|
3
|
+
// or `"1-3"`, slides counting from 1) or the options, told apart by type, as for `toSvg`.
|
|
4
|
+
import { parseSlideSelection } from "@openpresentation/opf";
|
|
5
|
+
import { OPFRenderError } from "./svg.js";
|
|
6
|
+
|
|
7
|
+
/** The slide numbers (from 1) a selection names: a list of numbers or text such as "1,3-5"; core's parser, with the renderer's error. */
|
|
8
|
+
export function selectSlides(slides, count) {
|
|
9
|
+
try {
|
|
10
|
+
return parseSlideSelection(slides, count, "slides");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new OPFRenderError("invalid-slide-selection", error.message, { option: "slides", slideCount: count, cause: error });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// The 0.17 option names (RR-73, RR-74): pre-v1 there is no alias, but a call that still passes one fails with the new name.
|
|
17
|
+
const RENAMED = { embedFonts: "text: \"system\" (embedFonts: false)", textAsPaths: "text: \"paths\"", mode: "raster: true (vector is the default)", fontDirs: "fonts: a font folder or a list of them" };
|
|
18
|
+
|
|
19
|
+
/** Throws for an option of 0.17 that 0.18 renamed, naming its replacement. */
|
|
20
|
+
export function rejectRenamedOptions(options, code) {
|
|
21
|
+
for (const name of Object.keys(RENAMED)) {
|
|
22
|
+
if (options?.[name] !== undefined) throw new OPFRenderError(code, `The ${name} option was replaced by ${RENAMED[name]}.`, { option: name });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `(slides, options)` with the options moved into place when the second argument is the options object. */
|
|
27
|
+
export function slideArguments(slides, options) {
|
|
28
|
+
if (slides !== null && typeof slides === "object" && !Array.isArray(slides)) return { slides: undefined, options: slides };
|
|
29
|
+
return { slides, options: options ?? {} };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isSvg = (value) => value instanceof Uint8Array || (typeof value === "string" && /^\s*</.test(value));
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The SVG slides to convert, and whether the caller asked for one (a slide number, or one SVG): `draw` is `toSvg`, called with the
|
|
36
|
+
* conversion options for a deck (they carry the fonts handle and the drawing options).
|
|
37
|
+
*/
|
|
38
|
+
export function slideSources(source, slides, options, draw) {
|
|
39
|
+
rejectRenamedOptions(options, "invalid-conversion-option");
|
|
40
|
+
if (Array.isArray(source) || isSvg(source)) {
|
|
41
|
+
if (slides !== undefined) throw new OPFRenderError("invalid-conversion-option", "A slide selection applies to a deck; SVG input is already drawn, so pass the slides you want.", { option: "slides" });
|
|
42
|
+
if (Array.isArray(source) && !source.every(isSvg)) throw new OPFRenderError("invalid-svg-input", "A list must hold SVG slides (strings or bytes starting with <).");
|
|
43
|
+
return { svgs: Array.isArray(source) ? source : [source], one: !Array.isArray(source) };
|
|
44
|
+
}
|
|
45
|
+
const drawn = draw(source, slides, options);
|
|
46
|
+
return typeof drawn === "string" ? { svgs: [drawn], one: true } : { svgs: drawn, one: false };
|
|
47
|
+
}
|