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 crypto from 'crypto';
import { Font } from 'fonteditor-core';
import fs from 'fs';
import os from 'os';
import parser from './libs/nomnom.js';
import opentype from 'opentype.js'
import path from 'path';
import puppeteer from 'puppeteer';
import URI from 'urijs';
@ -371,7 +371,9 @@ async function exportSlides(page, plugin, pdf, options) {
}
// Flush consolidated fonts
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;
}
@ -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,
// until it's fixed in Puppeteer > Chromium > Skia.
// 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) {
console.log(chalk.yellow('\nSkipping font compression: %s'), e.message);
return;
}
// Some fonts happen to have no metadata, which is required by fonteditor
if (!font.data.name) {
font.data.name = {};
// Some fonts happen to have missing metadata
if (!font.names["fontFamily"]) {
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,
// 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]) {
const f = context.pdfFonts[id].font;
font.data.glyf.forEach((g, i) => {
if (g.contours && g.contours.length > 0) {
if (!f.data.glyf[i] || !f.data.glyf[i].contours || f.data.glyf[i].contours.length === 0) {
mergeGlyph(f, i, g);
}
} else if (g.compound) {
if (!f.data.glyf[i] || typeof f.data.glyf[i].compound === 'undefined') {
mergeGlyph(f, i, g);
for (let i = 0; i < font.glyphs.length; i++) {
const glyph = font.glyphs.glyphs[i];
if (i < f.glyphs.length) {
if (glyph.unicode > 0) {
const g = f.glyphs.glyphs[i];
if (typeof g.unicode === 'undefined') {
f.glyphs.glyphs[i] = glyph;
}
}
} else {
f.glyphs.push(i, glyph);
}
});
};
descriptor.set(PDFName.of('FontFile2'), context.pdfFonts[id].ref);
duplicatedEntries.push(ref);
} 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) {
const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'preferredFamily', 'preferredSubFamily', 'uniqueSubFamily'];
const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'postscriptName', 'preferredFamily', 'preferredSubFamily', 'version'];
return Object.entries(font)
.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",
"dependencies": {
"chalk": "5.1.2",
"fonteditor-core": "2.1.10",
"opentype.js": "1.3.4",
"pdf-lib": "1.17.1",
"puppeteer": "20.1.0",
"puppeteer-core": "20.1.0",
@ -25,7 +25,7 @@
"koa-static": "5.0.0"
},
"engines": {
"node": ">=12.20"
"node": ">=16.0.0"
}
},
"node_modules/@babel/code-frame": {
@ -173,14 +173,6 @@
"@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": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -575,14 +567,6 @@
"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": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -997,6 +981,21 @@
"integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==",
"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": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -1282,6 +1281,11 @@
"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": {
"version": "6.0.1",
"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",
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1624,11 +1633,6 @@
"@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": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -1905,14 +1909,6 @@
"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": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -2219,6 +2215,15 @@
"integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==",
"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": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -2429,6 +2434,11 @@
"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": {
"version": "6.0.1",
"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",
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View File

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