officetopdf-js 0.0.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.
Files changed (40) hide show
  1. package/LICENSE +216 -0
  2. package/README.md +152 -0
  3. package/dist/browser.cjs +155 -0
  4. package/dist/browser.d.ts +8 -0
  5. package/dist/browser.d.ts.map +1 -0
  6. package/dist/browser.js +86 -0
  7. package/dist/chunk-A2DPXSQU.js +64 -0
  8. package/dist/errors/OfficeToPdfError.d.ts +11 -0
  9. package/dist/errors/OfficeToPdfError.d.ts.map +1 -0
  10. package/dist/index.cjs +229 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +153 -0
  14. package/dist/internal/binary.d.ts +2 -0
  15. package/dist/internal/binary.d.ts.map +1 -0
  16. package/dist/internal/cli-args.d.ts +4 -0
  17. package/dist/internal/cli-args.d.ts.map +1 -0
  18. package/dist/internal/convenience.d.ts +11 -0
  19. package/dist/internal/convenience.d.ts.map +1 -0
  20. package/dist/internal/shared.d.ts +4 -0
  21. package/dist/internal/shared.d.ts.map +1 -0
  22. package/dist/node.d.ts +8 -0
  23. package/dist/node.d.ts.map +1 -0
  24. package/dist/types.d.ts +37 -0
  25. package/dist/types.d.ts.map +1 -0
  26. package/package.json +92 -0
  27. package/src/__tests__/binary.test.ts +53 -0
  28. package/src/__tests__/cli-args.test.ts +87 -0
  29. package/src/__tests__/convenience.test.ts +36 -0
  30. package/src/__tests__/shared.test.ts +43 -0
  31. package/src/browser.ts +125 -0
  32. package/src/errors/OfficeToPdfError.ts +27 -0
  33. package/src/index.ts +1 -0
  34. package/src/internal/binary.ts +49 -0
  35. package/src/internal/cli-args.ts +28 -0
  36. package/src/internal/convenience.ts +19 -0
  37. package/src/internal/shared.ts +31 -0
  38. package/src/node.ts +124 -0
  39. package/src/types/officetopdf-wasm.d.ts +37 -0
  40. package/src/types.ts +42 -0
@@ -0,0 +1,53 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { UnsupportedPlatformError } from "../errors/OfficeToPdfError.js";
3
+ import { resolveBinaryPath } from "../internal/binary.js";
4
+
5
+ const originalEnv = process.env.OFFICE2PDF_BINARY;
6
+
7
+ function platformPackageInstalled(): boolean {
8
+ delete process.env.OFFICE2PDF_BINARY;
9
+ try {
10
+ resolveBinaryPath();
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ } finally {
15
+ restoreEnv();
16
+ }
17
+ }
18
+
19
+ function restoreEnv() {
20
+ if (originalEnv === undefined) delete process.env.OFFICE2PDF_BINARY;
21
+ else process.env.OFFICE2PDF_BINARY = originalEnv;
22
+ }
23
+
24
+ const bundled = platformPackageInstalled();
25
+
26
+ afterEach(restoreEnv);
27
+
28
+ describe("resolveBinaryPath", () => {
29
+ test("prefers an explicit override", () => {
30
+ process.env.OFFICE2PDF_BINARY = "/from/env";
31
+ expect(resolveBinaryPath("/explicit")).toBe("/explicit");
32
+ });
33
+
34
+ test("falls back to OFFICE2PDF_BINARY", () => {
35
+ process.env.OFFICE2PDF_BINARY = "/from/env";
36
+ expect(resolveBinaryPath()).toBe("/from/env");
37
+ });
38
+
39
+ test.skipIf(bundled)("ignores an empty OFFICE2PDF_BINARY", () => {
40
+ process.env.OFFICE2PDF_BINARY = "";
41
+ expect(() => resolveBinaryPath()).toThrow(UnsupportedPlatformError);
42
+ });
43
+
44
+ test.skipIf(bundled)("names the platform when no binary is installed", () => {
45
+ delete process.env.OFFICE2PDF_BINARY;
46
+ expect(() => resolveBinaryPath()).toThrow(`${process.platform}-${process.arch}`);
47
+ });
48
+
49
+ test.skipIf(!bundled)("resolves the bundled platform binary", () => {
50
+ delete process.env.OFFICE2PDF_BINARY;
51
+ expect(resolveBinaryPath()).toContain("office2pdf");
52
+ });
53
+ });
@@ -0,0 +1,87 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { buildArgs, fontExtension } from "../internal/cli-args.js";
3
+ import type { ConvertOptions } from "../types.js";
4
+
5
+ describe("buildArgs", () => {
6
+ const args = (options: ConvertOptions = {}, fontPaths: string[] = []) =>
7
+ buildArgs("in.docx", "out.pdf", fontPaths, options);
8
+
9
+ test("always passes the input and output paths", () => {
10
+ expect(args()).toEqual(["in.docx", "--output", "out.pdf"]);
11
+ });
12
+
13
+ test("omits every flag that was not requested", () => {
14
+ expect(args({ landscape: false, pdfA: false, sheets: [], slides: "" })).toEqual([
15
+ "in.docx",
16
+ "--output",
17
+ "out.pdf",
18
+ ]);
19
+ });
20
+
21
+ test("maps each option to its CLI flag", () => {
22
+ expect(
23
+ args({
24
+ paperSize: "letter",
25
+ landscape: true,
26
+ pdfA: true,
27
+ tagged: true,
28
+ pdfUa: true,
29
+ sheets: ["Sheet1", "Summary"],
30
+ slides: "1-5",
31
+ }),
32
+ ).toEqual([
33
+ "in.docx",
34
+ "--output",
35
+ "out.pdf",
36
+ "--paper",
37
+ "letter",
38
+ "--landscape",
39
+ "--pdf-a",
40
+ "--tagged",
41
+ "--pdf-ua",
42
+ "--sheets",
43
+ "Sheet1,Summary",
44
+ "--slides",
45
+ "1-5",
46
+ ]);
47
+ });
48
+
49
+ test("repeats --font-path once per directory", () => {
50
+ expect(args({}, ["/a", "/b"])).toEqual([
51
+ "in.docx",
52
+ "--output",
53
+ "out.pdf",
54
+ "--font-path",
55
+ "/a",
56
+ "--font-path",
57
+ "/b",
58
+ ]);
59
+ });
60
+
61
+ test("keeps every flag value directly after its flag", () => {
62
+ const result = args({ slides: "1-5", paperSize: "a4" });
63
+ expect(result[result.indexOf("--slides") + 1]).toBe("1-5");
64
+ expect(result[result.indexOf("--paper") + 1]).toBe("a4");
65
+ });
66
+ });
67
+
68
+ describe("fontExtension", () => {
69
+ const magic = (tag: string) =>
70
+ new Uint8Array([...tag].map((character) => character.charCodeAt(0)));
71
+
72
+ test("detects a TrueType collection", () => {
73
+ expect(fontExtension(magic("ttcf"))).toBe("ttc");
74
+ });
75
+
76
+ test("detects OpenType CFF outlines", () => {
77
+ expect(fontExtension(magic("OTTO"))).toBe("otf");
78
+ });
79
+
80
+ test("falls back to ttf for anything else", () => {
81
+ expect(fontExtension(new Uint8Array([0, 1, 0, 0]))).toBe("ttf");
82
+ });
83
+
84
+ test("falls back to ttf for bytes shorter than the magic", () => {
85
+ expect(fontExtension(new Uint8Array([0]))).toBe("ttf");
86
+ });
87
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { createFormatShortcuts } from "../internal/convenience.js";
3
+ import type { ConvertOptions } from "../types.js";
4
+
5
+ describe("createFormatShortcuts", () => {
6
+ const calls: ConvertOptions[] = [];
7
+ const shortcuts = createFormatShortcuts(async (_input, options = {}) => {
8
+ calls.push(options);
9
+ return new Uint8Array();
10
+ });
11
+
12
+ test("docxToPdf pins the docx format", async () => {
13
+ await shortcuts.docxToPdf(new Uint8Array());
14
+ expect(calls.at(-1)?.format).toBe("docx");
15
+ });
16
+
17
+ test("pptxToPdf pins the pptx format", async () => {
18
+ await shortcuts.pptxToPdf(new Uint8Array());
19
+ expect(calls.at(-1)?.format).toBe("pptx");
20
+ });
21
+
22
+ test("xlsxToPdf pins the xlsx format", async () => {
23
+ await shortcuts.xlsxToPdf(new Uint8Array());
24
+ expect(calls.at(-1)?.format).toBe("xlsx");
25
+ });
26
+
27
+ test("forwards the caller's other options", async () => {
28
+ await shortcuts.docxToPdf(new Uint8Array(), { landscape: true, paperSize: "a4" });
29
+ expect(calls.at(-1)).toEqual({ landscape: true, paperSize: "a4", format: "docx" });
30
+ });
31
+
32
+ test("does not let the caller override the pinned format", async () => {
33
+ await shortcuts.xlsxToPdf(new Uint8Array(), { format: "docx" } as ConvertOptions);
34
+ expect(calls.at(-1)?.format).toBe("xlsx");
35
+ });
36
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { OfficeToPdfError } from "../errors/OfficeToPdfError.js";
3
+ import { resolveFormat, toBytes } from "../internal/shared.js";
4
+
5
+ describe("toBytes", () => {
6
+ test("passes through a Uint8Array", async () => {
7
+ const bytes = new Uint8Array([1, 2, 3]);
8
+ expect(await toBytes(bytes)).toBe(bytes);
9
+ });
10
+
11
+ test("converts an ArrayBuffer", async () => {
12
+ expect(await toBytes(new Uint8Array([1, 2]).buffer)).toEqual(new Uint8Array([1, 2]));
13
+ });
14
+
15
+ test("converts a Blob", async () => {
16
+ expect(await toBytes(new Blob([new Uint8Array([7])]))).toEqual(new Uint8Array([7]));
17
+ });
18
+
19
+ test("rejects unsupported input", () => {
20
+ expect(toBytes("nope" as never)).rejects.toThrow(OfficeToPdfError);
21
+ });
22
+ });
23
+
24
+ describe("resolveFormat", () => {
25
+ const bytes = new Uint8Array();
26
+
27
+ test("prefers the explicit format", () => {
28
+ expect(resolveFormat(bytes, { format: "pptx" })).toBe("pptx");
29
+ });
30
+
31
+ test("infers the format from a File name", () => {
32
+ const file = new File([bytes], "Report.DOCX");
33
+ expect(resolveFormat(file, {})).toBe("docx");
34
+ });
35
+
36
+ test("throws when the format cannot be inferred", () => {
37
+ expect(() => resolveFormat(bytes, {})).toThrow(OfficeToPdfError);
38
+ });
39
+
40
+ test("rejects an unknown format", () => {
41
+ expect(() => resolveFormat(bytes, { format: "pdf" as never })).toThrow(OfficeToPdfError);
42
+ });
43
+ });
package/src/browser.ts ADDED
@@ -0,0 +1,125 @@
1
+ import { ConversionFailedError, OfficeToPdfError } from "./errors/OfficeToPdfError.js";
2
+ import { createFormatShortcuts } from "./internal/convenience.js";
3
+ import { resolveFormat, toBytes } from "./internal/shared.js";
4
+ import type {
5
+ ConversionWarning,
6
+ Converter,
7
+ ConverterOptions,
8
+ ConvertInput,
9
+ ConvertOptions,
10
+ } from "./types.js";
11
+
12
+ type WasmModule = typeof import("officetopdf-wasm");
13
+ type ConversionResult = InstanceType<WasmModule["ConversionResult"]>;
14
+ type WasmWarning = InstanceType<WasmModule["ConversionWarning"]>;
15
+ type WasmConverter = InstanceType<WasmModule["Office2PdfConverter"]>;
16
+
17
+ let wasmModule: Promise<WasmModule> | undefined;
18
+
19
+ async function loadWasm(): Promise<WasmModule> {
20
+ wasmModule ??= import("officetopdf-wasm")
21
+ .then(async (module) => {
22
+ await module.default();
23
+ return module;
24
+ })
25
+ .catch((cause) => {
26
+ wasmModule = undefined;
27
+ throw new OfficeToPdfError(
28
+ "Failed to load the office2pdf WebAssembly module. Install the optional " +
29
+ "`officetopdf-wasm` package to convert in the browser.",
30
+ { cause },
31
+ );
32
+ });
33
+ return wasmModule;
34
+ }
35
+
36
+ function toWarning(warning: WasmWarning): ConversionWarning {
37
+ const { kind, format, message, from, to, element, detail, reason } = warning;
38
+ warning.free();
39
+ return { kind, format, message, from, to, element, detail, reason };
40
+ }
41
+
42
+ function reportWarnings(result: ConversionResult, onWarning: ConvertOptions["onWarning"]) {
43
+ for (let index = 0; index < result.warningCount; index += 1) {
44
+ const warning = result.warningAt(index);
45
+ if (warning) onWarning?.(toWarning(warning));
46
+ }
47
+ }
48
+
49
+ class BrowserConverter implements Converter {
50
+ private instance?: Promise<WasmConverter>;
51
+
52
+ constructor(private readonly options: ConverterOptions = {}) {}
53
+
54
+ async convert(input: ConvertInput, options: ConvertOptions = {}): Promise<Uint8Array> {
55
+ const format = resolveFormat(input, options);
56
+ const bytes = await toBytes(input);
57
+ const converter = await this.getInstance();
58
+
59
+ try {
60
+ const result = converter.convertToPdf(bytes, format);
61
+ try {
62
+ reportWarnings(result, options.onWarning);
63
+ return result.pdf;
64
+ } finally {
65
+ result.free();
66
+ }
67
+ } catch (cause) {
68
+ throw new ConversionFailedError(
69
+ `Converting ${format} to PDF failed`,
70
+ cause instanceof Error ? cause.message : String(cause),
71
+ );
72
+ }
73
+ }
74
+
75
+ async dispose(): Promise<void> {
76
+ const instance = this.instance;
77
+ this.instance = undefined;
78
+ (await instance)?.free();
79
+ }
80
+
81
+ private getInstance(): Promise<WasmConverter> {
82
+ this.instance ??= (async () => {
83
+ const { Office2PdfConverter } = await loadWasm();
84
+ const converter = new Office2PdfConverter();
85
+
86
+ for (const font of this.options.fonts ?? []) converter.registerFont(await toBytes(font));
87
+ if (this.options.lastResortFontFamily) {
88
+ converter.setLastResortFontFamily(this.options.lastResortFontFamily);
89
+ }
90
+ return converter;
91
+ })();
92
+ return this.instance;
93
+ }
94
+ }
95
+
96
+ export function createConverter(options?: ConverterOptions): Converter {
97
+ return new BrowserConverter(options);
98
+ }
99
+
100
+ export async function convert(input: ConvertInput, options?: ConvertOptions): Promise<Uint8Array> {
101
+ const converter = new BrowserConverter();
102
+ try {
103
+ return await converter.convert(input, options);
104
+ } finally {
105
+ await converter.dispose();
106
+ }
107
+ }
108
+
109
+ export const { docxToPdf, pptxToPdf, xlsxToPdf } = createFormatShortcuts(convert);
110
+
111
+ export {
112
+ ConversionFailedError,
113
+ OfficeToPdfError,
114
+ UnsupportedPlatformError,
115
+ } from "./errors/OfficeToPdfError.js";
116
+ export type { FormatOptions, FormatShortcut } from "./internal/convenience.js";
117
+ export type {
118
+ ConversionWarning,
119
+ Converter,
120
+ ConverterOptions,
121
+ ConvertInput,
122
+ ConvertOptions,
123
+ OfficeFormat,
124
+ PaperSize,
125
+ } from "./types.js";
@@ -0,0 +1,27 @@
1
+ export class OfficeToPdfError extends Error {
2
+ constructor(message: string, options?: ErrorOptions) {
3
+ super(message, options);
4
+ this.name = "OfficeToPdfError";
5
+ }
6
+ }
7
+
8
+ export class UnsupportedPlatformError extends OfficeToPdfError {
9
+ constructor(platform: string, arch: string, options?: ErrorOptions) {
10
+ super(
11
+ `No office2pdf binary is available for ${platform}-${arch}. ` +
12
+ "Install the matching platform package, or set OFFICE2PDF_BINARY to a local binary.",
13
+ options,
14
+ );
15
+ this.name = "UnsupportedPlatformError";
16
+ }
17
+ }
18
+
19
+ export class ConversionFailedError extends OfficeToPdfError {
20
+ constructor(
21
+ message: string,
22
+ readonly detail?: string,
23
+ ) {
24
+ super(detail ? `${message}: ${detail}` : message);
25
+ this.name = "ConversionFailedError";
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./node.js";
@@ -0,0 +1,49 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, join } from "node:path";
3
+ import process from "node:process";
4
+ import { UnsupportedPlatformError } from "../errors/OfficeToPdfError.js";
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ const PLATFORM_PACKAGES: Record<string, string> = {
9
+ "darwin-arm64": "officetopdf-darwin-arm64",
10
+ "darwin-x64": "officetopdf-darwin-x64",
11
+ "linux-arm64": "officetopdf-linux-arm64",
12
+ "linux-x64": "officetopdf-linux-x64",
13
+ "linux-x64-musl": "officetopdf-linux-x64-musl",
14
+ "win32-x64": "officetopdf-windows-x64",
15
+ };
16
+
17
+ function isMusl(): boolean {
18
+ const report = process.report?.getReport();
19
+ if (typeof report === "object" && report !== null && "header" in report) {
20
+ const header = (report as { header?: { glibcVersionRuntime?: string } }).header;
21
+ return !header?.glibcVersionRuntime;
22
+ }
23
+ return false;
24
+ }
25
+
26
+ function currentTarget(): string {
27
+ const target = `${process.platform}-${process.arch}`;
28
+ return target === "linux-x64" && isMusl() ? "linux-x64-musl" : target;
29
+ }
30
+
31
+ let cached: string | undefined;
32
+
33
+ export function resolveBinaryPath(override?: string): string {
34
+ const explicit = override ?? process.env.OFFICE2PDF_BINARY;
35
+ if (explicit) return explicit;
36
+ if (cached) return cached;
37
+
38
+ const target = currentTarget();
39
+ const packageName = PLATFORM_PACKAGES[target];
40
+ if (!packageName) throw new UnsupportedPlatformError(process.platform, process.arch);
41
+
42
+ const executable = process.platform === "win32" ? "office2pdf.exe" : "office2pdf";
43
+ try {
44
+ cached = join(dirname(require.resolve(`${packageName}/package.json`)), executable);
45
+ } catch (cause) {
46
+ throw new UnsupportedPlatformError(process.platform, process.arch, { cause });
47
+ }
48
+ return cached;
49
+ }
@@ -0,0 +1,28 @@
1
+ import type { ConvertOptions } from "../types.js";
2
+
3
+ export function buildArgs(
4
+ inputPath: string,
5
+ outputPath: string,
6
+ fontPaths: readonly string[],
7
+ options: ConvertOptions,
8
+ ): string[] {
9
+ const args = [inputPath, "--output", outputPath];
10
+
11
+ if (options.paperSize) args.push("--paper", options.paperSize);
12
+ if (options.landscape) args.push("--landscape");
13
+ if (options.pdfA) args.push("--pdf-a");
14
+ if (options.tagged) args.push("--tagged");
15
+ if (options.pdfUa) args.push("--pdf-ua");
16
+ if (options.sheets?.length) args.push("--sheets", options.sheets.join(","));
17
+ if (options.slides) args.push("--slides", options.slides);
18
+ for (const fontPath of fontPaths) args.push("--font-path", fontPath);
19
+
20
+ return args;
21
+ }
22
+
23
+ export function fontExtension(bytes: Uint8Array): string {
24
+ const magic = String.fromCharCode(...bytes.subarray(0, 4));
25
+ if (magic === "ttcf") return "ttc";
26
+ if (magic === "OTTO") return "otf";
27
+ return "ttf";
28
+ }
@@ -0,0 +1,19 @@
1
+ import type { ConvertInput, ConvertOptions, OfficeFormat } from "../types.js";
2
+
3
+ export type FormatOptions = Omit<ConvertOptions, "format">;
4
+
5
+ export type FormatShortcut = (input: ConvertInput, options?: FormatOptions) => Promise<Uint8Array>;
6
+
7
+ type Convert = (input: ConvertInput, options?: ConvertOptions) => Promise<Uint8Array>;
8
+
9
+ function shortcut(convert: Convert, format: OfficeFormat): FormatShortcut {
10
+ return (input, options) => convert(input, { ...options, format });
11
+ }
12
+
13
+ export function createFormatShortcuts(convert: Convert) {
14
+ return {
15
+ docxToPdf: shortcut(convert, "docx"),
16
+ pptxToPdf: shortcut(convert, "pptx"),
17
+ xlsxToPdf: shortcut(convert, "xlsx"),
18
+ };
19
+ }
@@ -0,0 +1,31 @@
1
+ import { OfficeToPdfError } from "../errors/OfficeToPdfError.js";
2
+ import type { ConvertInput, ConvertOptions, OfficeFormat } from "../types.js";
3
+
4
+ const FORMATS: readonly OfficeFormat[] = ["docx", "pptx", "xlsx"];
5
+
6
+ export async function toBytes(input: ConvertInput): Promise<Uint8Array> {
7
+ if (input instanceof Uint8Array) return input;
8
+ if (input instanceof ArrayBuffer) return new Uint8Array(input);
9
+ if (typeof Blob !== "undefined" && input instanceof Blob) {
10
+ return new Uint8Array(await input.arrayBuffer());
11
+ }
12
+ throw new OfficeToPdfError("Input must be a Uint8Array, ArrayBuffer, or Blob.");
13
+ }
14
+
15
+ export function resolveFormat(input: ConvertInput, options: ConvertOptions): OfficeFormat {
16
+ if (options.format) {
17
+ if (!FORMATS.includes(options.format)) {
18
+ throw new OfficeToPdfError(`Unsupported format "${options.format}".`);
19
+ }
20
+ return options.format;
21
+ }
22
+
23
+ const name = (input as { name?: unknown }).name;
24
+ const extension = typeof name === "string" ? name.split(".").pop()?.toLowerCase() : undefined;
25
+ const inferred = FORMATS.find((format) => format === extension);
26
+ if (inferred) return inferred;
27
+
28
+ throw new OfficeToPdfError(
29
+ 'Unable to infer the input format. Pass { format: "docx" | "pptx" | "xlsx" }.',
30
+ );
31
+ }
package/src/node.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { ConversionFailedError } from "./errors/OfficeToPdfError.js";
7
+ import { resolveBinaryPath } from "./internal/binary.js";
8
+ import { buildArgs, fontExtension } from "./internal/cli-args.js";
9
+ import { createFormatShortcuts } from "./internal/convenience.js";
10
+ import { resolveFormat, toBytes } from "./internal/shared.js";
11
+ import type { Converter, ConverterOptions, ConvertInput, ConvertOptions } from "./types.js";
12
+
13
+ const execFileAsync = promisify(execFile);
14
+
15
+ const DEFAULT_TIMEOUT_MS = 120_000;
16
+
17
+ function reportWarnings(stderr: string, onWarning: ConvertOptions["onWarning"]) {
18
+ if (!onWarning) return;
19
+ for (const line of stderr.split("\n")) {
20
+ const message = line.trim();
21
+ if (message) onWarning({ message });
22
+ }
23
+ }
24
+
25
+ class NodeConverter implements Converter {
26
+ private readonly binaryPath: string;
27
+ private readonly timeoutMs: number;
28
+ private fontDir?: Promise<string | undefined>;
29
+
30
+ constructor(private readonly options: ConverterOptions = {}) {
31
+ this.binaryPath = resolveBinaryPath(options.binaryPath);
32
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
33
+ }
34
+
35
+ async convert(input: ConvertInput, options: ConvertOptions = {}): Promise<Uint8Array> {
36
+ const format = resolveFormat(input, options);
37
+ const bytes = await toBytes(input);
38
+ const fontPaths = await this.resolveFontPaths();
39
+
40
+ const workDir = await mkdtemp(join(tmpdir(), "office2pdf-"));
41
+ try {
42
+ const inputPath = join(workDir, `input.${format}`);
43
+ const outputPath = join(workDir, "output.pdf");
44
+ await writeFile(inputPath, bytes);
45
+
46
+ const { stderr } = await execFileAsync(
47
+ this.binaryPath,
48
+ buildArgs(inputPath, outputPath, fontPaths, options),
49
+ { timeout: this.timeoutMs, signal: options.signal, windowsHide: true },
50
+ ).catch((cause: NodeJS.ErrnoException & { stderr?: string }) => {
51
+ throw new ConversionFailedError(
52
+ `Converting ${format} to PDF failed`,
53
+ cause.stderr?.trim() || cause.message,
54
+ );
55
+ });
56
+
57
+ reportWarnings(stderr, options.onWarning);
58
+
59
+ return await readFile(outputPath);
60
+ } finally {
61
+ await rm(workDir, { recursive: true, force: true });
62
+ }
63
+ }
64
+
65
+ async dispose(): Promise<void> {
66
+ const dir = await this.fontDir;
67
+ this.fontDir = undefined;
68
+ if (dir) await rm(dir, { recursive: true, force: true });
69
+ }
70
+
71
+ private async resolveFontPaths(): Promise<readonly string[]> {
72
+ const configured = this.options.fontPaths ?? [];
73
+ const registered = await this.materializeFonts();
74
+ return registered ? [...configured, registered] : configured;
75
+ }
76
+
77
+ private materializeFonts(): Promise<string | undefined> {
78
+ this.fontDir ??= (async () => {
79
+ const fonts = this.options.fonts ?? [];
80
+ if (fonts.length === 0) return undefined;
81
+
82
+ const dir = await mkdtemp(join(tmpdir(), "office2pdf-fonts-"));
83
+ await Promise.all(
84
+ fonts.map(async (font, index) => {
85
+ const bytes = await toBytes(font);
86
+ return writeFile(join(dir, `font-${index}.${fontExtension(bytes)}`), bytes);
87
+ }),
88
+ );
89
+ return dir;
90
+ })();
91
+ return this.fontDir;
92
+ }
93
+ }
94
+
95
+ export function createConverter(options?: ConverterOptions): Converter {
96
+ return new NodeConverter(options);
97
+ }
98
+
99
+ export async function convert(input: ConvertInput, options?: ConvertOptions): Promise<Uint8Array> {
100
+ const converter = new NodeConverter();
101
+ try {
102
+ return await converter.convert(input, options);
103
+ } finally {
104
+ await converter.dispose();
105
+ }
106
+ }
107
+
108
+ export const { docxToPdf, pptxToPdf, xlsxToPdf } = createFormatShortcuts(convert);
109
+
110
+ export {
111
+ ConversionFailedError,
112
+ OfficeToPdfError,
113
+ UnsupportedPlatformError,
114
+ } from "./errors/OfficeToPdfError.js";
115
+ export type { FormatOptions, FormatShortcut } from "./internal/convenience.js";
116
+ export type {
117
+ ConversionWarning,
118
+ Converter,
119
+ ConverterOptions,
120
+ ConvertInput,
121
+ ConvertOptions,
122
+ OfficeFormat,
123
+ PaperSize,
124
+ } from "./types.js";
@@ -0,0 +1,37 @@
1
+ declare module "officetopdf-wasm" {
2
+ export class ConversionWarning {
3
+ readonly kind: string;
4
+ readonly format: string;
5
+ readonly message: string;
6
+ readonly from: string | undefined;
7
+ readonly to: string | undefined;
8
+ readonly element: string | undefined;
9
+ readonly detail: string | undefined;
10
+ readonly reason: string | undefined;
11
+ free(): void;
12
+ }
13
+
14
+ export class ConversionResult {
15
+ readonly pdf: Uint8Array;
16
+ readonly warningCount: number;
17
+ warningAt(index: number): ConversionWarning | undefined;
18
+ free(): void;
19
+ }
20
+
21
+ export class Office2PdfConverter {
22
+ registerFont(data: Uint8Array): void;
23
+ clearFonts(): void;
24
+ setLastResortFontFamily(family: string): void;
25
+ clearLastResortFontFamily(): void;
26
+ convertToPdf(data: Uint8Array, format: string): ConversionResult;
27
+ convertDocxToPdf(data: Uint8Array): ConversionResult;
28
+ convertPptxToPdf(data: Uint8Array): ConversionResult;
29
+ convertXlsxToPdf(data: Uint8Array): ConversionResult;
30
+ free(): void;
31
+ }
32
+
33
+ export function convertToPdf(data: Uint8Array, format: string): Uint8Array;
34
+ export function convertToPdfWithResult(data: Uint8Array, format: string): ConversionResult;
35
+
36
+ export default function init(input?: unknown): Promise<unknown>;
37
+ }