quicklook-pptx-renderer 0.3.2 → 0.3.3
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 +11 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lint.js +3 -3
- package/dist/resolve/font-metrics.d.ts +17 -1
- package/dist/resolve/font-metrics.js +60 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,14 +167,14 @@ for (const issue of result.issues) {
|
|
|
167
167
|
break;
|
|
168
168
|
|
|
169
169
|
case "strip-embedded-fonts":
|
|
170
|
-
// issue.fix.fonts = [{ name: "Montserrat", replacement: "
|
|
170
|
+
// issue.fix.fonts = [{ name: "Montserrat", replacement: "DIN Alternate" }]
|
|
171
171
|
// Strip embedded font data and replace with cross-platform alternatives
|
|
172
172
|
break;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Also available: `SUPPORTED_GEOMETRIES` (Set of 60 presets OfficeImport renders), `FONT_SUBSTITUTIONS` (Windows → macOS font map), `GEOMETRY_FALLBACKS` (unsupported → closest supported preset), `findClosestFont()` (metric-based font matching), `widthDelta()` (percentage width difference between fonts).
|
|
177
|
+
Also available: `SUPPORTED_GEOMETRIES` (Set of 60 presets OfficeImport renders), `FONT_SUBSTITUTIONS` (Windows → macOS font map), `GEOMETRY_FALLBACKS` (unsupported → closest supported preset), `findClosestFont()` (metric-based font matching with narrower-preference), `widthDelta()` (percentage width difference between fonts), `APPLE_SYSTEM_FONTS` (29 fonts preinstalled on both macOS and iOS), `MACOS_SYSTEM_FONTS` (38 fonts on macOS).
|
|
178
178
|
|
|
179
179
|
---
|
|
180
180
|
|
|
@@ -304,7 +304,7 @@ Plus CJK mappings: MS Gothic → Hiragino Sans, SimSun → STSong, Microsoft YaH
|
|
|
304
304
|
|
|
305
305
|
### Metric-Compatible Google Fonts (Croscore)
|
|
306
306
|
|
|
307
|
-
These open-source fonts (SIL OFL) were designed as drop-in replacements for Microsoft core fonts. Width deltas measured from OS/2 tables and included in the font metrics database (
|
|
307
|
+
These open-source fonts (SIL OFL) were designed as drop-in replacements for Microsoft core fonts. Width deltas measured from OS/2 tables and included in the font metrics database (106 fonts total):
|
|
308
308
|
|
|
309
309
|
| Microsoft Font | Google Font Replacement | Width Δ vs Original | License |
|
|
310
310
|
|---|---|---|---|
|
|
@@ -325,7 +325,14 @@ Verified by testing against actual OfficeImport output:
|
|
|
325
325
|
- **Font stacks / fallback chains**: OOXML supports only one `typeface` per text run. No CSS-like `font-family: "Carlito", "Calibri", sans-serif` — it's a single value.
|
|
326
326
|
- **Parent font inheritance as fallback**: OOXML property inheritance only applies when the property is **missing**, not when the font isn't installed. If a run specifies `typeface="Carlito"`, the parent's `typeface="Arial"` is not used as a fallback.
|
|
327
327
|
|
|
328
|
-
**The only way to control fonts in QuickLook is to use a font that macOS has installed.** The `FONT_SUBSTITUTIONS` map and `findClosestFont()` API provide the data to pick the best replacement.
|
|
328
|
+
**The only way to control fonts in QuickLook is to use a font that macOS has installed.** The `FONT_SUBSTITUTIONS` map and `findClosestFont()` API provide the data to pick the best replacement. Use `APPLE_SYSTEM_FONT_LIST` as candidates to ensure results work on both macOS and iOS:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { findClosestFont, APPLE_SYSTEM_FONT_LIST } from "quicklook-pptx-renderer";
|
|
332
|
+
|
|
333
|
+
// Montserrat → DIN Alternate (-4.3% width) — narrower is preferred over wider
|
|
334
|
+
const [best] = findClosestFont("Montserrat", { candidates: APPLE_SYSTEM_FONT_LIST });
|
|
335
|
+
```
|
|
329
336
|
|
|
330
337
|
---
|
|
331
338
|
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,6 @@ export { readPresentation } from "./reader/presentation.js";
|
|
|
26
26
|
export { generateHtml, type MapperOptions, type HtmlOutput } from "./mapper/html-generator.js";
|
|
27
27
|
export { resolveColor } from "./resolve/color-resolver.js";
|
|
28
28
|
export { resolveFontFamily, FONT_SUBSTITUTIONS } from "./resolve/font-map.js";
|
|
29
|
-
export { FONT_METRICS, findClosestFont, widthDelta, type FontMetrics, type FontMatch, type FontCategory } from "./resolve/font-metrics.js";
|
|
29
|
+
export { FONT_METRICS, findClosestFont, widthDelta, APPLE_SYSTEM_FONTS, APPLE_SYSTEM_FONT_LIST, MACOS_SYSTEM_FONTS, MACOS_SYSTEM_FONT_LIST, type FontMetrics, type FontMatch, type FontCategory, } from "./resolve/font-metrics.js";
|
|
30
30
|
export { SUPPORTED_GEOMETRIES } from "./mapper/shape-mapper.js";
|
|
31
31
|
export type { Presentation, Slide, SlideLayout, SlideMaster, Shape, Picture, Group, Connector, GraphicFrame, TextBody, Paragraph, TextRun, CharacterProperties, ParagraphProperties, Color, Fill, Stroke, Effect, Theme, Drawable, DrawableBase, OrientedBounds, } from "./model/types.js";
|
package/dist/index.js
CHANGED
|
@@ -70,5 +70,5 @@ export { readPresentation } from "./reader/presentation.js";
|
|
|
70
70
|
export { generateHtml } from "./mapper/html-generator.js";
|
|
71
71
|
export { resolveColor } from "./resolve/color-resolver.js";
|
|
72
72
|
export { resolveFontFamily, FONT_SUBSTITUTIONS } from "./resolve/font-map.js";
|
|
73
|
-
export { FONT_METRICS, findClosestFont, widthDelta } from "./resolve/font-metrics.js";
|
|
73
|
+
export { FONT_METRICS, findClosestFont, widthDelta, APPLE_SYSTEM_FONTS, APPLE_SYSTEM_FONT_LIST, MACOS_SYSTEM_FONTS, MACOS_SYSTEM_FONT_LIST, } from "./resolve/font-metrics.js";
|
|
74
74
|
export { SUPPORTED_GEOMETRIES } from "./mapper/shape-mapper.js";
|
package/dist/lint.js
CHANGED
|
@@ -12,7 +12,7 @@ import { PptxPackage } from "./package/package.js";
|
|
|
12
12
|
import { readPresentation } from "./reader/presentation.js";
|
|
13
13
|
import { SUPPORTED_GEOMETRIES } from "./mapper/shape-mapper.js";
|
|
14
14
|
import { FONT_SUBSTITUTIONS } from "./resolve/font-map.js";
|
|
15
|
-
import { FONT_METRICS, widthDelta, findClosestFont } from "./resolve/font-metrics.js";
|
|
15
|
+
import { FONT_METRICS, widthDelta, findClosestFont, APPLE_SYSTEM_FONT_LIST } from "./resolve/font-metrics.js";
|
|
16
16
|
import { resolveColor } from "./resolve/color-resolver.js";
|
|
17
17
|
// ── Geometry fallback map (unsupported → closest supported) ────────
|
|
18
18
|
/** Maps commonly-used unsupported OOXML presets to visually-closest supported alternative. */
|
|
@@ -111,7 +111,7 @@ export async function lint(pptxBuffer) {
|
|
|
111
111
|
const embFontLst = presNode.embeddedFontLst?.embeddedFont;
|
|
112
112
|
if (embFontLst) {
|
|
113
113
|
const entries = Array.isArray(embFontLst) ? embFontLst : [embFontLst];
|
|
114
|
-
const safeCandidates =
|
|
114
|
+
const safeCandidates = APPLE_SYSTEM_FONT_LIST;
|
|
115
115
|
const fonts = [];
|
|
116
116
|
for (const entry of entries) {
|
|
117
117
|
const name = entry.font?.["@_typeface"];
|
|
@@ -404,7 +404,7 @@ function lintTextBody(body, slide, element, issues) {
|
|
|
404
404
|
// Find metrically-closest alternatives (consumer decides which to use)
|
|
405
405
|
const alternatives = [];
|
|
406
406
|
if (FONT_METRICS[font]) {
|
|
407
|
-
const matches = findClosestFont(font, { limit: 5 });
|
|
407
|
+
const matches = findClosestFont(font, { candidates: APPLE_SYSTEM_FONT_LIST, limit: 5 });
|
|
408
408
|
for (const m of matches) {
|
|
409
409
|
alternatives.push({ font: m.font, widthDelta: m.widthDelta });
|
|
410
410
|
}
|
|
@@ -35,6 +35,22 @@ export interface FontMetrics {
|
|
|
35
35
|
* @capsizecss/metrics (capsize.dev), which is approximate.
|
|
36
36
|
*/
|
|
37
37
|
export declare const FONT_METRICS: Record<string, FontMetrics>;
|
|
38
|
+
/**
|
|
39
|
+
* Fonts preinstalled on BOTH macOS and iOS.
|
|
40
|
+
* Only includes fonts that have entries in FONT_METRICS above.
|
|
41
|
+
* Use as `candidates` in findClosestFont() to ensure the result is a real system font.
|
|
42
|
+
*/
|
|
43
|
+
export declare const APPLE_SYSTEM_FONTS: ReadonlySet<string>;
|
|
44
|
+
/** Array form for passing to findClosestFont({ candidates }) */
|
|
45
|
+
export declare const APPLE_SYSTEM_FONT_LIST: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* macOS-only system fonts (not on iOS).
|
|
48
|
+
* Combined with APPLE_SYSTEM_FONTS for macOS-specific matching.
|
|
49
|
+
*/
|
|
50
|
+
export declare const MACOS_ONLY_FONTS: ReadonlySet<string>;
|
|
51
|
+
/** All macOS system fonts (APPLE_SYSTEM_FONTS + MACOS_ONLY_FONTS) */
|
|
52
|
+
export declare const MACOS_SYSTEM_FONTS: ReadonlySet<string>;
|
|
53
|
+
export declare const MACOS_SYSTEM_FONT_LIST: readonly string[];
|
|
38
54
|
/** Width delta (%) between two fonts. Positive = target is wider. */
|
|
39
55
|
export declare function widthDelta(source: FontMetrics, target: FontMetrics): number;
|
|
40
56
|
export interface FontMatch {
|
|
@@ -54,7 +70,7 @@ export interface FontMatch {
|
|
|
54
70
|
* @param options.limit - Max results to return (default: 3)
|
|
55
71
|
*/
|
|
56
72
|
export declare function findClosestFont(source: string | FontMetrics, options?: {
|
|
57
|
-
candidates?: string[];
|
|
73
|
+
candidates?: readonly string[];
|
|
58
74
|
sameCategory?: boolean;
|
|
59
75
|
limit?: number;
|
|
60
76
|
}): FontMatch[];
|
|
@@ -58,7 +58,26 @@ export const FONT_METRICS = {
|
|
|
58
58
|
"Avenir Next": { cat: "sans-serif", upm: 1000, xWidthAvg: 624, capH: 708, xH: 498, asc: 1000, desc: -366, lineGap: 0 },
|
|
59
59
|
"Avenir Next Medium": { cat: "sans-serif", upm: 1000, xWidthAvg: 630, capH: 708, xH: 498, asc: 1000, desc: -366, lineGap: 0 },
|
|
60
60
|
"Geneva": { cat: "sans-serif", upm: 2048, xWidthAvg: 1225, capH: 1552, xH: 1117, asc: 2048, desc: -512, lineGap: 171 },
|
|
61
|
-
"Impact": { cat: "
|
|
61
|
+
"Impact": { cat: "display", upm: 2048, xWidthAvg: 989, capH: 1620, xH: 1327, asc: 2066, desc: -432, lineGap: 0 },
|
|
62
|
+
"Gill Sans": { cat: "sans-serif", upm: 2048, xWidthAvg: 1082, capH: 1407, xH: 921, asc: 1415, desc: -471, lineGap: 305 },
|
|
63
|
+
"Optima": { cat: "sans-serif", upm: 1000, xWidthAvg: 432, capH: 700, xH: 500, asc: 919, desc: -271, lineGap: 25 },
|
|
64
|
+
"Galvji": { cat: "sans-serif", upm: 2048, xWidthAvg: 1279, capH: 1443, xH: 1078, asc: 1948, desc: -426, lineGap: 0 },
|
|
65
|
+
"DIN Alternate": { cat: "sans-serif", upm: 2048, xWidthAvg: 986, capH: 1470, xH: 1470, asc: 1585, desc: -421, lineGap: 336 },
|
|
66
|
+
"DIN Condensed": { cat: "sans-serif", upm: 1000, xWidthAvg: 374, capH: 712, xH: 507, asc: 712, desc: -288, lineGap: 200 },
|
|
67
|
+
"Copperplate": { cat: "display", upm: 1000, xWidthAvg: 661, capH: 550, xH: 440, asc: 763, desc: -250, lineGap: 20 },
|
|
68
|
+
"Baskerville": { cat: "serif", upm: 2048, xWidthAvg: 1185, capH: 1370, xH: 819, asc: 1839, desc: -504, lineGap: 0 },
|
|
69
|
+
"Didot": { cat: "serif", upm: 1000, xWidthAvg: 549, capH: 721, xH: 436, asc: 941, desc: -299, lineGap: 26 },
|
|
70
|
+
"Cochin": { cat: "serif", upm: 1000, xWidthAvg: 583, capH: 658, xH: 372, asc: 712, desc: -250, lineGap: 72 },
|
|
71
|
+
"Big Caslon": { cat: "serif", upm: 1000, xWidthAvg: 540, capH: 725, xH: 478, asc: 726, desc: -252, lineGap: 18 },
|
|
72
|
+
"Hoefler Text": { cat: "serif", upm: 2000, xWidthAvg: 831, capH: 1379, xH: 850, asc: 1864, desc: -636, lineGap: 0 },
|
|
73
|
+
"Bodoni 72": { cat: "serif", upm: 1000, xWidthAvg: 367, capH: 702, xH: 439, asc: 936, desc: -266, lineGap: 0 },
|
|
74
|
+
"American Typewriter": { cat: "serif", upm: 1000, xWidthAvg: 479, capH: 671, xH: 501, asc: 691, desc: -180, lineGap: 199 },
|
|
75
|
+
"Rockwell": { cat: "serif", upm: 2048, xWidthAvg: 1285, capH: 1507, xH: 1051, asc: 1548, desc: -498, lineGap: 145 },
|
|
76
|
+
"Charter": { cat: "serif", upm: 2048, xWidthAvg: 898, capH: 1400, xH: 950, asc: 2007, desc: -492, lineGap: 0 },
|
|
77
|
+
"Monaco": { cat: "monospace", upm: 2048, xWidthAvg: 1228, capH: 1552, xH: 1117, asc: 2048, desc: -512, lineGap: 171 },
|
|
78
|
+
"PT Sans": { cat: "sans-serif", upm: 1000, xWidthAvg: 431, capH: 700, xH: 500, asc: 1018, desc: -276, lineGap: 0 },
|
|
79
|
+
"PT Serif": { cat: "serif", upm: 1000, xWidthAvg: 448, capH: 700, xH: 500, asc: 1039, desc: -286, lineGap: 0 },
|
|
80
|
+
"PT Mono": { cat: "monospace", upm: 1000, xWidthAvg: 500, capH: 700, xH: 500, asc: 1039, desc: -286, lineGap: 0 },
|
|
62
81
|
// ── Google Fonts (from @capsizecss/metrics — raw glyph widths) ────
|
|
63
82
|
"Roboto": { cat: "sans-serif", upm: 2048, xWidthAvg: 911, capH: 1456, xH: 1082, asc: 1900, desc: -500, lineGap: 0 },
|
|
64
83
|
"Open Sans": { cat: "sans-serif", upm: 2048, xWidthAvg: 960, capH: 1462, xH: 1096, asc: 2189, desc: -600, lineGap: 0 },
|
|
@@ -80,8 +99,6 @@ export const FONT_METRICS = {
|
|
|
80
99
|
"Noto Sans": { cat: "sans-serif", upm: 1000, xWidthAvg: 474, capH: 714, xH: 536, asc: 1069, desc: -293, lineGap: 0 },
|
|
81
100
|
"Noto Serif": { cat: "serif", upm: 1000, xWidthAvg: 481, capH: 714, xH: 536, asc: 1069, desc: -293, lineGap: 0 },
|
|
82
101
|
"Noto Sans Mono": { cat: "monospace", upm: 1000, xWidthAvg: 600, capH: 714, xH: 536, asc: 1069, desc: -293, lineGap: 0 },
|
|
83
|
-
"PT Sans": { cat: "sans-serif", upm: 1000, xWidthAvg: 431, capH: 700, xH: 500, asc: 1018, desc: -276, lineGap: 0 },
|
|
84
|
-
"PT Serif": { cat: "serif", upm: 1000, xWidthAvg: 448, capH: 700, xH: 500, asc: 1039, desc: -286, lineGap: 0 },
|
|
85
102
|
"Ubuntu": { cat: "sans-serif", upm: 1000, xWidthAvg: 455, capH: 693, xH: 520, asc: 932, desc: -189, lineGap: 28 },
|
|
86
103
|
"Ubuntu Mono": { cat: "monospace", upm: 1000, xWidthAvg: 500, capH: 693, xH: 520, asc: 830, desc: -170, lineGap: 0 },
|
|
87
104
|
// ── Croscore — metric-compatible Google Fonts (OS/2 table) ────────
|
|
@@ -111,6 +128,41 @@ export const FONT_METRICS = {
|
|
|
111
128
|
"EB Garamond": { cat: "serif", upm: 1000, xWidthAvg: 528, capH: 650, xH: 400, asc: 1007, desc: -298, lineGap: 0 },
|
|
112
129
|
"Zilla Slab": { cat: "serif", upm: 1000, xWidthAvg: 538, capH: 650, xH: 445, asc: 787, desc: -213, lineGap: 200 },
|
|
113
130
|
};
|
|
131
|
+
// ── Platform font sets ──────────────────────────────────────────────
|
|
132
|
+
/**
|
|
133
|
+
* Fonts preinstalled on BOTH macOS and iOS.
|
|
134
|
+
* Only includes fonts that have entries in FONT_METRICS above.
|
|
135
|
+
* Use as `candidates` in findClosestFont() to ensure the result is a real system font.
|
|
136
|
+
*/
|
|
137
|
+
export const APPLE_SYSTEM_FONTS = new Set([
|
|
138
|
+
// Sans-serif
|
|
139
|
+
"Arial", "Helvetica", "Helvetica Neue", "Helvetica Neue Light", "Helvetica Neue Medium",
|
|
140
|
+
"Avenir", "Avenir Next", "Avenir Next Medium",
|
|
141
|
+
"Futura", "Gill Sans", "Optima", "Galvji",
|
|
142
|
+
"DIN Alternate", "DIN Condensed",
|
|
143
|
+
"Verdana", "Trebuchet MS",
|
|
144
|
+
// Serif
|
|
145
|
+
"Georgia", "Times New Roman", "Palatino",
|
|
146
|
+
"Baskerville", "Didot", "Cochin", "Hoefler Text",
|
|
147
|
+
"Bodoni 72", "American Typewriter", "Rockwell", "Charter",
|
|
148
|
+
// Monospace
|
|
149
|
+
"Menlo", "Courier New",
|
|
150
|
+
]);
|
|
151
|
+
/** Array form for passing to findClosestFont({ candidates }) */
|
|
152
|
+
export const APPLE_SYSTEM_FONT_LIST = [...APPLE_SYSTEM_FONTS];
|
|
153
|
+
/**
|
|
154
|
+
* macOS-only system fonts (not on iOS).
|
|
155
|
+
* Combined with APPLE_SYSTEM_FONTS for macOS-specific matching.
|
|
156
|
+
*/
|
|
157
|
+
export const MACOS_ONLY_FONTS = new Set([
|
|
158
|
+
"PT Sans", "PT Serif", "PT Mono",
|
|
159
|
+
"Tahoma", "Geneva", "Monaco",
|
|
160
|
+
"Times", "Courier",
|
|
161
|
+
"Big Caslon",
|
|
162
|
+
]);
|
|
163
|
+
/** All macOS system fonts (APPLE_SYSTEM_FONTS + MACOS_ONLY_FONTS) */
|
|
164
|
+
export const MACOS_SYSTEM_FONTS = new Set([...APPLE_SYSTEM_FONTS, ...MACOS_ONLY_FONTS]);
|
|
165
|
+
export const MACOS_SYSTEM_FONT_LIST = [...MACOS_SYSTEM_FONTS];
|
|
114
166
|
// ── Similarity matching ─────────────────────────────────────────────
|
|
115
167
|
/** Normalize a metric by unitsPerEm so fonts at different scales are comparable. */
|
|
116
168
|
function norm(m) {
|
|
@@ -124,12 +176,14 @@ function norm(m) {
|
|
|
124
176
|
}
|
|
125
177
|
/**
|
|
126
178
|
* Compute similarity score between two fonts. Lower = more similar.
|
|
127
|
-
*
|
|
128
|
-
*
|
|
179
|
+
* Width cost is asymmetric: wider substitutes are penalised 3× more than
|
|
180
|
+
* narrower ones, because wider causes text overflow while narrower just
|
|
181
|
+
* leaves a small gap.
|
|
129
182
|
*/
|
|
130
183
|
function similarity(a, b) {
|
|
131
184
|
const na = norm(a), nb = norm(b);
|
|
132
|
-
const
|
|
185
|
+
const rawWd = (nb.w - na.w) / (na.w || 1); // positive = wider
|
|
186
|
+
const wd = rawWd > 0 ? rawWd * 3 : Math.abs(rawWd); // 3× penalty for wider
|
|
133
187
|
const lh = Math.abs(na.lh - nb.lh) / (na.lh || 1);
|
|
134
188
|
// capH/xH might be 0 for some fonts — only include if both have data
|
|
135
189
|
let vertScore = 0, vertWeight = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quicklook-pptx-renderer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Open-source PPTX rendering engine that replicates Apple's macOS QuickLook and iOS preview — pixel for pixel. Test how PowerPoint files look on Mac/iPhone without a Mac.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|