Migrate to opentype.js

This commit is contained in:
Antonin Stefanutti 2023-05-05 16:33:52 +02:00 committed by Antonin Stefanutti
parent fc465c4a50
commit ebbaa74e9f
3 changed files with 72 additions and 61 deletions

View File

@ -4,10 +4,10 @@
import chalk from 'chalk'; import chalk from 'chalk';
import crypto from 'crypto'; import crypto from 'crypto';
import { Font } from 'fonteditor-core';
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import os from 'os';
import parser from './libs/nomnom.js'; import parser from './libs/nomnom.js';
import opentype from 'opentype.js'
import path from 'path'; import path from 'path';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import URI from 'urijs'; import URI from 'urijs';
@ -371,7 +371,9 @@ async function exportSlides(page, plugin, pdf, options) {
} }
// Flush consolidated fonts // Flush consolidated fonts
Object.values(context.pdfFonts).forEach(({ ref, font }) => { Object.values(context.pdfFonts).forEach(({ ref, font }) => {
pdf.context.assign(ref, pdf.context.flateStream(font.write({ type: 'ttf', hinting: true }))); // Set missing glyph names to avoid opentype.js warnings
Object.values(font.glyphs.glyphs).forEach(g => g.name |= "name");
pdf.context.assign(ref, pdf.context.flateStream(new Uint8Array(font.toArrayBuffer())));
}); });
return context; return context;
} }
@ -465,31 +467,36 @@ async function printSlide(pdf, slide, context) {
// Some fonts written in the PDF may be ill-formed. Let's skip font compression in that case, // Some fonts written in the PDF may be ill-formed. Let's skip font compression in that case,
// until it's fixed in Puppeteer > Chromium > Skia. // until it's fixed in Puppeteer > Chromium > Skia.
// This happens for system fonts like Helvetica Neue for which cmap table is missing. // This happens for system fonts like Helvetica Neue for which cmap table is missing.
font = Font.create(Buffer.from(bytes), { type: 'ttf', hinting: true }); font = opentype.parse(bytes.buffer);
} catch (e) { } catch (e) {
console.log(chalk.yellow('\nSkipping font compression: %s'), e.message); console.log(chalk.yellow('\nSkipping font compression: %s'), e.message);
return; return;
} }
// Some fonts happen to have no metadata, which is required by fonteditor // Some fonts happen to have missing metadata
if (!font.data.name) { if (!font.names["fontFamily"]) {
font.data.name = {}; font.names["fontFamily"] = { en: descriptor.get(PDFName.of('FontName')).value() || "fontFamily" };
}
if (!font.names["version"]) {
font.names["version"] = { en: "version" };
} }
// PDF font name does not contain sub family on Windows 10, // PDF font name does not contain sub family on Windows 10,
// so a more robust key is computed from the font metadata // so a more robust key is computed from the font metadata
const id = descriptor.get(PDFName.of('FontName')).value() + ' - ' + fontMetadataKey(font.data.name); const id = descriptor.get(PDFName.of('FontName')).value() + ' - ' + fontMetadataKey(font.names);
if (context.pdfFonts[id]) { if (context.pdfFonts[id]) {
const f = context.pdfFonts[id].font; const f = context.pdfFonts[id].font;
font.data.glyf.forEach((g, i) => { for (let i = 0; i < font.glyphs.length; i++) {
if (g.contours && g.contours.length > 0) { const glyph = font.glyphs.glyphs[i];
if (!f.data.glyf[i] || !f.data.glyf[i].contours || f.data.glyf[i].contours.length === 0) { if (i < f.glyphs.length) {
mergeGlyph(f, i, g); if (glyph.unicode > 0) {
} const g = f.glyphs.glyphs[i];
} else if (g.compound) { if (typeof g.unicode === 'undefined') {
if (!f.data.glyf[i] || typeof f.data.glyf[i].compound === 'undefined') { f.glyphs.glyphs[i] = glyph;
mergeGlyph(f, i, g); }
} }
} else {
f.glyphs.push(i, glyph);
} }
}); };
descriptor.set(PDFName.of('FontFile2'), context.pdfFonts[id].ref); descriptor.set(PDFName.of('FontFile2'), context.pdfFonts[id].ref);
duplicatedEntries.push(ref); duplicatedEntries.push(ref);
} else { } else {
@ -499,22 +506,11 @@ async function printSlide(pdf, slide, context) {
} }
}; };
function mergeGlyph(font, index, glyf) {
if (font.data.glyf.length <= index) {
for (let i = font.data.glyf.length; i < index; i++) {
font.data.glyf.push({ contours: Array(0), advanceWidth: 0, leftSideBearing: 0 });
}
font.data.glyf.push(glyf);
} else {
font.data.glyf[index] = glyf;
}
}
function fontMetadataKey(font) { function fontMetadataKey(font) {
const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'preferredFamily', 'preferredSubFamily', 'uniqueSubFamily']; const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'postscriptName', 'preferredFamily', 'preferredSubFamily', 'version'];
return Object.entries(font) return Object.entries(font)
.filter(([key, _]) => keys.includes(key)) .filter(([key, _]) => keys.includes(key))
.reduce((r, [k, v], i) => r + (i > 0 ? ',' : '') + k + '=' + v, ''); .reduce((r, [k, v], i) => r + (i > 0 ? ',' : '') + k + '=' + v.en || v, '');
} }
} }

77
npm-shrinkwrap.json generated
View File

@ -10,7 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "5.1.2", "chalk": "5.1.2",
"fonteditor-core": "2.1.10", "opentype.js": "1.3.4",
"pdf-lib": "1.17.1", "pdf-lib": "1.17.1",
"puppeteer": "20.1.0", "puppeteer": "20.1.0",
"puppeteer-core": "20.1.0", "puppeteer-core": "20.1.0",
@ -25,7 +25,7 @@
"koa-static": "5.0.0" "koa-static": "5.0.0"
}, },
"engines": { "engines": {
"node": ">=12.20" "node": ">=16.0.0"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@ -173,14 +173,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@xmldom/xmldom": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz",
"integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -575,14 +567,6 @@
"pend": "~1.2.0" "pend": "~1.2.0"
} }
}, },
"node_modules/fonteditor-core": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/fonteditor-core/-/fonteditor-core-2.1.10.tgz",
"integrity": "sha512-NQvTBstkzkJeNTb6UUaliQs493mHj4Su0yH2d8eHQbQZQK9fIOh7X/pzKdW7BtQpDQZPSjh65ruLBqOqwGTHKQ==",
"dependencies": {
"@xmldom/xmldom": "^0.8.3"
}
},
"node_modules/fresh": { "node_modules/fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -997,6 +981,21 @@
"integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==",
"dev": true "dev": true
}, },
"node_modules/opentype.js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz",
"integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==",
"dependencies": {
"string.prototype.codepointat": "^0.2.1",
"tiny-inflate": "^1.0.3"
},
"bin": {
"ot": "bin/ot"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/pako": { "node_modules/pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -1282,6 +1281,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/string.prototype.codepointat": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
},
"node_modules/strip-ansi": { "node_modules/strip-ansi": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -1335,6 +1339,11 @@
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
}, },
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1624,11 +1633,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@xmldom/xmldom": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz",
"integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg=="
},
"accepts": { "accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -1905,14 +1909,6 @@
"pend": "~1.2.0" "pend": "~1.2.0"
} }
}, },
"fonteditor-core": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/fonteditor-core/-/fonteditor-core-2.1.10.tgz",
"integrity": "sha512-NQvTBstkzkJeNTb6UUaliQs493mHj4Su0yH2d8eHQbQZQK9fIOh7X/pzKdW7BtQpDQZPSjh65ruLBqOqwGTHKQ==",
"requires": {
"@xmldom/xmldom": "^0.8.3"
}
},
"fresh": { "fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -2219,6 +2215,15 @@
"integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==",
"dev": true "dev": true
}, },
"opentype.js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz",
"integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==",
"requires": {
"string.prototype.codepointat": "^0.2.1",
"tiny-inflate": "^1.0.3"
}
},
"pako": { "pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -2429,6 +2434,11 @@
"strip-ansi": "^6.0.1" "strip-ansi": "^6.0.1"
} }
}, },
"string.prototype.codepointat": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
},
"strip-ansi": { "strip-ansi": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -2473,6 +2483,11 @@
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
}, },
"tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
},
"toidentifier": { "toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View File

@ -25,7 +25,7 @@
}, },
"dependencies": { "dependencies": {
"chalk": "5.1.2", "chalk": "5.1.2",
"fonteditor-core": "2.1.10", "opentype.js": "1.3.4",
"pdf-lib": "1.17.1", "pdf-lib": "1.17.1",
"puppeteer": "20.1.0", "puppeteer": "20.1.0",
"puppeteer-core": "20.1.0", "puppeteer-core": "20.1.0",