diff --git a/src/converters/imageToPdf.js b/src/converters/imageToPdf.js index 9ea3d1c..804fafd 100644 --- a/src/converters/imageToPdf.js +++ b/src/converters/imageToPdf.js @@ -5,13 +5,17 @@ import { register } from './registry.js'; const IMAGE_FORMATS = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'tiff', 'avif']; -async function convert(inputPath, outputPath) { - const pngBuffer = await sharp(inputPath).png().toBuffer(); - const metadata = await sharp(pngBuffer).metadata(); +async function convert(inputPath, outputPath, { quality } = {}) { + const imageBuffer = + quality == null + ? await sharp(inputPath).png().toBuffer() + : await sharp(inputPath).jpeg({ quality }).toBuffer(); + const metadata = await sharp(imageBuffer).metadata(); const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage([metadata.width, metadata.height]); - const embeddedImage = await pdfDoc.embedPng(pngBuffer); + const embeddedImage = + quality == null ? await pdfDoc.embedPng(imageBuffer) : await pdfDoc.embedJpg(imageBuffer); page.drawImage(embeddedImage, { x: 0, diff --git a/test/converters/imageToPdf.test.js b/test/converters/imageToPdf.test.js index 17bf3b1..2d4b249 100644 --- a/test/converters/imageToPdf.test.js +++ b/test/converters/imageToPdf.test.js @@ -2,6 +2,7 @@ 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 sharp from 'sharp'; import { registerImageToPdfConverter } from '../../src/converters/imageToPdf.js'; import { resolve, listTargetFormats } from '../../src/converters/registry.js'; import { detectInputMime } from '../../src/mime.js'; @@ -33,4 +34,34 @@ describe('image to PDF converter', () => { const detected = await detectInputMime(outputPath); expect(detected.mime).toBe('application/pdf'); }); + + it('embeds a JPEG instead of a PNG when a quality is given, shrinking the output for a noisy image', async () => { + const inputPath = path.join(tmpDir, 'noisy.png'); + await sharp({ + create: { + width: 256, + height: 256, + channels: 3, + noise: { type: 'gaussian', mean: 128, sigma: 40 }, + }, + }) + .png() + .toFile(inputPath); + + const defaultOutputPath = path.join(tmpDir, 'default.pdf'); + const compressedOutputPath = path.join(tmpDir, 'compressed.pdf'); + const entry = resolve('png', 'pdf'); + + await entry.convert(inputPath, defaultOutputPath); + await entry.convert(inputPath, compressedOutputPath, { quality: 20 }); + + const compressedDetected = await detectInputMime(compressedOutputPath); + expect(compressedDetected.mime).toBe('application/pdf'); + + const [defaultStat, compressedStat] = await Promise.all([ + fs.stat(defaultOutputPath), + fs.stat(compressedOutputPath), + ]); + expect(compressedStat.size).toBeLessThan(defaultStat.size); + }); });