document-cli 1.11.0 → 1.11.1

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 CHANGED
@@ -1,8 +1,9 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodeMarkdownText, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readOdfFormulaContent, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, writePdf, xlsxToPdf } from "documents.js";
2
+ import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, UnsupportedFontSourceFormatError, buildDocumentBytes, createLocalDocumentConverter, decodePackage, documentFromJson, documentPackageWithSchema, evaluateSelect, extractSourceFontsForFormat, hsqldbCellDisplayText, layoutDocumentWithSchema, odbReportToDocx, odbReportToOdt, odbReportToPdf, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocumentMetadata, readDocxExtras, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, setDocumentMetadata } from "documents.js";
3
+ import { readFontFace } from "pdf-codec";
3
4
  import { basename, dirname, extname, join } from "node:path";
4
5
  import { Command, InvalidArgumentError } from "commander";
5
- import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
6
+ import { decodePackage as decodePackage$1 } from "odf.js";
6
7
  import { existsSync, readFileSync } from "node:fs";
7
8
  //#region src/format.ts
8
9
  const EXTENSION_TO_FORMAT = {
@@ -163,166 +164,17 @@ function mapErrorToExit(error, abortReason) {
163
164
  if (abortReason === "timeout") return 124;
164
165
  if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError || error instanceof OdbReportNotSpecifiedError) return 3;
165
166
  if (error instanceof HsqldbSqlUnsupportedError || error instanceof HsqldbSqlParseError || error instanceof HsqldbSqlEvaluationError) return 1;
167
+ if (error instanceof UnsupportedFontSourceFormatError) return 2;
166
168
  if (error instanceof PdfEncryptedError || error instanceof PdfParseError) return 1;
167
169
  return 1;
168
170
  }
169
171
  //#endregion
170
- //#region src/runtime/font-face.ts
171
- var FontFaceError = class extends Error {
172
- constructor(message) {
173
- super(message);
174
- this.name = "FontFaceError";
175
- }
176
- };
177
- const SFNT_VERSION_TRUETYPE = 65536;
178
- const SFNT_VERSION_CFF = 1330926671;
179
- const SFNT_VERSION_APPLE_TRUE = 1953658213;
180
- const SFNT_VERSION_APPLE_TYP1 = 1954115633;
181
- const SFNT_VERSION_COLLECTION = 1953784678;
182
- const SFNT_VERSIONS = /* @__PURE__ */ new Set([
183
- SFNT_VERSION_TRUETYPE,
184
- SFNT_VERSION_CFF,
185
- SFNT_VERSION_APPLE_TRUE,
186
- SFNT_VERSION_APPLE_TYP1
187
- ]);
188
- const TABLE_DIRECTORY_HEADER_SIZE = 12;
189
- const TABLE_RECORD_SIZE = 16;
190
- const TABLE_TAG_SIZE = 4;
191
- const NAME_HEADER_SIZE = 6;
192
- const NAME_RECORD_SIZE = 12;
193
- const NAME_ID_FAMILY = 1;
194
- const NAME_ID_TYPOGRAPHIC_FAMILY = 16;
195
- const PLATFORM_UNICODE = 0;
196
- const PLATFORM_MACINTOSH = 1;
197
- const PLATFORM_WINDOWS = 3;
198
- const MACINTOSH_ENCODING_ROMAN = 0;
199
- const OS2_FS_SELECTION_OFFSET = 62;
200
- const OS2_MINIMUM_SIZE = 64;
201
- const OS2_FS_SELECTION_ITALIC = 1;
202
- const OS2_FS_SELECTION_BOLD = 32;
203
- const HEAD_MAC_STYLE_OFFSET = 44;
204
- const HEAD_MINIMUM_SIZE = 46;
205
- const HEAD_MAC_STYLE_BOLD = 1;
206
- const HEAD_MAC_STYLE_ITALIC = 2;
207
- function viewOf(bytes) {
208
- return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
209
- }
210
- function decodeTag(view, offset) {
211
- let tag = "";
212
- for (let i = 0; i < TABLE_TAG_SIZE; i++) tag += String.fromCharCode(view.getUint8(offset + i));
213
- return tag;
214
- }
215
- function decodeUtf16Be(view, offset, length) {
216
- let text = "";
217
- for (let i = 0; i + 1 < length; i += 2) text += String.fromCharCode(view.getUint16(offset + i));
218
- return text;
219
- }
220
- function decodeMacintoshAscii(view, offset, length) {
221
- let text = "";
222
- for (let i = 0; i < length; i++) {
223
- const byte = view.getUint8(offset + i);
224
- if (byte > 126) return;
225
- text += String.fromCharCode(byte);
226
- }
227
- return text;
228
- }
229
- function readTableDirectory(view, source) {
230
- if (view.byteLength < TABLE_DIRECTORY_HEADER_SIZE) throw new FontFaceError(`${source} is too short to be a font file (${String(view.byteLength)} bytes)`);
231
- const sfntVersion = view.getUint32(0);
232
- if (sfntVersion === SFNT_VERSION_COLLECTION) throw new FontFaceError(`${source} is a TrueType Collection (.ttc), which packs several faces into one file; extract the single face you want and pass that instead`);
233
- if (!SFNT_VERSIONS.has(sfntVersion)) throw new FontFaceError(`${source} is not a TrueType/OpenType font file (no recognised sfnt version); a .woff/.woff2 file must be converted to .ttf/.otf first`);
234
- const numTables = view.getUint16(4);
235
- if (view.byteLength < TABLE_DIRECTORY_HEADER_SIZE + numTables * TABLE_RECORD_SIZE) throw new FontFaceError(`${source} declares ${String(numTables)} tables but is too short to hold that many table records`);
236
- const tables = /* @__PURE__ */ new Map();
237
- for (let i = 0; i < numTables; i++) {
238
- const recordOffset = TABLE_DIRECTORY_HEADER_SIZE + i * TABLE_RECORD_SIZE;
239
- const tag = decodeTag(view, recordOffset);
240
- const offset = view.getUint32(recordOffset + 8);
241
- const length = view.getUint32(recordOffset + 12);
242
- if (offset + length > view.byteLength || tables.has(tag)) continue;
243
- tables.set(tag, {
244
- offset,
245
- length
246
- });
247
- }
248
- return tables;
249
- }
250
- function tableView(bytes, table) {
251
- return new DataView(bytes.buffer, bytes.byteOffset + table.offset, table.length);
252
- }
253
- function readNameRecords(name) {
254
- if (name.byteLength < NAME_HEADER_SIZE) return [];
255
- const count = name.getUint16(2);
256
- const storageOffset = name.getUint16(4);
257
- if (name.byteLength < NAME_HEADER_SIZE + count * NAME_RECORD_SIZE) return [];
258
- const records = [];
259
- for (let i = 0; i < count; i++) {
260
- const recordOffset = NAME_HEADER_SIZE + i * NAME_RECORD_SIZE;
261
- records.push({
262
- platformId: name.getUint16(recordOffset),
263
- encodingId: name.getUint16(recordOffset + 2),
264
- nameId: name.getUint16(recordOffset + 6),
265
- length: name.getUint16(recordOffset + 8),
266
- stringOffset: storageOffset + name.getUint16(recordOffset + 10)
267
- });
268
- }
269
- return records;
270
- }
271
- function platformPreference(record) {
272
- if (record.platformId === PLATFORM_WINDOWS) return 0;
273
- if (record.platformId === PLATFORM_UNICODE) return 1;
274
- if (record.platformId === PLATFORM_MACINTOSH && record.encodingId === MACINTOSH_ENCODING_ROMAN) return 2;
275
- return Number.POSITIVE_INFINITY;
276
- }
277
- function readNameString(name, records, nameId) {
278
- const candidates = records.filter((record) => record.nameId === nameId && Number.isFinite(platformPreference(record))).sort((left, right) => platformPreference(left) - platformPreference(right));
279
- for (const record of candidates) {
280
- if (record.stringOffset + record.length > name.byteLength) continue;
281
- const text = record.platformId === PLATFORM_MACINTOSH ? decodeMacintoshAscii(name, record.stringOffset, record.length) : decodeUtf16Be(name, record.stringOffset, record.length);
282
- if (text !== void 0 && text.length > 0) return text;
283
- }
284
- }
285
- function readStyleBits(bytes, tables, source) {
286
- const os2 = tables.get("OS/2");
287
- if (os2 !== void 0 && os2.length >= OS2_MINIMUM_SIZE) {
288
- const fsSelection = tableView(bytes, os2).getUint16(OS2_FS_SELECTION_OFFSET);
289
- return {
290
- bold: (fsSelection & OS2_FS_SELECTION_BOLD) !== 0,
291
- italic: (fsSelection & OS2_FS_SELECTION_ITALIC) !== 0
292
- };
293
- }
294
- const head = tables.get("head");
295
- if (head !== void 0 && head.length >= HEAD_MINIMUM_SIZE) {
296
- const macStyle = tableView(bytes, head).getUint16(HEAD_MAC_STYLE_OFFSET);
297
- return {
298
- bold: (macStyle & HEAD_MAC_STYLE_BOLD) !== 0,
299
- italic: (macStyle & HEAD_MAC_STYLE_ITALIC) !== 0
300
- };
301
- }
302
- throw new FontFaceError(`${source} declares neither a readable 'OS/2' nor a readable 'head' table, so its weight and slope cannot be determined`);
303
- }
304
- function describeFontFace(bytes, source) {
305
- const tables = readTableDirectory(viewOf(bytes), source);
306
- const nameTable = tables.get("name");
307
- if (nameTable === void 0) throw new FontFaceError(`${source} declares no 'name' table, so the font family it provides cannot be determined`);
308
- const name = tableView(bytes, nameTable);
309
- const records = readNameRecords(name);
310
- const family = readNameString(name, records, NAME_ID_TYPOGRAPHIC_FAMILY) ?? readNameString(name, records, NAME_ID_FAMILY);
311
- if (family === void 0) throw new FontFaceError(`${source} declares no family name in its 'name' table, so the font family it provides cannot be determined`);
312
- const { bold, italic } = readStyleBits(bytes, tables, source);
313
- return {
314
- family,
315
- bold,
316
- italic
317
- };
318
- }
319
- //#endregion
320
172
  //#region src/runtime/fonts.ts
321
173
  async function loadProvidedFonts(paths, options) {
322
174
  const fonts = [];
323
175
  for (const path of paths) {
324
176
  const bytes = new Uint8Array(await readFile(path, { signal: options?.signal }));
325
- const face = describeFontFace(bytes, path);
177
+ const face = readFontFace(bytes, path);
326
178
  fonts.push({
327
179
  family: face.family,
328
180
  bold: face.bold,
@@ -616,27 +468,6 @@ function registerDocxExtrasCommand(program) {
616
468
  }
617
469
  //#endregion
618
470
  //#region src/commands/fonts.ts
619
- const FONT_SOURCE_FORMATS = {
620
- docx: true,
621
- pptx: true,
622
- odt: true,
623
- odp: true,
624
- ods: true,
625
- odg: true
626
- };
627
- function isFontSourceFormat(format) {
628
- return format in FONT_SOURCE_FORMATS;
629
- }
630
- function resolveFontSourcePackage(format, bytes) {
631
- if (format === "docx" || format === "pptx") return {
632
- kind: format,
633
- package: decodePackage(bytes)
634
- };
635
- return {
636
- kind: "odf",
637
- package: decodePackage$1(bytes)
638
- };
639
- }
640
471
  async function runFonts(input, options) {
641
472
  const command = "fonts";
642
473
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -646,13 +477,8 @@ async function runFonts(input, options) {
646
477
  process.stderr.write(`[${command}] cannot infer a document format from '${input}'; expected one of docx, pptx, odt, odp, ods, odg\n`);
647
478
  return 2;
648
479
  }
649
- if (!isFontSourceFormat(format)) {
650
- 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`);
651
- return 2;
652
- }
653
480
  const inputBytes = await readInput(input, { signal });
654
- const source = resolveFontSourcePackage(format, new Uint8Array(inputBytes));
655
- const summaries = extractSourceFonts(source).map((face) => ({
481
+ const summaries = extractSourceFontsForFormat(format, new Uint8Array(inputBytes)).map((face) => ({
656
482
  family: face.family,
657
483
  bold: face.bold,
658
484
  italic: face.italic,
@@ -697,23 +523,6 @@ function registerFormatsCommand(program) {
697
523
  }
698
524
  //#endregion
699
525
  //#region src/commands/from-package.ts
700
- function buildBytesForTarget(pkg, target) {
701
- if (target === "pdf") {
702
- if (pkg.layout === void 0) throw new Error("this DocumentPackage has no layout -- only a package dumped from a <format>-to-pdf or pdf-to-<format> conversion carries one; a bridge conversion's own dump (e.g. odt-to-docx) never does, so 'pdf' is not a reachable target from it");
703
- return writePdf(pkg.layout);
704
- }
705
- switch (target) {
706
- case "docx": return encodePackage(buildDocxPackage(pkg.content));
707
- case "pptx": return encodePackage(buildPptxPackage(pkg.content));
708
- case "odt": return encodePackage$1(buildOdtPackage(pkg.content));
709
- case "odp": return encodePackage$1(buildOdpPackage(pkg.content));
710
- case "ods": return encodePackage$1(buildOdsPackage(pkg.content));
711
- case "odg": return encodePackage$1(buildOdgPackage(pkg.content));
712
- case "markdown": return encodeMarkdownText(buildMarkdownText(pkg.content));
713
- case "xlsx": throw new Error("'xlsx' cannot be built from a DocumentPackage directly -- documents.js does not re-export a ContentDocument-to-xlsx builder; convert to 'ods' here, then run 'ods-to-xlsx' on the result instead");
714
- case "odf": throw new Error("'odf' (a standalone formula document) cannot be built from a DocumentPackage -- there is no ContentDocument-to-odf builder");
715
- }
716
- }
717
526
  async function runFromPackage(input, output, options) {
718
527
  const command = "from-package";
719
528
  if (output !== void 0 && options.out !== void 0 && output !== options.out) {
@@ -742,7 +551,7 @@ async function runFromPackage(input, output, options) {
742
551
  process.stderr.write(`[${command}] '${input}' is a ${result.kind}, not a DocumentPackage -- only a file written by --dump-package can be read back by this command\n`);
743
552
  return 2;
744
553
  }
745
- const bytes = buildBytesForTarget(result.value, target.format);
554
+ const bytes = buildDocumentBytes(result.value, target.format);
746
555
  await writeOutput(resolvedOutput, bytes);
747
556
  createDiagnosticReporter({
748
557
  json: options.json,
@@ -801,20 +610,6 @@ function formatMetadataLines(metadata) {
801
610
  }
802
611
  //#endregion
803
612
  //#region src/commands/metadata.ts
804
- function readMetadataForFormat(format, bytes, signal) {
805
- switch (format) {
806
- case "docx": return readDocxContent(decodePackage(bytes)).metadata;
807
- case "pptx": return readPptxContent(decodePackage(bytes)).metadata;
808
- case "odt": return readOdtContent(decodePackage$1(bytes)).metadata;
809
- case "odp": return readOdpContent(decodePackage$1(bytes)).metadata;
810
- case "ods": return readOdsContent(decodePackage$1(bytes)).metadata;
811
- case "odg": return readOdgContent(decodePackage$1(bytes)).metadata;
812
- case "odf": return readOdfFormulaContent(decodePackage$1(bytes)).metadata;
813
- case "markdown": return readMarkdownContent(decodeMarkdownText(bytes)).metadata;
814
- case "pdf": return readPdf(bytes, { signal }).metadata;
815
- case "xlsx": return readPdf(xlsxToPdf(bytes, { signal }), { signal }).metadata;
816
- }
817
- }
818
613
  async function runMetadata(input, options) {
819
614
  const command = "metadata";
820
615
  const source = inferFormatFromExtension(input);
@@ -825,7 +620,7 @@ async function runMetadata(input, options) {
825
620
  const { signal, getAbortReason } = createRuntimeSignal({});
826
621
  try {
827
622
  const inputBytes = await readInput(input, { signal });
828
- const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
623
+ const metadata = readDocumentMetadata(source, new Uint8Array(inputBytes), { signal });
829
624
  if (options.json) {
830
625
  process.stdout.write(`${JSON.stringify(metadata)}\n`);
831
626
  return 0;
@@ -1205,32 +1000,6 @@ const ODB_REPORT_TARGET_FORMATS = {
1205
1000
  function isOdbReportTargetFormat(format) {
1206
1001
  return format in ODB_REPORT_TARGET_FORMATS;
1207
1002
  }
1208
- function renderOdbReportBytes(content, target, options) {
1209
- if (target === "docx") return encodePackage(buildDocxPackage(content));
1210
- if (target === "odt") return encodePackage$1(buildOdtPackage(content));
1211
- if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
1212
- const fonts = createFontRegistry({
1213
- fonts: options.fonts,
1214
- onSubstitution: (substitution) => {
1215
- if (options.reportFontSubstitution !== void 0) {
1216
- options.reportFontSubstitution(substitution);
1217
- return;
1218
- }
1219
- options.onDiagnosticCounted();
1220
- options.reporter.report(fontSubstitutionToDiagnostic(substitution));
1221
- }
1222
- });
1223
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
1224
- return writePdf(layout, {
1225
- signal: options.signal,
1226
- onSubstitution: (substitution, context) => {
1227
- options.onDiagnosticCounted();
1228
- options.reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
1229
- },
1230
- formulas,
1231
- fonts
1232
- });
1233
- }
1234
1003
  async function runOdbRenderReport(input, output, options) {
1235
1004
  const command = "odb-render-report";
1236
1005
  if (output !== void 0 && options.out !== void 0 && output !== options.out) {
@@ -1264,13 +1033,21 @@ async function runOdbRenderReport(input, output, options) {
1264
1033
  const inputBytes = await readInput(input, { signal });
1265
1034
  const fonts = await loadProvidedFonts(options.fontFile ?? [], { signal });
1266
1035
  const pkg = decodePackage$1(new Uint8Array(inputBytes));
1267
- const bytes = renderOdbReportBytes(readOdbReportContent(pkg, { report: options.report }), targetFormat, {
1268
- fonts,
1036
+ const content = readOdbReportContent(pkg, { report: options.report });
1037
+ const bytes = targetFormat === "docx" ? odbReportToDocx(content) : targetFormat === "odt" ? odbReportToOdt(content) : odbReportToPdf(content, {
1269
1038
  signal,
1270
- reporter,
1271
- reportFontSubstitution,
1272
- onDiagnosticCounted: () => {
1039
+ fonts,
1040
+ onFontSubstitution: (substitution) => {
1041
+ if (reportFontSubstitution !== void 0) {
1042
+ reportFontSubstitution(substitution);
1043
+ return;
1044
+ }
1045
+ diagnosticCount += 1;
1046
+ reporter.report(fontSubstitutionToDiagnostic(substitution));
1047
+ },
1048
+ onSubstitution: (substitution, context) => {
1273
1049
  diagnosticCount += 1;
1050
+ reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
1274
1051
  }
1275
1052
  });
1276
1053
  await writeOutput(resolvedOutput, bytes);
@@ -1513,63 +1290,9 @@ function registerPdfInspectCommand(program) {
1513
1290
  }
1514
1291
  //#endregion
1515
1292
  //#region src/commands/set-metadata.ts
1516
- const REBUILD_FORMATS = {
1517
- docx: true,
1518
- pptx: true,
1519
- odt: true,
1520
- odp: true,
1521
- ods: true,
1522
- odg: true,
1523
- markdown: true
1524
- };
1525
- function isRebuildFormat(format) {
1526
- return format in REBUILD_FORMATS;
1527
- }
1528
- function readContentForFormat(format, bytes) {
1529
- switch (format) {
1530
- case "docx": return readDocxContent(decodePackage(bytes));
1531
- case "pptx": return readPptxContent(decodePackage(bytes));
1532
- case "odt": return readOdtContent(decodePackage$1(bytes));
1533
- case "odp": return readOdpContent(decodePackage$1(bytes));
1534
- case "ods": return readOdsContent(decodePackage$1(bytes));
1535
- case "odg": return readOdgContent(decodePackage$1(bytes));
1536
- case "markdown": return readMarkdownContent(decodeMarkdownText(bytes));
1537
- }
1538
- }
1539
- function buildBytesForRebuildFormat(format, content) {
1540
- switch (format) {
1541
- case "docx": return encodePackage(buildDocxPackage(content));
1542
- case "pptx": return encodePackage(buildPptxPackage(content));
1543
- case "odt": return encodePackage$1(buildOdtPackage(content));
1544
- case "odp": return encodePackage$1(buildOdpPackage(content));
1545
- case "ods": return encodePackage$1(buildOdsPackage(content));
1546
- case "odg": return encodePackage$1(buildOdgPackage(content));
1547
- case "markdown": return encodeMarkdownText(buildMarkdownText(content));
1548
- }
1549
- }
1550
- function mergeMetadata(current, overrides) {
1551
- return {
1552
- ...current,
1553
- ...overrides.title !== void 0 ? { title: overrides.title } : {},
1554
- ...overrides.author !== void 0 ? { author: overrides.author } : {},
1555
- ...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
1556
- ...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
1557
- };
1558
- }
1559
1293
  function parseKeywords(csv) {
1560
1294
  return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1561
1295
  }
1562
- function classifyWritePath(source, target) {
1563
- if (source === "pdf" && target === "pdf") return { kind: "pdf" };
1564
- 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" };
1565
- 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" };
1566
- 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.` };
1567
- 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.` };
1568
- return {
1569
- kind: "rebuild",
1570
- format: source
1571
- };
1572
- }
1573
1296
  async function runSetMetadata(input, output, options) {
1574
1297
  const command = "set-metadata";
1575
1298
  if (output !== void 0 && options.out !== void 0 && output !== options.out) {
@@ -1586,11 +1309,6 @@ async function runSetMetadata(input, output, options) {
1586
1309
  process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
1587
1310
  return 2;
1588
1311
  }
1589
- const writePath = classifyWritePath(source, target.format);
1590
- if ("errorMessage" in writePath) {
1591
- process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
1592
- return 2;
1593
- }
1594
1312
  const overrides = {
1595
1313
  title: options.setTitle,
1596
1314
  author: options.setAuthor,
@@ -1601,21 +1319,7 @@ async function runSetMetadata(input, output, options) {
1601
1319
  const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1602
1320
  try {
1603
1321
  const inputBytes = await readInput(input, { signal });
1604
- const bytes = writePath.kind === "pdf" ? (() => {
1605
- const layout = readPdf(new Uint8Array(inputBytes), { signal });
1606
- const patched = {
1607
- ...layout,
1608
- metadata: mergeMetadata(layout.metadata, overrides)
1609
- };
1610
- return writePdf(patched, { signal });
1611
- })() : (() => {
1612
- const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
1613
- const nextContent = {
1614
- ...content,
1615
- metadata: mergeMetadata(content.metadata, overrides)
1616
- };
1617
- return buildBytesForRebuildFormat(writePath.format, nextContent);
1618
- })();
1322
+ const bytes = setDocumentMetadata(source, target.format, new Uint8Array(inputBytes), overrides, { signal });
1619
1323
  await writeOutput(resolvedOutput, bytes);
1620
1324
  createDiagnosticReporter({
1621
1325
  json: options.json,
@@ -1660,7 +1364,7 @@ function registerSetMetadataCommand(program) {
1660
1364
  }
1661
1365
  //#endregion
1662
1366
  //#region package.json
1663
- var version = "1.11.0";
1367
+ var version = "1.11.1";
1664
1368
  //#endregion
1665
1369
  //#region src/program.ts
1666
1370
  function createProgram() {
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import "documents.js";
3
+ import { readFontFace } from "pdf-codec";
3
4
  import { basename, dirname, extname, join } from "node:path";
4
5
  //#region src/format.ts
5
6
  const EXTENSION_TO_FORMAT = {
@@ -42,162 +43,12 @@ function formatToExtension(format) {
42
43
  return FORMAT_TO_EXTENSION[format];
43
44
  }
44
45
  //#endregion
45
- //#region src/runtime/font-face.ts
46
- var FontFaceError = class extends Error {
47
- constructor(message) {
48
- super(message);
49
- this.name = "FontFaceError";
50
- }
51
- };
52
- const SFNT_VERSION_TRUETYPE = 65536;
53
- const SFNT_VERSION_CFF = 1330926671;
54
- const SFNT_VERSION_APPLE_TRUE = 1953658213;
55
- const SFNT_VERSION_APPLE_TYP1 = 1954115633;
56
- const SFNT_VERSION_COLLECTION = 1953784678;
57
- const SFNT_VERSIONS = /* @__PURE__ */ new Set([
58
- SFNT_VERSION_TRUETYPE,
59
- SFNT_VERSION_CFF,
60
- SFNT_VERSION_APPLE_TRUE,
61
- SFNT_VERSION_APPLE_TYP1
62
- ]);
63
- const TABLE_DIRECTORY_HEADER_SIZE = 12;
64
- const TABLE_RECORD_SIZE = 16;
65
- const TABLE_TAG_SIZE = 4;
66
- const NAME_HEADER_SIZE = 6;
67
- const NAME_RECORD_SIZE = 12;
68
- const NAME_ID_FAMILY = 1;
69
- const NAME_ID_TYPOGRAPHIC_FAMILY = 16;
70
- const PLATFORM_UNICODE = 0;
71
- const PLATFORM_MACINTOSH = 1;
72
- const PLATFORM_WINDOWS = 3;
73
- const MACINTOSH_ENCODING_ROMAN = 0;
74
- const OS2_FS_SELECTION_OFFSET = 62;
75
- const OS2_MINIMUM_SIZE = 64;
76
- const OS2_FS_SELECTION_ITALIC = 1;
77
- const OS2_FS_SELECTION_BOLD = 32;
78
- const HEAD_MAC_STYLE_OFFSET = 44;
79
- const HEAD_MINIMUM_SIZE = 46;
80
- const HEAD_MAC_STYLE_BOLD = 1;
81
- const HEAD_MAC_STYLE_ITALIC = 2;
82
- function viewOf(bytes) {
83
- return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
84
- }
85
- function decodeTag(view, offset) {
86
- let tag = "";
87
- for (let i = 0; i < TABLE_TAG_SIZE; i++) tag += String.fromCharCode(view.getUint8(offset + i));
88
- return tag;
89
- }
90
- function decodeUtf16Be(view, offset, length) {
91
- let text = "";
92
- for (let i = 0; i + 1 < length; i += 2) text += String.fromCharCode(view.getUint16(offset + i));
93
- return text;
94
- }
95
- function decodeMacintoshAscii(view, offset, length) {
96
- let text = "";
97
- for (let i = 0; i < length; i++) {
98
- const byte = view.getUint8(offset + i);
99
- if (byte > 126) return;
100
- text += String.fromCharCode(byte);
101
- }
102
- return text;
103
- }
104
- function readTableDirectory(view, source) {
105
- if (view.byteLength < TABLE_DIRECTORY_HEADER_SIZE) throw new FontFaceError(`${source} is too short to be a font file (${String(view.byteLength)} bytes)`);
106
- const sfntVersion = view.getUint32(0);
107
- if (sfntVersion === SFNT_VERSION_COLLECTION) throw new FontFaceError(`${source} is a TrueType Collection (.ttc), which packs several faces into one file; extract the single face you want and pass that instead`);
108
- if (!SFNT_VERSIONS.has(sfntVersion)) throw new FontFaceError(`${source} is not a TrueType/OpenType font file (no recognised sfnt version); a .woff/.woff2 file must be converted to .ttf/.otf first`);
109
- const numTables = view.getUint16(4);
110
- if (view.byteLength < TABLE_DIRECTORY_HEADER_SIZE + numTables * TABLE_RECORD_SIZE) throw new FontFaceError(`${source} declares ${String(numTables)} tables but is too short to hold that many table records`);
111
- const tables = /* @__PURE__ */ new Map();
112
- for (let i = 0; i < numTables; i++) {
113
- const recordOffset = TABLE_DIRECTORY_HEADER_SIZE + i * TABLE_RECORD_SIZE;
114
- const tag = decodeTag(view, recordOffset);
115
- const offset = view.getUint32(recordOffset + 8);
116
- const length = view.getUint32(recordOffset + 12);
117
- if (offset + length > view.byteLength || tables.has(tag)) continue;
118
- tables.set(tag, {
119
- offset,
120
- length
121
- });
122
- }
123
- return tables;
124
- }
125
- function tableView(bytes, table) {
126
- return new DataView(bytes.buffer, bytes.byteOffset + table.offset, table.length);
127
- }
128
- function readNameRecords(name) {
129
- if (name.byteLength < NAME_HEADER_SIZE) return [];
130
- const count = name.getUint16(2);
131
- const storageOffset = name.getUint16(4);
132
- if (name.byteLength < NAME_HEADER_SIZE + count * NAME_RECORD_SIZE) return [];
133
- const records = [];
134
- for (let i = 0; i < count; i++) {
135
- const recordOffset = NAME_HEADER_SIZE + i * NAME_RECORD_SIZE;
136
- records.push({
137
- platformId: name.getUint16(recordOffset),
138
- encodingId: name.getUint16(recordOffset + 2),
139
- nameId: name.getUint16(recordOffset + 6),
140
- length: name.getUint16(recordOffset + 8),
141
- stringOffset: storageOffset + name.getUint16(recordOffset + 10)
142
- });
143
- }
144
- return records;
145
- }
146
- function platformPreference(record) {
147
- if (record.platformId === PLATFORM_WINDOWS) return 0;
148
- if (record.platformId === PLATFORM_UNICODE) return 1;
149
- if (record.platformId === PLATFORM_MACINTOSH && record.encodingId === MACINTOSH_ENCODING_ROMAN) return 2;
150
- return Number.POSITIVE_INFINITY;
151
- }
152
- function readNameString(name, records, nameId) {
153
- const candidates = records.filter((record) => record.nameId === nameId && Number.isFinite(platformPreference(record))).sort((left, right) => platformPreference(left) - platformPreference(right));
154
- for (const record of candidates) {
155
- if (record.stringOffset + record.length > name.byteLength) continue;
156
- const text = record.platformId === PLATFORM_MACINTOSH ? decodeMacintoshAscii(name, record.stringOffset, record.length) : decodeUtf16Be(name, record.stringOffset, record.length);
157
- if (text !== void 0 && text.length > 0) return text;
158
- }
159
- }
160
- function readStyleBits(bytes, tables, source) {
161
- const os2 = tables.get("OS/2");
162
- if (os2 !== void 0 && os2.length >= OS2_MINIMUM_SIZE) {
163
- const fsSelection = tableView(bytes, os2).getUint16(OS2_FS_SELECTION_OFFSET);
164
- return {
165
- bold: (fsSelection & OS2_FS_SELECTION_BOLD) !== 0,
166
- italic: (fsSelection & OS2_FS_SELECTION_ITALIC) !== 0
167
- };
168
- }
169
- const head = tables.get("head");
170
- if (head !== void 0 && head.length >= HEAD_MINIMUM_SIZE) {
171
- const macStyle = tableView(bytes, head).getUint16(HEAD_MAC_STYLE_OFFSET);
172
- return {
173
- bold: (macStyle & HEAD_MAC_STYLE_BOLD) !== 0,
174
- italic: (macStyle & HEAD_MAC_STYLE_ITALIC) !== 0
175
- };
176
- }
177
- throw new FontFaceError(`${source} declares neither a readable 'OS/2' nor a readable 'head' table, so its weight and slope cannot be determined`);
178
- }
179
- function describeFontFace(bytes, source) {
180
- const tables = readTableDirectory(viewOf(bytes), source);
181
- const nameTable = tables.get("name");
182
- if (nameTable === void 0) throw new FontFaceError(`${source} declares no 'name' table, so the font family it provides cannot be determined`);
183
- const name = tableView(bytes, nameTable);
184
- const records = readNameRecords(name);
185
- const family = readNameString(name, records, NAME_ID_TYPOGRAPHIC_FAMILY) ?? readNameString(name, records, NAME_ID_FAMILY);
186
- if (family === void 0) throw new FontFaceError(`${source} declares no family name in its 'name' table, so the font family it provides cannot be determined`);
187
- const { bold, italic } = readStyleBits(bytes, tables, source);
188
- return {
189
- family,
190
- bold,
191
- italic
192
- };
193
- }
194
- //#endregion
195
46
  //#region src/runtime/fonts.ts
196
47
  async function loadProvidedFonts(paths, options) {
197
48
  const fonts = [];
198
49
  for (const path of paths) {
199
50
  const bytes = new Uint8Array(await readFile(path, { signal: options?.signal }));
200
- const face = describeFontFace(bytes, path);
51
+ const face = readFontFace(bytes, path);
201
52
  fonts.push({
202
53
  family: face.family,
203
54
  bold: face.bold,
@@ -1,8 +1,8 @@
1
- import { c as formatDocxExtrasLines, f as loadProvidedFonts, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, p as formatToExtension, r as formatOdbFormLines, t as describeOdbForm } from "./odb-structure-Du1w_hOU.js";
1
+ import { c as formatDocxExtrasLines, f as loadProvidedFonts, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, p as formatToExtension, r as formatOdbFormLines, t as describeOdbForm } from "./odb-structure-CS2Eybob.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
- import { buildDocxPackage, buildOdtPackage, bytesToBase64, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPdf, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openMarkdown, openOdg, openOdp, openOds, openOdt, openPdf, openPptx, parseXml, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
3
+ import { bytesToBase64, createDocx, createOdg, createOdp, createOds, createOdt, createPdf, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, hsqldbCellDisplayText, markdownToPdf, odbReportToDocx, odbReportToOdt, odbReportToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openMarkdown, openOdg, openOdp, openOds, openOdt, openPdf, openPptx, parseXml, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, xlsxToPdf } from "documents.js";
4
4
  import { basename, dirname, extname, join } from "node:path";
5
- import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
5
+ import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1 } from "odf.js";
6
6
  import { readdirSync } from "node:fs";
7
7
  import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
8
8
  import { createContext, useContext, useEffect, useReducer, useState } from "react";
@@ -3808,40 +3808,13 @@ const REPORT_RENDER_TARGET_FORMATS = {
3808
3808
  function isReportRenderTargetFormat(format) {
3809
3809
  return format !== void 0 && format in REPORT_RENDER_TARGET_FORMATS;
3810
3810
  }
3811
- function renderReportBytes(content, format, fonts, options) {
3812
- if (format === "docx") return encodePackage(buildDocxPackage(content));
3813
- if (format === "odt") return encodePackage$1(buildOdtPackage(content));
3814
- if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
3815
- const registry = createFontRegistry({
3816
- fonts,
3817
- onSubstitution: (substitution) => {
3818
- const requested = `${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`;
3819
- options.onDiagnostic({
3820
- severity: "info",
3821
- message: `No "${requested}" face available; drew it in "${substitution.resolvedFamily}"`
3822
- });
3823
- }
3824
- });
3825
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(registry) });
3826
- return writePdf(layout, {
3827
- signal: options.signal,
3828
- onSubstitution: (substitution, context) => {
3829
- options.onDiagnostic({
3830
- severity: "info",
3831
- message: `Substituted "${substitution.to}" for "${substitution.from}"`,
3832
- pageIndex: context.pageIndex
3833
- });
3834
- },
3835
- formulas,
3836
- fonts: registry
3837
- });
3838
- }
3839
3811
  async function renderOdbReportTo(doc, destinationPath, options) {
3840
3812
  const format = detectFormat(destinationPath);
3841
3813
  if (!isReportRenderTargetFormat(format)) throw new Error(`Cannot tell whether to render "${options.reportName}" as docx, odt, or pdf from '${destinationPath}' -- give the destination one of those three extensions`);
3842
3814
  const fonts = await loadProvidedFonts(options.fontFiles ?? [], { signal: options.signal });
3843
3815
  const pkg = decodePackage$1(new Uint8Array(await readFile(doc.path)));
3844
- const bytes = renderReportBytes(readOdbReportContent(pkg, { report: options.reportName }), format, fonts, options);
3816
+ const content = readOdbReportContent(pkg, { report: options.reportName });
3817
+ const bytes = format === "docx" ? odbReportToDocx(content) : format === "odt" ? odbReportToOdt(content) : odbReportToPdf(content, toPdfOptions(options, fonts));
3845
3818
  await writeFile(destinationPath, bytes);
3846
3819
  }
3847
3820
  //#endregion