@openpresentation/opf-render 0.15.0 → 0.17.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 +129 -9
- package/dist/annotations.js +1 -1
- package/dist/converters.js +41 -0
- package/dist/deck-runtime.js +11 -4
- package/dist/element.d.ts +6 -0
- package/dist/element.js +31 -8
- package/dist/export-browser.d.ts +6 -0
- package/dist/export-browser.js +16 -2
- package/dist/font-registry.js +9 -6
- package/dist/fonts-browser.js +3 -0
- package/dist/fonts-node.js +27 -5
- package/dist/fonts.d.ts +9 -3
- package/dist/index.d.ts +13 -1
- package/dist/pdf-text.js +8 -5
- package/dist/pdf-vector.js +2 -0
- package/dist/pdf.d.ts +2 -0
- package/dist/pdf.js +4 -0
- package/dist/player.d.ts +2 -1
- package/dist/png.d.ts +2 -0
- package/dist/png.js +4 -0
- package/dist/preview-fonts-node.js +2 -2
- package/dist/raster-images.js +3 -1
- package/dist/raster.js +4 -20
- package/dist/script-font-pack.js +23 -16
- package/dist/svg.d.ts +13 -1
- package/dist/svg.js +133 -47
- package/dist/text-paths.js +278 -0
- package/package.json +70 -17
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// RR-64: `textAsPaths: true` draws every <text> of a slide as glyph outlines, so the SVG needs no font. The renderer has
|
|
2
|
+
// already decided every line, position and width (x, y, text-anchor, textLength); the vector PDF export's SVG text layout
|
|
3
|
+
// (layoutText: text chunks, bidirectional runs, per-character fallback, fontkit shaping, textLength) turns that into positioned
|
|
4
|
+
// glyph runs, and this module writes each glyph as a <use> of its outline. Outlines are kept once per slide in <defs>, in font
|
|
5
|
+
// units, under an id made of the face's content hash and the glyph id, so SVGs inlined on one page never resolve to another
|
|
6
|
+
// face's glyph. Everything outside the <text> elements is left byte for byte as the renderer wrote it.
|
|
7
|
+
//
|
|
8
|
+
// A <text> becomes a <g> that keeps the element's own attributes except the text-only ones (so its transform, opacity and
|
|
9
|
+
// data-opf-* trace stay), labelled with its text for assistive technology unless it is aria-hidden; a tspan with a trace keeps
|
|
10
|
+
// it on a nested <g>; a link becomes an <a> around its runs; underline and line-through are rectangles in the text colour from
|
|
11
|
+
// the font's own metrics. Text drawn with a colour or bitmap font (COLR, CBDT, sbix, SVG tables) stays <text>, reported as
|
|
12
|
+
// `text-as-paths-kept-text`, and the SVG then embeds that face as usual.
|
|
13
|
+
|
|
14
|
+
import { FontLibrary } from "./pdf-fonts.js";
|
|
15
|
+
import { layoutText } from "./pdf-text.js";
|
|
16
|
+
import { ROOT_STYLE, attributesOf, inheritStyle, parseLength } from "./pdf-style.js";
|
|
17
|
+
import { decodeEntities, escapeAttribute, parseXml } from "./pdf-xml.js";
|
|
18
|
+
|
|
19
|
+
const TOKENS = /<(\/?)([A-Za-z][\w:.-]*)((?:[^>"']|"[^"]*"|'[^']*')*?)(\/?)>|<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|[^<]+/g;
|
|
20
|
+
const ATTRIBUTE = /([^\s=/>"']+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
21
|
+
// Attributes that only mean something on text: the outline group does not carry them.
|
|
22
|
+
const TEXT_ONLY = new Set(["x", "y", "dx", "dy", "rotate", "textLength", "lengthAdjust", "text-anchor", "text-rendering", "xml:space", "font-family", "font-size",
|
|
23
|
+
"font-weight", "font-style", "font-variant", "font-stretch", "font-feature-settings", "font-variant-ligatures", "letter-spacing", "word-spacing", "text-decoration",
|
|
24
|
+
"dominant-baseline", "alignment-baseline", "baseline-shift", "direction", "unicode-bidi", "writing-mode"]);
|
|
25
|
+
const COLOUR_TABLES = ["COLR", "CBDT", "sbix", "SVG "];
|
|
26
|
+
const libraries = new WeakMap();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The outline font library of a fonts handle: every face of its registry (`registry.exportFaces()`), or the faces of its
|
|
30
|
+
* `embeddedFonts` list. Built once per registry and extended when the registry gains faces (`ensure`, `addFaces`). Null when
|
|
31
|
+
* the handle holds no font bytes.
|
|
32
|
+
*/
|
|
33
|
+
export function outlineLibrary(fonts) {
|
|
34
|
+
const registry = fonts?.registry;
|
|
35
|
+
if (typeof registry?.exportFaces === "function") {
|
|
36
|
+
const exported = registry.exportFaces();
|
|
37
|
+
const described = typeof registry.describeFaces === "function" ? registry.describeFaces() : [];
|
|
38
|
+
let entry = libraries.get(registry);
|
|
39
|
+
if (!entry) { entry = { library: newLibrary(fonts), added: 0 }; libraries.set(registry, entry); }
|
|
40
|
+
// Registries only ever append faces.
|
|
41
|
+
for (let index = entry.added; index < exported.length; index++) addFace(entry.library, exported[index].data, exported[index].family, described[index]);
|
|
42
|
+
entry.added = exported.length;
|
|
43
|
+
return entry.library;
|
|
44
|
+
}
|
|
45
|
+
const list = fonts?.embeddedFonts;
|
|
46
|
+
if (!Array.isArray(list) || !list.length) return null;
|
|
47
|
+
let library = libraries.get(list);
|
|
48
|
+
if (!library) {
|
|
49
|
+
library = newLibrary(fonts);
|
|
50
|
+
for (const font of list) {
|
|
51
|
+
const match = /^data:font\/[a-z0-9]+;base64,([A-Za-z0-9+/=]+)$/.exec(String(font?.dataUrl ?? ""));
|
|
52
|
+
if (match) addFace(library, decodeBase64(match[1]), font.family, font);
|
|
53
|
+
}
|
|
54
|
+
libraries.set(list, library);
|
|
55
|
+
}
|
|
56
|
+
return library;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function newLibrary(fonts) {
|
|
60
|
+
const fallback = fonts?.registry?.fallbackFamily ?? "Roboto";
|
|
61
|
+
return new FontLibrary({ genericFamilies: () => [fallback] });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The face is matched by the family, weight and style the registry gives it, the ones the SVG names.
|
|
65
|
+
function addFace(library, data, family, described) {
|
|
66
|
+
const face = library.addData(data, "fonts", family);
|
|
67
|
+
if (!face || face.outlineStyled) return;
|
|
68
|
+
if (Number.isInteger(described?.weight)) face.weight = described.weight;
|
|
69
|
+
if (typeof described?.italic === "boolean") face.italic = described.italic;
|
|
70
|
+
face.outlineStyled = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function decodeBase64(text) {
|
|
74
|
+
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(text, "base64"));
|
|
75
|
+
const binary = atob(text), bytes = new Uint8Array(binary.length);
|
|
76
|
+
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
77
|
+
return bytes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Replace the <text> elements of the slide's content strings by glyph outlines. `library` comes from `outlineLibrary`,
|
|
82
|
+
* `rootStyle` holds what the <svg> element gives its children (lang), `report(diagnostic)` receives what cannot be outlined
|
|
83
|
+
* and `fail(code, message)` builds the error thrown when no face draws a text. Returns the new content and the <defs> markup.
|
|
84
|
+
*/
|
|
85
|
+
export function outlineSlideText(content, { library, rootStyle = {}, report = () => {}, fail }) {
|
|
86
|
+
const defs = new Map();
|
|
87
|
+
const reported = new Set();
|
|
88
|
+
const env = {
|
|
89
|
+
fonts: library,
|
|
90
|
+
defaults: [],
|
|
91
|
+
splitByOwner: true,
|
|
92
|
+
// The renderer reports its own font substitutions and missing glyphs while it lays the slide out.
|
|
93
|
+
diagnostic: () => {},
|
|
94
|
+
missing: () => {},
|
|
95
|
+
unavailable: (families) => fail("text-as-paths-font-unavailable", `No font face can outline text in ${families.length ? `'${families.join("', '")}'` : "the requested family"}; pass the fonts handle the slide was laid out with.`),
|
|
96
|
+
};
|
|
97
|
+
const base = inheritStyle(ROOT_STYLE, rootStyle);
|
|
98
|
+
const keep = (family) => {
|
|
99
|
+
if (reported.has(family)) return;
|
|
100
|
+
reported.add(family);
|
|
101
|
+
report({ code: "text-as-paths-kept-text", fontFamily: family, message: `'${family}' is a colour or bitmap font, which has no plain outlines; its text stays text and the SVG embeds the face.` });
|
|
102
|
+
};
|
|
103
|
+
const outlined = content.map((markup) => typeof markup === "string" && markup.includes("<text") ? outlineMarkup(markup, base, env, defs, keep) : markup);
|
|
104
|
+
const definitions = [...defs].map(([id, d]) => `<path id="${id}" d="${d}"/>`).join("");
|
|
105
|
+
return { content: outlined, defs: definitions ? `<defs>${definitions}</defs>` : "" };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Splices each <text> element of `markup`; the style it inherits is folded from its ancestors' attributes.
|
|
109
|
+
function outlineMarkup(markup, base, env, defs, keep) {
|
|
110
|
+
const ancestors = [];
|
|
111
|
+
let out = "", from = 0, start = -1, depth = 0, parentStyle = null;
|
|
112
|
+
for (const match of markup.matchAll(TOKENS)) {
|
|
113
|
+
const [token, close, name, attributes = "", selfClose] = match;
|
|
114
|
+
if (name === undefined) continue;
|
|
115
|
+
if (start >= 0) {
|
|
116
|
+
if (name === "text") depth += close ? -1 : selfClose ? 0 : 1;
|
|
117
|
+
if (depth > 0) continue;
|
|
118
|
+
const end = match.index + token.length;
|
|
119
|
+
out += markup.slice(from, start) + outlineText(markup.slice(start, end), parentStyle, env, defs, keep);
|
|
120
|
+
from = end;
|
|
121
|
+
start = -1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (close) { ancestors.pop(); continue; }
|
|
125
|
+
if (name === "text" && selfClose) {
|
|
126
|
+
// An empty <text/> (an empty table cell) draws nothing: it becomes an empty group with the element's other attributes.
|
|
127
|
+
const end = match.index + token.length;
|
|
128
|
+
out += markup.slice(from, match.index) + outlineText(token, base, env, defs, keep);
|
|
129
|
+
from = end;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (name === "text") {
|
|
133
|
+
start = match.index;
|
|
134
|
+
depth = 1;
|
|
135
|
+
parentStyle = ancestors.reduce((style, attrs) => inheritStyle(style, attrs), base);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (!selfClose) ancestors.push(attributesOf({ attrs: parseAttributes(attributes) }));
|
|
139
|
+
}
|
|
140
|
+
return from === 0 ? markup : out + markup.slice(from);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseAttributes(text) {
|
|
144
|
+
const attrs = {};
|
|
145
|
+
for (const attribute of text.matchAll(ATTRIBUTE)) attrs[attribute[1]] = decodeEntities(attribute[2] ?? attribute[3] ?? "");
|
|
146
|
+
return attrs;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function outlineText(source, parentStyle, env, defs, keep) {
|
|
150
|
+
const node = parseXml(`<svg>${source}</svg>`)?.children.find((child) => child.name === "text");
|
|
151
|
+
if (!node) return source;
|
|
152
|
+
const { runs } = layoutText(node, env, parentStyle, null);
|
|
153
|
+
const colour = runs.find((run) => COLOUR_TABLES.some((table) => run.face.font.directory?.tables?.[table]));
|
|
154
|
+
if (colour) { keep([...colour.face.names][0]); return source; }
|
|
155
|
+
const attrs = Object.entries(node.attrs).filter(([key]) => !TEXT_ONLY.has(key));
|
|
156
|
+
const hidden = node.attrs["aria-hidden"] === "true", words = logicalText(runs);
|
|
157
|
+
const label = hidden || !words ? [] : [["role", "img"], ["aria-label", words]];
|
|
158
|
+
const groups = [];
|
|
159
|
+
for (const run of runs) {
|
|
160
|
+
const drawn = drawRun(run, defs);
|
|
161
|
+
if (!drawn) continue;
|
|
162
|
+
const owner = run.owner !== node && run.owner ? run.owner : null;
|
|
163
|
+
const last = groups.at(-1);
|
|
164
|
+
if (last && last.owner === owner) last.parts.push(drawn);
|
|
165
|
+
else groups.push({ owner, parts: [drawn] });
|
|
166
|
+
}
|
|
167
|
+
const body = groups.map(({ owner, parts }) => {
|
|
168
|
+
const trace = owner ? Object.entries(owner.attrs).filter(([key]) => key.startsWith("data-")) : [];
|
|
169
|
+
return trace.length ? element("g", trace, parts.join("")) : parts.join("");
|
|
170
|
+
}).join("");
|
|
171
|
+
return element("g", [...attrs, ...label], body);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// The text in reading order: runs are placed in visual order, so a right-to-left chunk is read from its logical text.
|
|
175
|
+
function logicalText(runs) {
|
|
176
|
+
const chunks = [];
|
|
177
|
+
for (const run of runs) if (!chunks.includes(run.chunk)) chunks.push(run.chunk);
|
|
178
|
+
return chunks.map((chunk) => chunk.logical).join(" ").replace(/\s+/g, " ").trim();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function drawRun(run, defs) {
|
|
182
|
+
const style = run.style, size = run.size, face = run.face, upem = face.upem;
|
|
183
|
+
const scale = size / upem, extra = run.extraSpacing ?? 0, scaleX = run.scaleX ?? 1;
|
|
184
|
+
// The run is drawn in font units (y up) under one transform; each glyph is a <use> at its pen position in font units.
|
|
185
|
+
const uses = [];
|
|
186
|
+
let pen = 0;
|
|
187
|
+
for (const glyph of run.glyphs) {
|
|
188
|
+
const id = glyphId(face, glyph.gid, defs);
|
|
189
|
+
if (id) {
|
|
190
|
+
const x = units(pen / scale + glyph.xOffset), y = units(glyph.yOffset);
|
|
191
|
+
uses.push(`<use href="#${id}"${x === "0" ? "" : ` x="${x}"`}${y === "0" ? "" : ` y="${y}"`}/>`);
|
|
192
|
+
}
|
|
193
|
+
pen += glyph.advance * scale + glyph.spacing + extra;
|
|
194
|
+
}
|
|
195
|
+
const decorations = [...run.decoration].map((kind) => decorationRect(kind, run, scale));
|
|
196
|
+
if (!uses.length && !decorations.length) return "";
|
|
197
|
+
const paint = [["fill", style.fill]];
|
|
198
|
+
if (style["fill-opacity"] !== undefined && style["fill-opacity"] !== "1") paint.push(["fill-opacity", style["fill-opacity"]]);
|
|
199
|
+
if (style.stroke && style.stroke !== "none") {
|
|
200
|
+
// Glyphs are drawn at the em scale, so the stroke width is given in font units.
|
|
201
|
+
const width = parseLength(style["stroke-width"], { fontSize: size }) ?? 1;
|
|
202
|
+
paint.push(["stroke", style.stroke], ["stroke-width", precise(width / scale)]);
|
|
203
|
+
if (style["stroke-opacity"] !== undefined && style["stroke-opacity"] !== "1") paint.push(["stroke-opacity", style["stroke-opacity"]]);
|
|
204
|
+
}
|
|
205
|
+
if (style.visibility && style.visibility !== "visible") paint.push(["visibility", style.visibility]);
|
|
206
|
+
const glyphs = uses.length ? `<g transform="matrix(${precise(scale * scaleX)} 0 0 ${precise(-scale)} ${fixed(run.x)} ${fixed(run.y)})">${uses.join("")}</g>` : "";
|
|
207
|
+
const drawn = element("g", paint, glyphs + decorations.join(""));
|
|
208
|
+
// A link inside the text keeps its element's attributes (href, rel, target).
|
|
209
|
+
return run.link ? element("a", Object.entries(run.link.node?.attrs ?? { href: run.link.href }), drawn) : drawn;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Underline, line-through and overline in the text colour, from the font's own metrics (the vector PDF draws the same).
|
|
213
|
+
function decorationRect(kind, run, scale) {
|
|
214
|
+
const font = run.face.font, upem = run.face.upem;
|
|
215
|
+
let position, thickness;
|
|
216
|
+
if (kind === "underline") { position = -(font.underlinePosition || -upem * 0.1) * scale; thickness = (font.underlineThickness || upem * 0.05) * scale; }
|
|
217
|
+
else if (kind === "line-through") { position = -(font["OS/2"]?.yStrikeoutPosition || upem * 0.3) * scale; thickness = (font["OS/2"]?.yStrikeoutSize || upem * 0.05) * scale; }
|
|
218
|
+
else { position = -font.ascent * scale; thickness = (font.underlineThickness || upem * 0.05) * scale; }
|
|
219
|
+
return `<rect x="${fixed(run.x)}" y="${fixed(run.y + position - thickness / 2)}" width="${fixed(run.width)}" height="${fixed(thickness)}" stroke="none"/>`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// The outline of a glyph, once per slide, in font units (y up); null for a glyph that draws nothing (a space).
|
|
223
|
+
function glyphId(face, gid, defs) {
|
|
224
|
+
const id = `opf-g-${face.hash.slice(0, 12)}-${gid}`;
|
|
225
|
+
if (defs.has(id)) return id;
|
|
226
|
+
const d = outlinePath(face.font.getGlyph(gid).path.commands);
|
|
227
|
+
if (!d) return null;
|
|
228
|
+
defs.set(id, d);
|
|
229
|
+
return id;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Relative commands, each delta taken from the previous rounded point, so rounding to a tenth of a unit never drifts.
|
|
233
|
+
function outlinePath(commands) {
|
|
234
|
+
const parts = [];
|
|
235
|
+
let x = 0, y = 0, startX = 0, startY = 0, draws = false;
|
|
236
|
+
for (const { command, args } of commands) {
|
|
237
|
+
const letter = { moveTo: "m", lineTo: "l", quadraticCurveTo: "q", bezierCurveTo: "c", closePath: "z" }[command];
|
|
238
|
+
if (!letter) continue;
|
|
239
|
+
if (letter === "z") { parts.push("z"); x = startX; y = startY; continue; }
|
|
240
|
+
const points = [];
|
|
241
|
+
for (let index = 0; index < args.length; index += 2) points.push([round(args[index]), round(args[index + 1])]);
|
|
242
|
+
parts.push(letter + numbers(points.flatMap(([px, py]) => [px - x, py - y])));
|
|
243
|
+
[x, y] = points.at(-1);
|
|
244
|
+
if (letter === "m") { startX = x; startY = y; } else draws = true;
|
|
245
|
+
}
|
|
246
|
+
return draws ? parts.join("") : "";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function round(value) { return Math.round(value * 10) / 10; }
|
|
250
|
+
// Numbers joined the short SVG way: no separator before a minus sign.
|
|
251
|
+
function numbers(values) {
|
|
252
|
+
return values.map((value, index) => { const text = units(Math.round(value * 10) / 10); return index && !text.startsWith("-") ? " " + text : text; }).join("");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function element(name, attrs, body) {
|
|
256
|
+
const text = attrs.filter(([, value]) => value !== undefined && value !== null).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
|
|
257
|
+
return body ? `<${name}${text}>${body}</${name}>` : `<${name}${text}/>`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Font units to a tenth; positions to a thousandth of a pixel; the em scale to six significant digits.
|
|
261
|
+
function units(value) { const text = String(Math.round(value * 10) / 10); return text === "-0" ? "0" : text; }
|
|
262
|
+
function fixed(value) { const text = Number(value).toFixed(3).replace(/\.?0+$/, ""); return text === "-0" ? "0" : text; }
|
|
263
|
+
function precise(value) { return String(Number(Number(value).toPrecision(6))); }
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The outline engine a fonts handle carries as `outlines` (RR-64): `renderSvg(deck, { fonts, textAsPaths: true })` draws each
|
|
268
|
+
* slide's text with it. The faces are read from the handle when a slide is outlined, so faces it loads later are used too.
|
|
269
|
+
*/
|
|
270
|
+
export function textOutlines(fonts) {
|
|
271
|
+
return Object.freeze({
|
|
272
|
+
outlineSlideText: (content, options) => {
|
|
273
|
+
const library = outlineLibrary(fonts);
|
|
274
|
+
if (!library) throw options.fail("text-as-paths-needs-fonts", "textAsPaths needs font faces to outline: pass the handle loadFonts() returns as `fonts`.");
|
|
275
|
+
return outlineSlideText(content, { ...options, library });
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Deterministic local OPF renderer for SVG, PNG, and PDF output.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,6 +51,14 @@
|
|
|
51
51
|
"types": "./dist/export-browser.d.ts",
|
|
52
52
|
"default": "./dist/export-browser.js"
|
|
53
53
|
},
|
|
54
|
+
"./png": {
|
|
55
|
+
"types": "./dist/png.d.ts",
|
|
56
|
+
"default": "./dist/png.js"
|
|
57
|
+
},
|
|
58
|
+
"./pdf": {
|
|
59
|
+
"types": "./dist/pdf.d.ts",
|
|
60
|
+
"default": "./dist/pdf.js"
|
|
61
|
+
},
|
|
54
62
|
"./svg": {
|
|
55
63
|
"types": "./dist/svg.d.ts",
|
|
56
64
|
"default": "./dist/svg.js"
|
|
@@ -127,30 +135,25 @@
|
|
|
127
135
|
"test:plain-whitespace-browser": "node test/plain-whitespace-browser.mjs",
|
|
128
136
|
"test:emoji-math-browser": "node test/emoji-math-browser.mjs artifacts/emoji-math-browser.json",
|
|
129
137
|
"test:player": "node test/player-unit.mjs",
|
|
130
|
-
"test:player-browser": "node test/player-browser.mjs artifacts/player"
|
|
138
|
+
"test:player-browser": "node test/player-browser.mjs artifacts/player",
|
|
139
|
+
"test:preview-a11y-browser": "node test/preview-a11y-browser.mjs"
|
|
131
140
|
},
|
|
132
141
|
"dependencies": {
|
|
133
|
-
"@
|
|
134
|
-
"@expo-google-fonts/caladea": "0.4.2",
|
|
135
|
-
"@expo-google-fonts/cousine": "0.4.3",
|
|
136
|
-
"@expo-google-fonts/gelasio": "0.4.1",
|
|
137
|
-
"@expo-google-fonts/noto-sans": "0.4.2",
|
|
138
|
-
"@expo-google-fonts/roboto": "0.4.3",
|
|
139
|
-
"@expo-google-fonts/roboto-mono": "0.4.2",
|
|
140
|
-
"@expo-google-fonts/tinos": "0.4.2",
|
|
141
|
-
"@openpresentation/opf": "^0.15.1",
|
|
142
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
142
|
+
"@openpresentation/opf": "^0.17.0",
|
|
143
143
|
"bidi-js": "1.1.0",
|
|
144
144
|
"fontkit": "^2.0.4",
|
|
145
|
-
"pako": "1.0.11"
|
|
146
|
-
"pdf-lib": "^1.17.1",
|
|
147
|
-
"sharp": "0.35.5"
|
|
145
|
+
"pako": "1.0.11"
|
|
148
146
|
},
|
|
149
147
|
"peerDependencies": {
|
|
148
|
+
"@expo-google-fonts/arimo": "0.4.3",
|
|
149
|
+
"@expo-google-fonts/caladea": "0.4.2",
|
|
150
|
+
"@expo-google-fonts/cousine": "0.4.3",
|
|
151
|
+
"@expo-google-fonts/gelasio": "0.4.1",
|
|
150
152
|
"@expo-google-fonts/noto-color-emoji": "0.4.6",
|
|
151
153
|
"@expo-google-fonts/noto-emoji": "0.4.7",
|
|
152
154
|
"@expo-google-fonts/noto-naskh-arabic": "0.4.5",
|
|
153
155
|
"@expo-google-fonts/noto-nastaliq-urdu": "0.4.2",
|
|
156
|
+
"@expo-google-fonts/noto-sans": "0.4.2",
|
|
154
157
|
"@expo-google-fonts/noto-sans-arabic": "0.4.3",
|
|
155
158
|
"@expo-google-fonts/noto-sans-armenian": "0.4.2",
|
|
156
159
|
"@expo-google-fonts/noto-sans-bengali": "0.4.4",
|
|
@@ -182,9 +185,27 @@
|
|
|
182
185
|
"@expo-google-fonts/noto-sans-thai": "0.4.2",
|
|
183
186
|
"@expo-google-fonts/noto-serif-hebrew": "0.4.2",
|
|
184
187
|
"@expo-google-fonts/noto-serif-tibetan": "0.4.2",
|
|
185
|
-
"@expo-google-fonts/
|
|
188
|
+
"@expo-google-fonts/roboto": "0.4.3",
|
|
189
|
+
"@expo-google-fonts/roboto-mono": "0.4.2",
|
|
190
|
+
"@expo-google-fonts/stix-two-math": "0.4.0",
|
|
191
|
+
"@expo-google-fonts/tinos": "0.4.2",
|
|
192
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
193
|
+
"pdf-lib": "^1.17.1",
|
|
194
|
+
"sharp": "^0.35.5"
|
|
186
195
|
},
|
|
187
196
|
"peerDependenciesMeta": {
|
|
197
|
+
"@expo-google-fonts/arimo": {
|
|
198
|
+
"optional": true
|
|
199
|
+
},
|
|
200
|
+
"@expo-google-fonts/caladea": {
|
|
201
|
+
"optional": true
|
|
202
|
+
},
|
|
203
|
+
"@expo-google-fonts/cousine": {
|
|
204
|
+
"optional": true
|
|
205
|
+
},
|
|
206
|
+
"@expo-google-fonts/gelasio": {
|
|
207
|
+
"optional": true
|
|
208
|
+
},
|
|
188
209
|
"@expo-google-fonts/noto-color-emoji": {
|
|
189
210
|
"optional": true
|
|
190
211
|
},
|
|
@@ -197,6 +218,9 @@
|
|
|
197
218
|
"@expo-google-fonts/noto-nastaliq-urdu": {
|
|
198
219
|
"optional": true
|
|
199
220
|
},
|
|
221
|
+
"@expo-google-fonts/noto-sans": {
|
|
222
|
+
"optional": true
|
|
223
|
+
},
|
|
200
224
|
"@expo-google-fonts/noto-sans-arabic": {
|
|
201
225
|
"optional": true
|
|
202
226
|
},
|
|
@@ -290,15 +314,38 @@
|
|
|
290
314
|
"@expo-google-fonts/noto-serif-tibetan": {
|
|
291
315
|
"optional": true
|
|
292
316
|
},
|
|
317
|
+
"@expo-google-fonts/roboto": {
|
|
318
|
+
"optional": true
|
|
319
|
+
},
|
|
320
|
+
"@expo-google-fonts/roboto-mono": {
|
|
321
|
+
"optional": true
|
|
322
|
+
},
|
|
293
323
|
"@expo-google-fonts/stix-two-math": {
|
|
294
324
|
"optional": true
|
|
325
|
+
},
|
|
326
|
+
"@expo-google-fonts/tinos": {
|
|
327
|
+
"optional": true
|
|
328
|
+
},
|
|
329
|
+
"@resvg/resvg-js": {
|
|
330
|
+
"optional": true
|
|
331
|
+
},
|
|
332
|
+
"pdf-lib": {
|
|
333
|
+
"optional": true
|
|
334
|
+
},
|
|
335
|
+
"sharp": {
|
|
336
|
+
"optional": true
|
|
295
337
|
}
|
|
296
338
|
},
|
|
297
339
|
"devDependencies": {
|
|
340
|
+
"@expo-google-fonts/arimo": "0.4.3",
|
|
341
|
+
"@expo-google-fonts/caladea": "0.4.2",
|
|
342
|
+
"@expo-google-fonts/cousine": "0.4.3",
|
|
343
|
+
"@expo-google-fonts/gelasio": "0.4.1",
|
|
298
344
|
"@expo-google-fonts/noto-color-emoji": "0.4.6",
|
|
299
345
|
"@expo-google-fonts/noto-emoji": "0.4.7",
|
|
300
346
|
"@expo-google-fonts/noto-naskh-arabic": "0.4.5",
|
|
301
347
|
"@expo-google-fonts/noto-nastaliq-urdu": "0.4.2",
|
|
348
|
+
"@expo-google-fonts/noto-sans": "0.4.2",
|
|
302
349
|
"@expo-google-fonts/noto-sans-arabic": "0.4.3",
|
|
303
350
|
"@expo-google-fonts/noto-sans-armenian": "0.4.2",
|
|
304
351
|
"@expo-google-fonts/noto-sans-bengali": "0.4.4",
|
|
@@ -330,15 +377,21 @@
|
|
|
330
377
|
"@expo-google-fonts/noto-sans-thai": "0.4.2",
|
|
331
378
|
"@expo-google-fonts/noto-serif-hebrew": "0.4.2",
|
|
332
379
|
"@expo-google-fonts/noto-serif-tibetan": "0.4.2",
|
|
380
|
+
"@expo-google-fonts/roboto": "0.4.3",
|
|
381
|
+
"@expo-google-fonts/roboto-mono": "0.4.2",
|
|
333
382
|
"@expo-google-fonts/stix-two-math": "0.4.0",
|
|
383
|
+
"@expo-google-fonts/tinos": "0.4.2",
|
|
334
384
|
"@hyzyla/pdfium": "2.1.13",
|
|
335
385
|
"@jspawn/qpdf-wasm": "0.0.2",
|
|
336
386
|
"@napi-rs/canvas": "1.0.9",
|
|
387
|
+
"@resvg/resvg-js": "2.6.2",
|
|
337
388
|
"axe-core": "4.13.0",
|
|
338
389
|
"esbuild": "0.28.2",
|
|
339
390
|
"harfbuzzjs": "1.6.2",
|
|
391
|
+
"pdf-lib": "1.17.1",
|
|
340
392
|
"pdfjs-dist": "6.3.289",
|
|
341
|
-
"playwright": "1.63.0"
|
|
393
|
+
"playwright": "1.63.0",
|
|
394
|
+
"sharp": "0.35.5"
|
|
342
395
|
},
|
|
343
396
|
"bin": {
|
|
344
397
|
"opf-preview-fonts": "./dist/preview-fonts-cli.js"
|