diff --git a/decktape.js b/decktape.js index c686857..75b6ea7 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'; @@ -395,9 +395,7 @@ async function exportSlides(page, plugin, pdf, options) { } // Flush consolidated fonts Object.values(context.pdfFonts).forEach(({ ref, font }) => { - // 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()))); + pdf.context.assign(ref, pdf.context.flateStream(font.write({ type: 'ttf', hinting: true }))); }); return context; } @@ -491,36 +489,31 @@ 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 = opentype.parse(bytes.buffer); + font = Font.create(Buffer.from(bytes), { type: 'ttf', hinting: true }); } catch (e) { console.log(chalk.yellow('\nSkipping font compression: %s'), e.message); return; } - // 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" }; + // Some fonts happen to have no metadata, which is required by fonteditor + if (!font.data.name) { + font.data.name = {}; } // 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.names); + const id = descriptor.get(PDFName.of('FontName')).value() + ' - ' + fontMetadataKey(font.data.name); if (context.pdfFonts[id]) { const f = context.pdfFonts[id].font; - 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; - } + 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); } - } else { - f.glyphs.push(i, glyph); } - }; + }); descriptor.set(PDFName.of('FontFile2'), context.pdfFonts[id].ref); duplicatedEntries.push(ref); } else { @@ -530,11 +523,22 @@ 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', 'postscriptName', 'preferredFamily', 'preferredSubFamily', 'version']; + const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'preferredFamily', 'preferredSubFamily', 'uniqueSubFamily']; return Object.entries(font) .filter(([key, _]) => keys.includes(key)) - .reduce((r, [k, v], i) => r + (i > 0 ? ',' : '') + k + '=' + v.en || v, ''); + .reduce((r, [k, v], i) => r + (i > 0 ? ',' : '') + k + '=' + v, ''); } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4537ee3..338086d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "chalk": "5.1.2", - "opentype.js": "1.3.4", + "fonteditor-core": "2.1.10", "pdf-lib": "1.17.1", "puppeteer": "20.3.0", "puppeteer-core": "20.3.0", @@ -25,7 +25,7 @@ "koa-static": "5.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=12.20" } }, "node_modules/@babel/code-frame": { @@ -182,6 +182,14 @@ "@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", @@ -576,6 +584,14 @@ "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", @@ -1003,21 +1019,6 @@ "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", @@ -1295,11 +1296,6 @@ "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", @@ -1353,11 +1349,6 @@ "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", @@ -1653,6 +1644,11 @@ "@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", @@ -1929,6 +1925,14 @@ "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", @@ -2245,15 +2249,6 @@ "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", @@ -2456,11 +2451,6 @@ "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", @@ -2505,11 +2495,6 @@ "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 ab4a165..4311228 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "chalk": "5.1.2", - "opentype.js": "1.3.4", + "fonteditor-core": "2.1.10", "pdf-lib": "1.17.1", "puppeteer": "20.3.0", "puppeteer-core": "20.3.0",