diff --git a/src/converters/document.js b/src/converters/document.js new file mode 100644 index 0000000..36795c7 --- /dev/null +++ b/src/converters/document.js @@ -0,0 +1,50 @@ +import fs from 'node:fs/promises'; +import mammoth from 'mammoth'; +import puppeteer from 'puppeteer'; +import { register } from './registry.js'; + +async function renderHtmlToPdf(html, outputPath) { + const browser = await puppeteer.launch({ + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + try { + const page = await browser.newPage(); + await page.setContent(html, { waitUntil: 'networkidle0' }); + await page.pdf({ path: outputPath, format: 'A4', printBackground: true }); + } finally { + await browser.close(); + } +} + +function escapeHtml(text) { + return text.replace(/&/g, '&').replace(//g, '>'); +} + +async function convertDocxToHtml(inputPath, outputPath) { + const result = await mammoth.convertToHtml({ path: inputPath }); + await fs.writeFile(outputPath, result.value); +} + +async function convertTxtToPdf(inputPath, outputPath) { + const text = await fs.readFile(inputPath, 'utf8'); + const html = `
${escapeHtml(text)}`;
+ await renderHtmlToPdf(html, outputPath);
+}
+
+async function convertHtmlToPdf(inputPath, outputPath) {
+ const html = await fs.readFile(inputPath, 'utf8');
+ await renderHtmlToPdf(html, outputPath);
+}
+
+async function convertDocxToPdf(inputPath, outputPath) {
+ const result = await mammoth.convertToHtml({ path: inputPath });
+ await renderHtmlToPdf(result.value, outputPath);
+}
+
+export function registerDocumentConverters() {
+ register({ family: 'document', sourceFormat: 'docx', targetFormat: 'html', convert: convertDocxToHtml });
+ register({ family: 'document', sourceFormat: 'txt', targetFormat: 'pdf', convert: convertTxtToPdf });
+ register({ family: 'document', sourceFormat: 'html', targetFormat: 'pdf', convert: convertHtmlToPdf });
+ register({ family: 'document', sourceFormat: 'docx', targetFormat: 'pdf', convert: convertDocxToPdf });
+}
diff --git a/test/converters/document.test.js b/test/converters/document.test.js
new file mode 100644
index 0000000..6df1ad7
--- /dev/null
+++ b/test/converters/document.test.js
@@ -0,0 +1,77 @@
+import { describe, it, expect, beforeAll, afterAll } from 'vitest';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import os from 'node:os';
+import { Document, Paragraph, TextRun, Packer } from 'docx';
+import { registerDocumentConverters } from '../../src/converters/document.js';
+import { resolve } from '../../src/converters/registry.js';
+import { detectInputMime } from '../../src/mime.js';
+
+let tmpDir;
+let docxFixturePath;
+
+beforeAll(async () => {
+ registerDocumentConverters();
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'converter-document-'));
+
+ const doc = new Document({
+ sections: [
+ {
+ children: [new Paragraph({ children: [new TextRun('Hello from the fixture document')] })],
+ },
+ ],
+ });
+ const buffer = await Packer.toBuffer(doc);
+ docxFixturePath = path.join(tmpDir, 'fixture.docx');
+ await fs.writeFile(docxFixturePath, buffer);
+});
+
+afterAll(async () => {
+ await fs.rm(tmpDir, { recursive: true, force: true });
+});
+
+describe('document converters', () => {
+ it('converts DOCX to HTML containing the source text', async () => {
+ const outputPath = path.join(tmpDir, 'output.html');
+ const entry = resolve('docx', 'html');
+
+ await entry.convert(docxFixturePath, outputPath);
+
+ const html = await fs.readFile(outputPath, 'utf8');
+ expect(html).toContain('Hello from the fixture document');
+ }, 20000);
+
+ it('converts TXT to a valid PDF', async () => {
+ const inputPath = path.join(tmpDir, 'fixture.txt');
+ await fs.writeFile(inputPath, 'Plain text content for the PDF');
+ const outputPath = path.join(tmpDir, 'from-txt.pdf');
+ const entry = resolve('txt', 'pdf');
+
+ await entry.convert(inputPath, outputPath);
+
+ const detected = await detectInputMime(outputPath);
+ expect(detected.mime).toBe('application/pdf');
+ }, 20000);
+
+ it('converts HTML to a valid PDF', async () => {
+ const inputPath = path.join(tmpDir, 'fixture.html');
+ await fs.writeFile(inputPath, '