document-cli 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -533,8 +533,158 @@ function registerConversionCommands(program) {
533
533
  });
534
534
  }
535
535
  //#endregion
536
+ //#region src/docx-extras-format.ts
537
+ const INDENT$1 = " ";
538
+ function indent$1(depth) {
539
+ return INDENT$1.repeat(depth);
540
+ }
541
+ function commentLine(comment, position) {
542
+ const author = comment.author ?? "(no author)";
543
+ return `${indent$1(1)}[${position}] ${author}: ${comment.text}`;
544
+ }
545
+ function footnoteLine(footnote, position) {
546
+ const typeSuffix = footnote.type === void 0 ? "" : ` (${footnote.type})`;
547
+ return `${indent$1(1)}[${position}]${typeSuffix} ${footnote.text}`;
548
+ }
549
+ function commentsSection(comments) {
550
+ if (comments.length === 0) return [];
551
+ return ["comments", ...comments.map((comment, index) => commentLine(comment, index + 1))];
552
+ }
553
+ function footnotesSection(footnotes) {
554
+ if (footnotes.length === 0) return [];
555
+ return ["footnotes", ...footnotes.map((footnote, index) => footnoteLine(footnote, index + 1))];
556
+ }
557
+ function headerOrFooterSection(label, values) {
558
+ if (values.length === 0) return [];
559
+ return [label, ...values.map((text, index) => `${indent$1(1)}[${index + 1}] ${text}`)];
560
+ }
561
+ function numberingLevelLine(ilvl, level) {
562
+ const restartSuffix = level.restart === void 0 ? "" : `, restarts at level ${level.restart}`;
563
+ return `${indent$1(2)}level ${ilvl}: ${level.format} ${JSON.stringify(level.text)} starting at ${level.startAt}${restartSuffix}`;
564
+ }
565
+ function numberingSection(numbering) {
566
+ const numIds = Object.keys(numbering);
567
+ if (numIds.length === 0) return [];
568
+ const lines = ["numbering"];
569
+ for (const numId of numIds) {
570
+ const definition = numbering[numId];
571
+ if (definition === void 0) continue;
572
+ lines.push(`${indent$1(1)}numId ${numId}`);
573
+ const ilvls = Object.keys(definition.levels).sort((a, b) => Number(a) - Number(b));
574
+ for (const ilvl of ilvls) {
575
+ const level = definition.levels[ilvl];
576
+ if (level === void 0) continue;
577
+ lines.push(numberingLevelLine(ilvl, level));
578
+ }
579
+ }
580
+ return lines;
581
+ }
582
+ function formatDocxExtrasLines(extras) {
583
+ const nonEmptySections = [
584
+ commentsSection(extras.comments),
585
+ footnotesSection(extras.footnotes),
586
+ headerOrFooterSection("headers", extras.headers),
587
+ headerOrFooterSection("footers", extras.footers),
588
+ numberingSection(extras.numbering)
589
+ ].filter((section) => section.length > 0);
590
+ if (nonEmptySections.length === 0) return ["This document carries no comments, footnotes, headers, footers, or numbering definitions."];
591
+ return nonEmptySections.flatMap((section, index) => index === 0 ? section : ["", ...section]);
592
+ }
593
+ //#endregion
594
+ //#region src/commands/docx-extras.ts
595
+ async function runDocxExtras(input, options) {
596
+ const command = "docx-extras";
597
+ const { signal, getAbortReason } = createRuntimeSignal({});
598
+ try {
599
+ const inputBytes = await readInput(input, { signal });
600
+ const pkg = (0, documents_js.decodePackage)(new Uint8Array(inputBytes));
601
+ const extras = (0, documents_js.readDocxExtras)(pkg);
602
+ if (options.json) {
603
+ process.stdout.write(`${JSON.stringify(extras)}\n`);
604
+ return 0;
605
+ }
606
+ for (const line of formatDocxExtrasLines(extras)) process.stdout.write(`${line}\n`);
607
+ return 0;
608
+ } catch (error) {
609
+ process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
610
+ return mapErrorToExit(error, getAbortReason());
611
+ }
612
+ }
613
+ function registerDocxExtrasCommand(program) {
614
+ program.command("docx-extras <input>").description("print a docx's own comments, footnotes, headers, footers, and numbering definitions -- data readDocxContent's ContentDocument cannot carry").option("--json", "emit the raw DocxExtras object as JSON instead of a human-readable report", false).action(async (input, options) => {
615
+ process.exitCode = await runDocxExtras(input, options);
616
+ });
617
+ }
618
+ //#endregion
619
+ //#region src/commands/fonts.ts
620
+ const FONT_SOURCE_FORMATS = {
621
+ docx: true,
622
+ pptx: true,
623
+ odt: true,
624
+ odp: true,
625
+ ods: true,
626
+ odg: true
627
+ };
628
+ function isFontSourceFormat(format) {
629
+ return format in FONT_SOURCE_FORMATS;
630
+ }
631
+ function resolveFontSourcePackage(format, bytes) {
632
+ if (format === "docx" || format === "pptx") return {
633
+ kind: format,
634
+ package: (0, documents_js.decodePackage)(bytes)
635
+ };
636
+ return {
637
+ kind: "odf",
638
+ package: (0, odf_js.decodePackage)(bytes)
639
+ };
640
+ }
641
+ async function runFonts(input, options) {
642
+ const command = "fonts";
643
+ const { signal, getAbortReason } = createRuntimeSignal({});
644
+ try {
645
+ const format = inferFormatFromExtension(input);
646
+ if (format === void 0) {
647
+ process.stderr.write(`[${command}] cannot infer a document format from '${input}'; expected one of docx, pptx, odt, odp, ods, odg\n`);
648
+ return 2;
649
+ }
650
+ if (!isFontSourceFormat(format)) {
651
+ process.stderr.write(`[${command}] '${format}' documents carry no source-embedded font faces this command can extract; expected one of docx, pptx, odt, odp, ods, odg\n`);
652
+ return 2;
653
+ }
654
+ const inputBytes = await readInput(input, { signal });
655
+ const source = resolveFontSourcePackage(format, new Uint8Array(inputBytes));
656
+ const summaries = (0, documents_js.extractSourceFonts)(source).map((face) => ({
657
+ family: face.family,
658
+ bold: face.bold,
659
+ italic: face.italic,
660
+ byteLength: face.bytes.length
661
+ }));
662
+ if (options.json) {
663
+ process.stdout.write(`${JSON.stringify(summaries)}\n`);
664
+ return 0;
665
+ }
666
+ if (summaries.length === 0) {
667
+ process.stdout.write("This document embeds no source fonts.\n");
668
+ return 0;
669
+ }
670
+ for (const face of summaries) {
671
+ const style = [face.bold ? "bold" : void 0, face.italic ? "italic" : void 0].filter((value) => value !== void 0).join(" ");
672
+ process.stdout.write(`${face.family}${style === "" ? "" : ` (${style})`} -- ${face.byteLength} bytes\n`);
673
+ }
674
+ return 0;
675
+ } catch (error) {
676
+ process.stderr.write(`${formatError(error, false)}\n`);
677
+ return mapErrorToExit(error, getAbortReason());
678
+ }
679
+ }
680
+ function registerFontsCommand(program) {
681
+ program.command("fonts <input>").description("list every source-embedded font face a docx/pptx/odt/odp/ods/odg document carries (family, weight/style, byte length)").option("--json", "emit the face list as a JSON array instead of a human-readable report", false).action(async (input, options) => {
682
+ process.exitCode = await runFonts(input, options);
683
+ });
684
+ }
685
+ //#endregion
536
686
  //#region src/commands/formats.ts
537
- const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package";
687
+ const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras, metadata, set-metadata";
538
688
  function registerFormatsCommand(program) {
539
689
  program.command("formats").description("list every source -> target conversion this CLI supports via a <source>-to-<target> command").option("--json", "emit the conversion list as a JSON array instead of a human-readable table", false).action((options) => {
540
690
  const { conversions } = (0, documents_js.createLocalDocumentConverter)();
@@ -627,6 +777,78 @@ function registerFromPackageCommand(program) {
627
777
  });
628
778
  }
629
779
  //#endregion
780
+ //#region src/runtime/metadata-format.ts
781
+ const METADATA_KEYS = [
782
+ "title",
783
+ "author",
784
+ "subject",
785
+ "keywords",
786
+ "creator",
787
+ "producer",
788
+ "createdIso",
789
+ "modifiedIso"
790
+ ];
791
+ function isPresent(entry) {
792
+ return entry[1] !== void 0;
793
+ }
794
+ function formatMetadataValue(value) {
795
+ return typeof value === "string" ? value : value.join(", ");
796
+ }
797
+ function presentMetadataEntries(metadata) {
798
+ return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
799
+ }
800
+ function formatMetadataLines(metadata) {
801
+ return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
802
+ }
803
+ //#endregion
804
+ //#region src/commands/metadata.ts
805
+ function readMetadataForFormat(format, bytes, signal) {
806
+ switch (format) {
807
+ case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes)).metadata;
808
+ case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes)).metadata;
809
+ case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes)).metadata;
810
+ case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes)).metadata;
811
+ case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes)).metadata;
812
+ case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes)).metadata;
813
+ case "odf": return (0, documents_js.readOdfFormulaContent)((0, odf_js.decodePackage)(bytes)).metadata;
814
+ case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes)).metadata;
815
+ case "pdf": return (0, documents_js.readPdf)(bytes, { signal }).metadata;
816
+ case "xlsx": return (0, documents_js.readPdf)((0, documents_js.xlsxToPdf)(bytes, { signal }), { signal }).metadata;
817
+ }
818
+ }
819
+ async function runMetadata(input, options) {
820
+ const command = "metadata";
821
+ const source = inferFormatFromExtension(input);
822
+ if (source === void 0) {
823
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
824
+ return 2;
825
+ }
826
+ const { signal, getAbortReason } = createRuntimeSignal({});
827
+ try {
828
+ const inputBytes = await readInput(input, { signal });
829
+ const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
830
+ if (options.json) {
831
+ process.stdout.write(`${JSON.stringify(metadata)}\n`);
832
+ return 0;
833
+ }
834
+ const lines = formatMetadataLines(metadata);
835
+ if (lines.length === 0) {
836
+ process.stdout.write("This document carries no metadata.\n");
837
+ return 0;
838
+ }
839
+ for (const line of lines) process.stdout.write(`${line}\n`);
840
+ return 0;
841
+ } catch (error) {
842
+ process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
843
+ return mapErrorToExit(error, getAbortReason());
844
+ }
845
+ }
846
+ function registerMetadataCommand(program) {
847
+ program.command("metadata <input>").description(`print a document's own title/author/subject/keywords/creator/producer/created/modified metadata (${KNOWN_DOCUMENT_FORMATS})`).option("--json", "emit the metadata as a JSON object instead of a human-readable report", false).action(async (input, options) => {
848
+ process.exitCode = await runMetadata(input, options);
849
+ });
850
+ }
851
+ //#endregion
630
852
  //#region src/odb-structure.ts
631
853
  const INDENT = " ";
632
854
  function indent(depth) {
@@ -1230,12 +1452,6 @@ function countImagesByFormat(images) {
1230
1452
  for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
1231
1453
  return counts;
1232
1454
  }
1233
- function isPresent(entry) {
1234
- return entry[1] !== void 0;
1235
- }
1236
- function formatMetadataValue(value) {
1237
- return typeof value === "string" ? value : value.join(", ");
1238
- }
1239
1455
  async function runPdfInspect(input, options) {
1240
1456
  const command = "pdf-inspect";
1241
1457
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -1253,7 +1469,7 @@ async function runPdfInspect(input, options) {
1253
1469
  }
1254
1470
  });
1255
1471
  if (options.full) {
1256
- process.stdout.write(`${JSON.stringify(layout, void 0, 2)}\n`);
1472
+ process.stdout.write(`${JSON.stringify((0, documents_js.layoutDocumentWithSchema)(layout), void 0, 2)}\n`);
1257
1473
  return 0;
1258
1474
  }
1259
1475
  const imagesByFormat = countImagesByFormat(layout.images);
@@ -1277,19 +1493,9 @@ async function runPdfInspect(input, options) {
1277
1493
  const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
1278
1494
  process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
1279
1495
  });
1280
- const presentMetadata = [
1281
- ["title", layout.metadata.title],
1282
- ["author", layout.metadata.author],
1283
- ["subject", layout.metadata.subject],
1284
- ["keywords", layout.metadata.keywords],
1285
- ["creator", layout.metadata.creator],
1286
- ["producer", layout.metadata.producer],
1287
- ["createdIso", layout.metadata.createdIso],
1288
- ["modifiedIso", layout.metadata.modifiedIso]
1289
- ].filter(isPresent);
1290
- if (presentMetadata.length > 0) {
1496
+ if (presentMetadataEntries(layout.metadata).length > 0) {
1291
1497
  process.stdout.write("metadata:\n");
1292
- for (const [key, value] of presentMetadata) process.stdout.write(` ${key}: ${formatMetadataValue(value)}\n`);
1498
+ for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
1293
1499
  }
1294
1500
  if (imagesByFormat.size > 0) {
1295
1501
  process.stdout.write("images:\n");
@@ -1307,8 +1513,155 @@ function registerPdfInspectCommand(program) {
1307
1513
  });
1308
1514
  }
1309
1515
  //#endregion
1516
+ //#region src/commands/set-metadata.ts
1517
+ const REBUILD_FORMATS = {
1518
+ docx: true,
1519
+ pptx: true,
1520
+ odt: true,
1521
+ odp: true,
1522
+ ods: true,
1523
+ odg: true,
1524
+ markdown: true
1525
+ };
1526
+ function isRebuildFormat(format) {
1527
+ return format in REBUILD_FORMATS;
1528
+ }
1529
+ function readContentForFormat(format, bytes) {
1530
+ switch (format) {
1531
+ case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes));
1532
+ case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes));
1533
+ case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes));
1534
+ case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes));
1535
+ case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes));
1536
+ case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes));
1537
+ case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes));
1538
+ }
1539
+ }
1540
+ function buildBytesForRebuildFormat(format, content) {
1541
+ switch (format) {
1542
+ case "docx": return (0, documents_js.encodePackage)((0, documents_js.buildDocxPackage)(content));
1543
+ case "pptx": return (0, documents_js.encodePackage)((0, documents_js.buildPptxPackage)(content));
1544
+ case "odt": return (0, odf_js.encodePackage)((0, documents_js.buildOdtPackage)(content));
1545
+ case "odp": return (0, odf_js.encodePackage)((0, documents_js.buildOdpPackage)(content));
1546
+ case "ods": return (0, odf_js.encodePackage)((0, documents_js.buildOdsPackage)(content));
1547
+ case "odg": return (0, odf_js.encodePackage)((0, documents_js.buildOdgPackage)(content));
1548
+ case "markdown": return (0, documents_js.encodeMarkdownText)((0, documents_js.buildMarkdownText)(content));
1549
+ }
1550
+ }
1551
+ function mergeMetadata(current, overrides) {
1552
+ return {
1553
+ ...current,
1554
+ ...overrides.title !== void 0 ? { title: overrides.title } : {},
1555
+ ...overrides.author !== void 0 ? { author: overrides.author } : {},
1556
+ ...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
1557
+ ...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
1558
+ };
1559
+ }
1560
+ function parseKeywords(csv) {
1561
+ return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1562
+ }
1563
+ function classifyWritePath(source, target) {
1564
+ if (source === "pdf" && target === "pdf") return { kind: "pdf" };
1565
+ if (target === "xlsx" || source === "xlsx") return { errorMessage: "'xlsx' is not a supported set-metadata source or target -- documents.js does not re-export a ContentDocument-to-xlsx builder or a readXlsxContent from its own public surface (see that package's own README, Architecture section); convert with 'xlsx-to-ods'/'ods-to-xlsx' first, then set metadata on the ods" };
1566
+ if (target === "odf" || source === "odf") return { errorMessage: "'odf' (a standalone formula document) is not a supported set-metadata source or target -- it has no write path back out at all" };
1567
+ if (!isRebuildFormat(source) || !isRebuildFormat(target)) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format (or both 'pdf'). Run 'convert'/'from-package' first if you need a different target format.` };
1568
+ if (source !== target) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format. Run 'convert'/'from-package' first if you need a different target format.` };
1569
+ return {
1570
+ kind: "rebuild",
1571
+ format: source
1572
+ };
1573
+ }
1574
+ async function runSetMetadata(input, output, options) {
1575
+ const command = "set-metadata";
1576
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
1577
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
1578
+ return 2;
1579
+ }
1580
+ const target = resolveTargetFormat(output, options.out, options.to);
1581
+ if ("errorMessage" in target) {
1582
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
1583
+ return 2;
1584
+ }
1585
+ const source = inferFormatFromExtension(input);
1586
+ if (source === void 0) {
1587
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
1588
+ return 2;
1589
+ }
1590
+ const writePath = classifyWritePath(source, target.format);
1591
+ if ("errorMessage" in writePath) {
1592
+ process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
1593
+ return 2;
1594
+ }
1595
+ const overrides = {
1596
+ title: options.setTitle,
1597
+ author: options.setAuthor,
1598
+ subject: options.setSubject,
1599
+ keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
1600
+ };
1601
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
1602
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1603
+ try {
1604
+ const inputBytes = await readInput(input, { signal });
1605
+ const bytes = writePath.kind === "pdf" ? (() => {
1606
+ const layout = (0, documents_js.readPdf)(new Uint8Array(inputBytes), { signal });
1607
+ const patched = {
1608
+ ...layout,
1609
+ metadata: mergeMetadata(layout.metadata, overrides)
1610
+ };
1611
+ return (0, documents_js.writePdf)(patched, { signal });
1612
+ })() : (() => {
1613
+ const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
1614
+ const nextContent = {
1615
+ ...content,
1616
+ metadata: mergeMetadata(content.metadata, overrides)
1617
+ };
1618
+ return buildBytesForRebuildFormat(writePath.format, nextContent);
1619
+ })();
1620
+ await writeOutput(resolvedOutput, bytes);
1621
+ createDiagnosticReporter({
1622
+ json: options.json,
1623
+ quiet: options.quiet,
1624
+ command
1625
+ }).summarize({
1626
+ output: resolvedOutput,
1627
+ bytes: bytes.byteLength,
1628
+ diagnosticCount: 0
1629
+ });
1630
+ return 0;
1631
+ } catch (error) {
1632
+ process.stderr.write(`${formatError(error, options.verbose)}\n`);
1633
+ return mapErrorToExit(error, getAbortReason());
1634
+ }
1635
+ }
1636
+ function registerSetMetadataCommand(program) {
1637
+ const command = program.command("set-metadata <input> [output]").description("patch a document's own title/author/subject/keywords, leaving every other field and every other flag as-is").addHelpText("after", [
1638
+ "",
1639
+ "Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
1640
+ "involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
1641
+ "odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
1642
+ "this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
1643
+ "since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
1644
+ "",
1645
+ "set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
1646
+ "set-metadata on the result, if you need a different target format."
1647
+ ].join("\n"));
1648
+ addOutOption(command);
1649
+ addTimeoutOption(command);
1650
+ addJsonOption(command);
1651
+ addQuietOption(command);
1652
+ addVerboseOption(command);
1653
+ command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
1654
+ command.option("--set-title <text>", "set the title field");
1655
+ command.option("--set-author <text>", "set the author field");
1656
+ command.option("--set-subject <text>", "set the subject field");
1657
+ command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
1658
+ command.action(async (input, output, options) => {
1659
+ process.exitCode = await runSetMetadata(input, output, options);
1660
+ });
1661
+ }
1662
+ //#endregion
1310
1663
  //#region package.json
1311
- var version = "1.3.0";
1664
+ var version = "1.5.0";
1312
1665
  //#endregion
1313
1666
  //#region src/program.ts
1314
1667
  function createProgram() {
@@ -1325,6 +1678,10 @@ function createProgram() {
1325
1678
  registerOdmCommand(program);
1326
1679
  registerOdbCommands(program);
1327
1680
  registerPdfInspectCommand(program);
1681
+ registerFontsCommand(program);
1682
+ registerDocxExtrasCommand(program);
1683
+ registerMetadataCommand(program);
1684
+ registerSetMetadataCommand(program);
1328
1685
  return program;
1329
1686
  }
1330
1687
  //#endregion