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, '

Hello HTML

'); + const outputPath = path.join(tmpDir, 'from-html.pdf'); + const entry = resolve('html', 'pdf'); + + await entry.convert(inputPath, outputPath); + + const detected = await detectInputMime(outputPath); + expect(detected.mime).toBe('application/pdf'); + }, 20000); + + it('converts DOCX to a valid PDF', async () => { + const outputPath = path.join(tmpDir, 'from-docx.pdf'); + const entry = resolve('docx', 'pdf'); + + await entry.convert(docxFixturePath, outputPath); + + const detected = await detectInputMime(outputPath); + expect(detected.mime).toBe('application/pdf'); + }, 20000); +});