boxpdf-html 1.0.0
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/LICENSE +21 -0
- package/README.md +298 -0
- package/dist/chunk-23KN7BKZ.js +2822 -0
- package/dist/chunk-23KN7BKZ.js.map +1 -0
- package/dist/cli.cjs +3052 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +249 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +2838 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +88 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
fontFamily,
|
|
4
|
+
htmlToBoxpdf
|
|
5
|
+
} from "./chunk-23KN7BKZ.js";
|
|
6
|
+
|
|
7
|
+
// src/cli.ts
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
10
|
+
import { PDFDocument, StandardFonts } from "pdf-lib";
|
|
11
|
+
import { loadFont, loadImage, renderFlow } from "boxpdf";
|
|
12
|
+
var help = `boxpdf-html
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
boxpdf-html <input.html> <output.pdf> [options]
|
|
16
|
+
boxpdf-html - <output.pdf> [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--css <file> Inject an extra stylesheet before rendering. Repeatable.
|
|
20
|
+
--base-url <dir-or-url> Base path for relative images and background URLs.
|
|
21
|
+
--font <file> Default normal TTF/OTF font.
|
|
22
|
+
--bold-font <file> Default bold TTF/OTF font.
|
|
23
|
+
--italic-font <file> Default italic TTF/OTF font.
|
|
24
|
+
--bold-italic-font <file> Default bold italic TTF/OTF font.
|
|
25
|
+
--font-family <mapping> Map a CSS family to loaded font files. Repeatable.
|
|
26
|
+
Example: Inter=normal:Inter.ttf,bold:Inter-Bold.ttf
|
|
27
|
+
--width <pt> CSS containing block width in PDF points.
|
|
28
|
+
--margin <pt> Page margin for renderFlow. Default: 40.
|
|
29
|
+
--debug Draw boxpdf debug overlays.
|
|
30
|
+
--unsupported-css Print aggregated unsupported CSS diagnostics.
|
|
31
|
+
--profile Print render phase timings.
|
|
32
|
+
-h, --help Show this help.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
boxpdf-html invoice.html invoice.pdf
|
|
36
|
+
boxpdf-html invoice.html invoice.pdf --css dist/tailwind.css
|
|
37
|
+
boxpdf-html invoice.html invoice.pdf --font ./Inter.ttf --bold-font ./Inter-Bold.ttf
|
|
38
|
+
boxpdf-html invoice.html invoice.pdf \\
|
|
39
|
+
--font-family 'Inter=normal:Inter.ttf,bold:Inter-Bold.ttf,italic:Inter-Italic.ttf'
|
|
40
|
+
`;
|
|
41
|
+
main().catch((error) => {
|
|
42
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43
|
+
console.error(`boxpdf-html: ${message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
async function main() {
|
|
47
|
+
const options = parseArgs(process.argv.slice(2));
|
|
48
|
+
if (!options.input || !options.output) {
|
|
49
|
+
printHelpAndExit(options.input || options.output ? 1 : 0);
|
|
50
|
+
}
|
|
51
|
+
const inputPath = options.input === "-" ? void 0 : resolve(options.input);
|
|
52
|
+
const baseUrl = options.baseUrl ? resolve(options.baseUrl) : inputPath ? dirname(inputPath) : process.cwd();
|
|
53
|
+
const html = injectCss(readInput(options.input), options.css.map((path) => readFileSync(resolve(path), "utf8")));
|
|
54
|
+
const pdf = await PDFDocument.create();
|
|
55
|
+
const faces = await loadFaces(pdf, options, baseUrl);
|
|
56
|
+
const images = await loadImages(pdf, html, baseUrl);
|
|
57
|
+
const result = htmlToBoxpdf(html, {
|
|
58
|
+
font: faces.normal,
|
|
59
|
+
boldFont: faces.bold,
|
|
60
|
+
italicFont: faces.italic,
|
|
61
|
+
resolveFont: fontFamily(faces.families),
|
|
62
|
+
resolveImage: ({ url }) => images.get(resolveAssetUrl(url, baseUrl)),
|
|
63
|
+
baseUrl,
|
|
64
|
+
width: options.width ?? Math.max(0, 612 - options.margin * 2),
|
|
65
|
+
diagnostics: options.unsupportedCss ? { unsupportedCss: true, sampleLimit: 5 } : void 0,
|
|
66
|
+
profile: options.profile ? (event) => console.error(`[profile] ${event.phase} ${event.elapsedMs.toFixed(1)}ms`) : void 0
|
|
67
|
+
});
|
|
68
|
+
for (const warning of result.warnings) console.warn(`boxpdf-html: ${warning}`);
|
|
69
|
+
if (options.unsupportedCss) printUnsupportedCss(result.diagnostics?.unsupportedCss ?? []);
|
|
70
|
+
await renderFlow(pdf, result.nodes, { margin: options.margin, debug: options.debug });
|
|
71
|
+
writeFileSync(resolve(options.output), await pdf.save());
|
|
72
|
+
}
|
|
73
|
+
function parseArgs(args) {
|
|
74
|
+
const options = {
|
|
75
|
+
css: [],
|
|
76
|
+
families: [],
|
|
77
|
+
margin: 40,
|
|
78
|
+
debug: false,
|
|
79
|
+
unsupportedCss: false,
|
|
80
|
+
profile: false
|
|
81
|
+
};
|
|
82
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
83
|
+
const arg = args[i];
|
|
84
|
+
if (arg === "-h" || arg === "--help" || arg === "help") printHelpAndExit(0);
|
|
85
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
86
|
+
if (!options.input) options.input = arg;
|
|
87
|
+
else if (!options.output) options.output = arg;
|
|
88
|
+
else fail(`unexpected argument "${arg}"`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const next = () => {
|
|
92
|
+
const value = args[i + 1];
|
|
93
|
+
if (!value) fail(`${arg} requires a value`);
|
|
94
|
+
i += 1;
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
switch (arg) {
|
|
98
|
+
case "--css":
|
|
99
|
+
options.css.push(next());
|
|
100
|
+
break;
|
|
101
|
+
case "--base-url":
|
|
102
|
+
options.baseUrl = next();
|
|
103
|
+
break;
|
|
104
|
+
case "--font":
|
|
105
|
+
options.font = next();
|
|
106
|
+
break;
|
|
107
|
+
case "--bold-font":
|
|
108
|
+
options.boldFont = next();
|
|
109
|
+
break;
|
|
110
|
+
case "--italic-font":
|
|
111
|
+
options.italicFont = next();
|
|
112
|
+
break;
|
|
113
|
+
case "--bold-italic-font":
|
|
114
|
+
options.boldItalicFont = next();
|
|
115
|
+
break;
|
|
116
|
+
case "--font-family":
|
|
117
|
+
options.families.push(next());
|
|
118
|
+
break;
|
|
119
|
+
case "--width":
|
|
120
|
+
options.width = parseNumber(next(), arg);
|
|
121
|
+
break;
|
|
122
|
+
case "--margin":
|
|
123
|
+
options.margin = parseNumber(next(), arg);
|
|
124
|
+
break;
|
|
125
|
+
case "--debug":
|
|
126
|
+
options.debug = true;
|
|
127
|
+
break;
|
|
128
|
+
case "--unsupported-css":
|
|
129
|
+
options.unsupportedCss = true;
|
|
130
|
+
break;
|
|
131
|
+
case "--profile":
|
|
132
|
+
options.profile = true;
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
fail(`unknown option "${arg}"`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return options;
|
|
139
|
+
}
|
|
140
|
+
function readInput(input) {
|
|
141
|
+
if (input === "-") return readFileSync(0, "utf8");
|
|
142
|
+
return readFileSync(resolve(input), "utf8");
|
|
143
|
+
}
|
|
144
|
+
function injectCss(html, stylesheets) {
|
|
145
|
+
if (stylesheets.length === 0) return html;
|
|
146
|
+
const style = `<style>
|
|
147
|
+
${stylesheets.join("\n")}
|
|
148
|
+
</style>`;
|
|
149
|
+
if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, `${style}
|
|
150
|
+
</head>`);
|
|
151
|
+
return `${style}
|
|
152
|
+
${html}`;
|
|
153
|
+
}
|
|
154
|
+
async function loadFaces(pdf, options, baseUrl) {
|
|
155
|
+
const normal = options.font ? await loadFont(pdf, readFileSync(resolve(options.font))) : await pdf.embedFont(StandardFonts.Helvetica);
|
|
156
|
+
const bold = options.boldFont ? await loadFont(pdf, readFileSync(resolve(options.boldFont))) : await pdf.embedFont(StandardFonts.HelveticaBold);
|
|
157
|
+
const italic = options.italicFont ? await loadFont(pdf, readFileSync(resolve(options.italicFont))) : await pdf.embedFont(StandardFonts.HelveticaOblique);
|
|
158
|
+
const boldItalic = options.boldItalicFont ? await loadFont(pdf, readFileSync(resolve(options.boldItalicFont))) : await pdf.embedFont(StandardFonts.HelveticaBoldOblique);
|
|
159
|
+
const families = {
|
|
160
|
+
Helvetica: { normal, bold, italic, boldItalic },
|
|
161
|
+
Arial: { normal, bold, italic, boldItalic },
|
|
162
|
+
"sans-serif": { normal, bold, italic, boldItalic },
|
|
163
|
+
serif: { normal, bold, italic, boldItalic },
|
|
164
|
+
monospace: { normal, bold, italic, boldItalic }
|
|
165
|
+
};
|
|
166
|
+
for (const mapping of options.families) {
|
|
167
|
+
const [name, spec] = splitOnce(mapping, "=");
|
|
168
|
+
if (!name || !spec) fail(`invalid --font-family "${mapping}"`);
|
|
169
|
+
families[name.trim()] = await loadFamily(pdf, spec, baseUrl);
|
|
170
|
+
}
|
|
171
|
+
return { normal, bold, italic, families };
|
|
172
|
+
}
|
|
173
|
+
async function loadFamily(pdf, spec, baseUrl) {
|
|
174
|
+
const out = {};
|
|
175
|
+
for (const part of spec.split(",")) {
|
|
176
|
+
const [rawKey, rawPath] = splitOnce(part, ":");
|
|
177
|
+
if (!rawKey || !rawPath) fail(`invalid font family face "${part}"`);
|
|
178
|
+
const key = rawKey.trim();
|
|
179
|
+
if (!["normal", "bold", "italic", "boldItalic"].includes(key) && !/^\d+$/.test(key)) {
|
|
180
|
+
fail(`invalid font face key "${key}"`);
|
|
181
|
+
}
|
|
182
|
+
out[key] = await loadFont(pdf, readFileSync(resolveAssetUrl(rawPath.trim(), baseUrl)));
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
async function loadImages(pdf, html, baseUrl) {
|
|
187
|
+
const images = /* @__PURE__ */ new Map();
|
|
188
|
+
for (const url of imageUrls(html)) {
|
|
189
|
+
const resolved = resolveAssetUrl(url, baseUrl);
|
|
190
|
+
if (images.has(resolved)) continue;
|
|
191
|
+
try {
|
|
192
|
+
images.set(resolved, await loadImage(pdf, assetSource(resolved)));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
195
|
+
console.warn(`boxpdf-html: image "${url}" did not load: ${message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return images;
|
|
199
|
+
}
|
|
200
|
+
function imageUrls(source) {
|
|
201
|
+
const urls = [];
|
|
202
|
+
for (const match of source.matchAll(/url\(\s*(?:"([^"]+)"|'([^']+)'|([^)]*?))\s*\)/gi)) {
|
|
203
|
+
const url = (match[1] ?? match[2] ?? match[3])?.trim();
|
|
204
|
+
if (url) urls.push(url);
|
|
205
|
+
}
|
|
206
|
+
for (const match of source.matchAll(/<(?:img|source)\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/gi)) {
|
|
207
|
+
const url = (match[1] ?? match[2] ?? match[3])?.trim();
|
|
208
|
+
if (url) urls.push(url);
|
|
209
|
+
}
|
|
210
|
+
return urls;
|
|
211
|
+
}
|
|
212
|
+
function resolveAssetUrl(url, baseUrl) {
|
|
213
|
+
if (/^(https?:|data:)/i.test(url)) return url;
|
|
214
|
+
if (url.startsWith("file://")) return new URL(url).pathname;
|
|
215
|
+
if (/^[a-z]+:\/\//i.test(url)) return url;
|
|
216
|
+
return isAbsolute(url) ? url : resolve(baseUrl, url);
|
|
217
|
+
}
|
|
218
|
+
function assetSource(resolved) {
|
|
219
|
+
if (/^(https?:|data:)/i.test(resolved)) return resolved;
|
|
220
|
+
if (!existsSync(resolved)) throw new Error(`file not found: ${resolved}`);
|
|
221
|
+
return readFileSync(resolved);
|
|
222
|
+
}
|
|
223
|
+
function printUnsupportedCss(items) {
|
|
224
|
+
if (items.length === 0) return;
|
|
225
|
+
console.error("Unsupported CSS:");
|
|
226
|
+
for (const item of items) {
|
|
227
|
+
console.error(`- ${item.property}: ${item.value} (${item.count})`);
|
|
228
|
+
for (const sample of item.samples ?? []) console.error(` ${sample}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function parseNumber(value, option) {
|
|
232
|
+
const parsed = Number(value);
|
|
233
|
+
if (!Number.isFinite(parsed) || parsed < 0) fail(`${option} must be a non-negative number`);
|
|
234
|
+
return parsed;
|
|
235
|
+
}
|
|
236
|
+
function splitOnce(value, separator) {
|
|
237
|
+
const index = value.indexOf(separator);
|
|
238
|
+
if (index === -1) return [value, void 0];
|
|
239
|
+
return [value.slice(0, index), value.slice(index + separator.length)];
|
|
240
|
+
}
|
|
241
|
+
function printHelpAndExit(code) {
|
|
242
|
+
console.log(help);
|
|
243
|
+
process.exit(code);
|
|
244
|
+
}
|
|
245
|
+
function fail(message) {
|
|
246
|
+
console.error(`boxpdf-html: ${message}`);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { PDFDocument, StandardFonts, type PDFFont, type PDFImage } from \"pdf-lib\";\nimport { loadFont, loadImage, renderFlow } from \"boxpdf\";\nimport { fontFamily, htmlToBoxpdf, type FontFamilyMap } from \"./index.js\";\n\ninterface CliOptions {\n input?: string;\n output?: string;\n css: string[];\n baseUrl?: string;\n font?: string;\n boldFont?: string;\n italicFont?: string;\n boldItalicFont?: string;\n families: string[];\n width?: number;\n margin: number;\n debug: boolean;\n unsupportedCss: boolean;\n profile: boolean;\n}\n\nconst help = `boxpdf-html\n\nUsage:\n boxpdf-html <input.html> <output.pdf> [options]\n boxpdf-html - <output.pdf> [options]\n\nOptions:\n --css <file> Inject an extra stylesheet before rendering. Repeatable.\n --base-url <dir-or-url> Base path for relative images and background URLs.\n --font <file> Default normal TTF/OTF font.\n --bold-font <file> Default bold TTF/OTF font.\n --italic-font <file> Default italic TTF/OTF font.\n --bold-italic-font <file> Default bold italic TTF/OTF font.\n --font-family <mapping> Map a CSS family to loaded font files. Repeatable.\n Example: Inter=normal:Inter.ttf,bold:Inter-Bold.ttf\n --width <pt> CSS containing block width in PDF points.\n --margin <pt> Page margin for renderFlow. Default: 40.\n --debug Draw boxpdf debug overlays.\n --unsupported-css Print aggregated unsupported CSS diagnostics.\n --profile Print render phase timings.\n -h, --help Show this help.\n\nExamples:\n boxpdf-html invoice.html invoice.pdf\n boxpdf-html invoice.html invoice.pdf --css dist/tailwind.css\n boxpdf-html invoice.html invoice.pdf --font ./Inter.ttf --bold-font ./Inter-Bold.ttf\n boxpdf-html invoice.html invoice.pdf \\\\\n --font-family 'Inter=normal:Inter.ttf,bold:Inter-Bold.ttf,italic:Inter-Italic.ttf'\n`;\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`boxpdf-html: ${message}`);\n process.exit(1);\n});\n\nasync function main(): Promise<void> {\n const options = parseArgs(process.argv.slice(2));\n if (!options.input || !options.output) {\n printHelpAndExit(options.input || options.output ? 1 : 0);\n }\n\n const inputPath = options.input === \"-\" ? undefined : resolve(options.input);\n const baseUrl = options.baseUrl ? resolve(options.baseUrl) : inputPath ? dirname(inputPath) : process.cwd();\n const html = injectCss(readInput(options.input), options.css.map((path) => readFileSync(resolve(path), \"utf8\")));\n const pdf = await PDFDocument.create();\n const faces = await loadFaces(pdf, options, baseUrl);\n const images = await loadImages(pdf, html, baseUrl);\n\n const result = htmlToBoxpdf(html, {\n font: faces.normal,\n boldFont: faces.bold,\n italicFont: faces.italic,\n resolveFont: fontFamily(faces.families),\n resolveImage: ({ url }) => images.get(resolveAssetUrl(url, baseUrl)),\n baseUrl,\n width: options.width ?? Math.max(0, 612 - options.margin * 2),\n diagnostics: options.unsupportedCss ? { unsupportedCss: true, sampleLimit: 5 } : undefined,\n profile: options.profile ? (event) => console.error(`[profile] ${event.phase} ${event.elapsedMs.toFixed(1)}ms`) : undefined\n });\n\n for (const warning of result.warnings) console.warn(`boxpdf-html: ${warning}`);\n if (options.unsupportedCss) printUnsupportedCss(result.diagnostics?.unsupportedCss ?? []);\n\n await renderFlow(pdf, result.nodes, { margin: options.margin, debug: options.debug });\n writeFileSync(resolve(options.output), await pdf.save());\n}\n\nfunction parseArgs(args: string[]): CliOptions {\n const options: CliOptions = {\n css: [],\n families: [],\n margin: 40,\n debug: false,\n unsupportedCss: false,\n profile: false\n };\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i]!;\n if (arg === \"-h\" || arg === \"--help\" || arg === \"help\") printHelpAndExit(0);\n if (!arg.startsWith(\"-\") || arg === \"-\") {\n if (!options.input) options.input = arg;\n else if (!options.output) options.output = arg;\n else fail(`unexpected argument \"${arg}\"`);\n continue;\n }\n\n const next = (): string => {\n const value = args[i + 1];\n if (!value) fail(`${arg} requires a value`);\n i += 1;\n return value;\n };\n\n switch (arg) {\n case \"--css\":\n options.css.push(next());\n break;\n case \"--base-url\":\n options.baseUrl = next();\n break;\n case \"--font\":\n options.font = next();\n break;\n case \"--bold-font\":\n options.boldFont = next();\n break;\n case \"--italic-font\":\n options.italicFont = next();\n break;\n case \"--bold-italic-font\":\n options.boldItalicFont = next();\n break;\n case \"--font-family\":\n options.families.push(next());\n break;\n case \"--width\":\n options.width = parseNumber(next(), arg);\n break;\n case \"--margin\":\n options.margin = parseNumber(next(), arg);\n break;\n case \"--debug\":\n options.debug = true;\n break;\n case \"--unsupported-css\":\n options.unsupportedCss = true;\n break;\n case \"--profile\":\n options.profile = true;\n break;\n default:\n fail(`unknown option \"${arg}\"`);\n }\n }\n\n return options;\n}\n\nfunction readInput(input: string): string {\n if (input === \"-\") return readFileSync(0, \"utf8\");\n return readFileSync(resolve(input), \"utf8\");\n}\n\nfunction injectCss(html: string, stylesheets: string[]): string {\n if (stylesheets.length === 0) return html;\n const style = `<style>\\n${stylesheets.join(\"\\n\")}\\n</style>`;\n if (/<\\/head>/i.test(html)) return html.replace(/<\\/head>/i, `${style}\\n</head>`);\n return `${style}\\n${html}`;\n}\n\nasync function loadFaces(\n pdf: PDFDocument,\n options: CliOptions,\n baseUrl: string\n): Promise<{ normal: PDFFont; bold: PDFFont; italic: PDFFont; families: FontFamilyMap }> {\n const normal = options.font ? await loadFont(pdf, readFileSync(resolve(options.font))) : await pdf.embedFont(StandardFonts.Helvetica);\n const bold = options.boldFont ? await loadFont(pdf, readFileSync(resolve(options.boldFont))) : await pdf.embedFont(StandardFonts.HelveticaBold);\n const italic = options.italicFont ? await loadFont(pdf, readFileSync(resolve(options.italicFont))) : await pdf.embedFont(StandardFonts.HelveticaOblique);\n const boldItalic = options.boldItalicFont\n ? await loadFont(pdf, readFileSync(resolve(options.boldItalicFont)))\n : await pdf.embedFont(StandardFonts.HelveticaBoldOblique);\n\n const families: FontFamilyMap = {\n Helvetica: { normal, bold, italic, boldItalic },\n Arial: { normal, bold, italic, boldItalic },\n \"sans-serif\": { normal, bold, italic, boldItalic },\n serif: { normal, bold, italic, boldItalic },\n monospace: { normal, bold, italic, boldItalic }\n };\n\n for (const mapping of options.families) {\n const [name, spec] = splitOnce(mapping, \"=\");\n if (!name || !spec) fail(`invalid --font-family \"${mapping}\"`);\n families[name.trim()] = await loadFamily(pdf, spec, baseUrl);\n }\n\n return { normal, bold, italic, families };\n}\n\nasync function loadFamily(pdf: PDFDocument, spec: string, baseUrl: string): Promise<FontFamilyMap[string]> {\n const out: Exclude<FontFamilyMap[string], PDFFont> = {};\n for (const part of spec.split(\",\")) {\n const [rawKey, rawPath] = splitOnce(part, \":\");\n if (!rawKey || !rawPath) fail(`invalid font family face \"${part}\"`);\n const key = rawKey.trim();\n if (![\"normal\", \"bold\", \"italic\", \"boldItalic\"].includes(key) && !/^\\d+$/.test(key)) {\n fail(`invalid font face key \"${key}\"`);\n }\n out[key as keyof typeof out] = await loadFont(pdf, readFileSync(resolveAssetUrl(rawPath.trim(), baseUrl)));\n }\n return out;\n}\n\nasync function loadImages(pdf: PDFDocument, html: string, baseUrl: string): Promise<Map<string, PDFImage>> {\n const images = new Map<string, PDFImage>();\n for (const url of imageUrls(html)) {\n const resolved = resolveAssetUrl(url, baseUrl);\n if (images.has(resolved)) continue;\n try {\n images.set(resolved, await loadImage(pdf, assetSource(resolved)));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`boxpdf-html: image \"${url}\" did not load: ${message}`);\n }\n }\n return images;\n}\n\nfunction imageUrls(source: string): string[] {\n const urls: string[] = [];\n for (const match of source.matchAll(/url\\(\\s*(?:\"([^\"]+)\"|'([^']+)'|([^)]*?))\\s*\\)/gi)) {\n const url = (match[1] ?? match[2] ?? match[3])?.trim();\n if (url) urls.push(url);\n }\n for (const match of source.matchAll(/<(?:img|source)\\b[^>]*\\bsrc\\s*=\\s*(?:\"([^\"]+)\"|'([^']+)'|([^\\s>]+))/gi)) {\n const url = (match[1] ?? match[2] ?? match[3])?.trim();\n if (url) urls.push(url);\n }\n return urls;\n}\n\nfunction resolveAssetUrl(url: string, baseUrl: string): string {\n if (/^(https?:|data:)/i.test(url)) return url;\n if (url.startsWith(\"file://\")) return new URL(url).pathname;\n if (/^[a-z]+:\\/\\//i.test(url)) return url;\n return isAbsolute(url) ? url : resolve(baseUrl, url);\n}\n\nfunction assetSource(resolved: string): string | Uint8Array {\n if (/^(https?:|data:)/i.test(resolved)) return resolved;\n if (!existsSync(resolved)) throw new Error(`file not found: ${resolved}`);\n return readFileSync(resolved);\n}\n\nfunction printUnsupportedCss(items: Array<{ property: string; value: string; count: number; samples?: string[] }>): void {\n if (items.length === 0) return;\n console.error(\"Unsupported CSS:\");\n for (const item of items) {\n console.error(`- ${item.property}: ${item.value} (${item.count})`);\n for (const sample of item.samples ?? []) console.error(` ${sample}`);\n }\n}\n\nfunction parseNumber(value: string, option: string): number {\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed < 0) fail(`${option} must be a non-negative number`);\n return parsed;\n}\n\nfunction splitOnce(value: string, separator: string): [string, string] | [string, undefined] {\n const index = value.indexOf(separator);\n if (index === -1) return [value, undefined];\n return [value.slice(0, index), value.slice(index + separator.length)];\n}\n\nfunction printHelpAndExit(code: number): never {\n console.log(help);\n process.exit(code);\n}\n\nfunction fail(message: string): never {\n console.error(`boxpdf-html: ${message}`);\n process.exit(1);\n}\n"],"mappings":"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","names":[]}
|