quicklook-pptx-renderer 0.3.1 → 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 +16 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lint.d.ts +7 -1
- package/dist/lint.js +37 -3
- package/dist/package/package.js +1 -1
- 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
|
@@ -165,11 +165,16 @@ for (const issue of result.issues) {
|
|
|
165
165
|
// issue.fix.childCount = 5, issue.fix.containsPictures = false
|
|
166
166
|
// Promote group children to slide-level drawables
|
|
167
167
|
break;
|
|
168
|
+
|
|
169
|
+
case "strip-embedded-fonts":
|
|
170
|
+
// issue.fix.fonts = [{ name: "Montserrat", replacement: "DIN Alternate" }]
|
|
171
|
+
// Strip embedded font data and replace with cross-platform alternatives
|
|
172
|
+
break;
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
```
|
|
171
176
|
|
|
172
|
-
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).
|
|
173
178
|
|
|
174
179
|
---
|
|
175
180
|
|
|
@@ -189,6 +194,7 @@ Every issue carries a typed `fix` object with machine-readable remediation data
|
|
|
189
194
|
| `geometry-forces-pdf` | info | Non-rect geometry renders as PDF — opaque background may cover adjacent content | — |
|
|
190
195
|
| `rotation-forces-pdf` | info | Rotated rect renders as PDF — enlarged bounding box may cover adjacent content | — |
|
|
191
196
|
| `text-inscription-shift` | info | Text in non-rect shape uses geometry-inscribed bounds (text may shift) | `replace-geometry` (suggests `rect`) |
|
|
197
|
+
| `embedded-font` | warn | Embedded fonts ignored by QuickLook — text renders with system substitutes | `strip-embedded-fonts` (includes per-font replacement when metrics available) |
|
|
192
198
|
| `vertical-text` | info | Vertical text uses CSS writing-mode | — |
|
|
193
199
|
|
|
194
200
|
---
|
|
@@ -298,7 +304,7 @@ Plus CJK mappings: MS Gothic → Hiragino Sans, SimSun → STSong, Microsoft YaH
|
|
|
298
304
|
|
|
299
305
|
### Metric-Compatible Google Fonts (Croscore)
|
|
300
306
|
|
|
301
|
-
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):
|
|
302
308
|
|
|
303
309
|
| Microsoft Font | Google Font Replacement | Width Δ vs Original | License |
|
|
304
310
|
|---|---|---|---|
|
|
@@ -319,7 +325,14 @@ Verified by testing against actual OfficeImport output:
|
|
|
319
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.
|
|
320
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.
|
|
321
327
|
|
|
322
|
-
**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
|
+
```
|
|
323
336
|
|
|
324
337
|
---
|
|
325
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.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* re-detecting the problem.
|
|
10
10
|
*/
|
|
11
11
|
export type Severity = "error" | "warn" | "info";
|
|
12
|
-
export type RuleId = "unsupported-geometry" | "opaque-pdf-block" | "gradient-flattened" | "font-substitution" | "table-style-unresolved" | "group-as-pdf" | "chart-no-fallback" | "text-inscription-shift" | "rotation-forces-pdf" | "geometry-forces-pdf" | "vertical-text";
|
|
12
|
+
export type RuleId = "unsupported-geometry" | "opaque-pdf-block" | "gradient-flattened" | "font-substitution" | "table-style-unresolved" | "group-as-pdf" | "chart-no-fallback" | "text-inscription-shift" | "rotation-forces-pdf" | "geometry-forces-pdf" | "vertical-text" | "embedded-font";
|
|
13
13
|
/** Machine-readable remediation data — one variant per fix strategy. */
|
|
14
14
|
export type LintFix = {
|
|
15
15
|
action: "replace-geometry";
|
|
@@ -41,6 +41,12 @@ export type LintFix = {
|
|
|
41
41
|
action: "ungroup";
|
|
42
42
|
childCount: number;
|
|
43
43
|
containsPictures: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
action: "strip-embedded-fonts";
|
|
46
|
+
fonts: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
replacement?: string;
|
|
49
|
+
}>;
|
|
44
50
|
};
|
|
45
51
|
export interface LintIssue {
|
|
46
52
|
rule: RuleId;
|
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. */
|
|
@@ -105,6 +105,40 @@ export async function lint(pptxBuffer) {
|
|
|
105
105
|
const pkg = await PptxPackage.open(pptxBuffer);
|
|
106
106
|
const pres = await readPresentation(pkg);
|
|
107
107
|
const issues = [];
|
|
108
|
+
// Presentation-level: detect embedded fonts (QuickLook ignores them)
|
|
109
|
+
const presXml = await pkg.getPartXml("ppt/presentation.xml");
|
|
110
|
+
const presNode = presXml?.Presentation ?? presXml?.presentation ?? presXml ?? {};
|
|
111
|
+
const embFontLst = presNode.embeddedFontLst?.embeddedFont;
|
|
112
|
+
if (embFontLst) {
|
|
113
|
+
const entries = Array.isArray(embFontLst) ? embFontLst : [embFontLst];
|
|
114
|
+
const safeCandidates = APPLE_SYSTEM_FONT_LIST;
|
|
115
|
+
const fonts = [];
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const name = entry.font?.["@_typeface"];
|
|
118
|
+
if (!name)
|
|
119
|
+
continue;
|
|
120
|
+
let replacement;
|
|
121
|
+
if (FONT_METRICS[name]) {
|
|
122
|
+
const matches = findClosestFont(name, { candidates: safeCandidates, sameCategory: true, limit: 1 });
|
|
123
|
+
if (matches.length > 0)
|
|
124
|
+
replacement = matches[0].font;
|
|
125
|
+
}
|
|
126
|
+
fonts.push({ name, replacement });
|
|
127
|
+
}
|
|
128
|
+
if (fonts.length > 0) {
|
|
129
|
+
const details = fonts.map(f => f.replacement ? `${f.name} → ${f.replacement}` : f.name).join(", ");
|
|
130
|
+
issues.push({
|
|
131
|
+
rule: "embedded-font",
|
|
132
|
+
severity: "warn",
|
|
133
|
+
slide: 0,
|
|
134
|
+
message: `Embedded font${fonts.length > 1 ? "s" : ""}: ${details} — QuickLook ignores embedded fonts, text will render with system substitutes`,
|
|
135
|
+
suggestion: fonts.some(f => f.replacement)
|
|
136
|
+
? `Strip embedded data and replace with cross-platform alternatives`
|
|
137
|
+
: `Remove embedded fonts to reduce file size; use cross-platform fonts instead`,
|
|
138
|
+
fix: { action: "strip-embedded-fonts", fonts },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
108
142
|
for (let i = 0; i < pres.slides.length; i++) {
|
|
109
143
|
const slide = pres.slides[i];
|
|
110
144
|
const slideNum = i + 1;
|
|
@@ -370,7 +404,7 @@ function lintTextBody(body, slide, element, issues) {
|
|
|
370
404
|
// Find metrically-closest alternatives (consumer decides which to use)
|
|
371
405
|
const alternatives = [];
|
|
372
406
|
if (FONT_METRICS[font]) {
|
|
373
|
-
const matches = findClosestFont(font, { limit: 5 });
|
|
407
|
+
const matches = findClosestFont(font, { candidates: APPLE_SYSTEM_FONT_LIST, limit: 5 });
|
|
374
408
|
for (const m of matches) {
|
|
375
409
|
alternatives.push({ font: m.font, widthDelta: m.widthDelta });
|
|
376
410
|
}
|
|
@@ -431,7 +465,7 @@ export function formatIssues(result) {
|
|
|
431
465
|
for (const issue of result.issues) {
|
|
432
466
|
if (issue.slide !== currentSlide) {
|
|
433
467
|
currentSlide = issue.slide;
|
|
434
|
-
lines.push(`\nSlide ${currentSlide}:`);
|
|
468
|
+
lines.push(currentSlide === 0 ? `\nPresentation:` : `\nSlide ${currentSlide}:`);
|
|
435
469
|
}
|
|
436
470
|
const icon = SEVERITY_ICON[issue.severity];
|
|
437
471
|
const elem = issue.element ? ` (${issue.element})` : "";
|
package/dist/package/package.js
CHANGED
|
@@ -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",
|