js.documents 1.62.0 → 1.63.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.
@@ -7,6 +7,7 @@ import { buildOdtPackage } from "../edit/odt/content.js";
7
7
  import { buildOdpPackage } from "../edit/odp/content.js";
8
8
  import { buildOdsPackage } from "../edit/ods/content.js";
9
9
  import { buildOdgPackage } from "../edit/odg/content.js";
10
+ import { createDocumentFontRegistry, extractSourceFonts } from "../fonts/registry.js";
10
11
  import { layoutFormula } from "../mathml/layout.js";
11
12
  import { readDocxContent } from "../ooxml/docx/read.js";
12
13
  import { readPptxContent } from "../ooxml/pptx/read.js";
@@ -30,13 +31,17 @@ import { odbTablesToSpreadsheetDocument } from "../odb/spreadsheet.js";
30
31
  import { buildXlsxPackage, decodePackage, encodePackage, readXlsxContent } from "ooxml.js";
31
32
  import { COLOR_BLACK, CONTENT_FORMAT_VERSION, DOCUMENT_PACKAGE_FORMAT_VERSION, LAYOUT_FORMAT_VERSION } from "document-schema.js";
32
33
  import { decodePackage as decodePackage$1, encodePackage as encodePackage$1, readOdfMetadata, readOdfParagraph, readOdfTable, readOdm } from "odf.js";
33
- import { createStandardFontMeasurer, loadMathFont, readPdf, writePdf } from "pdf-codec";
34
+ import { createFontMeasurer, createFontRegistry, loadMathFont, readPdf, writePdf } from "pdf-codec";
34
35
  //#region src/convert/convert.ts
35
36
  function docxToPdf(bytes, options) {
36
37
  const pkg = openDocx(bytes).toPackage();
37
38
  const content = readDocxContent(pkg);
38
39
  if (content.kind !== "wordprocessing") throw new Error("readDocxContent returned a non-wordprocessing ContentDocument");
39
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createStandardFontMeasurer() });
40
+ const fonts = createDocumentFontRegistry({
41
+ kind: "docx",
42
+ package: pkg
43
+ }, options);
44
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
40
45
  options?.onDocument?.({
41
46
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
42
47
  content,
@@ -45,14 +50,19 @@ function docxToPdf(bytes, options) {
45
50
  return writePdf(layout, {
46
51
  signal: options?.signal,
47
52
  onSubstitution: options?.onSubstitution,
48
- formulas
53
+ formulas,
54
+ fonts
49
55
  });
50
56
  }
51
57
  function odtToPdf(bytes, options) {
52
58
  const pkg = decodePackage$1(bytes);
53
59
  const content = readOdtContent(pkg);
54
60
  if (content.kind !== "wordprocessing") throw new Error("readOdtContent returned a non-wordprocessing ContentDocument");
55
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createStandardFontMeasurer() });
61
+ const fonts = createDocumentFontRegistry({
62
+ kind: "odf",
63
+ package: pkg
64
+ }, options);
65
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
56
66
  options?.onDocument?.({
57
67
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
58
68
  content,
@@ -61,14 +71,19 @@ function odtToPdf(bytes, options) {
61
71
  return writePdf(layout, {
62
72
  signal: options?.signal,
63
73
  onSubstitution: options?.onSubstitution,
64
- formulas
74
+ formulas,
75
+ fonts
65
76
  });
66
77
  }
67
78
  function pptxToPdf(bytes, options) {
68
79
  const pkg = openPptx(bytes).toPackage();
69
80
  const content = readPptxContent(pkg);
70
81
  if (content.kind !== "presentation") throw new Error("readPptxContent returned a non-presentation ContentDocument");
71
- const { document: layout, formulas } = convertPresentationToLayout(content, { measurer: createStandardFontMeasurer() });
82
+ const fonts = createDocumentFontRegistry({
83
+ kind: "pptx",
84
+ package: pkg
85
+ }, options);
86
+ const { document: layout, formulas } = convertPresentationToLayout(content, { measurer: createFontMeasurer(fonts) });
72
87
  options?.onDocument?.({
73
88
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
74
89
  content,
@@ -77,14 +92,19 @@ function pptxToPdf(bytes, options) {
77
92
  return writePdf(layout, {
78
93
  signal: options?.signal,
79
94
  onSubstitution: options?.onSubstitution,
80
- formulas
95
+ formulas,
96
+ fonts
81
97
  });
82
98
  }
83
99
  function odpToPdf(bytes, options) {
84
100
  const pkg = decodePackage$1(bytes);
85
101
  const content = readOdpContent(pkg);
86
102
  if (content.kind !== "presentation") throw new Error("readOdpContent returned a non-presentation ContentDocument");
87
- const { document: layout, formulas } = convertPresentationToLayout(content, { measurer: createStandardFontMeasurer() });
103
+ const fonts = createDocumentFontRegistry({
104
+ kind: "odf",
105
+ package: pkg
106
+ }, options);
107
+ const { document: layout, formulas } = convertPresentationToLayout(content, { measurer: createFontMeasurer(fonts) });
88
108
  options?.onDocument?.({
89
109
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
90
110
  content,
@@ -93,15 +113,20 @@ function odpToPdf(bytes, options) {
93
113
  return writePdf(layout, {
94
114
  signal: options?.signal,
95
115
  onSubstitution: options?.onSubstitution,
96
- formulas
116
+ formulas,
117
+ fonts
97
118
  });
98
119
  }
99
120
  function odsToPdf(bytes, options) {
100
121
  const pkg = decodePackage$1(bytes);
101
122
  const content = readOdsContent(pkg);
102
123
  if (content.kind !== "spreadsheet") throw new Error("readOdsContent returned a non-spreadsheet ContentDocument");
124
+ const fonts = createDocumentFontRegistry({
125
+ kind: "odf",
126
+ package: pkg
127
+ }, options);
103
128
  const layout = convertSpreadsheetToLayout(content, {
104
- measurer: createStandardFontMeasurer(),
129
+ measurer: createFontMeasurer(fonts),
105
130
  signal: options?.signal
106
131
  });
107
132
  options?.onDocument?.({
@@ -111,14 +136,19 @@ function odsToPdf(bytes, options) {
111
136
  });
112
137
  return writePdf(layout, {
113
138
  signal: options?.signal,
114
- onSubstitution: options?.onSubstitution
139
+ onSubstitution: options?.onSubstitution,
140
+ fonts
115
141
  });
116
142
  }
117
143
  function odgToPdf(bytes, options) {
118
144
  const pkg = decodePackage$1(bytes);
119
145
  const content = readOdgContent(pkg);
120
146
  if (content.kind !== "drawing") throw new Error("readOdgContent returned a non-drawing ContentDocument");
121
- const layout = convertDrawingToLayout(content, { measurer: createStandardFontMeasurer() });
147
+ const fonts = createDocumentFontRegistry({
148
+ kind: "odf",
149
+ package: pkg
150
+ }, options);
151
+ const layout = convertDrawingToLayout(content, { measurer: createFontMeasurer(fonts) });
122
152
  options?.onDocument?.({
123
153
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
124
154
  content,
@@ -126,7 +156,8 @@ function odgToPdf(bytes, options) {
126
156
  });
127
157
  return writePdf(layout, {
128
158
  signal: options?.signal,
129
- onSubstitution: options?.onSubstitution
159
+ onSubstitution: options?.onSubstitution,
160
+ fonts
130
161
  });
131
162
  }
132
163
  function markdownToPdf(bytes, options) {
@@ -134,7 +165,11 @@ function markdownToPdf(bytes, options) {
134
165
  const text = decodeMarkdownText(bytes);
135
166
  const content = readMarkdownContent(text, { signal: options?.signal });
136
167
  if (content.kind !== "wordprocessing") throw new Error("readMarkdownContent returned a non-wordprocessing ContentDocument");
137
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createStandardFontMeasurer() });
168
+ const fonts = createFontRegistry({
169
+ fonts: options?.fonts,
170
+ onSubstitution: options?.onFontSubstitution
171
+ });
172
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
138
173
  options?.onDocument?.({
139
174
  formatVersion: DOCUMENT_PACKAGE_FORMAT_VERSION,
140
175
  content,
@@ -143,7 +178,8 @@ function markdownToPdf(bytes, options) {
143
178
  return writePdf(layout, {
144
179
  signal: options?.signal,
145
180
  onSubstitution: options?.onSubstitution,
146
- formulas
181
+ formulas,
182
+ fonts
147
183
  });
148
184
  }
149
185
  const STANDALONE_FORMULA_SIZE_PT = 18;
@@ -412,7 +448,9 @@ function xlsxToPdf(bytes, options) {
412
448
  return odsToPdf(odsBytes, {
413
449
  signal: options?.signal,
414
450
  onSubstitution: options?.onSubstitution,
415
- onDocument: options?.onDocument
451
+ onDocument: options?.onDocument,
452
+ fonts: options?.fonts,
453
+ onFontSubstitution: options?.onFontSubstitution
416
454
  });
417
455
  }
418
456
  function pdfToXlsx(bytes, options) {
@@ -468,6 +506,10 @@ function odmToPdf(bytes, options) {
468
506
  const odm = readOdm(pkg);
469
507
  const unresolvedHrefs = [];
470
508
  const chapterSections = [];
509
+ const sourceFonts = extractSourceFonts({
510
+ kind: "odf",
511
+ package: pkg
512
+ });
471
513
  for (const section of odm.sections) {
472
514
  throwIfAborted(options?.signal);
473
515
  if (section.inlineContent !== void 0) {
@@ -480,6 +522,10 @@ function odmToPdf(bytes, options) {
480
522
  continue;
481
523
  }
482
524
  const chapterPkg = decodePackage$1(chapterBytes);
525
+ sourceFonts.push(...extractSourceFonts({
526
+ kind: "odf",
527
+ package: chapterPkg
528
+ }));
483
529
  const chapterContent = readOdtContent(chapterPkg);
484
530
  if (chapterContent.kind !== "wordprocessing") throw new Error("readOdtContent returned a non-wordprocessing ContentDocument");
485
531
  chapterSections.push(chapterContent.sections);
@@ -500,11 +546,17 @@ function odmToPdf(bytes, options) {
500
546
  metadata: readOdfMetadata(pkg),
501
547
  sections: combinedSections
502
548
  };
503
- const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createStandardFontMeasurer() });
549
+ const fonts = createFontRegistry({
550
+ sourceFonts,
551
+ fonts: options?.fonts,
552
+ onSubstitution: options?.onFontSubstitution
553
+ });
554
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
504
555
  return writePdf(layout, {
505
556
  signal: options?.signal,
506
557
  onSubstitution: options?.onSubstitution,
507
- formulas
558
+ formulas,
559
+ fonts
508
560
  });
509
561
  }
510
562
  function odbToXlsx(bytes, options) {
@@ -13,6 +13,13 @@ function substitutionDiagnostic(substitution, context) {
13
13
  pageIndex: context.pageIndex
14
14
  };
15
15
  }
16
+ function fontSubstitutionDiagnostic(substitution) {
17
+ return {
18
+ severity: "info",
19
+ code: "font/substituted",
20
+ message: `"${`${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`}" is not available; ${substitution.reason === "vendored-substitute" ? `substituted the metric-compatible "${substitution.resolvedFamily}"` : `substituted another face of "${substitution.resolvedFamily}"`}`
21
+ };
22
+ }
16
23
  function fromPdfDiagnostic(diagnostic) {
17
24
  return {
18
25
  severity: diagnostic.severity,
@@ -23,7 +30,7 @@ function fromPdfDiagnostic(diagnostic) {
23
30
  }
24
31
  function createLocalDocumentConverter() {
25
32
  return {
26
- contractVersion: 2,
33
+ contractVersion: 3,
27
34
  conversions: SUPPORTED_CONVERSIONS,
28
35
  convert(request, options) {
29
36
  const { source, targetFormat } = request;
@@ -32,6 +39,10 @@ function createLocalDocumentConverter() {
32
39
  const onDocument = (pkg) => {
33
40
  documentPackage = pkg;
34
41
  };
42
+ const onFontSubstitution = (substitution) => {
43
+ diagnostics.push(fontSubstitutionDiagnostic(substitution));
44
+ options.onFontSubstitution?.(substitution);
45
+ };
35
46
  const strategy = require_convert_capability.resolveConversionPath(source.format, targetFormat);
36
47
  if (strategy?.kind !== "direct") return Promise.reject(/* @__PURE__ */ new Error(`unsupported conversion: ${source.format} -> ${targetFormat}`));
37
48
  const edge = strategy.edge;
@@ -39,7 +50,9 @@ function createLocalDocumentConverter() {
39
50
  const bytes = edge.convert(source.bytes, {
40
51
  signal: options.signal,
41
52
  onSubstitution: (s, c) => diagnostics.push(substitutionDiagnostic(s, c)),
42
- onDocument
53
+ onDocument,
54
+ fonts: options.fonts,
55
+ onFontSubstitution
43
56
  });
44
57
  return Promise.resolve({
45
58
  document: {
@@ -12,6 +12,13 @@ function substitutionDiagnostic(substitution, context) {
12
12
  pageIndex: context.pageIndex
13
13
  };
14
14
  }
15
+ function fontSubstitutionDiagnostic(substitution) {
16
+ return {
17
+ severity: "info",
18
+ code: "font/substituted",
19
+ message: `"${`${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`}" is not available; ${substitution.reason === "vendored-substitute" ? `substituted the metric-compatible "${substitution.resolvedFamily}"` : `substituted another face of "${substitution.resolvedFamily}"`}`
20
+ };
21
+ }
15
22
  function fromPdfDiagnostic(diagnostic) {
16
23
  return {
17
24
  severity: diagnostic.severity,
@@ -22,7 +29,7 @@ function fromPdfDiagnostic(diagnostic) {
22
29
  }
23
30
  function createLocalDocumentConverter() {
24
31
  return {
25
- contractVersion: 2,
32
+ contractVersion: 3,
26
33
  conversions: SUPPORTED_CONVERSIONS,
27
34
  convert(request, options) {
28
35
  const { source, targetFormat } = request;
@@ -31,6 +38,10 @@ function createLocalDocumentConverter() {
31
38
  const onDocument = (pkg) => {
32
39
  documentPackage = pkg;
33
40
  };
41
+ const onFontSubstitution = (substitution) => {
42
+ diagnostics.push(fontSubstitutionDiagnostic(substitution));
43
+ options.onFontSubstitution?.(substitution);
44
+ };
34
45
  const strategy = resolveConversionPath(source.format, targetFormat);
35
46
  if (strategy?.kind !== "direct") return Promise.reject(/* @__PURE__ */ new Error(`unsupported conversion: ${source.format} -> ${targetFormat}`));
36
47
  const edge = strategy.edge;
@@ -38,7 +49,9 @@ function createLocalDocumentConverter() {
38
49
  const bytes = edge.convert(source.bytes, {
39
50
  signal: options.signal,
40
51
  onSubstitution: (s, c) => diagnostics.push(substitutionDiagnostic(s, c)),
41
- onDocument
52
+ onDocument,
53
+ fonts: options.fonts,
54
+ onFontSubstitution
42
55
  });
43
56
  return Promise.resolve({
44
57
  document: {
@@ -1,4 +1,5 @@
1
1
  import { DocumentPackage } from "document-schema.js";
2
+ import { FontSubstitution, ProvidedFont } from "pdf-codec";
2
3
  //#region src/convert/port.d.ts
3
4
  type DocumentFormat = 'docx' | 'pptx' | 'xlsx' | 'odt' | 'odp' | 'ods' | 'odg' | 'odf' | 'markdown' | 'pdf';
4
5
  interface DocumentPayload {
@@ -20,15 +21,18 @@ interface ConversionResult {
20
21
  readonly diagnostics: readonly Diagnostic[];
21
22
  readonly package?: DocumentPackage;
22
23
  }
24
+ interface ConversionOptions {
25
+ readonly signal: AbortSignal;
26
+ readonly fonts?: readonly ProvidedFont[];
27
+ readonly onFontSubstitution?: (substitution: FontSubstitution) => void;
28
+ }
23
29
  interface DocumentConverter {
24
30
  readonly contractVersion: number;
25
31
  readonly conversions: readonly {
26
32
  readonly source: DocumentFormat;
27
33
  readonly target: DocumentFormat;
28
34
  }[];
29
- convert(request: ConversionRequest, options: {
30
- readonly signal: AbortSignal;
31
- }): Promise<ConversionResult>;
35
+ convert(request: ConversionRequest, options: ConversionOptions): Promise<ConversionResult>;
32
36
  }
33
37
  //#endregion
34
- export { ConversionRequest, ConversionResult, Diagnostic, DocumentConverter, DocumentFormat, DocumentPayload };
38
+ export { ConversionOptions, ConversionRequest, ConversionResult, Diagnostic, DocumentConverter, DocumentFormat, DocumentPayload };
@@ -1,4 +1,5 @@
1
1
  import { DocumentPackage } from "document-schema.js";
2
+ import { FontSubstitution, ProvidedFont } from "pdf-codec";
2
3
  //#region src/convert/port.d.ts
3
4
  type DocumentFormat = 'docx' | 'pptx' | 'xlsx' | 'odt' | 'odp' | 'ods' | 'odg' | 'odf' | 'markdown' | 'pdf';
4
5
  interface DocumentPayload {
@@ -20,15 +21,18 @@ interface ConversionResult {
20
21
  readonly diagnostics: readonly Diagnostic[];
21
22
  readonly package?: DocumentPackage;
22
23
  }
24
+ interface ConversionOptions {
25
+ readonly signal: AbortSignal;
26
+ readonly fonts?: readonly ProvidedFont[];
27
+ readonly onFontSubstitution?: (substitution: FontSubstitution) => void;
28
+ }
23
29
  interface DocumentConverter {
24
30
  readonly contractVersion: number;
25
31
  readonly conversions: readonly {
26
32
  readonly source: DocumentFormat;
27
33
  readonly target: DocumentFormat;
28
34
  }[];
29
- convert(request: ConversionRequest, options: {
30
- readonly signal: AbortSignal;
31
- }): Promise<ConversionResult>;
35
+ convert(request: ConversionRequest, options: ConversionOptions): Promise<ConversionResult>;
32
36
  }
33
37
  //#endregion
34
- export { ConversionRequest, ConversionResult, Diagnostic, DocumentConverter, DocumentFormat, DocumentPayload };
38
+ export { ConversionOptions, ConversionRequest, ConversionResult, Diagnostic, DocumentConverter, DocumentFormat, DocumentPayload };
@@ -0,0 +1,50 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let pdf_codec_sfnt = require("pdf-codec/sfnt");
3
+ //#region src/fonts/obfuscation.ts
4
+ const FONT_KEY_HEX_LENGTH = 32;
5
+ const FONT_KEY_BYTE_LENGTH = 16;
6
+ const OBFUSCATED_PREFIX_LENGTH = 32;
7
+ const SFNT_HEADER_LENGTH = 12;
8
+ const HEX_PAIR_PATTERN = /^[0-9A-Fa-f]{2}$/;
9
+ var FontDeobfuscationError = class extends Error {
10
+ constructor(detail) {
11
+ super(`deobfuscateEmbeddedFont: ${detail}`);
12
+ this.name = "FontDeobfuscationError";
13
+ }
14
+ };
15
+ function deriveFontKey(fontKey) {
16
+ const hex = fontKey.replace(/[{}-]/g, "");
17
+ if (hex.length !== FONT_KEY_HEX_LENGTH) throw new FontDeobfuscationError(`w:fontKey ${JSON.stringify(fontKey)} has ${String(hex.length)} hex digit(s) after stripping braces and hyphens, expected ${String(FONT_KEY_HEX_LENGTH)}`);
18
+ const key = new Uint8Array(FONT_KEY_BYTE_LENGTH);
19
+ for (let i = 0; i < FONT_KEY_BYTE_LENGTH; i++) {
20
+ const start = 30 - i * 2;
21
+ const pair = hex.slice(start, start + 2);
22
+ if (!HEX_PAIR_PATTERN.test(pair)) throw new FontDeobfuscationError(`w:fontKey ${JSON.stringify(fontKey)} contains the non-hexadecimal pair ${JSON.stringify(pair)}`);
23
+ key[i] = Number.parseInt(pair, 16);
24
+ }
25
+ return key;
26
+ }
27
+ function looksLikeSfnt(bytes) {
28
+ return bytes.length >= SFNT_HEADER_LENGTH && (0, pdf_codec_sfnt.parseSfnt)(bytes) !== void 0;
29
+ }
30
+ function deobfuscateEmbeddedFont(bytes, fontKey) {
31
+ if (looksLikeSfnt(bytes)) return bytes;
32
+ if (fontKey === void 0) throw new FontDeobfuscationError("the part does not begin with an sfnt signature and carries no font key to deobfuscate it with");
33
+ if (bytes.length < OBFUSCATED_PREFIX_LENGTH) throw new FontDeobfuscationError(`an obfuscated part must be at least ${String(OBFUSCATED_PREFIX_LENGTH)} bytes long, got ${String(bytes.length)}`);
34
+ const key = deriveFontKey(fontKey);
35
+ const clear = new Uint8Array(bytes.length);
36
+ clear.set(bytes);
37
+ for (let i = 0; i < OBFUSCATED_PREFIX_LENGTH; i++) {
38
+ const keyByte = key[i % FONT_KEY_BYTE_LENGTH];
39
+ const cipherByte = clear[i];
40
+ if (keyByte === void 0 || cipherByte === void 0) throw new FontDeobfuscationError(`byte ${String(i)} of the obfuscated prefix is out of range`);
41
+ clear[i] = cipherByte ^ keyByte;
42
+ }
43
+ if (!looksLikeSfnt(clear)) throw new FontDeobfuscationError(`deobfuscating with font key ${JSON.stringify(fontKey)} did not produce a recognisable sfnt font`);
44
+ return clear;
45
+ }
46
+ //#endregion
47
+ exports.FontDeobfuscationError = FontDeobfuscationError;
48
+ exports.deobfuscateEmbeddedFont = deobfuscateEmbeddedFont;
49
+ exports.deriveFontKey = deriveFontKey;
50
+ exports.looksLikeSfnt = looksLikeSfnt;
@@ -0,0 +1,9 @@
1
+ //#region src/fonts/obfuscation.d.ts
2
+ declare class FontDeobfuscationError extends Error {
3
+ constructor(detail: string);
4
+ }
5
+ declare function deriveFontKey(fontKey: string): Uint8Array<ArrayBuffer>;
6
+ declare function looksLikeSfnt(bytes: Uint8Array<ArrayBuffer>): boolean;
7
+ declare function deobfuscateEmbeddedFont(bytes: Uint8Array<ArrayBuffer>, fontKey: string | undefined): Uint8Array<ArrayBuffer>;
8
+ //#endregion
9
+ export { FontDeobfuscationError, deobfuscateEmbeddedFont, deriveFontKey, looksLikeSfnt };
@@ -0,0 +1,9 @@
1
+ //#region src/fonts/obfuscation.d.ts
2
+ declare class FontDeobfuscationError extends Error {
3
+ constructor(detail: string);
4
+ }
5
+ declare function deriveFontKey(fontKey: string): Uint8Array<ArrayBuffer>;
6
+ declare function looksLikeSfnt(bytes: Uint8Array<ArrayBuffer>): boolean;
7
+ declare function deobfuscateEmbeddedFont(bytes: Uint8Array<ArrayBuffer>, fontKey: string | undefined): Uint8Array<ArrayBuffer>;
8
+ //#endregion
9
+ export { FontDeobfuscationError, deobfuscateEmbeddedFont, deriveFontKey, looksLikeSfnt };
@@ -0,0 +1,46 @@
1
+ import { parseSfnt } from "pdf-codec/sfnt";
2
+ //#region src/fonts/obfuscation.ts
3
+ const FONT_KEY_HEX_LENGTH = 32;
4
+ const FONT_KEY_BYTE_LENGTH = 16;
5
+ const OBFUSCATED_PREFIX_LENGTH = 32;
6
+ const SFNT_HEADER_LENGTH = 12;
7
+ const HEX_PAIR_PATTERN = /^[0-9A-Fa-f]{2}$/;
8
+ var FontDeobfuscationError = class extends Error {
9
+ constructor(detail) {
10
+ super(`deobfuscateEmbeddedFont: ${detail}`);
11
+ this.name = "FontDeobfuscationError";
12
+ }
13
+ };
14
+ function deriveFontKey(fontKey) {
15
+ const hex = fontKey.replace(/[{}-]/g, "");
16
+ if (hex.length !== FONT_KEY_HEX_LENGTH) throw new FontDeobfuscationError(`w:fontKey ${JSON.stringify(fontKey)} has ${String(hex.length)} hex digit(s) after stripping braces and hyphens, expected ${String(FONT_KEY_HEX_LENGTH)}`);
17
+ const key = new Uint8Array(FONT_KEY_BYTE_LENGTH);
18
+ for (let i = 0; i < FONT_KEY_BYTE_LENGTH; i++) {
19
+ const start = 30 - i * 2;
20
+ const pair = hex.slice(start, start + 2);
21
+ if (!HEX_PAIR_PATTERN.test(pair)) throw new FontDeobfuscationError(`w:fontKey ${JSON.stringify(fontKey)} contains the non-hexadecimal pair ${JSON.stringify(pair)}`);
22
+ key[i] = Number.parseInt(pair, 16);
23
+ }
24
+ return key;
25
+ }
26
+ function looksLikeSfnt(bytes) {
27
+ return bytes.length >= SFNT_HEADER_LENGTH && parseSfnt(bytes) !== void 0;
28
+ }
29
+ function deobfuscateEmbeddedFont(bytes, fontKey) {
30
+ if (looksLikeSfnt(bytes)) return bytes;
31
+ if (fontKey === void 0) throw new FontDeobfuscationError("the part does not begin with an sfnt signature and carries no font key to deobfuscate it with");
32
+ if (bytes.length < OBFUSCATED_PREFIX_LENGTH) throw new FontDeobfuscationError(`an obfuscated part must be at least ${String(OBFUSCATED_PREFIX_LENGTH)} bytes long, got ${String(bytes.length)}`);
33
+ const key = deriveFontKey(fontKey);
34
+ const clear = new Uint8Array(bytes.length);
35
+ clear.set(bytes);
36
+ for (let i = 0; i < OBFUSCATED_PREFIX_LENGTH; i++) {
37
+ const keyByte = key[i % FONT_KEY_BYTE_LENGTH];
38
+ const cipherByte = clear[i];
39
+ if (keyByte === void 0 || cipherByte === void 0) throw new FontDeobfuscationError(`byte ${String(i)} of the obfuscated prefix is out of range`);
40
+ clear[i] = cipherByte ^ keyByte;
41
+ }
42
+ if (!looksLikeSfnt(clear)) throw new FontDeobfuscationError(`deobfuscating with font key ${JSON.stringify(fontKey)} did not produce a recognisable sfnt font`);
43
+ return clear;
44
+ }
45
+ //#endregion
46
+ export { FontDeobfuscationError, deobfuscateEmbeddedFont, deriveFontKey, looksLikeSfnt };
@@ -0,0 +1,86 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_fonts_obfuscation = require("./obfuscation.cjs");
3
+ let odf_js = require("odf.js");
4
+ let pdf_codec_sfnt = require("pdf-codec/sfnt");
5
+ let pdf_codec_font_tables = require("pdf-codec/font-tables");
6
+ //#region src/fonts/odf.ts
7
+ const FONT_FACE_DECL_PARTS = ["content.xml", "styles.xml"];
8
+ const FS_SELECTION_ITALIC = 1;
9
+ const FS_SELECTION_BOLD = 32;
10
+ const BOLD_WEIGHT_THRESHOLD = 600;
11
+ var OdfEmbeddedFontError = class extends Error {
12
+ constructor(detail) {
13
+ super(`extractOdfEmbeddedFonts: ${detail}`);
14
+ this.name = "OdfEmbeddedFontError";
15
+ }
16
+ };
17
+ function normaliseFamily(value) {
18
+ const first = (0, odf_js.decodeXmlText)(value).split(",")[0]?.trim() ?? "";
19
+ const quoted = /^'(.*)'$|^"(.*)"$/.exec(first);
20
+ return (quoted?.[1] ?? quoted?.[2] ?? first).trim();
21
+ }
22
+ function boldFromWeightAttribute(weight) {
23
+ const keyword = weight.trim().toLowerCase();
24
+ if (keyword === "bold" || keyword === "bolder") return true;
25
+ if (keyword === "normal" || keyword === "lighter") return false;
26
+ const numeric = Number.parseInt(keyword, 10);
27
+ return Number.isNaN(numeric) ? void 0 : numeric >= BOLD_WEIGHT_THRESHOLD;
28
+ }
29
+ function italicFromStyleAttribute(style) {
30
+ const keyword = style.trim().toLowerCase();
31
+ if (keyword === "italic" || keyword === "oblique") return true;
32
+ return keyword === "normal" ? false : void 0;
33
+ }
34
+ function styleFromFontBytes(bytes) {
35
+ const font = (0, pdf_codec_sfnt.parseSfnt)(bytes);
36
+ if (font === void 0) return;
37
+ const os2 = (0, pdf_codec_font_tables.parseOs2)(font);
38
+ if (os2 === void 0) return;
39
+ return {
40
+ bold: (os2.fsSelection & FS_SELECTION_BOLD) !== 0,
41
+ italic: (os2.fsSelection & FS_SELECTION_ITALIC) !== 0
42
+ };
43
+ }
44
+ function fontPartBytes(pkg, href) {
45
+ const part = pkg.parts[href];
46
+ if (part === void 0) throw new OdfEmbeddedFontError(`svg:font-face-uri references ${JSON.stringify(href)}, which is not a part of this package`);
47
+ if (part.kind !== "binary") throw new OdfEmbeddedFontError(`embedded font part ${JSON.stringify(href)} is an XML part, not binary font data`);
48
+ return (0, odf_js.base64ToBytes)(part.base64);
49
+ }
50
+ function collectFontFace(pkg, fontFace, seenHrefs, out) {
51
+ const declaredFamily = (0, odf_js.attrValue)(fontFace, "svg:font-family") ?? (0, odf_js.attrValue)(fontFace, "style:name");
52
+ for (const src of (0, odf_js.childrenWithTag)(fontFace, "svg:font-face-src")) for (const uri of (0, odf_js.childrenWithTag)(src, "svg:font-face-uri")) {
53
+ const rawHref = (0, odf_js.attrValue)(uri, "xlink:href");
54
+ if (rawHref === void 0) throw new OdfEmbeddedFontError("a <svg:font-face-uri> carries no xlink:href");
55
+ const href = (0, odf_js.decodeXmlText)(rawHref);
56
+ if (seenHrefs.has(href)) continue;
57
+ seenHrefs.add(href);
58
+ if (declaredFamily === void 0) throw new OdfEmbeddedFontError(`the <style:font-face> embedding ${JSON.stringify(href)} declares neither svg:font-family nor style:name`);
59
+ const bytes = fontPartBytes(pkg, href);
60
+ if (!require_fonts_obfuscation.looksLikeSfnt(bytes)) throw new OdfEmbeddedFontError(`embedded font part ${JSON.stringify(href)} does not begin with a recognisable sfnt signature`);
61
+ const declaredWeight = (0, odf_js.attrValue)(uri, "loext:font-weight");
62
+ const declaredStyle = (0, odf_js.attrValue)(uri, "loext:font-style");
63
+ const intrinsic = styleFromFontBytes(bytes);
64
+ out.push({
65
+ family: normaliseFamily(declaredFamily),
66
+ bold: (declaredWeight === void 0 ? void 0 : boldFromWeightAttribute(declaredWeight)) ?? intrinsic?.bold ?? false,
67
+ italic: (declaredStyle === void 0 ? void 0 : italicFromStyleAttribute(declaredStyle)) ?? intrinsic?.italic ?? false,
68
+ bytes
69
+ });
70
+ }
71
+ }
72
+ function extractOdfEmbeddedFonts(pkg) {
73
+ const out = [];
74
+ const seenHrefs = /* @__PURE__ */ new Set();
75
+ for (const partPath of FONT_FACE_DECL_PARTS) {
76
+ const part = pkg.parts[partPath];
77
+ if (part?.kind !== "xml") continue;
78
+ const root = (0, odf_js.rootElement)(part.nodes);
79
+ if (root === void 0) continue;
80
+ for (const decls of (0, odf_js.childrenWithTag)(root, "office:font-face-decls")) for (const fontFace of (0, odf_js.childrenWithTag)(decls, "style:font-face")) collectFontFace(pkg, fontFace, seenHrefs, out);
81
+ }
82
+ return out;
83
+ }
84
+ //#endregion
85
+ exports.OdfEmbeddedFontError = OdfEmbeddedFontError;
86
+ exports.extractOdfEmbeddedFonts = extractOdfEmbeddedFonts;
@@ -0,0 +1,9 @@
1
+ import { Package } from "odf.js";
2
+ import { ProvidedFont } from "pdf-codec";
3
+ //#region src/fonts/odf.d.ts
4
+ declare class OdfEmbeddedFontError extends Error {
5
+ constructor(detail: string);
6
+ }
7
+ declare function extractOdfEmbeddedFonts(pkg: Package): ProvidedFont[];
8
+ //#endregion
9
+ export { OdfEmbeddedFontError, extractOdfEmbeddedFonts };
@@ -0,0 +1,9 @@
1
+ import { Package } from "odf.js";
2
+ import { ProvidedFont } from "pdf-codec";
3
+ //#region src/fonts/odf.d.ts
4
+ declare class OdfEmbeddedFontError extends Error {
5
+ constructor(detail: string);
6
+ }
7
+ declare function extractOdfEmbeddedFonts(pkg: Package): ProvidedFont[];
8
+ //#endregion
9
+ export { OdfEmbeddedFontError, extractOdfEmbeddedFonts };