From ebbaa74e9fc326ad3b9571fec7113967f4c610b1 Mon Sep 17 00:00:00 2001 From: Antonin Stefanutti Date: Fri, 5 May 2023 16:33:52 +0200 Subject: [PATCH] Migrate to opentype.js --- decktape.js | 54 +++++++++++++++---------------- npm-shrinkwrap.json | 77 +++++++++++++++++++++++++++------------------ package.json | 2 +- 3 files changed, 72 insertions(+), 61 deletions(-) diff --git a/decktape.js b/decktape.js index 63828a4..8ad02fa 100755 --- a/decktape.js +++ b/decktape.js @@ -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, ''); } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 38cc6b3..291eec7 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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", diff --git a/package.json b/package.json index 6fc0645..a4db382 100644 --- a/package.json +++ b/package.json @@ -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",