@pdfme/cli 5.5.10-dev.125

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/dist/index.js ADDED
@@ -0,0 +1,3107 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from "citty";
3
+ import { PDFDocument } from "@pdfme/pdf-lib";
4
+ import { generate } from "@pdfme/generator";
5
+ import { pdf2img, pdf2size } from "@pdfme/converter";
6
+ import { DEFAULT_FONT_NAME, checkGenerateProps, checkTemplate, getDefaultFont, isUrlSafeToFetch } from "@pdfme/common";
7
+ import { basename, dirname, extname, join, resolve } from "node:path";
8
+ import * as schemas from "@pdfme/schemas";
9
+ import { accessSync, constants, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
10
+ import { homedir, tmpdir } from "node:os";
11
+ //#region src/contract.ts
12
+ var CliError = class extends Error {
13
+ code;
14
+ exitCode;
15
+ details;
16
+ constructor(message, options = {}) {
17
+ super(message);
18
+ this.name = "CliError";
19
+ this.code = options.code ?? "ECLI";
20
+ this.exitCode = options.exitCode ?? 1;
21
+ this.details = options.details;
22
+ if (options.cause !== void 0) this.cause = options.cause;
23
+ }
24
+ };
25
+ function fail(message, options = {}) {
26
+ throw new CliError(message, options);
27
+ }
28
+ function printJson(value) {
29
+ console.log(JSON.stringify(value, null, 2));
30
+ }
31
+ async function runWithContract(options, task) {
32
+ try {
33
+ return await task();
34
+ } catch (error) {
35
+ return handleCommandError(error, options.json);
36
+ }
37
+ }
38
+ function handleCommandError(error, json) {
39
+ const normalized = normalizeCliError(error);
40
+ if (json) {
41
+ const payload = {
42
+ ok: false,
43
+ error: {
44
+ code: normalized.code,
45
+ message: normalized.message
46
+ }
47
+ };
48
+ if (normalized.details !== void 0) payload.error.details = normalized.details;
49
+ printJson(payload);
50
+ } else console.error(`Error: ${normalized.message}`);
51
+ process.exit(normalized.exitCode);
52
+ }
53
+ function assertNoUnknownFlags(rawArgs, argsDefinition) {
54
+ const booleanFlags = /* @__PURE__ */ new Set();
55
+ const valueFlags = /* @__PURE__ */ new Set();
56
+ const negatedBooleanFlags = /* @__PURE__ */ new Set();
57
+ for (const [name, definition] of Object.entries(argsDefinition)) {
58
+ if (definition.type === "positional") continue;
59
+ for (const flag of getFlagVariants(name, definition.alias)) if (definition.type === "boolean") {
60
+ booleanFlags.add(flag);
61
+ if (flag.startsWith("--") && !name.startsWith("no")) negatedBooleanFlags.add(`--no-${flag.slice(2)}`);
62
+ } else valueFlags.add(flag);
63
+ }
64
+ for (let i = 0; i < rawArgs.length; i++) {
65
+ const token = rawArgs[i];
66
+ if (token === "--") break;
67
+ if (!token.startsWith("-") || token === "-") continue;
68
+ const [flag, inlineValue] = splitFlagToken(token);
69
+ if (booleanFlags.has(flag) || negatedBooleanFlags.has(flag)) {
70
+ if (inlineValue !== void 0) fail(`Boolean flag ${flag} does not take a value.`, {
71
+ code: "EARG",
72
+ exitCode: 1
73
+ });
74
+ continue;
75
+ }
76
+ if (valueFlags.has(flag)) {
77
+ if (inlineValue !== void 0) {
78
+ if (inlineValue.length === 0) fail(`Missing value for argument ${flag}.`, {
79
+ code: "EARG",
80
+ exitCode: 1
81
+ });
82
+ } else {
83
+ const next = rawArgs[i + 1];
84
+ if (!next || next === "--" || next.startsWith("-")) fail(`Missing value for argument ${flag}.`, {
85
+ code: "EARG",
86
+ exitCode: 1
87
+ });
88
+ i++;
89
+ }
90
+ continue;
91
+ }
92
+ fail(`Unknown argument: ${flag}`, {
93
+ code: "EARG",
94
+ exitCode: 1
95
+ });
96
+ }
97
+ }
98
+ function isOptionProvided(rawArgs, name, alias) {
99
+ const allowedFlags = new Set(getFlagVariants(name, alias));
100
+ return rawArgs.some((token) => {
101
+ if (!token.startsWith("-") || token === "-") return false;
102
+ const [flag] = splitFlagToken(token);
103
+ return allowedFlags.has(flag);
104
+ });
105
+ }
106
+ function parseEnumArg(optionName, value, allowedValues) {
107
+ if (typeof value !== "string" || !allowedValues.includes(value)) fail(`Invalid value for --${optionName}: expected one of ${allowedValues.join(", ")}, received ${formatValue(value)}.`, {
108
+ code: "EARG",
109
+ exitCode: 1
110
+ });
111
+ return value;
112
+ }
113
+ function parsePositiveNumberArg(optionName, value) {
114
+ const parsed = typeof value === "number" ? value : Number(value);
115
+ if (!Number.isFinite(parsed) || parsed <= 0) fail(`Invalid value for --${optionName}: expected a positive number, received ${formatValue(value)}.`, {
116
+ code: "EARG",
117
+ exitCode: 1
118
+ });
119
+ return parsed;
120
+ }
121
+ function normalizeCliError(error) {
122
+ if (error instanceof CliError) return error;
123
+ if (error instanceof Error) {
124
+ const errorCode = typeof error.code === "string" ? error.code : void 0;
125
+ if (errorCode && errorCode.startsWith("E")) return new CliError(error.message, {
126
+ code: "EIO",
127
+ exitCode: 3,
128
+ details: { errno: errorCode },
129
+ cause: error
130
+ });
131
+ return new CliError(error.message, {
132
+ code: "ERUNTIME",
133
+ exitCode: 2,
134
+ cause: error
135
+ });
136
+ }
137
+ return new CliError(String(error), {
138
+ code: "ERUNTIME",
139
+ exitCode: 2
140
+ });
141
+ }
142
+ function splitFlagToken(token) {
143
+ const eqIndex = token.indexOf("=");
144
+ if (eqIndex === -1) return [token, void 0];
145
+ return [token.slice(0, eqIndex), token.slice(eqIndex + 1)];
146
+ }
147
+ function getFlagVariants(name, alias) {
148
+ const flags = /* @__PURE__ */ new Set();
149
+ addFlagVariants(flags, name);
150
+ for (const item of toArray(alias)) addFlagVariants(flags, item);
151
+ return [...flags];
152
+ }
153
+ function addFlagVariants(flags, value) {
154
+ if (value.length === 1) {
155
+ flags.add(`-${value}`);
156
+ return;
157
+ }
158
+ flags.add(`--${value}`);
159
+ flags.add(`--${toKebabCase(value)}`);
160
+ flags.add(`--${toCamelCase(value)}`);
161
+ }
162
+ function toArray(value) {
163
+ if (!value) return [];
164
+ return Array.isArray(value) ? value : [value];
165
+ }
166
+ function toKebabCase(value) {
167
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
168
+ }
169
+ function toCamelCase(value) {
170
+ return value.replace(/[-_ ]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
171
+ }
172
+ function formatValue(value) {
173
+ if (typeof value === "string") return JSON.stringify(value);
174
+ return String(value);
175
+ }
176
+ //#endregion
177
+ //#region src/schema-plugins.ts
178
+ function isPlugin(value) {
179
+ if (!value || typeof value !== "object") return false;
180
+ const plugin = value;
181
+ return typeof plugin.pdf === "function" && typeof plugin.ui === "function" && typeof plugin.propPanel === "object" && plugin.propPanel !== null && typeof plugin.propPanel.defaultSchema === "object" && plugin.propPanel.defaultSchema !== null && typeof plugin.propPanel.defaultSchema.type === "string";
182
+ }
183
+ function collectPluginsByType(value, plugins, seen) {
184
+ if (!value || typeof value !== "object") return;
185
+ if (seen.has(value)) return;
186
+ seen.add(value);
187
+ if (isPlugin(value)) {
188
+ const type = value.propPanel.defaultSchema.type;
189
+ if (!(type in plugins)) plugins[type] = value;
190
+ return;
191
+ }
192
+ for (const child of Object.values(value)) collectPluginsByType(child, plugins, seen);
193
+ }
194
+ function buildSchemaPlugins() {
195
+ const plugins = {};
196
+ collectPluginsByType(schemas, plugins, /* @__PURE__ */ new WeakSet());
197
+ return plugins;
198
+ }
199
+ var schemaPlugins = buildSchemaPlugins();
200
+ var schemaTypes = new Set(Object.keys(schemaPlugins));
201
+ //#endregion
202
+ //#region src/utils.ts
203
+ function readJsonFile(filePath) {
204
+ const resolvedPath = resolve(filePath);
205
+ if (!existsSync(resolvedPath)) fail(`File not found: ${resolvedPath}`, {
206
+ code: "EIO",
207
+ exitCode: 3
208
+ });
209
+ try {
210
+ const content = readFileSync(resolvedPath, "utf8");
211
+ return JSON.parse(content);
212
+ } catch (error) {
213
+ fail(`Failed to parse JSON file: ${resolvedPath}. ${error instanceof Error ? error.message : String(error)}`, {
214
+ code: "EIO",
215
+ exitCode: 3,
216
+ cause: error
217
+ });
218
+ }
219
+ }
220
+ function loadInput(args) {
221
+ const positionalFile = args._[0];
222
+ if (positionalFile && !args.template && !args.inputs) {
223
+ const jobFilePath = resolve(positionalFile);
224
+ const data = readJsonFile(jobFilePath);
225
+ if ("template" in data && "inputs" in data) return {
226
+ template: data.template,
227
+ inputs: data.inputs,
228
+ options: data.options,
229
+ templateDir: dirname(jobFilePath)
230
+ };
231
+ fail("Positional file must be a unified format with \"template\" and \"inputs\" keys. Use -t and -i for separate files.", {
232
+ code: "EARG",
233
+ exitCode: 1
234
+ });
235
+ }
236
+ if (args.template) {
237
+ if (!args.inputs) fail("--inputs (-i) is required when using --template (-t).", {
238
+ code: "EARG",
239
+ exitCode: 1
240
+ });
241
+ const templatePath = resolve(args.template);
242
+ return {
243
+ template: readJsonFile(templatePath),
244
+ inputs: readJsonFile(resolve(args.inputs)),
245
+ templateDir: dirname(templatePath)
246
+ };
247
+ }
248
+ fail("No input provided. Use a unified job file or pass --template/-t with --inputs/-i.", {
249
+ code: "EARG",
250
+ exitCode: 1
251
+ });
252
+ }
253
+ function resolveBasePdf(template, basePdfArg, templateDir) {
254
+ if (basePdfArg) {
255
+ const resolvedBasePdf = resolve(basePdfArg);
256
+ if (!existsSync(resolvedBasePdf)) fail(`Base PDF file not found: ${resolvedBasePdf}`, {
257
+ code: "EIO",
258
+ exitCode: 3
259
+ });
260
+ const pdfData = new Uint8Array(readFileSync(resolvedBasePdf));
261
+ return {
262
+ ...template,
263
+ basePdf: pdfData
264
+ };
265
+ }
266
+ const basePdf = template.basePdf;
267
+ if (typeof basePdf === "string" && basePdf.endsWith(".pdf") && !basePdf.startsWith("data:")) {
268
+ const resolvedBasePdf = templateDir ? resolve(templateDir, basePdf) : resolve(basePdf);
269
+ if (!existsSync(resolvedBasePdf)) fail(`Base PDF file not found: ${resolvedBasePdf}`, {
270
+ code: "EIO",
271
+ exitCode: 3
272
+ });
273
+ const pdfData = new Uint8Array(readFileSync(resolvedBasePdf));
274
+ return {
275
+ ...template,
276
+ basePdf: pdfData
277
+ };
278
+ }
279
+ return template;
280
+ }
281
+ function getImageOutputPaths(pdfOutputPath, pageCount, imageFormat) {
282
+ const dir = dirname(pdfOutputPath);
283
+ const base = basename(pdfOutputPath, extname(pdfOutputPath));
284
+ const ext = imageFormat === "jpeg" ? "jpg" : "png";
285
+ return Array.from({ length: pageCount }, (_, i) => join(dir, `${base}-${i + 1}.${ext}`));
286
+ }
287
+ function writeOutput(filePath, data) {
288
+ try {
289
+ const dir = dirname(filePath);
290
+ if (dir && dir !== "." && !existsSync(dir)) mkdirSync(dir, { recursive: true });
291
+ writeFileSync(filePath, data instanceof ArrayBuffer ? new Uint8Array(data) : data);
292
+ } catch (error) {
293
+ fail(`Failed to write file: ${filePath}. ${error instanceof Error ? error.message : String(error)}`, {
294
+ code: "EIO",
295
+ exitCode: 3,
296
+ cause: error
297
+ });
298
+ }
299
+ }
300
+ function readPdfFile(filePath) {
301
+ const resolvedPath = resolve(filePath);
302
+ if (!existsSync(resolvedPath)) fail(`PDF file not found: ${resolvedPath}`, {
303
+ code: "EIO",
304
+ exitCode: 3
305
+ });
306
+ try {
307
+ return new Uint8Array(readFileSync(resolvedPath));
308
+ } catch (error) {
309
+ fail(`Failed to read PDF file: ${resolvedPath}. ${error instanceof Error ? error.message : String(error)}`, {
310
+ code: "EIO",
311
+ exitCode: 3,
312
+ cause: error
313
+ });
314
+ }
315
+ }
316
+ var PAPER_SIZES = {
317
+ A3: [297, 420],
318
+ A4: [210, 297],
319
+ A5: [148, 210],
320
+ A6: [105, 148],
321
+ B4: [250, 353],
322
+ B5: [176, 250],
323
+ Letter: [216, 279],
324
+ Legal: [216, 356],
325
+ Tabloid: [279, 432]
326
+ };
327
+ function detectPaperSize(width, height) {
328
+ const tolerance = 2;
329
+ for (const [name, [w, h]] of Object.entries(PAPER_SIZES)) if (Math.abs(width - w) <= tolerance && Math.abs(height - h) <= tolerance || Math.abs(width - h) <= tolerance && Math.abs(height - w) <= tolerance) return `${name} ${width < height ? "portrait" : "landscape"}`;
330
+ return null;
331
+ }
332
+ function parsePageRange(rangeStr, totalPages) {
333
+ const pages = /* @__PURE__ */ new Set();
334
+ for (const part of rangeStr.split(",")) {
335
+ const trimmed = part.trim();
336
+ if (!trimmed) fail(`Invalid page range: ${JSON.stringify(rangeStr)}. Empty segments are not allowed.`, {
337
+ code: "EARG",
338
+ exitCode: 1
339
+ });
340
+ if (trimmed.includes("-")) {
341
+ const [startStr, endStr] = trimmed.split("-");
342
+ if (!startStr || !endStr || !/^\d+$/.test(startStr) || !/^\d+$/.test(endStr)) fail(`Invalid page range segment: ${JSON.stringify(trimmed)}. Use formats like "1-3" or "2".`, {
343
+ code: "EARG",
344
+ exitCode: 1
345
+ });
346
+ const start = Number.parseInt(startStr, 10);
347
+ const end = Number.parseInt(endStr, 10);
348
+ if (start < 1 || end < 1 || start > end || end > totalPages) fail(`Invalid page range segment: ${JSON.stringify(trimmed)}. Pages must be between 1 and ${totalPages}.`, {
349
+ code: "EARG",
350
+ exitCode: 1
351
+ });
352
+ for (let i = start; i <= end; i++) pages.add(i);
353
+ } else {
354
+ if (!/^\d+$/.test(trimmed)) fail(`Invalid page range segment: ${JSON.stringify(trimmed)}. Use formats like "1-3" or "2".`, {
355
+ code: "EARG",
356
+ exitCode: 1
357
+ });
358
+ const p = Number.parseInt(trimmed, 10);
359
+ if (p < 1 || p > totalPages) fail(`Invalid page number: ${p}. Pages must be between 1 and ${totalPages}.`, {
360
+ code: "EARG",
361
+ exitCode: 1
362
+ });
363
+ pages.add(p);
364
+ }
365
+ }
366
+ return [...pages].sort((a, b) => a - b);
367
+ }
368
+ async function readJsonFromStdin() {
369
+ const chunks = [];
370
+ for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
371
+ const content = Buffer.concat(chunks).toString("utf8").trim();
372
+ if (!content) fail("No JSON input received on stdin.", {
373
+ code: "EARG",
374
+ exitCode: 1
375
+ });
376
+ try {
377
+ return JSON.parse(content);
378
+ } catch (error) {
379
+ fail(`Failed to parse JSON from stdin. ${error instanceof Error ? error.message : String(error)}`, {
380
+ code: "EIO",
381
+ exitCode: 3,
382
+ cause: error
383
+ });
384
+ }
385
+ }
386
+ function ensureSafeDefaultOutputPath(options) {
387
+ const issue = getSafeDefaultOutputPathIssue(options);
388
+ if (issue) fail(issue, {
389
+ code: "EARG",
390
+ exitCode: 1
391
+ });
392
+ }
393
+ function getSafeDefaultOutputPathIssue(options) {
394
+ const { filePath, rawArgs, optionName, optionAlias, defaultValue, force = false } = options;
395
+ if (force || isOptionProvided(rawArgs, optionName, optionAlias) || filePath !== defaultValue) return;
396
+ const resolvedPath = resolve(filePath);
397
+ if (!existsSync(resolvedPath)) return;
398
+ return `Refusing to overwrite implicit default output file: ${resolvedPath}. Use -o to choose an explicit path or --force to overwrite.`;
399
+ }
400
+ function inspectWriteTarget(filePath) {
401
+ const resolvedPath = resolve(filePath);
402
+ const parentDir = dirname(resolvedPath);
403
+ const exists = existsSync(resolvedPath);
404
+ let existingType;
405
+ if (exists) {
406
+ const stat = statSync(resolvedPath);
407
+ if (stat.isFile()) existingType = "file";
408
+ else if (stat.isDirectory()) existingType = "directory";
409
+ else existingType = "other";
410
+ }
411
+ const checkedPath = exists && existingType === "file" ? resolvedPath : findExistingParent$1(parentDir);
412
+ const checkedType = getFsEntryType(checkedPath);
413
+ try {
414
+ accessSync(checkedPath, constants.W_OK);
415
+ return {
416
+ path: filePath,
417
+ resolvedPath,
418
+ parentDir,
419
+ exists,
420
+ existingType,
421
+ writable: true,
422
+ checkedPath: checkedPath !== resolvedPath ? checkedPath : void 0,
423
+ checkedType
424
+ };
425
+ } catch (error) {
426
+ return {
427
+ path: filePath,
428
+ resolvedPath,
429
+ parentDir,
430
+ exists,
431
+ existingType,
432
+ writable: false,
433
+ checkedPath: checkedPath !== resolvedPath ? checkedPath : void 0,
434
+ checkedType,
435
+ error: error instanceof Error ? error.message : String(error)
436
+ };
437
+ }
438
+ }
439
+ function findExistingParent$1(path) {
440
+ let current = path;
441
+ while (!existsSync(current)) {
442
+ const parent = dirname(current);
443
+ if (parent === current) break;
444
+ current = parent;
445
+ }
446
+ return current;
447
+ }
448
+ function getFsEntryType(path) {
449
+ const stat = statSync(path);
450
+ if (stat.isFile()) return "file";
451
+ if (stat.isDirectory()) return "directory";
452
+ return "other";
453
+ }
454
+ //#endregion
455
+ //#region src/diagnostics.ts
456
+ var KNOWN_TEMPLATE_KEYS = new Set([
457
+ "author",
458
+ "basePdf",
459
+ "columns",
460
+ "pdfmeVersion",
461
+ "schemas"
462
+ ]);
463
+ var KNOWN_JOB_KEYS = new Set([
464
+ "template",
465
+ "inputs",
466
+ "options"
467
+ ]);
468
+ function findClosestType(type) {
469
+ let bestMatch = null;
470
+ let bestDist = Infinity;
471
+ for (const known of schemaTypes) {
472
+ const dist = levenshtein(type.toLowerCase(), known.toLowerCase());
473
+ if (dist < bestDist && dist <= 3) {
474
+ bestDist = dist;
475
+ bestMatch = known;
476
+ }
477
+ }
478
+ return bestMatch;
479
+ }
480
+ function levenshtein(a, b) {
481
+ const m = a.length;
482
+ const n = b.length;
483
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
484
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
485
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
486
+ for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
487
+ return dp[m][n];
488
+ }
489
+ function validateTemplate(template) {
490
+ const errors = [];
491
+ const warnings = [];
492
+ try {
493
+ checkTemplate(template);
494
+ } catch (error) {
495
+ errors.push(error instanceof Error ? error.message : String(error));
496
+ }
497
+ const schemaPages = normalizeSchemaPages$2(template.schemas);
498
+ if (schemaPages.length === 0) return {
499
+ errors,
500
+ warnings,
501
+ pages: 0,
502
+ fields: 0
503
+ };
504
+ const totalFields = schemaPages.reduce((sum, page) => sum + page.length, 0);
505
+ let pageWidth = 210;
506
+ let pageHeight = 297;
507
+ if (template.basePdf && typeof template.basePdf === "object" && "width" in template.basePdf) {
508
+ pageWidth = template.basePdf.width;
509
+ pageHeight = template.basePdf.height;
510
+ }
511
+ const allNames = /* @__PURE__ */ new Map();
512
+ for (let pageIdx = 0; pageIdx < schemaPages.length; pageIdx++) {
513
+ const page = schemaPages[pageIdx];
514
+ if (!Array.isArray(page)) continue;
515
+ const pageNames = /* @__PURE__ */ new Set();
516
+ for (const schema of page) {
517
+ if (typeof schema !== "object" || schema === null) continue;
518
+ const name = schema.name;
519
+ const type = schema.type;
520
+ const position = schema.position;
521
+ const width = schema.width;
522
+ const height = schema.height;
523
+ if (type && !schemaTypes.has(type)) {
524
+ const suggestion = findClosestType(type);
525
+ const hint = suggestion ? ` Did you mean: ${suggestion}?` : "";
526
+ errors.push(`Field "${name}" has unknown type "${type}".${hint} Available types: ${[...schemaTypes].join(", ")}`);
527
+ }
528
+ if (name && pageNames.has(name)) errors.push(`Duplicate field name "${name}" on page ${pageIdx + 1}`);
529
+ if (name) {
530
+ pageNames.add(name);
531
+ if (!allNames.has(name)) allNames.set(name, []);
532
+ allNames.get(name).push(pageIdx + 1);
533
+ }
534
+ if (position && width !== void 0 && height !== void 0) {
535
+ if (position.x + width > pageWidth + 1) warnings.push(`Field "${name}" at (${position.x},${position.y}) extends beyond page width (${pageWidth}mm)`);
536
+ if (position.y + height > pageHeight + 1) warnings.push(`Field "${name}" at (${position.x},${position.y}) extends beyond page height (${pageHeight}mm)`);
537
+ if (position.x < 0 || position.y < 0) warnings.push(`Field "${name}" has negative position (${position.x},${position.y})`);
538
+ }
539
+ }
540
+ }
541
+ for (const [name, pages] of allNames) if (pages.length > 1) warnings.push(`Field name "${name}" appears on multiple pages: ${pages.join(", ")}`);
542
+ return {
543
+ errors,
544
+ warnings,
545
+ pages: schemaPages.length,
546
+ fields: totalFields
547
+ };
548
+ }
549
+ function inspectTemplate(template, templateDir) {
550
+ const flattenedSchemas = normalizeSchemaPages$2(template.schemas).flat();
551
+ const collectedSchemaTypes = getUniqueStringValues(flattenedSchemas.map((schema) => schema.type));
552
+ const requiredFonts = getUniqueStringValues(flattenedSchemas.map((schema) => schema.fontName));
553
+ return {
554
+ schemaTypes: collectedSchemaTypes,
555
+ requiredPlugins: collectedSchemaTypes.filter((type) => schemaTypes.has(type)),
556
+ requiredFonts,
557
+ basePdf: summarizeBasePdf(template.basePdf, templateDir)
558
+ };
559
+ }
560
+ function collectInputHints(template) {
561
+ const hintMap = /* @__PURE__ */ new Map();
562
+ const schemaPages = normalizeSchemaPages$2(template.schemas);
563
+ const radioGroupMembers = collectRadioGroupMembers(schemaPages);
564
+ for (let pageIdx = 0; pageIdx < schemaPages.length; pageIdx++) for (const schema of schemaPages[pageIdx]) {
565
+ const name = typeof schema.name === "string" ? schema.name : "";
566
+ const type = typeof schema.type === "string" ? schema.type : "";
567
+ const readOnly = schema.readOnly === true;
568
+ if (!name || !type || readOnly) continue;
569
+ const hint = buildFieldInputHint(schema, pageIdx + 1, radioGroupMembers);
570
+ const key = [
571
+ hint.name,
572
+ hint.type,
573
+ hint.expectedInput.kind,
574
+ JSON.stringify(hint.expectedInput.example ?? null),
575
+ hint.expectedInput.format ?? "",
576
+ hint.expectedInput.canonicalFormat ?? "",
577
+ hint.expectedInput.contentKind ?? "",
578
+ hint.expectedInput.rule ?? "",
579
+ (hint.expectedInput.variableNames ?? []).join("\0"),
580
+ (hint.expectedInput.allowedValues ?? []).join("\0"),
581
+ hint.expectedInput.groupName ?? "",
582
+ (hint.expectedInput.groupMemberNames ?? []).join("\0"),
583
+ String(hint.expectedInput.columnCount ?? ""),
584
+ (hint.expectedInput.columnHeaders ?? []).join("\0"),
585
+ hint.expectedInput.acceptsJsonString === true ? "1" : "0"
586
+ ].join("");
587
+ const existing = hintMap.get(key);
588
+ if (existing) {
589
+ existing.pages = [...new Set([...existing.pages, pageIdx + 1])].sort((a, b) => a - b);
590
+ existing.required = existing.required || hint.required;
591
+ continue;
592
+ }
593
+ hintMap.set(key, hint);
594
+ }
595
+ return [...hintMap.values()].sort((a, b) => a.name.localeCompare(b.name) || a.type.localeCompare(b.type));
596
+ }
597
+ function validateInputContracts(template, inputs) {
598
+ const issues = getInputContractIssues(template, inputs);
599
+ if (issues.length > 0) fail(issues[0], {
600
+ code: "EVALIDATE",
601
+ exitCode: 1
602
+ });
603
+ }
604
+ function getInputContractIssues(template, inputs) {
605
+ const hints = collectInputHints(template);
606
+ const issues = [];
607
+ for (let inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
608
+ const input = inputs[inputIndex] ?? {};
609
+ for (const hint of hints) {
610
+ const issue = getInputContractIssue(hint, input, inputIndex);
611
+ if (issue) issues.push(issue);
612
+ }
613
+ issues.push(...getRadioGroupSelectionIssues(hints, input, inputIndex));
614
+ }
615
+ return issues;
616
+ }
617
+ async function loadValidationSource(file, options) {
618
+ const data = await loadValidationInput(file, options.noInputMessage);
619
+ const record = assertRecordObject(data.json, "Validation input");
620
+ const hasTemplate = "template" in record;
621
+ const hasInputs = "inputs" in record;
622
+ if (hasTemplate || hasInputs) {
623
+ if (!hasTemplate || !hasInputs) fail("Unified job validation requires both \"template\" and \"inputs\" keys.", {
624
+ code: "EARG",
625
+ exitCode: 1
626
+ });
627
+ return {
628
+ mode: "job",
629
+ template: assertRecordObject(record.template, "Unified job template"),
630
+ inputs: record.inputs,
631
+ options: record.options,
632
+ templateDir: data.templateDir,
633
+ jobWarnings: Object.keys(record).filter((key) => !KNOWN_JOB_KEYS.has(key)).sort().map((key) => `Unknown unified job field: ${key}`)
634
+ };
635
+ }
636
+ return {
637
+ mode: "template",
638
+ template: assertRecordObject(record, "Template"),
639
+ templateDir: data.templateDir,
640
+ jobWarnings: []
641
+ };
642
+ }
643
+ async function loadValidationInput(file, noInputMessage) {
644
+ if (!file || file === "-") {
645
+ if (file === "-" || !process.stdin.isTTY) return { json: await readJsonFromStdin() };
646
+ fail(noInputMessage, {
647
+ code: "EARG",
648
+ exitCode: 1
649
+ });
650
+ }
651
+ const resolvedFile = resolve(file);
652
+ return {
653
+ json: readJsonFile(resolvedFile),
654
+ templateDir: dirname(resolvedFile)
655
+ };
656
+ }
657
+ function assertRecordObject(value, label) {
658
+ if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`${label} must be a JSON object.`, {
659
+ code: "EARG",
660
+ exitCode: 1
661
+ });
662
+ return value;
663
+ }
664
+ function normalizeSchemaPages$2(rawSchemas) {
665
+ if (!Array.isArray(rawSchemas)) return [];
666
+ return rawSchemas.map((page) => {
667
+ if (Array.isArray(page)) return page.filter((schema) => typeof schema === "object" && schema !== null);
668
+ if (typeof page === "object" && page !== null) return Object.values(page).filter((schema) => typeof schema === "object" && schema !== null);
669
+ return [];
670
+ });
671
+ }
672
+ function buildFieldInputHint(schema, page, radioGroupMembers) {
673
+ const type = schema.type;
674
+ if (type === "multiVariableText") {
675
+ const variableNames = getUniqueStringValues(Array.isArray(schema.variables) ? schema.variables : []);
676
+ return {
677
+ name: schema.name,
678
+ type,
679
+ pages: [page],
680
+ required: schema.required === true,
681
+ expectedInput: {
682
+ kind: "jsonStringObject",
683
+ variableNames,
684
+ example: buildMultiVariableTextExample(variableNames)
685
+ }
686
+ };
687
+ }
688
+ if (type === "checkbox") return {
689
+ name: schema.name,
690
+ type,
691
+ pages: [page],
692
+ required: schema.required === true,
693
+ expectedInput: {
694
+ kind: "enumString",
695
+ allowedValues: ["false", "true"],
696
+ example: "true"
697
+ }
698
+ };
699
+ if (type === "radioGroup") {
700
+ const groupName = typeof schema.group === "string" ? schema.group : "";
701
+ const groupMemberNames = groupName ? radioGroupMembers.get(groupName) ?? [] : [];
702
+ return {
703
+ name: schema.name,
704
+ type,
705
+ pages: [page],
706
+ required: schema.required === true,
707
+ expectedInput: {
708
+ kind: "enumString",
709
+ allowedValues: ["false", "true"],
710
+ example: "true",
711
+ ...groupName ? { groupName } : {},
712
+ ...groupMemberNames.length > 0 ? { groupMemberNames } : {}
713
+ }
714
+ };
715
+ }
716
+ if (type === "select") {
717
+ const allowedValues = getUniqueOrderedStringValues(Array.isArray(schema.options) ? schema.options : []);
718
+ if (allowedValues.length > 0) return {
719
+ name: schema.name,
720
+ type,
721
+ pages: [page],
722
+ required: schema.required === true,
723
+ expectedInput: {
724
+ kind: "enumString",
725
+ allowedValues,
726
+ example: allowedValues[0]
727
+ }
728
+ };
729
+ }
730
+ const barcodeRule = getBarcodeRule(type);
731
+ if (barcodeRule) return {
732
+ name: schema.name,
733
+ type,
734
+ pages: [page],
735
+ required: schema.required === true,
736
+ expectedInput: {
737
+ kind: "string",
738
+ contentKind: "barcodeText",
739
+ rule: barcodeRule
740
+ }
741
+ };
742
+ const assetContentKind = getAssetContentKind(type);
743
+ if (assetContentKind) return {
744
+ name: schema.name,
745
+ type,
746
+ pages: [page],
747
+ required: schema.required === true,
748
+ expectedInput: {
749
+ kind: "string",
750
+ contentKind: assetContentKind
751
+ }
752
+ };
753
+ if (type === "table") {
754
+ const columnHeaders = getOrderedStringValues(Array.isArray(schema.head) ? schema.head : []);
755
+ const columnCount = getTableColumnCount(schema, columnHeaders);
756
+ return {
757
+ name: schema.name,
758
+ type,
759
+ pages: [page],
760
+ required: schema.required === true,
761
+ expectedInput: {
762
+ kind: "stringMatrix",
763
+ ...columnCount > 0 ? { columnCount } : {},
764
+ ...columnHeaders.length > 0 ? { columnHeaders } : {},
765
+ example: buildTableInputExample(columnHeaders, columnCount),
766
+ acceptsJsonString: true
767
+ }
768
+ };
769
+ }
770
+ if (type === "date" || type === "time" || type === "dateTime") {
771
+ const canonicalFormat = getCanonicalDateStoredFormat(type);
772
+ return {
773
+ name: schema.name,
774
+ type,
775
+ pages: [page],
776
+ required: schema.required === true,
777
+ expectedInput: {
778
+ kind: "string",
779
+ format: getDateHintFormat(schema, canonicalFormat),
780
+ canonicalFormat,
781
+ example: getDateInputExample(type)
782
+ }
783
+ };
784
+ }
785
+ return {
786
+ name: schema.name,
787
+ type,
788
+ pages: [page],
789
+ required: schema.required === true,
790
+ expectedInput: { kind: "string" }
791
+ };
792
+ }
793
+ function buildMultiVariableTextExample(variableNames) {
794
+ return JSON.stringify(Object.fromEntries(variableNames.map((variableName) => [variableName, variableName.toUpperCase()])));
795
+ }
796
+ function buildTableInputExample(columnHeaders, columnCount) {
797
+ if (columnCount <= 0) return [];
798
+ return [Array.from({ length: columnCount }, (_, index) => {
799
+ const header = columnHeaders[index];
800
+ return header ? `${header} value` : `cell-${index + 1}`;
801
+ })];
802
+ }
803
+ function getTableColumnCount(schema, columnHeaders) {
804
+ if (columnHeaders.length > 0) return columnHeaders.length;
805
+ if (Array.isArray(schema.headWidthPercentages) && schema.headWidthPercentages.length > 0) return schema.headWidthPercentages.length;
806
+ return parseTableStringMatrix(schema.content)?.[0]?.length ?? 0;
807
+ }
808
+ function getAssetContentKind(type) {
809
+ switch (type) {
810
+ case "image": return "imageDataUrl";
811
+ case "signature": return "signatureImageDataUrl";
812
+ case "svg": return "svgMarkup";
813
+ default: return null;
814
+ }
815
+ }
816
+ function getBarcodeRule(type) {
817
+ switch (type) {
818
+ case "qrcode": return "Any non-empty string up to 499 characters.";
819
+ case "japanpost": return "Start with 7 digits, then continue with digits, A-Z, or hyphen (-).";
820
+ case "ean13": return "12 or 13 digits; if 13 digits are provided, the final check digit must be valid.";
821
+ case "ean8": return "7 or 8 digits; if 8 digits are provided, the final check digit must be valid.";
822
+ case "code39": return "Use uppercase A-Z, digits, spaces, and symbols - . $ / + %.";
823
+ case "code128": return "Text must not contain Kanji, Hiragana, Katakana, or full-width ASCII characters.";
824
+ case "nw7": return "Start and end with A-D; inner characters may be digits or - . $ : / +.";
825
+ case "itf14": return "13 or 14 digits; if 14 digits are provided, the final check digit must be valid.";
826
+ case "upca": return "11 or 12 digits; if 12 digits are provided, the final check digit must be valid.";
827
+ case "upce": return "Must start with 0 and be 7 or 8 digits total; if 8 digits are provided, the final check digit must be valid.";
828
+ case "gs1datamatrix": return "Include (01) followed by a GTIN of 8, 12, 13, or 14 digits with a valid check digit; total length must be 52 characters or fewer.";
829
+ case "pdf417": return "Any non-empty string up to 1000 characters.";
830
+ default: return null;
831
+ }
832
+ }
833
+ function getCanonicalDateStoredFormat(type) {
834
+ switch (type) {
835
+ case "date": return "yyyy/MM/dd";
836
+ case "time": return "HH:mm";
837
+ case "dateTime": return "yyyy/MM/dd HH:mm";
838
+ }
839
+ }
840
+ function getDateHintFormat(schema, canonicalFormat) {
841
+ const formatValue = typeof schema.format === "string" ? schema.format.trim() : "";
842
+ if (!formatValue || formatValue === "undefined") return canonicalFormat;
843
+ return formatValue;
844
+ }
845
+ function getDateInputExample(type) {
846
+ switch (type) {
847
+ case "date": return "2026/03/28";
848
+ case "time": return "14:30";
849
+ case "dateTime": return "2026/03/28 14:30";
850
+ }
851
+ }
852
+ function getInputContractIssue(hint, input, inputIndex) {
853
+ if (hint.expectedInput.kind === "jsonStringObject") return getMultiVariableTextInputIssue(hint, input, inputIndex);
854
+ if (hint.expectedInput.kind === "enumString") return getEnumStringInputIssue(hint, input, inputIndex);
855
+ if (hint.expectedInput.kind === "stringMatrix") return getStringMatrixInputIssue(hint, input, inputIndex);
856
+ if (isCanonicalDateHint(hint)) return getCanonicalDateInputIssue(hint, input, inputIndex);
857
+ return null;
858
+ }
859
+ function getMultiVariableTextInputIssue(hint, input, inputIndex) {
860
+ const rawValue = input[hint.name];
861
+ const variableNames = hint.expectedInput.variableNames ?? [];
862
+ const example = hint.expectedInput.example ?? "{}";
863
+ if (rawValue === void 0 || rawValue === "") {
864
+ if (!hint.required || variableNames.length === 0) return null;
865
+ return buildMultiVariableTextErrorMessage({
866
+ hint,
867
+ inputIndex,
868
+ extra: `Missing variables: ${variableNames.join(", ")}.`,
869
+ example
870
+ });
871
+ }
872
+ if (typeof rawValue !== "string") return buildMultiVariableTextErrorMessage({
873
+ hint,
874
+ inputIndex,
875
+ extra: `Received ${describeValue(rawValue)}.`,
876
+ example
877
+ });
878
+ let parsedValue;
879
+ try {
880
+ parsedValue = JSON.parse(rawValue);
881
+ } catch {
882
+ return buildMultiVariableTextErrorMessage({
883
+ hint,
884
+ inputIndex,
885
+ extra: `Received ${describeValue(rawValue)}.`,
886
+ example
887
+ });
888
+ }
889
+ if (typeof parsedValue !== "object" || parsedValue === null || Array.isArray(parsedValue)) return buildMultiVariableTextErrorMessage({
890
+ hint,
891
+ inputIndex,
892
+ extra: `Received ${describeValue(parsedValue)}.`,
893
+ example
894
+ });
895
+ if (!hint.required || variableNames.length === 0) return null;
896
+ const values = parsedValue;
897
+ const missingVariables = variableNames.filter((variableName) => !values[variableName]);
898
+ if (missingVariables.length > 0) return buildMultiVariableTextErrorMessage({
899
+ hint,
900
+ inputIndex,
901
+ extra: `Missing variables: ${missingVariables.join(", ")}.`,
902
+ example
903
+ });
904
+ return null;
905
+ }
906
+ function getRadioGroupSelectionIssues(hints, input, inputIndex) {
907
+ const groups = /* @__PURE__ */ new Map();
908
+ for (const hint of hints) {
909
+ if (hint.type !== "radioGroup" || !hint.expectedInput.groupName || (hint.expectedInput.groupMemberNames?.length ?? 0) <= 1) continue;
910
+ const groupName = hint.expectedInput.groupName;
911
+ const existing = groups.get(groupName);
912
+ if (existing) existing.push(hint);
913
+ else groups.set(groupName, [hint]);
914
+ }
915
+ const issues = [];
916
+ for (const [groupName, groupHints] of groups) {
917
+ const selectedNames = groupHints.filter((hint) => input[hint.name] === "true").map((hint) => hint.name);
918
+ if (selectedNames.length <= 1) continue;
919
+ issues.push(buildRadioGroupSelectionErrorMessage({
920
+ groupName,
921
+ inputIndex,
922
+ groupMemberNames: groupHints[0]?.expectedInput.groupMemberNames ?? groupHints.map((hint) => hint.name),
923
+ selectedNames
924
+ }));
925
+ }
926
+ return issues;
927
+ }
928
+ function getEnumStringInputIssue(hint, input, inputIndex) {
929
+ const rawValue = input[hint.name];
930
+ const allowedValues = hint.expectedInput.allowedValues ?? [];
931
+ const example = hint.expectedInput.example;
932
+ if (rawValue === void 0 || rawValue === "") return null;
933
+ if (typeof rawValue !== "string") return buildEnumStringErrorMessage({
934
+ hint,
935
+ inputIndex,
936
+ extra: `Received ${describeValue(rawValue)}.`,
937
+ example
938
+ });
939
+ if (allowedValues.length === 0 || allowedValues.includes(rawValue)) return null;
940
+ return buildEnumStringErrorMessage({
941
+ hint,
942
+ inputIndex,
943
+ extra: `Received ${describeValue(rawValue)}.`,
944
+ example
945
+ });
946
+ }
947
+ function getStringMatrixInputIssue(hint, input, inputIndex) {
948
+ const rawValue = input[hint.name];
949
+ const example = hint.expectedInput.example;
950
+ if (rawValue === void 0 || rawValue === "") return null;
951
+ const issue = getStringMatrixShapeIssue(typeof rawValue === "string" && hint.expectedInput.acceptsJsonString === true ? parseTableStringMatrix(rawValue) ?? rawValue : rawValue, hint.expectedInput.columnCount);
952
+ if (!issue) return null;
953
+ return buildStringMatrixErrorMessage({
954
+ hint,
955
+ inputIndex,
956
+ extra: issue,
957
+ example
958
+ });
959
+ }
960
+ function isCanonicalDateHint(hint) {
961
+ return (hint.type === "date" || hint.type === "time" || hint.type === "dateTime") && typeof hint.expectedInput.canonicalFormat === "string" && hint.expectedInput.canonicalFormat.length > 0;
962
+ }
963
+ function getCanonicalDateInputIssue(hint, input, inputIndex) {
964
+ const rawValue = input[hint.name];
965
+ const example = hint.expectedInput.example;
966
+ if (rawValue === void 0 || rawValue === "") return null;
967
+ if (typeof rawValue !== "string") return buildCanonicalDateErrorMessage({
968
+ hint,
969
+ inputIndex,
970
+ extra: `Received ${describeValue(rawValue)}.`,
971
+ example
972
+ });
973
+ if (isValidCanonicalDateValue(rawValue, hint.type)) return null;
974
+ return buildCanonicalDateErrorMessage({
975
+ hint,
976
+ inputIndex,
977
+ extra: `Received ${describeValue(rawValue)}.`,
978
+ example
979
+ });
980
+ }
981
+ function getStringMatrixShapeIssue(value, expectedColumnCount) {
982
+ if (!Array.isArray(value)) return `Received ${describeValue(value)}.`;
983
+ const columnCount = expectedColumnCount ?? getFirstArrayLength(value);
984
+ for (let rowIndex = 0; rowIndex < value.length; rowIndex++) {
985
+ const row = value[rowIndex];
986
+ if (!Array.isArray(row)) return `Row ${rowIndex + 1} must be an array of strings. Received ${describeValue(row)}.`;
987
+ if (columnCount > 0 && row.length !== columnCount) return `Row ${rowIndex + 1} must contain ${columnCount} cells. Received ${row.length}.`;
988
+ for (let colIndex = 0; colIndex < row.length; colIndex++) {
989
+ const cell = row[colIndex];
990
+ if (typeof cell !== "string") return `Cell [${rowIndex + 1}, ${colIndex + 1}] must be a string. Received ${describeValue(cell)}.`;
991
+ }
992
+ }
993
+ return null;
994
+ }
995
+ function getFirstArrayLength(rows) {
996
+ for (const row of rows) if (Array.isArray(row)) return row.length;
997
+ return 0;
998
+ }
999
+ function parseTableStringMatrix(rawValue) {
1000
+ if (typeof rawValue !== "string") return null;
1001
+ try {
1002
+ return JSON.parse(rawValue);
1003
+ } catch {
1004
+ return null;
1005
+ }
1006
+ }
1007
+ function isValidCanonicalDateValue(value, type) {
1008
+ switch (type) {
1009
+ case "date": return isValidCanonicalDate(value);
1010
+ case "time": return isValidCanonicalTime(value);
1011
+ case "dateTime": return isValidCanonicalDateTime(value);
1012
+ }
1013
+ }
1014
+ function isValidCanonicalDate(value) {
1015
+ const match = value.match(/^(\d{4})\/(\d{2})\/(\d{2})$/);
1016
+ if (!match) return false;
1017
+ const [, year, month, day] = match;
1018
+ if (!isValidCalendarDate(Number(year), Number(month), Number(day))) return false;
1019
+ const parsed = parseRendererDateValue(value, "date");
1020
+ return parsed !== null && formatCanonicalDateValue(parsed, "date") === value;
1021
+ }
1022
+ function isValidCanonicalTime(value) {
1023
+ const match = value.match(/^(\d{2}):(\d{2})$/);
1024
+ if (!match) return false;
1025
+ const [, hours, minutes] = match;
1026
+ return isValidClockTime(Number(hours), Number(minutes));
1027
+ }
1028
+ function isValidCanonicalDateTime(value) {
1029
+ const match = value.match(/^(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2})$/);
1030
+ if (!match) return false;
1031
+ const [, year, month, day, hours, minutes] = match;
1032
+ if (!isValidCalendarDate(Number(year), Number(month), Number(day)) || !isValidClockTime(Number(hours), Number(minutes))) return false;
1033
+ const parsed = parseRendererDateValue(value, "dateTime");
1034
+ return parsed !== null && formatCanonicalDateValue(parsed, "dateTime") === value;
1035
+ }
1036
+ function isValidCalendarDate(year, month, day) {
1037
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return false;
1038
+ if (month < 1 || month > 12 || day < 1) return false;
1039
+ const candidate = new Date(year, month - 1, day);
1040
+ return candidate.getFullYear() === year && candidate.getMonth() === month - 1 && candidate.getDate() === day;
1041
+ }
1042
+ function isValidClockTime(hours, minutes) {
1043
+ return Number.isInteger(hours) && Number.isInteger(minutes) && hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59;
1044
+ }
1045
+ function parseRendererDateValue(value, type) {
1046
+ const parsed = type === "time" ? /* @__PURE__ */ new Date(`2021-01-01T${value}`) : new Date(value);
1047
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
1048
+ }
1049
+ function formatCanonicalDateValue(date, type) {
1050
+ const year = String(date.getFullYear()).padStart(4, "0");
1051
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1052
+ const day = String(date.getDate()).padStart(2, "0");
1053
+ const hours = String(date.getHours()).padStart(2, "0");
1054
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1055
+ switch (type) {
1056
+ case "date": return `${year}/${month}/${day}`;
1057
+ case "time": return `${hours}:${minutes}`;
1058
+ case "dateTime": return `${year}/${month}/${day} ${hours}:${minutes}`;
1059
+ }
1060
+ }
1061
+ function buildMultiVariableTextErrorMessage(args) {
1062
+ const variableLabel = args.hint.expectedInput.variableNames && args.hint.expectedInput.variableNames.length > 0 ? ` with variables: ${args.hint.expectedInput.variableNames.join(", ")}` : "";
1063
+ return `Field "${args.hint.name}" (multiVariableText) in input ${args.inputIndex + 1} expects a JSON string object${variableLabel}. Example: ${args.example}. ${args.extra}`;
1064
+ }
1065
+ function buildEnumStringErrorMessage(args) {
1066
+ const allowedValues = (args.hint.expectedInput.allowedValues ?? []).map((value) => JSON.stringify(value));
1067
+ const allowedLabel = allowedValues.length > 0 ? ` one of: ${allowedValues.join(", ")}` : " a supported string value";
1068
+ const exampleLabel = args.example !== void 0 ? ` Example: ${JSON.stringify(args.example)}.` : "";
1069
+ return `Field "${args.hint.name}" (${args.hint.type}) in input ${args.inputIndex + 1} expects${allowedLabel}.${exampleLabel} ${args.extra}`.trim();
1070
+ }
1071
+ function buildStringMatrixErrorMessage(args) {
1072
+ const columnCount = args.hint.expectedInput.columnCount;
1073
+ const columnHeaders = args.hint.expectedInput.columnHeaders ?? [];
1074
+ const columnLabel = typeof columnCount === "number" && columnCount > 0 ? ` with ${columnCount} cells per row` : "";
1075
+ const headerLabel = columnHeaders.length > 0 ? ` Column headers: ${columnHeaders.join(", ")}.` : "";
1076
+ const exampleLabel = args.example !== void 0 ? ` Example: ${JSON.stringify(args.example)}.` : "";
1077
+ const compatibilityLabel = args.hint.expectedInput.acceptsJsonString === true ? " JSON string input is also accepted for compatibility." : "";
1078
+ return `Field "${args.hint.name}" (${args.hint.type}) in input ${args.inputIndex + 1} expects a JSON array of string arrays${columnLabel}.${headerLabel}${exampleLabel}${compatibilityLabel} ${args.extra}`.trim();
1079
+ }
1080
+ function buildCanonicalDateErrorMessage(args) {
1081
+ const displayFormat = args.hint.expectedInput.format;
1082
+ const displayLabel = typeof displayFormat === "string" && displayFormat.length > 0 && displayFormat !== args.hint.expectedInput.canonicalFormat ? ` Display format hint: ${displayFormat}.` : "";
1083
+ const exampleLabel = args.example !== void 0 ? ` Example: ${JSON.stringify(args.example)}.` : "";
1084
+ return `Field "${args.hint.name}" (${args.hint.type}) in input ${args.inputIndex + 1} expects canonical stored content in format ${args.hint.expectedInput.canonicalFormat}.${displayLabel}${exampleLabel} ${args.extra}`.trim();
1085
+ }
1086
+ function buildRadioGroupSelectionErrorMessage(args) {
1087
+ return `Radio group "${args.groupName}" in input ${args.inputIndex + 1} allows at most one "true" value across fields: ${args.groupMemberNames.join(", ")}. Received "true" for: ${args.selectedNames.join(", ")}. Set one field to "true" and the others to "false".`;
1088
+ }
1089
+ function describeValue(value) {
1090
+ if (typeof value === "string") {
1091
+ const trimmed = value.trim();
1092
+ return `${trimmed.startsWith("{") || trimmed.startsWith("[") ? "string" : "plain string"} ${JSON.stringify(value)}`;
1093
+ }
1094
+ if (value === null) return "null";
1095
+ if (Array.isArray(value)) return "array";
1096
+ if (typeof value === "number" || typeof value === "boolean") return `${typeof value} ${JSON.stringify(value)}`;
1097
+ return typeof value;
1098
+ }
1099
+ function getUniqueStringValues(values) {
1100
+ return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))].sort();
1101
+ }
1102
+ function getUniqueOrderedStringValues(values) {
1103
+ return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))];
1104
+ }
1105
+ function getOrderedStringValues(values) {
1106
+ return values.filter((value) => typeof value === "string" && value.length > 0);
1107
+ }
1108
+ function collectRadioGroupMembers(schemaPages) {
1109
+ const groups = /* @__PURE__ */ new Map();
1110
+ for (const page of schemaPages) for (const schema of page) {
1111
+ if (schema.readOnly === true || schema.type !== "radioGroup") continue;
1112
+ const name = typeof schema.name === "string" ? schema.name : "";
1113
+ const groupName = typeof schema.group === "string" ? schema.group : "";
1114
+ if (!name || !groupName) continue;
1115
+ const existing = groups.get(groupName);
1116
+ if (existing) {
1117
+ if (!existing.includes(name)) existing.push(name);
1118
+ } else groups.set(groupName, [name]);
1119
+ }
1120
+ return groups;
1121
+ }
1122
+ function summarizeBasePdf(basePdf, templateDir) {
1123
+ if (typeof basePdf === "string") {
1124
+ if (basePdf.startsWith("data:")) return { kind: "dataUri" };
1125
+ if (basePdf.endsWith(".pdf")) return {
1126
+ kind: "pdfPath",
1127
+ path: basePdf,
1128
+ resolvedPath: templateDir ? resolve(templateDir, basePdf) : resolve(basePdf)
1129
+ };
1130
+ return { kind: "string" };
1131
+ }
1132
+ if (basePdf && typeof basePdf === "object") {
1133
+ if ("width" in basePdf && "height" in basePdf) {
1134
+ const width = typeof basePdf.width === "number" ? basePdf.width : void 0;
1135
+ const height = typeof basePdf.height === "number" ? basePdf.height : void 0;
1136
+ return {
1137
+ kind: "blank",
1138
+ width,
1139
+ height,
1140
+ paperSize: width !== void 0 && height !== void 0 ? detectPaperSize(width, height) : null
1141
+ };
1142
+ }
1143
+ return { kind: "object" };
1144
+ }
1145
+ return { kind: "missing" };
1146
+ }
1147
+ //#endregion
1148
+ //#region src/fonts.ts
1149
+ var CACHE_DIR = join(homedir(), ".pdfme", "fonts");
1150
+ var NOTO_SANS_JP_URL = "https://github.com/google/fonts/raw/main/ofl/notosansjp/NotoSansJP%5Bwght%5D.ttf";
1151
+ var NOTO_CACHE_FILE = join(CACHE_DIR, "NotoSansJP-Regular.ttf");
1152
+ var REMOTE_FONT_TIMEOUT_MS = 15e3;
1153
+ var MAX_REMOTE_FONT_BYTES = 32 * 1024 * 1024;
1154
+ function ensureCacheDir() {
1155
+ if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
1156
+ }
1157
+ async function downloadNotoSansJP(verbose) {
1158
+ if (existsSync(NOTO_CACHE_FILE)) {
1159
+ if (verbose) console.error("Using cached NotoSansJP from", NOTO_CACHE_FILE);
1160
+ return new Uint8Array(readFileSync(NOTO_CACHE_FILE));
1161
+ }
1162
+ ensureCacheDir();
1163
+ console.error("Downloading NotoSansJP for CJK support...");
1164
+ try {
1165
+ const response = await fetch(NOTO_SANS_JP_URL);
1166
+ if (!response.ok) {
1167
+ console.error(`Warning: Failed to download NotoSansJP (HTTP ${response.status})`);
1168
+ return null;
1169
+ }
1170
+ const buffer = new Uint8Array(await response.arrayBuffer());
1171
+ writeFileSync(NOTO_CACHE_FILE, buffer);
1172
+ console.error("Cached NotoSansJP to", NOTO_CACHE_FILE);
1173
+ return buffer;
1174
+ } catch (error) {
1175
+ console.error("Warning: Could not download NotoSansJP. CJK text may not render correctly.", error instanceof Error ? error.message : "");
1176
+ return null;
1177
+ }
1178
+ }
1179
+ function parseCustomFonts(fontArgs) {
1180
+ const font = {};
1181
+ for (let i = 0; i < fontArgs.length; i++) {
1182
+ const arg = fontArgs[i];
1183
+ const eqIndex = arg.indexOf("=");
1184
+ if (eqIndex === -1) fail(`Invalid --font format ${JSON.stringify(arg)}. Expected name=path, for example "NotoSansJP=./fonts/NotoSansJP.ttf".`, {
1185
+ code: "EARG",
1186
+ exitCode: 1
1187
+ });
1188
+ const name = arg.slice(0, eqIndex);
1189
+ const filePath = resolve(arg.slice(eqIndex + 1));
1190
+ if (!existsSync(filePath)) fail(`Font file not found: ${filePath}`, {
1191
+ code: "EIO",
1192
+ exitCode: 3
1193
+ });
1194
+ if (extname(filePath).toLowerCase() !== ".ttf") fail(`Unsupported font format for ${filePath}. @pdfme/cli currently guarantees only .ttf custom fonts.`, {
1195
+ code: "EUNSUPPORTED",
1196
+ exitCode: 1
1197
+ });
1198
+ font[name] = {
1199
+ data: new Uint8Array(readFileSync(filePath)),
1200
+ fallback: i === 0,
1201
+ subset: true
1202
+ };
1203
+ }
1204
+ return font;
1205
+ }
1206
+ function analyzeExplicitFontRecord(fontRecord, templateDir) {
1207
+ const issues = [];
1208
+ const warnings = [];
1209
+ const sources = [];
1210
+ for (const fontName of Object.keys(fontRecord).sort()) {
1211
+ const result = analyzeExplicitFontSource(fontName, fontRecord[fontName], templateDir);
1212
+ sources.push(result.source);
1213
+ issues.push(...result.issues);
1214
+ warnings.push(...result.warnings);
1215
+ }
1216
+ return {
1217
+ sources,
1218
+ issues,
1219
+ warnings
1220
+ };
1221
+ }
1222
+ async function normalizeExplicitFontOption(jobFont, templateDir) {
1223
+ if (jobFont === void 0) return;
1224
+ if (typeof jobFont !== "object" || jobFont === null || Array.isArray(jobFont)) fail("Unified job options.font must be an object.", {
1225
+ code: "EARG",
1226
+ exitCode: 1
1227
+ });
1228
+ const normalized = {};
1229
+ const fontRecord = jobFont;
1230
+ for (const fontName of Object.keys(fontRecord).sort()) normalized[fontName] = await normalizeExplicitFontSource(fontName, fontRecord[fontName], templateDir);
1231
+ return normalized;
1232
+ }
1233
+ function analyzeExplicitFontSource(fontName, value, templateDir) {
1234
+ const issues = [];
1235
+ const warnings = [];
1236
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1237
+ issues.push(`Font config for ${fontName} must be an object with a "data" field.`);
1238
+ return {
1239
+ source: {
1240
+ fontName,
1241
+ kind: "invalid",
1242
+ needsNetwork: false,
1243
+ dataType: getValueType(value)
1244
+ },
1245
+ issues,
1246
+ warnings
1247
+ };
1248
+ }
1249
+ const data = value.data;
1250
+ if (data === void 0) {
1251
+ issues.push(`Font config for ${fontName} is missing "data".`);
1252
+ return {
1253
+ source: {
1254
+ fontName,
1255
+ kind: "invalid",
1256
+ needsNetwork: false,
1257
+ dataType: "missing"
1258
+ },
1259
+ issues,
1260
+ warnings
1261
+ };
1262
+ }
1263
+ if (typeof data === "string") {
1264
+ if (data.startsWith("data:")) return analyzeDataUriFontSource(fontName, data);
1265
+ const parsedUrl = tryParseUrl(data);
1266
+ if (parsedUrl) {
1267
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return analyzeUrlFontSource(fontName, parsedUrl);
1268
+ issues.push(`Font source for ${fontName} uses unsupported URL protocol "${parsedUrl.protocol}". Use a local .ttf path, a data URI, or an https URL.`);
1269
+ return {
1270
+ source: {
1271
+ fontName,
1272
+ kind: "invalid",
1273
+ needsNetwork: false,
1274
+ dataType: "string"
1275
+ },
1276
+ issues,
1277
+ warnings
1278
+ };
1279
+ }
1280
+ return analyzeLocalFontSource(fontName, data, templateDir);
1281
+ }
1282
+ if (data instanceof Uint8Array || data instanceof ArrayBuffer) return {
1283
+ source: {
1284
+ fontName,
1285
+ kind: "inlineBytes",
1286
+ needsNetwork: false,
1287
+ dataType: getValueType(data)
1288
+ },
1289
+ issues,
1290
+ warnings
1291
+ };
1292
+ issues.push(`Font source for ${fontName} has unsupported data type ${getValueType(data)}.`);
1293
+ return {
1294
+ source: {
1295
+ fontName,
1296
+ kind: "invalid",
1297
+ needsNetwork: false,
1298
+ dataType: getValueType(data)
1299
+ },
1300
+ issues,
1301
+ warnings
1302
+ };
1303
+ }
1304
+ function analyzeLocalFontSource(fontName, pathValue, templateDir) {
1305
+ const issues = [];
1306
+ const warnings = [];
1307
+ const resolvedPath = templateDir ? resolve(templateDir, pathValue) : resolve(pathValue);
1308
+ const exists = existsSync(resolvedPath);
1309
+ const formatHint = detectPathFormatHint(resolvedPath);
1310
+ const formatResult = evaluateFontFormat(fontName, formatHint, `Font file for ${fontName}`);
1311
+ if (!exists) issues.push(`Font file for ${fontName} not found: ${resolvedPath}`);
1312
+ if (formatResult.issue) issues.push(formatResult.issue);
1313
+ if (formatResult.warning) warnings.push(formatResult.warning);
1314
+ return {
1315
+ source: {
1316
+ fontName,
1317
+ kind: "localPath",
1318
+ path: pathValue,
1319
+ resolvedPath,
1320
+ exists,
1321
+ formatHint,
1322
+ supportedFormat: formatResult.supportedFormat,
1323
+ needsNetwork: false,
1324
+ dataType: "string"
1325
+ },
1326
+ issues,
1327
+ warnings
1328
+ };
1329
+ }
1330
+ function analyzeUrlFontSource(fontName, url) {
1331
+ const issues = [];
1332
+ const warnings = [];
1333
+ const provider = detectRemoteFontProvider(url);
1334
+ const formatHint = detectPathFormatHint(url.pathname);
1335
+ const formatResult = evaluateFontFormat(fontName, formatHint, `Font URL for ${fontName}`);
1336
+ if (provider === "googleFontsStylesheet") issues.push(`Font URL for ${fontName} uses the unsupported Google Fonts stylesheet API. Use the direct fonts.gstatic.com asset URL or download the font locally.`);
1337
+ if (!isUrlSafeToFetch(url.toString())) issues.push(`Font URL for ${fontName} is invalid or unsafe. Only http: and https: URLs pointing to public hosts are allowed.`);
1338
+ if (provider !== "googleFontsStylesheet" && formatResult.issue) issues.push(formatResult.issue);
1339
+ if (provider !== "googleFontsStylesheet" && formatResult.warning) warnings.push(formatResult.warning);
1340
+ return {
1341
+ source: {
1342
+ fontName,
1343
+ kind: "url",
1344
+ provider,
1345
+ url: url.toString(),
1346
+ formatHint,
1347
+ supportedFormat: formatResult.supportedFormat,
1348
+ needsNetwork: true,
1349
+ dataType: "string"
1350
+ },
1351
+ issues,
1352
+ warnings
1353
+ };
1354
+ }
1355
+ function analyzeDataUriFontSource(fontName, dataUri) {
1356
+ const issues = [];
1357
+ const warnings = [];
1358
+ const mediaType = getDataUriMediaType(dataUri);
1359
+ const formatHint = detectDataUriFormatHint(mediaType);
1360
+ const formatResult = evaluateFontFormat(fontName, formatHint, `Font data URI for ${fontName}`);
1361
+ if (formatResult.issue) issues.push(formatResult.issue);
1362
+ if (formatResult.warning) warnings.push(formatResult.warning);
1363
+ return {
1364
+ source: {
1365
+ fontName,
1366
+ kind: "dataUri",
1367
+ mediaType,
1368
+ formatHint,
1369
+ supportedFormat: formatResult.supportedFormat,
1370
+ needsNetwork: false,
1371
+ dataType: "string"
1372
+ },
1373
+ issues,
1374
+ warnings
1375
+ };
1376
+ }
1377
+ async function normalizeExplicitFontSource(fontName, value, templateDir) {
1378
+ const analysis = analyzeExplicitFontSource(fontName, value, templateDir);
1379
+ for (const issue of analysis.issues) {
1380
+ const code = issue.includes("not found") ? "EIO" : issue.includes("unsupported") || issue.includes("unsafe") || issue.includes("uses .") ? "EUNSUPPORTED" : "EARG";
1381
+ fail(issue, {
1382
+ code,
1383
+ exitCode: code === "EIO" ? 3 : 1
1384
+ });
1385
+ }
1386
+ const record = value;
1387
+ const data = record.data;
1388
+ if (analysis.source.kind === "localPath") return {
1389
+ ...record,
1390
+ data: new Uint8Array(readFileSync(analysis.source.resolvedPath))
1391
+ };
1392
+ if (analysis.source.kind === "url") return {
1393
+ ...record,
1394
+ data: await fetchRemoteFontSource(analysis.source)
1395
+ };
1396
+ if (analysis.source.kind === "dataUri" || analysis.source.kind === "inlineBytes") {
1397
+ const normalizedData = typeof data === "string" ? data : data instanceof Uint8Array ? data : data;
1398
+ return {
1399
+ ...record,
1400
+ data: normalizedData
1401
+ };
1402
+ }
1403
+ fail(`Font source for ${fontName} has unsupported data type ${getValueType(data)}.`, {
1404
+ code: "EARG",
1405
+ exitCode: 1
1406
+ });
1407
+ }
1408
+ async function fetchRemoteFontSource(source) {
1409
+ const url = source.url;
1410
+ try {
1411
+ const response = await fetch(url, { signal: AbortSignal.timeout(REMOTE_FONT_TIMEOUT_MS) });
1412
+ if (!response.ok) failRemoteFontFetch(source, `Failed to fetch remote font data from ${url}. HTTP ${response.status}`);
1413
+ const contentLengthHeader = response.headers.get("content-length");
1414
+ const declaredLength = contentLengthHeader ? Number(contentLengthHeader) : NaN;
1415
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_REMOTE_FONT_BYTES) failRemoteFontFetch(source, `Remote font data from ${url} exceeds the ${MAX_REMOTE_FONT_BYTES}-byte safety limit.`);
1416
+ return await readResponseBodyWithLimit(response, source);
1417
+ } catch (error) {
1418
+ if (error instanceof CliError) throw error;
1419
+ failRemoteFontFetch(source, `Failed to fetch remote font data from ${url}. ${error instanceof Error ? error.message : String(error)}`);
1420
+ }
1421
+ }
1422
+ function failRemoteFontFetch(source, message) {
1423
+ fail(message, {
1424
+ code: "EFONT",
1425
+ exitCode: 2,
1426
+ details: {
1427
+ fontName: source.fontName,
1428
+ url: source.url,
1429
+ provider: source.provider,
1430
+ timeoutMs: REMOTE_FONT_TIMEOUT_MS,
1431
+ maxBytes: MAX_REMOTE_FONT_BYTES
1432
+ }
1433
+ });
1434
+ }
1435
+ async function readResponseBodyWithLimit(response, source) {
1436
+ if (!response.body) {
1437
+ const buffer = new Uint8Array(await response.arrayBuffer());
1438
+ if (buffer.byteLength > MAX_REMOTE_FONT_BYTES) failRemoteFontFetch(source, `Remote font data from ${source.url} exceeds the ${MAX_REMOTE_FONT_BYTES}-byte safety limit.`);
1439
+ return buffer;
1440
+ }
1441
+ const reader = response.body.getReader();
1442
+ const chunks = [];
1443
+ let total = 0;
1444
+ while (true) {
1445
+ const { done, value } = await reader.read();
1446
+ if (done) break;
1447
+ if (!value) continue;
1448
+ total += value.byteLength;
1449
+ if (total > MAX_REMOTE_FONT_BYTES) failRemoteFontFetch(source, `Remote font data from ${source.url} exceeds the ${MAX_REMOTE_FONT_BYTES}-byte safety limit.`);
1450
+ chunks.push(value);
1451
+ }
1452
+ const merged = new Uint8Array(total);
1453
+ let offset = 0;
1454
+ for (const chunk of chunks) {
1455
+ merged.set(chunk, offset);
1456
+ offset += chunk.byteLength;
1457
+ }
1458
+ return merged;
1459
+ }
1460
+ function tryParseUrl(value) {
1461
+ try {
1462
+ return new URL(value);
1463
+ } catch {
1464
+ return null;
1465
+ }
1466
+ }
1467
+ function detectRemoteFontProvider(url) {
1468
+ const hostname = url.hostname.toLowerCase();
1469
+ if (hostname === "fonts.gstatic.com" || hostname.endsWith(".fonts.gstatic.com")) return "googleFontsAsset";
1470
+ if (hostname === "fonts.googleapis.com" || hostname.endsWith(".fonts.googleapis.com")) return "googleFontsStylesheet";
1471
+ return "genericPublic";
1472
+ }
1473
+ function getDataUriMediaType(value) {
1474
+ const match = value.match(/^data:([^;,]+)/i);
1475
+ return match ? match[1] : void 0;
1476
+ }
1477
+ function detectPathFormatHint(value) {
1478
+ const extension = extname(value).toLowerCase();
1479
+ return extension ? extension.slice(1) : null;
1480
+ }
1481
+ function detectDataUriFormatHint(mediaType) {
1482
+ if (!mediaType) return null;
1483
+ const lower = mediaType.toLowerCase();
1484
+ if (lower.includes("ttf") || lower.endsWith("/sfnt")) return "ttf";
1485
+ if (lower.includes("otf")) return "otf";
1486
+ if (lower.includes("ttc")) return "ttc";
1487
+ return null;
1488
+ }
1489
+ function evaluateFontFormat(fontName, formatHint, sourceLabel) {
1490
+ if (formatHint === "ttf") return { supportedFormat: true };
1491
+ if (formatHint === null) return { warning: `${sourceLabel} does not clearly advertise a .ttf format. @pdfme/cli currently guarantees only .ttf custom fonts.` };
1492
+ return {
1493
+ supportedFormat: false,
1494
+ issue: `${sourceLabel} uses .${formatHint}. @pdfme/cli currently guarantees only .ttf custom fonts for ${fontName}.`
1495
+ };
1496
+ }
1497
+ function getValueType(value) {
1498
+ if (value === void 0) return "undefined";
1499
+ if (value === null) return "null";
1500
+ if (value instanceof Uint8Array) return "Uint8Array";
1501
+ if (value instanceof ArrayBuffer) return "ArrayBuffer";
1502
+ if (Array.isArray(value)) return "array";
1503
+ return typeof value;
1504
+ }
1505
+ async function resolveFont(options) {
1506
+ const { fontArgs, hasCJK, noAutoFont, verbose, hasExplicitFontConfig = false } = options;
1507
+ if (fontArgs && fontArgs.length > 0) return parseCustomFonts(fontArgs);
1508
+ const defaultFont = getDefaultFont();
1509
+ if (!hasCJK || hasExplicitFontConfig) return defaultFont;
1510
+ if (noAutoFont) fail("CJK text detected, but automatic NotoSansJP download is disabled by --noAutoFont and no explicit font source was provided. Provide --font or options.font.", {
1511
+ code: "EFONT",
1512
+ exitCode: 2,
1513
+ details: {
1514
+ fontName: "NotoSansJP",
1515
+ cacheFile: NOTO_CACHE_FILE,
1516
+ autoFont: false
1517
+ }
1518
+ });
1519
+ const notoData = await downloadNotoSansJP(verbose);
1520
+ if (!notoData) fail("CJK text detected, but NotoSansJP could not be resolved automatically. Re-run with network access, warm the font cache, or provide --font / options.font.", {
1521
+ code: "EFONT",
1522
+ exitCode: 2,
1523
+ details: {
1524
+ fontName: "NotoSansJP",
1525
+ cacheFile: NOTO_CACHE_FILE,
1526
+ downloadUrl: NOTO_SANS_JP_URL,
1527
+ autoFont: true
1528
+ }
1529
+ });
1530
+ return {
1531
+ NotoSansJP: {
1532
+ data: notoData,
1533
+ fallback: true,
1534
+ subset: true
1535
+ },
1536
+ ...Object.fromEntries(Object.entries(defaultFont).map(([k, v]) => [k, {
1537
+ ...v,
1538
+ fallback: false
1539
+ }]))
1540
+ };
1541
+ }
1542
+ //#endregion
1543
+ //#region src/cjk-detect.ts
1544
+ var CJK_RANGES = [
1545
+ [19968, 40959],
1546
+ [13312, 19903],
1547
+ [12352, 12447],
1548
+ [12448, 12543],
1549
+ [44032, 55215],
1550
+ [12288, 12351],
1551
+ [65280, 65519]
1552
+ ];
1553
+ function containsCJK(text) {
1554
+ for (let i = 0; i < text.length; i++) {
1555
+ const code = text.charCodeAt(i);
1556
+ for (const [start, end] of CJK_RANGES) if (code >= start && code <= end) return true;
1557
+ }
1558
+ return false;
1559
+ }
1560
+ function detectCJKInTemplate(template) {
1561
+ if (!Array.isArray(template.schemas)) return false;
1562
+ for (const page of template.schemas) {
1563
+ if (!Array.isArray(page)) continue;
1564
+ for (const schema of page) {
1565
+ if (typeof schema !== "object" || schema === null) continue;
1566
+ if (typeof schema.content === "string" && containsCJK(schema.content)) return true;
1567
+ }
1568
+ }
1569
+ return false;
1570
+ }
1571
+ function detectCJKInInputs(inputs) {
1572
+ if (!Array.isArray(inputs)) return false;
1573
+ for (const input of inputs) {
1574
+ if (typeof input !== "object" || input === null) continue;
1575
+ for (const value of Object.values(input)) if (typeof value === "string" && containsCJK(value)) return true;
1576
+ }
1577
+ return false;
1578
+ }
1579
+ //#endregion
1580
+ //#region src/grid.ts
1581
+ var schemaColorCache = /* @__PURE__ */ new Map();
1582
+ function hashString(value) {
1583
+ let hash = 0;
1584
+ for (let i = 0; i < value.length; i++) hash = hash * 31 + value.charCodeAt(i) >>> 0;
1585
+ return hash;
1586
+ }
1587
+ function hslToHex(hue, saturation, lightness) {
1588
+ const s = saturation / 100;
1589
+ const l = lightness / 100;
1590
+ const c = (1 - Math.abs(2 * l - 1)) * s;
1591
+ const h = hue / 60;
1592
+ const x = c * (1 - Math.abs(h % 2 - 1));
1593
+ let r = 0;
1594
+ let g = 0;
1595
+ let b = 0;
1596
+ if (h >= 0 && h < 1) {
1597
+ r = c;
1598
+ g = x;
1599
+ } else if (h < 2) {
1600
+ r = x;
1601
+ g = c;
1602
+ } else if (h < 3) {
1603
+ g = c;
1604
+ b = x;
1605
+ } else if (h < 4) {
1606
+ g = x;
1607
+ b = c;
1608
+ } else if (h < 5) {
1609
+ r = x;
1610
+ b = c;
1611
+ } else {
1612
+ r = c;
1613
+ b = x;
1614
+ }
1615
+ const m = l - c / 2;
1616
+ const toHex = (channel) => Math.round((channel + m) * 255).toString(16).padStart(2, "0");
1617
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1618
+ }
1619
+ function getSchemaColor(type) {
1620
+ const normalizedType = type.trim().toLowerCase();
1621
+ const cached = schemaColorCache.get(normalizedType);
1622
+ if (cached) return cached;
1623
+ const hash = hashString(normalizedType);
1624
+ const color = hslToHex(hash % 360, 65 + hash % 12, 42 + (hash >> 8) % 10);
1625
+ schemaColorCache.set(normalizedType, color);
1626
+ return color;
1627
+ }
1628
+ function drawGridLines(ctx, pxPerMm, pageWidthMm, pageHeightMm, imgWidth, imgHeight, gridSizeMm, withLabels) {
1629
+ ctx.strokeStyle = "rgba(128, 128, 128, 0.3)";
1630
+ ctx.lineWidth = 1;
1631
+ for (let x = 0; x <= pageWidthMm; x += gridSizeMm) {
1632
+ const px = x * pxPerMm;
1633
+ ctx.beginPath();
1634
+ ctx.moveTo(px, 0);
1635
+ ctx.lineTo(px, imgHeight);
1636
+ ctx.stroke();
1637
+ if (withLabels && x % (gridSizeMm * 2) === 0 && x > 0) {
1638
+ ctx.fillStyle = "rgba(100, 100, 100, 0.7)";
1639
+ ctx.font = `${Math.max(9, pxPerMm * 2.5)}px sans-serif`;
1640
+ ctx.fillText(`${x}`, px + 2, 12);
1641
+ }
1642
+ }
1643
+ for (let y = 0; y <= pageHeightMm; y += gridSizeMm) {
1644
+ const px = y * pxPerMm;
1645
+ ctx.beginPath();
1646
+ ctx.moveTo(0, px);
1647
+ ctx.lineTo(imgWidth, px);
1648
+ ctx.stroke();
1649
+ if (withLabels && y % (gridSizeMm * 2) === 0 && y > 0) {
1650
+ ctx.fillStyle = "rgba(100, 100, 100, 0.7)";
1651
+ ctx.font = `${Math.max(9, pxPerMm * 2.5)}px sans-serif`;
1652
+ ctx.fillText(`${y}`, 2, px - 2);
1653
+ }
1654
+ }
1655
+ }
1656
+ function drawSchemaOverlays(ctx, schemas, pxPerMm) {
1657
+ for (const schema of schemas) {
1658
+ const color = getSchemaColor(schema.type);
1659
+ const x = schema.position.x * pxPerMm;
1660
+ const y = schema.position.y * pxPerMm;
1661
+ const w = schema.width * pxPerMm;
1662
+ const h = schema.height * pxPerMm;
1663
+ ctx.strokeStyle = color;
1664
+ ctx.lineWidth = 2;
1665
+ ctx.setLineDash([4, 2]);
1666
+ ctx.strokeRect(x, y, w, h);
1667
+ ctx.setLineDash([]);
1668
+ const label = `${schema.name} (${schema.type})`;
1669
+ const fontSize = Math.max(10, Math.min(14, pxPerMm * 3));
1670
+ ctx.font = `${fontSize}px sans-serif`;
1671
+ const metrics = ctx.measureText(label);
1672
+ const labelHeight = fontSize + 4;
1673
+ const labelWidth = metrics.width + 6;
1674
+ ctx.fillStyle = color;
1675
+ ctx.globalAlpha = .85;
1676
+ ctx.fillRect(x, y - labelHeight, labelWidth, labelHeight);
1677
+ ctx.globalAlpha = 1;
1678
+ ctx.fillStyle = "#FFFFFF";
1679
+ ctx.fillText(label, x + 3, y - 3);
1680
+ }
1681
+ }
1682
+ async function loadAndPrepareCanvas(imageBuffer) {
1683
+ const { createCanvas, loadImage } = await import("@napi-rs/canvas");
1684
+ const img = await loadImage(Buffer.from(imageBuffer));
1685
+ const canvas = createCanvas(img.width, img.height);
1686
+ const ctx = canvas.getContext("2d");
1687
+ ctx.drawImage(img, 0, 0);
1688
+ return {
1689
+ canvas,
1690
+ ctx,
1691
+ img
1692
+ };
1693
+ }
1694
+ function bufferToArrayBuffer(buffer) {
1695
+ const arrayBuffer = new ArrayBuffer(buffer.byteLength);
1696
+ new Uint8Array(arrayBuffer).set(buffer);
1697
+ return arrayBuffer;
1698
+ }
1699
+ /**
1700
+ * Draw grid lines and schema boundary overlays on a generated PDF page image.
1701
+ * Used by `pdfme generate --grid`.
1702
+ */
1703
+ async function drawGridOnImage(imageBuffer, schemas, gridSizeMm, pageWidthMm, pageHeightMm, imageType) {
1704
+ const { canvas, ctx, img } = await loadAndPrepareCanvas(imageBuffer);
1705
+ const pxPerMm = img.width / pageWidthMm;
1706
+ drawGridLines(ctx, pxPerMm, pageWidthMm, pageHeightMm, img.width, img.height, gridSizeMm, false);
1707
+ drawSchemaOverlays(ctx, schemas, pxPerMm);
1708
+ const mimeType = imageType === "jpeg" ? "image/jpeg" : "image/png";
1709
+ return bufferToArrayBuffer(canvas.toBuffer(mimeType));
1710
+ }
1711
+ /**
1712
+ * Draw grid lines with mm coordinate labels on a plain PDF image.
1713
+ * Used by `pdfme pdf2img --grid`.
1714
+ */
1715
+ async function drawGridOnPdfImage(imageBuffer, gridSizeMm, pageWidthMm, pageHeightMm, imageType) {
1716
+ const { canvas, ctx, img } = await loadAndPrepareCanvas(imageBuffer);
1717
+ drawGridLines(ctx, img.width / pageWidthMm, pageWidthMm, pageHeightMm, img.width, img.height, gridSizeMm, true);
1718
+ const mimeType = imageType === "jpeg" ? "image/jpeg" : "image/png";
1719
+ return bufferToArrayBuffer(canvas.toBuffer(mimeType));
1720
+ }
1721
+ //#endregion
1722
+ //#region src/commands/generate.ts
1723
+ var generateArgs = {
1724
+ file: {
1725
+ type: "positional",
1726
+ description: "Unified JSON file: { template, inputs }",
1727
+ required: false
1728
+ },
1729
+ template: {
1730
+ type: "string",
1731
+ alias: "t",
1732
+ description: "Template JSON file"
1733
+ },
1734
+ inputs: {
1735
+ type: "string",
1736
+ alias: "i",
1737
+ description: "Input data JSON file"
1738
+ },
1739
+ output: {
1740
+ type: "string",
1741
+ alias: "o",
1742
+ description: "Output PDF path",
1743
+ default: "output.pdf"
1744
+ },
1745
+ force: {
1746
+ type: "boolean",
1747
+ description: "Allow overwriting the implicit default output path",
1748
+ default: false
1749
+ },
1750
+ image: {
1751
+ type: "boolean",
1752
+ description: "Also output PNG images per page",
1753
+ default: false
1754
+ },
1755
+ imageFormat: {
1756
+ type: "string",
1757
+ description: "Image format: png | jpeg",
1758
+ default: "png"
1759
+ },
1760
+ scale: {
1761
+ type: "string",
1762
+ description: "Image render scale",
1763
+ default: "1"
1764
+ },
1765
+ grid: {
1766
+ type: "boolean",
1767
+ description: "Overlay grid + schema boundaries on images",
1768
+ default: false
1769
+ },
1770
+ gridSize: {
1771
+ type: "string",
1772
+ description: "Grid spacing in mm",
1773
+ default: "10"
1774
+ },
1775
+ font: {
1776
+ type: "string",
1777
+ description: "Custom font(s): name=path (comma-separated for multiple)"
1778
+ },
1779
+ basePdf: {
1780
+ type: "string",
1781
+ description: "Override basePdf with PDF file path"
1782
+ },
1783
+ noAutoFont: {
1784
+ type: "boolean",
1785
+ description: "Disable automatic CJK font download",
1786
+ default: false
1787
+ },
1788
+ verbose: {
1789
+ type: "boolean",
1790
+ alias: "v",
1791
+ description: "Verbose output",
1792
+ default: false
1793
+ },
1794
+ json: {
1795
+ type: "boolean",
1796
+ description: "Machine-readable JSON output",
1797
+ default: false
1798
+ }
1799
+ };
1800
+ var generate_default = defineCommand({
1801
+ meta: {
1802
+ name: "generate",
1803
+ description: "Generate PDF from template and inputs"
1804
+ },
1805
+ args: generateArgs,
1806
+ async run({ args, rawArgs }) {
1807
+ return runWithContract({ json: Boolean(args.json) }, async () => {
1808
+ assertNoUnknownFlags(rawArgs, generateArgs);
1809
+ const imageFormat = parseEnumArg("imageFormat", args.imageFormat, ["png", "jpeg"]);
1810
+ const scale = parsePositiveNumberArg("scale", args.scale);
1811
+ const gridSize = parsePositiveNumberArg("gridSize", args.gridSize);
1812
+ ensureSafeDefaultOutputPath({
1813
+ filePath: args.output,
1814
+ rawArgs,
1815
+ optionName: "output",
1816
+ optionAlias: "o",
1817
+ defaultValue: "output.pdf",
1818
+ force: Boolean(args.force)
1819
+ });
1820
+ const { template: rawTemplate, inputs, options: rawJobOptions, templateDir } = loadInput({
1821
+ _: args.file ? [args.file] : [],
1822
+ template: args.template,
1823
+ inputs: args.inputs
1824
+ });
1825
+ const template = resolveBasePdf(rawTemplate, args.basePdf, templateDir);
1826
+ const mode = args.file ? "job" : "template+inputs";
1827
+ const templatePageCount = normalizeSchemaPages$1(template.schemas).length;
1828
+ const jobOptions = normalizeJobOptions(rawJobOptions);
1829
+ assertSupportedSchemaTypes(template);
1830
+ const resolvedFont = await resolveFont({
1831
+ fontArgs: args.font ? args.font.split(",").map((value) => value.trim()).filter(Boolean) : void 0,
1832
+ hasCJK: detectCJKInTemplate(template) || detectCJKInInputs(inputs),
1833
+ noAutoFont: Boolean(args.noAutoFont),
1834
+ verbose: Boolean(args.verbose),
1835
+ hasExplicitFontConfig: hasExplicitFontEntries(jobOptions.font)
1836
+ });
1837
+ const font = await normalizeExplicitFontOption(mergeFontConfig(jobOptions.font, resolvedFont), templateDir) ?? resolvedFont;
1838
+ const generateOptions = {
1839
+ ...jobOptions,
1840
+ font
1841
+ };
1842
+ try {
1843
+ checkGenerateProps({
1844
+ template,
1845
+ inputs,
1846
+ options: generateOptions
1847
+ });
1848
+ } catch (error) {
1849
+ fail(`Invalid generation input. ${error instanceof Error ? error.message : String(error)}`, {
1850
+ code: "EVALIDATE",
1851
+ exitCode: 1,
1852
+ cause: error
1853
+ });
1854
+ }
1855
+ validateInputContracts(template, inputs);
1856
+ if (args.verbose) {
1857
+ console.error(`Input: ${describeGenerateInput(args.file, args.template, args.inputs)}`);
1858
+ console.error(`Mode: ${mode}`);
1859
+ console.error(`Template pages: ${templatePageCount}`);
1860
+ console.error(`Inputs: ${inputs.length} set(s)`);
1861
+ console.error(`Output: ${args.output}`);
1862
+ console.error(`Images: ${args.image || args.grid ? `enabled (${imageFormat}, scale=${scale}, grid=${args.grid ? `${gridSize}mm` : "disabled"})` : "disabled"}`);
1863
+ console.error(`Fonts: ${Object.keys(font).join(", ")}`);
1864
+ if (args.basePdf) console.error(`Base PDF override: ${args.basePdf}`);
1865
+ }
1866
+ const pdf = await generate({
1867
+ template,
1868
+ inputs,
1869
+ options: generateOptions,
1870
+ plugins: schemaPlugins
1871
+ });
1872
+ const pageCount = (await PDFDocument.load(pdf, { updateMetadata: false })).getPageCount();
1873
+ writeOutput(args.output, pdf);
1874
+ const result = {
1875
+ command: "generate",
1876
+ mode,
1877
+ templatePageCount,
1878
+ inputCount: inputs.length,
1879
+ pageCount,
1880
+ outputPath: args.output,
1881
+ outputBytes: pdf.byteLength
1882
+ };
1883
+ if (args.image || args.grid) {
1884
+ const images = await pdf2img(pdf, {
1885
+ scale,
1886
+ imageType: imageFormat
1887
+ });
1888
+ const imagePaths = getImageOutputPaths(args.output, images.length, imageFormat);
1889
+ if (args.grid) {
1890
+ const renderedPageSizes = await pdf2size(pdf);
1891
+ for (let i = 0; i < images.length; i++) {
1892
+ const templateSchemas = template.schemas ?? [];
1893
+ const pageSchemas = templateSchemas[i % templateSchemas.length] ?? [];
1894
+ const size = renderedPageSizes[i] ?? renderedPageSizes[0] ?? {
1895
+ width: 210,
1896
+ height: 297
1897
+ };
1898
+ const gridImage = await drawGridOnImage(images[i], pageSchemas, gridSize, size.width, size.height, imageFormat);
1899
+ writeOutput(imagePaths[i], gridImage);
1900
+ }
1901
+ } else for (let i = 0; i < images.length; i++) writeOutput(imagePaths[i], images[i]);
1902
+ result.imagePaths = imagePaths;
1903
+ }
1904
+ if (args.json) printJson({
1905
+ ok: true,
1906
+ ...result
1907
+ });
1908
+ else {
1909
+ console.log(`\u2713 Output: ${args.output} (${formatBytes(pdf.byteLength)})`);
1910
+ if (result.imagePaths) for (const img of result.imagePaths) console.log(`\u2713 Image: ${img}`);
1911
+ }
1912
+ });
1913
+ }
1914
+ });
1915
+ function formatBytes(bytes) {
1916
+ if (bytes < 1024) return `${bytes}B`;
1917
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
1918
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1919
+ }
1920
+ function normalizeJobOptions(rawJobOptions) {
1921
+ if (rawJobOptions === void 0) return {};
1922
+ if (typeof rawJobOptions !== "object" || rawJobOptions === null || Array.isArray(rawJobOptions)) fail("Unified job options must be a JSON object.", {
1923
+ code: "EARG",
1924
+ exitCode: 1
1925
+ });
1926
+ return rawJobOptions;
1927
+ }
1928
+ function mergeFontConfig(jobFont, resolvedFont) {
1929
+ if (jobFont === void 0) return resolvedFont;
1930
+ if (typeof jobFont !== "object" || jobFont === null || Array.isArray(jobFont)) fail("Unified job options.font must be an object.", {
1931
+ code: "EARG",
1932
+ exitCode: 1
1933
+ });
1934
+ return {
1935
+ ...jobFont,
1936
+ ...resolvedFont
1937
+ };
1938
+ }
1939
+ function hasExplicitFontEntries(jobFont) {
1940
+ if (typeof jobFont !== "object" || jobFont === null || Array.isArray(jobFont)) return false;
1941
+ return Object.keys(jobFont).length > 0;
1942
+ }
1943
+ function assertSupportedSchemaTypes(template) {
1944
+ const unsupported = [];
1945
+ for (const page of normalizeSchemaPages$1(template.schemas)) for (const schema of page) {
1946
+ const type = schema.type;
1947
+ if (typeof type === "string" && !schemaTypes.has(type)) {
1948
+ const name = typeof schema.name === "string" && schema.name.length > 0 ? schema.name : null;
1949
+ unsupported.push(name ? `Field "${name}" has unknown type "${type}"` : `Unknown schema type "${type}"`);
1950
+ }
1951
+ }
1952
+ if (unsupported.length > 0) fail(`Invalid generation input. ${unsupported.join("; ")}`, {
1953
+ code: "EVALIDATE",
1954
+ exitCode: 1
1955
+ });
1956
+ }
1957
+ function describeGenerateInput(file, template, inputs) {
1958
+ if (file) return file;
1959
+ if (template || inputs) return `${template ?? "(missing template)"} + ${inputs ?? "(missing inputs)"}`;
1960
+ return "(unknown)";
1961
+ }
1962
+ function normalizeSchemaPages$1(rawSchemas) {
1963
+ if (!Array.isArray(rawSchemas)) return [];
1964
+ return rawSchemas.map((page) => {
1965
+ if (Array.isArray(page)) return page.filter((schema) => typeof schema === "object" && schema !== null);
1966
+ if (typeof page === "object" && page !== null) return Object.values(page).filter((schema) => typeof schema === "object" && schema !== null);
1967
+ return [];
1968
+ });
1969
+ }
1970
+ //#endregion
1971
+ //#region src/commands/validate.ts
1972
+ var validateArgs = {
1973
+ file: {
1974
+ type: "positional",
1975
+ description: "Template JSON file, unified job file, or \"-\" for stdin",
1976
+ required: false
1977
+ },
1978
+ verbose: {
1979
+ type: "boolean",
1980
+ alias: "v",
1981
+ description: "Verbose output",
1982
+ default: false
1983
+ },
1984
+ json: {
1985
+ type: "boolean",
1986
+ description: "Machine-readable JSON output",
1987
+ default: false
1988
+ },
1989
+ strict: {
1990
+ type: "boolean",
1991
+ description: "Treat warnings as errors",
1992
+ default: false
1993
+ }
1994
+ };
1995
+ var validate_default = defineCommand({
1996
+ meta: {
1997
+ name: "validate",
1998
+ description: "Validate a pdfme template JSON file"
1999
+ },
2000
+ args: validateArgs,
2001
+ async run({ args, rawArgs }) {
2002
+ return runWithContract({ json: Boolean(args.json) }, async () => {
2003
+ assertNoUnknownFlags(rawArgs, validateArgs);
2004
+ const source = await loadValidationSource(args.file, { noInputMessage: "No validation input provided. Pass a file path or pipe JSON via stdin." });
2005
+ const templateUnknownKeys = Object.keys(source.template).filter((key) => !KNOWN_TEMPLATE_KEYS.has(key)).sort();
2006
+ const inspection = inspectTemplate(source.template, source.templateDir);
2007
+ const resolvedTemplate = resolveBasePdf(source.template, void 0, source.templateDir);
2008
+ const result = validateTemplate(resolvedTemplate);
2009
+ result.warnings.push(...source.jobWarnings);
2010
+ if (templateUnknownKeys.length > 0) result.warnings.push(`Unknown template top-level field(s): ${templateUnknownKeys.join(", ")}`);
2011
+ if (source.mode === "job") {
2012
+ try {
2013
+ checkGenerateProps({
2014
+ template: resolvedTemplate,
2015
+ inputs: source.inputs,
2016
+ options: source.options
2017
+ });
2018
+ } catch (error) {
2019
+ result.errors.unshift(error instanceof Error ? error.message : String(error));
2020
+ }
2021
+ result.errors.push(...getInputContractIssues(resolvedTemplate, source.inputs ?? []));
2022
+ }
2023
+ const valid = result.errors.length === 0 && (!args.strict || result.warnings.length === 0);
2024
+ const inputCount = source.mode === "job" ? source.inputs?.length ?? 0 : void 0;
2025
+ if (args.verbose) {
2026
+ console.error(`Input: ${describeValidationInput(args.file)}`);
2027
+ console.error(`Mode: ${source.mode}`);
2028
+ console.error(`Template pages: ${result.pages}`);
2029
+ console.error(`Fields: ${result.fields}`);
2030
+ if (inputCount !== void 0) console.error(`Inputs: ${inputCount} set(s)`);
2031
+ console.error(`Valid: ${valid ? "yes" : "no"}`);
2032
+ console.error(`Strict: ${args.strict ? "enabled" : "disabled"}`);
2033
+ console.error(`Errors: ${result.errors.length}`);
2034
+ console.error(`Warnings: ${result.warnings.length}`);
2035
+ }
2036
+ if (args.json) printJson({
2037
+ ok: true,
2038
+ command: "validate",
2039
+ valid,
2040
+ mode: source.mode,
2041
+ templatePageCount: result.pages,
2042
+ fieldCount: result.fields,
2043
+ ...inputCount !== void 0 ? { inputCount } : {},
2044
+ errors: result.errors,
2045
+ warnings: result.warnings,
2046
+ inspection,
2047
+ inputHints: collectInputHints(source.template)
2048
+ });
2049
+ else {
2050
+ if (result.errors.length === 0 && result.warnings.length === 0) console.log(`\u2713 Template is valid (${result.pages} page(s), ${result.fields} field(s))`);
2051
+ for (const err of result.errors) console.log(`\u2717 Error: ${err}`);
2052
+ for (const warn of result.warnings) console.log(`\u26a0 Warning: ${warn}`);
2053
+ }
2054
+ if (!valid) process.exit(1);
2055
+ });
2056
+ }
2057
+ });
2058
+ function describeValidationInput(file) {
2059
+ if (!file || file === "-") return "stdin";
2060
+ return file;
2061
+ }
2062
+ //#endregion
2063
+ //#region src/commands/pdf2img.ts
2064
+ var pdf2imgArgs = {
2065
+ file: {
2066
+ type: "positional",
2067
+ description: "Input PDF file",
2068
+ required: false
2069
+ },
2070
+ output: {
2071
+ type: "string",
2072
+ alias: "o",
2073
+ description: "Output directory"
2074
+ },
2075
+ grid: {
2076
+ type: "boolean",
2077
+ description: "Overlay mm grid on images",
2078
+ default: false
2079
+ },
2080
+ gridSize: {
2081
+ type: "string",
2082
+ description: "Grid spacing in mm",
2083
+ default: "10"
2084
+ },
2085
+ scale: {
2086
+ type: "string",
2087
+ description: "Render scale",
2088
+ default: "1"
2089
+ },
2090
+ imageFormat: {
2091
+ type: "string",
2092
+ description: "Image format: png | jpeg",
2093
+ default: "png"
2094
+ },
2095
+ pages: {
2096
+ type: "string",
2097
+ description: "Page range (e.g., 1-3, 1,3,5)"
2098
+ },
2099
+ verbose: {
2100
+ type: "boolean",
2101
+ alias: "v",
2102
+ description: "Verbose output",
2103
+ default: false
2104
+ },
2105
+ json: {
2106
+ type: "boolean",
2107
+ description: "Machine-readable JSON output (includes size info)",
2108
+ default: false
2109
+ }
2110
+ };
2111
+ var pdf2img_default = defineCommand({
2112
+ meta: {
2113
+ name: "pdf2img",
2114
+ description: "Convert PDF pages to images"
2115
+ },
2116
+ args: pdf2imgArgs,
2117
+ async run({ args, rawArgs }) {
2118
+ return runWithContract({ json: Boolean(args.json) }, async () => {
2119
+ assertNoUnknownFlags(rawArgs, pdf2imgArgs);
2120
+ if (!args.file) fail("No input PDF provided.", {
2121
+ code: "EARG",
2122
+ exitCode: 1
2123
+ });
2124
+ const scale = parsePositiveNumberArg("scale", args.scale);
2125
+ const gridSize = parsePositiveNumberArg("gridSize", args.gridSize);
2126
+ const imageFormat = parseEnumArg("imageFormat", args.imageFormat, ["png", "jpeg"]);
2127
+ const pdfData = readPdfFile(args.file);
2128
+ const sizes = await pdf2size(pdfData);
2129
+ const pageIndices = args.pages ? parsePageRange(args.pages, sizes.length).map((page) => page - 1) : Array.from({ length: sizes.length }, (_, index) => index);
2130
+ const allImages = await pdf2img(pdfData, {
2131
+ scale,
2132
+ imageType: imageFormat
2133
+ });
2134
+ const inputBase = basename(args.file, extname(args.file));
2135
+ const ext = imageFormat === "jpeg" ? "jpg" : "png";
2136
+ let outputDir = ".";
2137
+ if (args.output) {
2138
+ outputDir = args.output;
2139
+ if (existsSync(outputDir)) {
2140
+ if (!statSync(outputDir).isDirectory()) fail(`Output path must be a directory for pdf2img: ${args.output}`, {
2141
+ code: "EIO",
2142
+ exitCode: 3
2143
+ });
2144
+ } else mkdirSync(outputDir, { recursive: true });
2145
+ }
2146
+ if (args.verbose) {
2147
+ console.error(`Input: ${args.file}`);
2148
+ console.error(`Pages: ${sizes.length}`);
2149
+ console.error(`Selected pages: ${pageIndices.map((pageIdx) => pageIdx + 1).join(", ")}`);
2150
+ console.error(`Output: ${outputDir}`);
2151
+ console.error(`Image format: ${imageFormat}`);
2152
+ console.error(`Scale: ${scale}`);
2153
+ console.error(`Grid: ${args.grid ? `enabled (${gridSize}mm)` : "disabled"}`);
2154
+ }
2155
+ const results = [];
2156
+ for (const pageIdx of pageIndices) {
2157
+ let imageData = allImages[pageIdx];
2158
+ const size = sizes[pageIdx] ?? {
2159
+ width: 210,
2160
+ height: 297
2161
+ };
2162
+ if (args.grid) imageData = await drawGridOnPdfImage(imageData, gridSize, size.width, size.height, imageFormat);
2163
+ const outputPath = join(outputDir, `${inputBase}-${pageIdx + 1}.${ext}`);
2164
+ writeOutput(outputPath, imageData);
2165
+ const paperSize = detectPaperSize(size.width, size.height);
2166
+ results.push({
2167
+ outputPath,
2168
+ pageNumber: pageIdx + 1,
2169
+ width: Math.round(size.width * 100) / 100,
2170
+ height: Math.round(size.height * 100) / 100
2171
+ });
2172
+ if (!args.json) {
2173
+ const sizeLabel = paperSize ? `, ${paperSize}` : "";
2174
+ console.log(`\u2713 ${outputPath} (${size.width.toFixed(0)}\u00d7${size.height.toFixed(0)}mm${sizeLabel})`);
2175
+ }
2176
+ }
2177
+ if (args.json) printJson({
2178
+ ok: true,
2179
+ command: "pdf2img",
2180
+ pageCount: sizes.length,
2181
+ selectedPageCount: results.length,
2182
+ outputDir,
2183
+ outputPaths: results.map((result) => result.outputPath),
2184
+ pages: results
2185
+ });
2186
+ });
2187
+ }
2188
+ });
2189
+ //#endregion
2190
+ //#region src/commands/pdf2size.ts
2191
+ var pdf2sizeArgs = {
2192
+ file: {
2193
+ type: "positional",
2194
+ description: "Input PDF file",
2195
+ required: false
2196
+ },
2197
+ verbose: {
2198
+ type: "boolean",
2199
+ alias: "v",
2200
+ description: "Verbose output",
2201
+ default: false
2202
+ },
2203
+ json: {
2204
+ type: "boolean",
2205
+ description: "Machine-readable JSON output",
2206
+ default: false
2207
+ }
2208
+ };
2209
+ var pdf2size_default = defineCommand({
2210
+ meta: {
2211
+ name: "pdf2size",
2212
+ description: "Get page dimensions of a PDF file"
2213
+ },
2214
+ args: pdf2sizeArgs,
2215
+ async run({ args, rawArgs }) {
2216
+ return runWithContract({ json: Boolean(args.json) }, async () => {
2217
+ assertNoUnknownFlags(rawArgs, pdf2sizeArgs);
2218
+ if (!args.file) fail("No input PDF provided.", {
2219
+ code: "EARG",
2220
+ exitCode: 1
2221
+ });
2222
+ const sizes = await pdf2size(readPdfFile(args.file));
2223
+ if (args.verbose) {
2224
+ console.error(`Input: ${args.file}`);
2225
+ console.error(`Pages: ${sizes.length}`);
2226
+ }
2227
+ const result = sizes.map((size, index) => ({
2228
+ pageNumber: index + 1,
2229
+ width: Math.round(size.width * 100) / 100,
2230
+ height: Math.round(size.height * 100) / 100
2231
+ }));
2232
+ if (args.json) printJson({
2233
+ ok: true,
2234
+ command: "pdf2size",
2235
+ pageCount: result.length,
2236
+ pages: result
2237
+ });
2238
+ else for (let i = 0; i < sizes.length; i++) {
2239
+ const size = sizes[i];
2240
+ const paperSize = detectPaperSize(size.width, size.height);
2241
+ const sizeLabel = paperSize ? ` (${paperSize})` : "";
2242
+ console.log(`Page ${i + 1}: ${size.width.toFixed(0)} \u00d7 ${size.height.toFixed(0)} mm${sizeLabel}`);
2243
+ }
2244
+ });
2245
+ }
2246
+ });
2247
+ //#endregion
2248
+ //#region src/version.ts
2249
+ var CLI_VERSION = "0.1.0-alpha.0";
2250
+ //#endregion
2251
+ //#region src/example-templates.ts
2252
+ function getExamplesBaseUrl() {
2253
+ return process.env.PDFME_EXAMPLES_BASE_URL ?? "https://playground.pdfme.com/template-assets";
2254
+ }
2255
+ async function fetchJson(url) {
2256
+ const response = await fetch(url, { signal: AbortSignal.timeout(15e3) });
2257
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
2258
+ return await response.json();
2259
+ }
2260
+ async function getExampleManifest() {
2261
+ let lastError;
2262
+ for (const url of getManifestUrls()) try {
2263
+ return {
2264
+ manifest: normalizeManifest(await fetchJson(url)),
2265
+ source: "remote",
2266
+ url
2267
+ };
2268
+ } catch (error) {
2269
+ lastError = error;
2270
+ }
2271
+ throw new Error(`Could not load examples manifest. ${formatError(lastError)}`);
2272
+ }
2273
+ async function fetchExampleTemplateWithSource(name, options = {}) {
2274
+ const entry = (options.manifest ?? (await getExampleManifest()).manifest).templates.find((template) => template.name === name);
2275
+ if (!entry) throw new Error(`Template "${name}" is not present in the examples manifest.`);
2276
+ const relativePath = entry.path;
2277
+ const templateUrl = `${getExamplesBaseUrl().replace(/\/$/, "")}/${relativePath}`;
2278
+ return {
2279
+ template: await fetchJson(templateUrl),
2280
+ source: "remote",
2281
+ url: templateUrl
2282
+ };
2283
+ }
2284
+ function getManifestUrls() {
2285
+ const baseUrl = getExamplesBaseUrl().replace(/\/$/, "");
2286
+ return [`${baseUrl}/manifest.json`, `${baseUrl}/index.json`];
2287
+ }
2288
+ function normalizeManifest(raw) {
2289
+ if (Array.isArray(raw)) return {
2290
+ schemaVersion: 1,
2291
+ cliVersion: CLI_VERSION,
2292
+ templates: normalizeEntries(raw)
2293
+ };
2294
+ if (typeof raw !== "object" || raw === null) throw new Error("Examples manifest must be a JSON object or array.");
2295
+ const record = raw;
2296
+ const rawTemplates = Array.isArray(record.templates) ? record.templates : Array.isArray(record.entries) ? record.entries : void 0;
2297
+ if (!rawTemplates) throw new Error("Examples manifest is missing templates.");
2298
+ return {
2299
+ schemaVersion: typeof record.schemaVersion === "number" && Number.isFinite(record.schemaVersion) ? record.schemaVersion : 1,
2300
+ cliVersion: typeof record.cliVersion === "string" ? record.cliVersion : CLI_VERSION,
2301
+ templates: normalizeEntries(rawTemplates)
2302
+ };
2303
+ }
2304
+ function normalizeEntries(rawTemplates) {
2305
+ return rawTemplates.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => {
2306
+ const name = typeof entry.name === "string" ? entry.name : "";
2307
+ return {
2308
+ name,
2309
+ author: typeof entry.author === "string" && entry.author.length > 0 ? entry.author : "pdfme",
2310
+ path: typeof entry.path === "string" && entry.path.length > 0 ? entry.path : `${name}/template.json`,
2311
+ thumbnailPath: typeof entry.thumbnailPath === "string" && entry.thumbnailPath.length > 0 ? entry.thumbnailPath : `${name}/thumbnail.png`,
2312
+ pageCount: typeof entry.pageCount === "number" && Number.isFinite(entry.pageCount) ? entry.pageCount : 0,
2313
+ fieldCount: typeof entry.fieldCount === "number" && Number.isFinite(entry.fieldCount) ? entry.fieldCount : 0,
2314
+ schemaTypes: normalizeStringArray(entry.schemaTypes),
2315
+ fontNames: normalizeStringArray(entry.fontNames),
2316
+ hasCJK: typeof entry.hasCJK === "boolean" ? entry.hasCJK : false,
2317
+ basePdfKind: typeof entry.basePdfKind === "string" && entry.basePdfKind.length > 0 ? entry.basePdfKind : "unknown"
2318
+ };
2319
+ }).filter((entry) => entry.name.length > 0);
2320
+ }
2321
+ function normalizeStringArray(value) {
2322
+ if (!Array.isArray(value)) return [];
2323
+ return value.filter((item) => typeof item === "string" && item.length > 0);
2324
+ }
2325
+ function formatError(error) {
2326
+ return error instanceof Error ? error.message : String(error);
2327
+ }
2328
+ //#endregion
2329
+ //#region src/example-fonts.ts
2330
+ var OFFICIAL_EXAMPLE_FONT_URLS = {
2331
+ NotoSansJP: "https://fonts.gstatic.com/s/notosansjp/v53/-F6jfjtqLzI2JPCgQBnw7HFyzSD-AsregP8VFBEj75vY0rw-oME.ttf",
2332
+ NotoSerifJP: "https://fonts.gstatic.com/s/notoserifjp/v30/xn71YHs72GKoTvER4Gn3b5eMRtWGkp6o7MjQ2bwxOubAILO5wBCU.ttf",
2333
+ "PinyonScript-Regular": "https://fonts.gstatic.com/s/pinyonscript/v22/6xKpdSJbL9-e9LuoeQiDRQR8aOLQO4bhiDY.ttf"
2334
+ };
2335
+ function collectTemplateFontNames(template) {
2336
+ const schemas = template.schemas;
2337
+ if (!Array.isArray(schemas)) return [];
2338
+ const fontNames = /* @__PURE__ */ new Set();
2339
+ for (const page of schemas) {
2340
+ const pageSchemas = Array.isArray(page) ? page : typeof page === "object" && page !== null ? Object.values(page) : [];
2341
+ for (const schema of pageSchemas) {
2342
+ if (typeof schema !== "object" || schema === null) continue;
2343
+ const fontName = schema.fontName;
2344
+ if (typeof fontName === "string" && fontName.length > 0) fontNames.add(fontName);
2345
+ }
2346
+ }
2347
+ return [...fontNames].sort();
2348
+ }
2349
+ function getOfficialExampleFonts(template) {
2350
+ const entries = collectTemplateFontNames(template).flatMap((fontName) => {
2351
+ const url = OFFICIAL_EXAMPLE_FONT_URLS[fontName];
2352
+ return url ? [[fontName, {
2353
+ data: url,
2354
+ fallback: false,
2355
+ subset: true
2356
+ }]] : [];
2357
+ });
2358
+ if (entries.length === 0) return;
2359
+ return Object.fromEntries(entries);
2360
+ }
2361
+ //#endregion
2362
+ //#region src/commands/examples.ts
2363
+ var examplesArgs = {
2364
+ name: {
2365
+ type: "positional",
2366
+ description: "Template name to output",
2367
+ required: false
2368
+ },
2369
+ list: {
2370
+ type: "boolean",
2371
+ description: "List available templates",
2372
+ default: false
2373
+ },
2374
+ output: {
2375
+ type: "string",
2376
+ alias: "o",
2377
+ description: "Output file path"
2378
+ },
2379
+ withInputs: {
2380
+ type: "boolean",
2381
+ alias: "w",
2382
+ description: "Output unified format with sample inputs",
2383
+ default: false
2384
+ },
2385
+ verbose: {
2386
+ type: "boolean",
2387
+ alias: "v",
2388
+ description: "Verbose output",
2389
+ default: false
2390
+ },
2391
+ json: {
2392
+ type: "boolean",
2393
+ description: "Machine-readable JSON output",
2394
+ default: false
2395
+ }
2396
+ };
2397
+ function generateSampleInputs(template) {
2398
+ const fields = normalizeSchemaPages(template.schemas).flat();
2399
+ if (fields.length === 0) return [{}];
2400
+ const input = {};
2401
+ for (const schema of fields) {
2402
+ if (typeof schema !== "object" || schema === null) continue;
2403
+ const name = schema.name;
2404
+ const content = schema.content;
2405
+ if (schema.readOnly) continue;
2406
+ if (name) input[name] = content || `Sample ${name}`;
2407
+ }
2408
+ return [input];
2409
+ }
2410
+ var examples_default = defineCommand({
2411
+ meta: {
2412
+ name: "examples",
2413
+ description: "List and output example pdfme templates"
2414
+ },
2415
+ args: examplesArgs,
2416
+ async run({ args, rawArgs }) {
2417
+ return runWithContract({ json: Boolean(args.json) }, async () => {
2418
+ assertNoUnknownFlags(rawArgs, examplesArgs);
2419
+ let manifestResult;
2420
+ try {
2421
+ manifestResult = await getExampleManifest();
2422
+ } catch (error) {
2423
+ fail(`Failed to load examples manifest. ${error instanceof Error ? error.message : String(error)}`, {
2424
+ code: "EIO",
2425
+ exitCode: 3,
2426
+ cause: error
2427
+ });
2428
+ }
2429
+ const templateEntries = manifestResult.manifest.templates;
2430
+ const templateNames = templateEntries.map((entry) => entry.name).filter((name) => typeof name === "string" && name.length > 0).sort();
2431
+ if (args.verbose) {
2432
+ console.error(`Base URL: ${getExamplesBaseUrl()}`);
2433
+ console.error(`Manifest source: ${manifestResult.source}`);
2434
+ if (manifestResult.url) console.error(`Manifest URL: ${manifestResult.url}`);
2435
+ console.error(`Templates: ${templateNames.length}`);
2436
+ }
2437
+ if (args.list || !args.name) {
2438
+ if (args.json) printJson({
2439
+ ok: true,
2440
+ command: "examples",
2441
+ mode: "list",
2442
+ templateCount: templateNames.length,
2443
+ source: manifestResult.source,
2444
+ baseUrl: getExamplesBaseUrl(),
2445
+ manifest: manifestResult.manifest
2446
+ });
2447
+ else {
2448
+ console.log("Available templates:");
2449
+ for (const name of templateNames) console.log(` ${name}`);
2450
+ }
2451
+ return;
2452
+ }
2453
+ if (!templateEntries.find((template) => template.name === args.name)) fail(`Template "${args.name}" not found. Available templates: ${templateNames.join(", ")}`, {
2454
+ code: "EARG",
2455
+ exitCode: 1
2456
+ });
2457
+ let templateResult;
2458
+ try {
2459
+ templateResult = await fetchExampleTemplateWithSource(args.name, { manifest: manifestResult.manifest });
2460
+ } catch (error) {
2461
+ fail(`Failed to load example template "${args.name}". ${error instanceof Error ? error.message : String(error)}`, {
2462
+ code: "EIO",
2463
+ exitCode: 3,
2464
+ cause: error
2465
+ });
2466
+ }
2467
+ const output = args.withInputs ? buildExampleJob(templateResult.template) : templateResult.template;
2468
+ const mode = args.withInputs ? "job" : "template";
2469
+ const stats = countTemplateStats(templateResult.template);
2470
+ const inputCount = args.withInputs ? output.inputs?.length ?? 0 : void 0;
2471
+ if (args.verbose) {
2472
+ console.error(`Template: ${args.name}`);
2473
+ console.error(`Template source: ${templateResult.source}`);
2474
+ if (templateResult.url) console.error(`Template URL: ${templateResult.url}`);
2475
+ console.error(`Mode: ${mode}`);
2476
+ console.error(`Template pages: ${stats.templatePageCount}`);
2477
+ console.error(`Fields: ${stats.fieldCount}`);
2478
+ if (inputCount !== void 0) console.error(`Inputs: ${inputCount} set(s)`);
2479
+ console.error(`Output: ${args.output ?? "stdout"}`);
2480
+ }
2481
+ if (args.output) {
2482
+ writeOutput(args.output, new TextEncoder().encode(JSON.stringify(output, null, 2)));
2483
+ if (args.json) printJson({
2484
+ ok: true,
2485
+ command: "examples",
2486
+ name: args.name,
2487
+ mode,
2488
+ source: templateResult.source,
2489
+ templatePageCount: stats.templatePageCount,
2490
+ fieldCount: stats.fieldCount,
2491
+ ...inputCount !== void 0 ? { inputCount } : {},
2492
+ outputPath: args.output
2493
+ });
2494
+ else {
2495
+ const label = args.withInputs ? "Job file" : "Template";
2496
+ console.log(`\u2713 ${label} written to ${args.output}`);
2497
+ }
2498
+ return;
2499
+ }
2500
+ if (args.json) printJson({
2501
+ ok: true,
2502
+ command: "examples",
2503
+ name: args.name,
2504
+ source: templateResult.source,
2505
+ mode,
2506
+ templatePageCount: stats.templatePageCount,
2507
+ fieldCount: stats.fieldCount,
2508
+ ...inputCount !== void 0 ? { inputCount } : {},
2509
+ data: output
2510
+ });
2511
+ else console.log(JSON.stringify(output, null, 2));
2512
+ });
2513
+ }
2514
+ });
2515
+ function buildExampleJob(template) {
2516
+ const job = {
2517
+ template,
2518
+ inputs: generateSampleInputs(template)
2519
+ };
2520
+ const font = getOfficialExampleFonts(template);
2521
+ if (font) job.options = { font };
2522
+ return job;
2523
+ }
2524
+ function countTemplateStats(template) {
2525
+ const schemaPages = normalizeSchemaPages(template.schemas);
2526
+ return {
2527
+ templatePageCount: schemaPages.length,
2528
+ fieldCount: schemaPages.reduce((count, page) => count + page.length, 0)
2529
+ };
2530
+ }
2531
+ function normalizeSchemaPages(rawSchemas) {
2532
+ if (!Array.isArray(rawSchemas)) return [];
2533
+ return rawSchemas.map((page) => {
2534
+ if (Array.isArray(page)) return page.filter((schema) => typeof schema === "object" && schema !== null);
2535
+ if (typeof page === "object" && page !== null) return Object.entries(page).map(([name, schema]) => typeof schema === "object" && schema !== null ? {
2536
+ ...schema,
2537
+ name: schema.name ?? name
2538
+ } : null).filter((schema) => schema !== null);
2539
+ return [];
2540
+ });
2541
+ }
2542
+ //#endregion
2543
+ //#region src/commands/doctor.ts
2544
+ var doctorArgs = {
2545
+ target: {
2546
+ type: "positional",
2547
+ description: "Optional job/template JSON file, or \"fonts\" for font-focused diagnosis",
2548
+ required: false
2549
+ },
2550
+ file: {
2551
+ type: "positional",
2552
+ description: "Job/template JSON file for \"doctor fonts\", or \"-\" for stdin",
2553
+ required: false
2554
+ },
2555
+ verbose: {
2556
+ type: "boolean",
2557
+ alias: "v",
2558
+ description: "Verbose output",
2559
+ default: false
2560
+ },
2561
+ json: {
2562
+ type: "boolean",
2563
+ description: "Machine-readable JSON output",
2564
+ default: false
2565
+ },
2566
+ noAutoFont: {
2567
+ type: "boolean",
2568
+ description: "Simulate generate with automatic CJK font download disabled",
2569
+ default: false
2570
+ },
2571
+ output: {
2572
+ type: "string",
2573
+ alias: "o",
2574
+ description: "Simulate generate output PDF path for runtime/path diagnosis",
2575
+ default: "output.pdf"
2576
+ },
2577
+ force: {
2578
+ type: "boolean",
2579
+ description: "Simulate generate --force for implicit default output path checks",
2580
+ default: false
2581
+ },
2582
+ image: {
2583
+ type: "boolean",
2584
+ description: "Simulate generate --image when previewing runtime output paths",
2585
+ default: false
2586
+ },
2587
+ imageFormat: {
2588
+ type: "string",
2589
+ description: "Image format to use when previewing runtime output paths: png | jpeg",
2590
+ default: "png"
2591
+ }
2592
+ };
2593
+ var doctor_default = defineCommand({
2594
+ meta: {
2595
+ name: "doctor",
2596
+ description: "Diagnose the local pdfme CLI environment and input readiness"
2597
+ },
2598
+ args: doctorArgs,
2599
+ async run({ args, rawArgs }) {
2600
+ return runWithContract({ json: Boolean(args.json) }, async () => {
2601
+ assertNoUnknownFlags(rawArgs, doctorArgs);
2602
+ const invocation = resolveDoctorInvocation(args);
2603
+ const imageFormat = parseEnumArg("imageFormat", args.imageFormat, ["png", "jpeg"]);
2604
+ const environment = getEnvironmentReport();
2605
+ if (invocation.target === "environment") {
2606
+ const issues = collectEnvironmentIssues(environment);
2607
+ const warnings = collectEnvironmentWarnings(environment);
2608
+ const healthy = issues.length === 0;
2609
+ if (args.verbose) printDoctorEnvironmentVerbose(environment, healthy, issues, warnings);
2610
+ if (args.json) printJson({
2611
+ ok: true,
2612
+ command: "doctor",
2613
+ target: "environment",
2614
+ healthy,
2615
+ environment,
2616
+ issues,
2617
+ warnings
2618
+ });
2619
+ else printEnvironmentReport(environment, issues, warnings);
2620
+ if (!healthy) process.exit(1);
2621
+ return;
2622
+ }
2623
+ const source = await loadValidationSource(invocation.file, { noInputMessage: invocation.target === "fonts" ? "No font diagnostic input provided. Pass a file path or pipe JSON via stdin." : "No diagnostic input provided. Pass a file path or pipe JSON via stdin." });
2624
+ const diagnosis = buildInputDiagnosis(source, environment, Boolean(args.noAutoFont), {
2625
+ includeBasePdfIssue: invocation.target === "input",
2626
+ includeRuntimeIssue: invocation.target === "input",
2627
+ runtime: {
2628
+ output: args.output,
2629
+ force: Boolean(args.force),
2630
+ image: Boolean(args.image),
2631
+ imageFormat,
2632
+ rawArgs
2633
+ }
2634
+ });
2635
+ if (args.verbose) printDoctorInputVerbose(invocation, source, diagnosis);
2636
+ const payload = invocation.target === "fonts" ? {
2637
+ ok: true,
2638
+ command: "doctor",
2639
+ target: "fonts",
2640
+ healthy: diagnosis.healthy,
2641
+ mode: source.mode,
2642
+ templatePageCount: diagnosis.validation.pages,
2643
+ fieldCount: diagnosis.validation.fields,
2644
+ ...source.mode === "job" ? { inputCount: source.inputs?.length ?? 0 } : {},
2645
+ environment,
2646
+ validation: createValidationPayload(diagnosis.validation),
2647
+ inspection: {
2648
+ schemaTypes: diagnosis.inspection.schemaTypes,
2649
+ requiredPlugins: diagnosis.inspection.requiredPlugins,
2650
+ requiredFonts: diagnosis.inspection.requiredFonts
2651
+ },
2652
+ inputHints: collectInputHints(source.template),
2653
+ diagnosis: { fonts: createFontPayload(diagnosis.fontDiagnosis) },
2654
+ issues: diagnosis.issues,
2655
+ warnings: diagnosis.warnings
2656
+ } : {
2657
+ ok: true,
2658
+ command: "doctor",
2659
+ target: "input",
2660
+ healthy: diagnosis.healthy,
2661
+ mode: source.mode,
2662
+ templatePageCount: diagnosis.validation.pages,
2663
+ fieldCount: diagnosis.validation.fields,
2664
+ ...source.mode === "job" ? { inputCount: source.inputs?.length ?? 0 } : {},
2665
+ estimatedPageCount: diagnosis.runtimeDiagnosis.estimatedPages,
2666
+ environment,
2667
+ validation: createValidationPayload(diagnosis.validation),
2668
+ inspection: diagnosis.inspection,
2669
+ inputHints: collectInputHints(source.template),
2670
+ diagnosis: {
2671
+ basePdf: diagnosis.basePdfDiagnosis,
2672
+ fonts: createFontPayload(diagnosis.fontDiagnosis),
2673
+ runtime: diagnosis.runtimeDiagnosis,
2674
+ plugins: {
2675
+ required: diagnosis.inspection.requiredPlugins,
2676
+ unsupportedSchemaTypes: diagnosis.inspection.schemaTypes.filter((type) => !diagnosis.inspection.requiredPlugins.includes(type))
2677
+ }
2678
+ },
2679
+ issues: diagnosis.issues,
2680
+ warnings: diagnosis.warnings
2681
+ };
2682
+ if (args.json) printJson(payload);
2683
+ else if (invocation.target === "fonts") printFontReport(payload);
2684
+ else printInputReport(payload);
2685
+ if (!diagnosis.healthy) process.exit(1);
2686
+ });
2687
+ }
2688
+ });
2689
+ function resolveDoctorInvocation(args) {
2690
+ const positionals = Array.isArray(args._) ? args._ : [];
2691
+ if (args.target === "fonts") {
2692
+ if (positionals.length > 2) fail(`Unexpected extra positional argument: ${JSON.stringify(positionals[2])}. Usage: pdfme doctor fonts <job-or-template>.`, {
2693
+ code: "EARG",
2694
+ exitCode: 1
2695
+ });
2696
+ return {
2697
+ target: "fonts",
2698
+ file: args.file
2699
+ };
2700
+ }
2701
+ if (positionals.length > 1) fail(`Unexpected extra positional argument: ${JSON.stringify(positionals[1])}. Usage: pdfme doctor [job-or-template].`, {
2702
+ code: "EARG",
2703
+ exitCode: 1
2704
+ });
2705
+ if (args.target) return {
2706
+ target: "input",
2707
+ file: args.target
2708
+ };
2709
+ return { target: "environment" };
2710
+ }
2711
+ function buildInputDiagnosis(source, environment, noAutoFont, options) {
2712
+ const inspection = inspectTemplate(source.template, source.templateDir);
2713
+ const validation = validateTemplate(source.template);
2714
+ validation.warnings.push(...source.jobWarnings);
2715
+ const templateUnknownKeys = Object.keys(source.template).filter((key) => !KNOWN_TEMPLATE_KEYS.has(key)).sort();
2716
+ if (templateUnknownKeys.length > 0) validation.warnings.push(`Unknown template top-level field(s): ${templateUnknownKeys.join(", ")}`);
2717
+ const fontDiagnosis = diagnoseFonts(source, environment, noAutoFont, inspection.requiredFonts);
2718
+ const basePdfDiagnosis = diagnoseBasePdf(source.template.basePdf, source.templateDir);
2719
+ const runtimeDiagnosis = diagnoseRuntime(source, options.runtime);
2720
+ const issues = [...validation.errors];
2721
+ const warnings = [...validation.warnings, ...fontDiagnosis.warnings];
2722
+ if (options.includeBasePdfIssue && basePdfDiagnosis.issue) issues.push(basePdfDiagnosis.issue);
2723
+ issues.push(...fontDiagnosis.issues);
2724
+ if (options.includeRuntimeIssue && runtimeDiagnosis.output.issue) issues.push(runtimeDiagnosis.output.issue);
2725
+ if (source.mode === "job" && fontDiagnosis.effectiveOptions) {
2726
+ try {
2727
+ checkGenerateProps({
2728
+ template: source.template,
2729
+ inputs: source.inputs,
2730
+ options: fontDiagnosis.effectiveOptions
2731
+ });
2732
+ } catch (error) {
2733
+ issues.unshift(error instanceof Error ? error.message : String(error));
2734
+ }
2735
+ issues.push(...getInputContractIssues(source.template, source.inputs ?? []));
2736
+ }
2737
+ return {
2738
+ validation: {
2739
+ valid: validation.errors.length === 0,
2740
+ pages: validation.pages,
2741
+ fields: validation.fields,
2742
+ errors: validation.errors,
2743
+ warnings: validation.warnings
2744
+ },
2745
+ inspection,
2746
+ basePdfDiagnosis,
2747
+ fontDiagnosis,
2748
+ runtimeDiagnosis,
2749
+ issues,
2750
+ warnings,
2751
+ healthy: issues.length === 0
2752
+ };
2753
+ }
2754
+ function createValidationPayload(validation) {
2755
+ return {
2756
+ valid: validation.valid,
2757
+ errors: validation.errors,
2758
+ warnings: validation.warnings
2759
+ };
2760
+ }
2761
+ function createFontPayload(fontDiagnosis) {
2762
+ return {
2763
+ hasCJK: fontDiagnosis.hasCJK,
2764
+ requiredFonts: fontDiagnosis.requiredFonts,
2765
+ explicitFonts: fontDiagnosis.explicitFonts,
2766
+ effectiveFonts: fontDiagnosis.effectiveFonts,
2767
+ missingFonts: fontDiagnosis.missingFonts,
2768
+ explicitSources: fontDiagnosis.explicitSources,
2769
+ implicitSources: fontDiagnosis.implicitSources,
2770
+ autoNotoSansJP: {
2771
+ needed: fontDiagnosis.autoFontNeeded,
2772
+ cached: fontDiagnosis.autoFontCached,
2773
+ cacheFile: NOTO_CACHE_FILE
2774
+ }
2775
+ };
2776
+ }
2777
+ function getEnvironmentReport() {
2778
+ const fontCacheDir = dirname(NOTO_CACHE_FILE);
2779
+ const cwdStatus = getWritableStatus(process.cwd());
2780
+ const tempStatus = getWritableStatus(tmpdir());
2781
+ const fontCacheStatus = getWritableStatus(fontCacheDir);
2782
+ return {
2783
+ nodeVersion: process.version,
2784
+ cliVersion: CLI_VERSION,
2785
+ platform: process.platform,
2786
+ arch: process.arch,
2787
+ cwd: cwdStatus,
2788
+ tempDir: tempStatus,
2789
+ homeDir: homedir(),
2790
+ fontCache: {
2791
+ file: NOTO_CACHE_FILE,
2792
+ dir: fontCacheDir,
2793
+ cached: existsSync(NOTO_CACHE_FILE),
2794
+ writable: fontCacheStatus.writable,
2795
+ checkedPath: fontCacheStatus.checkedPath,
2796
+ error: fontCacheStatus.error
2797
+ }
2798
+ };
2799
+ }
2800
+ function getWritableStatus(path) {
2801
+ const checkedPath = findExistingParent(path);
2802
+ try {
2803
+ accessSync(checkedPath, constants.W_OK);
2804
+ return {
2805
+ path,
2806
+ writable: true,
2807
+ checkedPath: checkedPath !== path ? checkedPath : void 0
2808
+ };
2809
+ } catch (error) {
2810
+ return {
2811
+ path,
2812
+ writable: false,
2813
+ checkedPath: checkedPath !== path ? checkedPath : void 0,
2814
+ error: error instanceof Error ? error.message : String(error)
2815
+ };
2816
+ }
2817
+ }
2818
+ function findExistingParent(path) {
2819
+ let current = path;
2820
+ while (!existsSync(current)) {
2821
+ const parent = dirname(current);
2822
+ if (parent === current) break;
2823
+ current = parent;
2824
+ }
2825
+ return current;
2826
+ }
2827
+ function collectEnvironmentIssues(environment) {
2828
+ const issues = [];
2829
+ if (!environment.cwd.writable) issues.push(`Current working directory is not writable: ${environment.cwd.path}`);
2830
+ if (!environment.tempDir.writable) issues.push(`Temporary directory is not writable: ${environment.tempDir.path}`);
2831
+ return issues;
2832
+ }
2833
+ function collectEnvironmentWarnings(environment) {
2834
+ const warnings = [];
2835
+ if (!environment.fontCache.writable) warnings.push(`Font cache directory is not writable: ${environment.fontCache.dir}`);
2836
+ if (!environment.fontCache.cached) warnings.push(`NotoSansJP is not cached at ${environment.fontCache.file}`);
2837
+ return warnings;
2838
+ }
2839
+ function diagnoseBasePdf(basePdf, templateDir) {
2840
+ const summary = summarizeBasePdf(basePdf, templateDir);
2841
+ if (summary.kind !== "pdfPath" || !summary.resolvedPath) return summary;
2842
+ const exists = existsSync(summary.resolvedPath);
2843
+ return {
2844
+ ...summary,
2845
+ exists,
2846
+ issue: exists ? void 0 : `Base PDF file not found: ${summary.resolvedPath}`
2847
+ };
2848
+ }
2849
+ function diagnoseRuntime(source, options) {
2850
+ const estimatedPages = source.mode === "job" ? (source.inputs?.length ?? 0) * getTemplatePageCount(source.template) : getTemplatePageCount(source.template);
2851
+ const output = diagnoseOutputPath(options);
2852
+ return {
2853
+ estimatedPages,
2854
+ output,
2855
+ imageOutputs: {
2856
+ enabled: options.image,
2857
+ format: options.imageFormat,
2858
+ paths: options.image ? getImageOutputPaths(options.output, estimatedPages, options.imageFormat) : [],
2859
+ directory: dirname(output.resolvedPath)
2860
+ }
2861
+ };
2862
+ }
2863
+ function diagnoseOutputPath(options) {
2864
+ const inspection = inspectWriteTarget(options.output);
2865
+ const implicitDefaultIssue = getSafeDefaultOutputPathIssue({
2866
+ filePath: options.output,
2867
+ rawArgs: options.rawArgs,
2868
+ optionName: "output",
2869
+ optionAlias: "o",
2870
+ defaultValue: "output.pdf",
2871
+ force: options.force
2872
+ });
2873
+ let issue = implicitDefaultIssue;
2874
+ if (!issue && inspection.exists && inspection.existingType === "directory") issue = `Output path points to a directory: ${inspection.resolvedPath}. Choose a file path like out.pdf.`;
2875
+ else if (!issue && inspection.exists && inspection.existingType === "other") issue = `Output path is not a regular file: ${inspection.resolvedPath}.`;
2876
+ else if (!issue && inspection.checkedType && inspection.checkedType !== "directory" && inspection.existingType !== "file") issue = `Output directory cannot be created because an existing path segment is not a directory: ${inspection.checkedPath ?? inspection.parentDir}.`;
2877
+ else if (!issue && !inspection.writable) issue = inspection.exists && inspection.existingType === "file" ? `Output file is not writable: ${inspection.resolvedPath}.` : `Output directory is not writable for ${inspection.resolvedPath}: ${inspection.checkedPath ?? inspection.parentDir}.`;
2878
+ return {
2879
+ ...inspection,
2880
+ implicitDefaultProtected: Boolean(implicitDefaultIssue),
2881
+ issue
2882
+ };
2883
+ }
2884
+ function getTemplatePageCount(template) {
2885
+ return Array.isArray(template.schemas) ? template.schemas.length : 0;
2886
+ }
2887
+ function diagnoseFonts(source, environment, noAutoFont, requiredFonts) {
2888
+ const hasCJK = detectCJKInTemplate(source.template) || detectCJKInInputs(source.inputs ?? []);
2889
+ const issues = [];
2890
+ const warnings = [];
2891
+ const autoFontCached = existsSync(NOTO_CACHE_FILE);
2892
+ const explicit = normalizeExplicitFontConfig(source.options, source.templateDir);
2893
+ issues.push(...explicit.issues);
2894
+ warnings.push(...explicit.warnings);
2895
+ const autoFontNeeded = hasCJK && explicit.fontNames.length === 0;
2896
+ if (autoFontNeeded && noAutoFont) issues.push("CJK text detected, but automatic NotoSansJP download is disabled by --noAutoFont and no explicit font source was provided.");
2897
+ else if (autoFontNeeded && !autoFontCached && !environment.fontCache.writable) issues.push(`CJK text detected and NotoSansJP is not cached at ${NOTO_CACHE_FILE}, but the font cache directory is not writable: ${environment.fontCache.dir}. Provide --font / options.font or warm the cache in a writable HOME.`);
2898
+ else if (autoFontNeeded && !autoFontCached) warnings.push(`CJK text detected and NotoSansJP is not cached at ${NOTO_CACHE_FILE}. Generate will require network access to fetch it.`);
2899
+ const resolvedFont = autoFontNeeded ? {
2900
+ NotoSansJP: {
2901
+ data: new Uint8Array(),
2902
+ fallback: true,
2903
+ subset: true
2904
+ },
2905
+ [DEFAULT_FONT_NAME]: {
2906
+ data: new Uint8Array(),
2907
+ fallback: false,
2908
+ subset: true
2909
+ }
2910
+ } : { [DEFAULT_FONT_NAME]: {
2911
+ data: new Uint8Array(),
2912
+ fallback: true,
2913
+ subset: true
2914
+ } };
2915
+ const effectiveFont = explicit.fontRecord ? {
2916
+ ...explicit.fontRecord,
2917
+ ...resolvedFont
2918
+ } : resolvedFont;
2919
+ const effectiveFonts = Object.keys(effectiveFont).sort();
2920
+ const missingFonts = requiredFonts.filter((fontName) => !effectiveFonts.includes(fontName));
2921
+ if (source.mode === "template" && missingFonts.length > 0) issues.push(`Template references font(s) that are not available by default: ${missingFonts.join(", ")}. Provide them via generate --font or unified job options.font.`);
2922
+ return {
2923
+ hasCJK,
2924
+ requiredFonts,
2925
+ explicitFonts: explicit.fontNames,
2926
+ effectiveFonts,
2927
+ missingFonts,
2928
+ autoFontNeeded,
2929
+ autoFontCached,
2930
+ issues,
2931
+ warnings,
2932
+ explicitSources: explicit.sources,
2933
+ implicitSources: buildImplicitFontSources(autoFontNeeded, autoFontCached),
2934
+ effectiveOptions: explicit.optionsRecord ? {
2935
+ ...explicit.optionsRecord,
2936
+ font: effectiveFont
2937
+ } : { font: effectiveFont }
2938
+ };
2939
+ }
2940
+ function normalizeExplicitFontConfig(options, templateDir) {
2941
+ if (options === void 0) return {
2942
+ issues: [],
2943
+ warnings: [],
2944
+ fontNames: [],
2945
+ sources: []
2946
+ };
2947
+ if (typeof options !== "object" || options === null || Array.isArray(options)) return {
2948
+ issues: ["Unified job options must be a JSON object."],
2949
+ warnings: [],
2950
+ fontNames: [],
2951
+ sources: []
2952
+ };
2953
+ const optionsRecord = options;
2954
+ const font = optionsRecord.font;
2955
+ if (font === void 0) return {
2956
+ issues: [],
2957
+ warnings: [],
2958
+ fontNames: [],
2959
+ optionsRecord,
2960
+ sources: []
2961
+ };
2962
+ if (typeof font !== "object" || font === null || Array.isArray(font)) return {
2963
+ issues: ["Unified job options.font must be an object."],
2964
+ warnings: [],
2965
+ fontNames: [],
2966
+ optionsRecord,
2967
+ sources: []
2968
+ };
2969
+ const fontRecord = font;
2970
+ const sourceDiagnostics = analyzeExplicitFontRecord(fontRecord, templateDir);
2971
+ return {
2972
+ issues: sourceDiagnostics.issues,
2973
+ warnings: sourceDiagnostics.warnings,
2974
+ fontNames: Object.keys(fontRecord).sort(),
2975
+ fontRecord,
2976
+ optionsRecord,
2977
+ sources: sortFontSources(sourceDiagnostics.sources.map(toDoctorExplicitSource))
2978
+ };
2979
+ }
2980
+ function toDoctorExplicitSource(source) {
2981
+ return {
2982
+ ...source,
2983
+ source: "explicit"
2984
+ };
2985
+ }
2986
+ function buildImplicitFontSources(autoFontNeeded, autoFontCached) {
2987
+ const sources = [{
2988
+ fontName: DEFAULT_FONT_NAME,
2989
+ source: "implicit",
2990
+ kind: "default",
2991
+ formatHint: "ttf",
2992
+ supportedFormat: true,
2993
+ needsNetwork: false
2994
+ }];
2995
+ if (autoFontNeeded) sources.push({
2996
+ fontName: "NotoSansJP",
2997
+ source: "implicit",
2998
+ kind: autoFontCached ? "autoCache" : "autoDownload",
2999
+ path: NOTO_CACHE_FILE,
3000
+ resolvedPath: NOTO_CACHE_FILE,
3001
+ exists: autoFontCached,
3002
+ formatHint: "ttf",
3003
+ supportedFormat: true,
3004
+ needsNetwork: !autoFontCached
3005
+ });
3006
+ return sortFontSources(sources);
3007
+ }
3008
+ function sortFontSources(sources) {
3009
+ return [...sources].sort((a, b) => a.fontName.localeCompare(b.fontName) || a.kind.localeCompare(b.kind));
3010
+ }
3011
+ function printEnvironmentReport(environment, issues, warnings) {
3012
+ const header = issues.length === 0 ? "✓ Environment looks ready" : "✗ Environment has blocking issues";
3013
+ console.log(header);
3014
+ console.log(`Node: ${environment.nodeVersion}`);
3015
+ console.log(`CLI: ${environment.cliVersion}`);
3016
+ console.log(`Platform: ${environment.platform} ${environment.arch}`);
3017
+ console.log(`cwd: ${environment.cwd.path} (${environment.cwd.writable ? "writable" : "not writable"})`);
3018
+ console.log(`temp: ${environment.tempDir.path} (${environment.tempDir.writable ? "writable" : "not writable"})`);
3019
+ console.log(`font cache: ${environment.fontCache.file} (${environment.fontCache.cached ? "cached" : "not cached"})`);
3020
+ for (const issue of issues) console.log(`\u2717 Issue: ${issue}`);
3021
+ for (const warning of warnings) console.log(`\u26a0 Warning: ${warning}`);
3022
+ }
3023
+ function printDoctorEnvironmentVerbose(environment, healthy, issues, warnings) {
3024
+ console.error("Target: environment");
3025
+ console.error(`Healthy: ${healthy ? "yes" : "no"}`);
3026
+ console.error(`Node: ${environment.nodeVersion}`);
3027
+ console.error(`CLI: ${environment.cliVersion}`);
3028
+ console.error(`Platform: ${environment.platform} ${environment.arch}`);
3029
+ console.error(`cwd: ${environment.cwd.path}`);
3030
+ console.error(`temp: ${environment.tempDir.path}`);
3031
+ console.error(`Font cache: ${environment.fontCache.cached ? "cached" : "not cached"} (${environment.fontCache.file})`);
3032
+ console.error(`Issues: ${issues.length}`);
3033
+ console.error(`Warnings: ${warnings.length}`);
3034
+ }
3035
+ function printInputReport(payload) {
3036
+ const healthy = Boolean(payload.healthy);
3037
+ const inspection = payload.inspection;
3038
+ const diagnosis = payload.diagnosis;
3039
+ const issues = payload.issues;
3040
+ const warnings = payload.warnings;
3041
+ console.log(healthy ? "✓ Doctor checks passed" : "✗ Doctor found blocking issues");
3042
+ console.log(`Mode: ${payload.mode}`);
3043
+ console.log(`Template pages: ${payload.templatePageCount}`);
3044
+ console.log(`Fields: ${payload.fieldCount}`);
3045
+ console.log(`Schema types: ${inspection.schemaTypes.join(", ") || "(none)"}`);
3046
+ if (diagnosis.runtime) {
3047
+ console.log(`Output: ${diagnosis.runtime.output.path} (${diagnosis.runtime.output.writable ? "writable" : "not writable"})`);
3048
+ if (diagnosis.runtime.imageOutputs.enabled) console.log(`Images: ${diagnosis.runtime.imageOutputs.paths.length} ${diagnosis.runtime.imageOutputs.format.toUpperCase()} file(s) in ${diagnosis.runtime.imageOutputs.directory}`);
3049
+ }
3050
+ for (const issue of issues) console.log(`\u2717 Issue: ${issue}`);
3051
+ for (const warning of warnings) console.log(`\u26a0 Warning: ${warning}`);
3052
+ }
3053
+ function printDoctorInputVerbose(invocation, source, diagnosis) {
3054
+ console.error(`Target: ${invocation.target}`);
3055
+ console.error(`Input: ${describeDoctorInput(invocation.file)}`);
3056
+ console.error(`Mode: ${source.mode}`);
3057
+ console.error(`Template pages: ${diagnosis.validation.pages}`);
3058
+ console.error(`Fields: ${diagnosis.validation.fields}`);
3059
+ if (source.mode === "job") console.error(`Inputs: ${source.inputs?.length ?? 0} set(s)`);
3060
+ if (invocation.target === "input") {
3061
+ console.error(`Estimated pages: ${diagnosis.runtimeDiagnosis.estimatedPages}`);
3062
+ console.error(`Output: ${diagnosis.runtimeDiagnosis.output.path}`);
3063
+ console.error(`Images: ${diagnosis.runtimeDiagnosis.imageOutputs.enabled ? `enabled (${diagnosis.runtimeDiagnosis.imageOutputs.format}, ${diagnosis.runtimeDiagnosis.imageOutputs.paths.length} file(s))` : "disabled"}`);
3064
+ }
3065
+ console.error(`Healthy: ${diagnosis.healthy ? "yes" : "no"}`);
3066
+ console.error(`Issues: ${diagnosis.issues.length}`);
3067
+ console.error(`Warnings: ${diagnosis.warnings.length}`);
3068
+ }
3069
+ function printFontReport(payload) {
3070
+ const healthy = Boolean(payload.healthy);
3071
+ const diagnosis = payload.diagnosis;
3072
+ const issues = payload.issues;
3073
+ const warnings = payload.warnings;
3074
+ console.log(healthy ? "✓ Font checks passed" : "✗ Font checks found blocking issues");
3075
+ console.log(`Mode: ${payload.mode}`);
3076
+ console.log(`Required fonts: ${diagnosis.fonts.requiredFonts.join(", ") || "(none)"}`);
3077
+ console.log(`Explicit fonts: ${diagnosis.fonts.explicitFonts.join(", ") || "(none)"}`);
3078
+ console.log(`Effective fonts: ${diagnosis.fonts.effectiveFonts.join(", ") || "(none)"}`);
3079
+ for (const source of diagnosis.fonts.explicitSources) console.log(`- explicit ${source.fontName}: ${source.kind}${source.path ? ` (${source.path})` : source.url ? ` (${source.url})` : ""}`);
3080
+ for (const source of diagnosis.fonts.implicitSources) console.log(`- implicit ${source.fontName}: ${source.kind}${source.path ? ` (${source.path})` : ""}`);
3081
+ for (const issue of issues) console.log(`\u2717 Issue: ${issue}`);
3082
+ for (const warning of warnings) console.log(`\u26a0 Warning: ${warning}`);
3083
+ }
3084
+ function describeDoctorInput(file) {
3085
+ if (!file || file === "-") return "stdin";
3086
+ return file;
3087
+ }
3088
+ //#endregion
3089
+ //#region src/index.ts
3090
+ runMain(defineCommand({
3091
+ meta: {
3092
+ name: "pdfme",
3093
+ version: CLI_VERSION,
3094
+ description: "CLI tool for pdfme - generate PDFs, convert images, validate templates"
3095
+ },
3096
+ subCommands: {
3097
+ generate: generate_default,
3098
+ validate: validate_default,
3099
+ pdf2img: pdf2img_default,
3100
+ pdf2size: pdf2size_default,
3101
+ examples: examples_default,
3102
+ doctor: doctor_default
3103
+ }
3104
+ }));
3105
+ //#endregion
3106
+
3107
+ //# sourceMappingURL=index.js.map