2025-08-01 16:09:17 +00:00
|
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
2022-03-24 14:32:49 +01:00
|
|
|
import * as smartpdf from '../ts/index.js';
|
2025-02-25 18:03:27 +00:00
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
2018-10-06 13:25:45 +00:00
|
|
|
|
|
|
|
let testSmartPdf: smartpdf.SmartPdf;
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
/**
|
|
|
|
* Ensures that a directory exists.
|
|
|
|
* @param dirPath - The directory path to ensure.
|
|
|
|
*/
|
|
|
|
function ensureDir(dirPath: string): void {
|
|
|
|
if (!fs.existsSync(dirPath)) {
|
|
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-02 12:37:48 +00:00
|
|
|
// Clean test results directory at start
|
|
|
|
const testResultsDir = path.join('.nogit', 'testresults');
|
|
|
|
if (fs.existsSync(testResultsDir)) {
|
|
|
|
fs.rmSync(testResultsDir, { recursive: true, force: true });
|
|
|
|
}
|
|
|
|
ensureDir(testResultsDir);
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should create a valid instance of SmartPdf', async () => {
|
2018-10-06 13:25:45 +00:00
|
|
|
testSmartPdf = new smartpdf.SmartPdf();
|
2022-03-24 14:32:49 +01:00
|
|
|
expect(testSmartPdf).toBeInstanceOf(smartpdf.SmartPdf);
|
2018-10-06 17:35:26 +02:00
|
|
|
});
|
2018-10-06 13:25:45 +00:00
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should start the SmartPdf instance', async () => {
|
2019-05-29 14:14:02 +02:00
|
|
|
await testSmartPdf.start();
|
|
|
|
});
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should create PDFs from HTML string', async () => {
|
|
|
|
const pdf1 = await testSmartPdf.getA4PdfResultForHtmlString('hi');
|
|
|
|
const pdf2 = await testSmartPdf.getA4PdfResultForHtmlString('hello');
|
|
|
|
expect(pdf1.buffer).toBeInstanceOf(Buffer);
|
|
|
|
expect(pdf2.buffer).toBeInstanceOf(Buffer);
|
2022-01-05 17:20:28 +01:00
|
|
|
});
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should create PDFs from websites', async () => {
|
|
|
|
const pdfA4 = await testSmartPdf.getPdfResultForWebsite('https://www.wikipedia.org');
|
|
|
|
const pdfSingle = await testSmartPdf.getFullWebsiteAsSinglePdf('https://www.wikipedia.org');
|
|
|
|
expect(pdfA4.buffer).toBeInstanceOf(Buffer);
|
|
|
|
expect(pdfSingle.buffer).toBeInstanceOf(Buffer);
|
2018-10-06 13:25:45 +00:00
|
|
|
});
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should create valid PDF results and write them to disk', async () => {
|
|
|
|
const writePdfToDisk = async (urlArg: string, fileName: string) => {
|
2021-10-14 10:59:45 +02:00
|
|
|
const pdfResult = await testSmartPdf.getFullWebsiteAsSinglePdf(urlArg);
|
2022-03-24 14:32:49 +01:00
|
|
|
expect(pdfResult.buffer).toBeInstanceOf(Buffer);
|
2025-02-25 18:03:27 +00:00
|
|
|
ensureDir('.nogit');
|
|
|
|
fs.writeFileSync(path.join('.nogit', fileName), pdfResult.buffer as Buffer);
|
2021-10-14 10:59:45 +02:00
|
|
|
};
|
2025-02-25 18:03:27 +00:00
|
|
|
await writePdfToDisk('https://lossless.com/', '1.pdf');
|
|
|
|
await writePdfToDisk('https://layer.io', '2.pdf');
|
2021-10-14 10:59:45 +02:00
|
|
|
});
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should merge PDFs into a combined PDF', async () => {
|
2022-06-15 22:14:55 +02:00
|
|
|
const pdf1 = await testSmartPdf.readFileToPdfObject('.nogit/1.pdf');
|
|
|
|
const pdf2 = await testSmartPdf.readFileToPdfObject('.nogit/2.pdf');
|
2025-02-25 18:03:27 +00:00
|
|
|
const mergedBuffer = await testSmartPdf.mergePdfs([pdf1.buffer, pdf2.buffer]);
|
|
|
|
ensureDir('.nogit');
|
|
|
|
fs.writeFileSync(path.join('.nogit', 'combined.pdf'), mergedBuffer);
|
2022-10-26 23:04:59 +02:00
|
|
|
});
|
2019-05-29 19:49:23 +02:00
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should create PNG images from combined PDF using Puppeteer conversion', async () => {
|
2024-04-25 18:48:08 +02:00
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/combined.pdf');
|
2024-04-27 12:07:16 +02:00
|
|
|
const images = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer);
|
2025-02-25 18:03:27 +00:00
|
|
|
expect(images.length).toBeGreaterThan(0);
|
|
|
|
console.log('Puppeteer-based conversion image sizes:', images.map(img => img.length));
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should store PNG results from both conversion functions in .nogit/testresults', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/combined.pdf');
|
|
|
|
|
|
|
|
// Convert using Puppeteer-based function and store images
|
|
|
|
const imagesPuppeteer = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer);
|
|
|
|
imagesPuppeteer.forEach((img, index) => {
|
2025-08-02 12:37:48 +00:00
|
|
|
const filePath = path.join(testResultsDir, `png_combined_page${index + 1}.png`);
|
2025-02-25 18:03:27 +00:00
|
|
|
fs.writeFileSync(filePath, Buffer.from(img));
|
|
|
|
});
|
2024-04-25 18:48:08 +02:00
|
|
|
});
|
|
|
|
|
2025-08-02 12:37:48 +00:00
|
|
|
tap.test('should create WebP preview images from PDF', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
const webpPreviews = await testSmartPdf.convertPDFToWebpBytes(pdfObject.buffer);
|
|
|
|
expect(webpPreviews.length).toBeGreaterThan(0);
|
|
|
|
console.log('WebP preview sizes:', webpPreviews.map(img => img.length));
|
|
|
|
|
|
|
|
// Also create PNG previews for comparison
|
|
|
|
const pngPreviews = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer);
|
|
|
|
console.log('PNG preview sizes:', pngPreviews.map(img => img.length));
|
|
|
|
|
|
|
|
// Save the first page as both WebP and PNG preview
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, 'webp_default_page1.webp'), Buffer.from(webpPreviews[0]));
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, 'png_default_page1.png'), Buffer.from(pngPreviews[0]));
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should create WebP previews with custom scale and quality', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
|
|
|
|
// Create smaller previews with lower quality for thumbnails
|
|
|
|
const thumbnails = await testSmartPdf.convertPDFToWebpBytes(pdfObject.buffer, {
|
|
|
|
scale: 0.5, // Create readable thumbnails at ~36 DPI
|
|
|
|
quality: 70
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(thumbnails.length).toBeGreaterThan(0);
|
|
|
|
console.log('Thumbnail sizes:', thumbnails.map(img => img.length));
|
|
|
|
|
|
|
|
// Save thumbnails
|
|
|
|
thumbnails.forEach((thumb, index) => {
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, `webp_thumbnail_page${index + 1}.webp`), Buffer.from(thumb));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should create WebP previews with max dimensions', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
|
|
|
|
// Create previews with maximum dimensions (will use high scale but constrain to max size)
|
|
|
|
const constrainedPreviews = await testSmartPdf.convertPDFToWebpBytes(pdfObject.buffer, {
|
|
|
|
scale: smartpdf.SmartPdf.SCALE_HIGH, // Start with high quality
|
|
|
|
quality: 90,
|
|
|
|
maxWidth: 800,
|
|
|
|
maxHeight: 1000
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(constrainedPreviews.length).toBeGreaterThan(0);
|
|
|
|
console.log('Constrained preview sizes:', constrainedPreviews.map(img => img.length));
|
|
|
|
|
|
|
|
// Save constrained preview
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, 'webp_constrained_page1.webp'), Buffer.from(constrainedPreviews[0]));
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should verify WebP files are smaller than PNG', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
|
|
|
|
// Generate both PNG and WebP versions at the same scale for fair comparison
|
|
|
|
const comparisonScale = smartpdf.SmartPdf.SCALE_HIGH; // Both use 3.0 scale
|
|
|
|
|
|
|
|
const pngImages = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer, {
|
|
|
|
scale: comparisonScale
|
|
|
|
});
|
|
|
|
const webpImages = await testSmartPdf.convertPDFToWebpBytes(pdfObject.buffer, {
|
|
|
|
scale: comparisonScale,
|
|
|
|
quality: 85
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(pngImages.length).toEqual(webpImages.length);
|
|
|
|
|
|
|
|
// Compare sizes
|
|
|
|
let totalPngSize = 0;
|
|
|
|
let totalWebpSize = 0;
|
|
|
|
|
|
|
|
pngImages.forEach((png, index) => {
|
|
|
|
const pngSize = png.length;
|
|
|
|
const webpSize = webpImages[index].length;
|
|
|
|
totalPngSize += pngSize;
|
|
|
|
totalWebpSize += webpSize;
|
|
|
|
|
|
|
|
const reduction = ((pngSize - webpSize) / pngSize * 100).toFixed(1);
|
|
|
|
console.log(`Page ${index + 1}: PNG=${pngSize} bytes, WebP=${webpSize} bytes, Reduction=${reduction}%`);
|
|
|
|
|
|
|
|
// Save comparison files
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, `comparison_png_page${index + 1}.png`), Buffer.from(png));
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, `comparison_webp_page${index + 1}.webp`), Buffer.from(webpImages[index]));
|
|
|
|
});
|
|
|
|
|
|
|
|
const totalReduction = ((totalPngSize - totalWebpSize) / totalPngSize * 100).toFixed(1);
|
|
|
|
console.log(`Total size reduction: ${totalReduction}% (PNG: ${totalPngSize} bytes, WebP: ${totalWebpSize} bytes)`);
|
|
|
|
|
|
|
|
// WebP should be smaller
|
|
|
|
expect(totalWebpSize).toBeLessThan(totalPngSize);
|
|
|
|
});
|
|
|
|
|
2025-08-02 17:29:38 +00:00
|
|
|
tap.test('should create JPEG images from PDF', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
const jpegImages = await testSmartPdf.convertPDFToJpegBytes(pdfObject.buffer);
|
|
|
|
expect(jpegImages.length).toBeGreaterThan(0);
|
|
|
|
console.log('JPEG image sizes:', jpegImages.map(img => img.length));
|
|
|
|
|
|
|
|
// Save the first page as JPEG
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, 'jpeg_default_page1.jpg'), Buffer.from(jpegImages[0]));
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should create JPEG images with different quality levels', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
|
|
|
|
// Test different quality levels
|
|
|
|
const qualityLevels = [50, 70, 85, 95];
|
|
|
|
|
|
|
|
for (const quality of qualityLevels) {
|
|
|
|
const jpegImages = await testSmartPdf.convertPDFToJpegBytes(pdfObject.buffer, {
|
|
|
|
scale: smartpdf.SmartPdf.SCALE_HIGH,
|
|
|
|
quality: quality
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log(`JPEG quality ${quality}: ${jpegImages[0].length} bytes`);
|
|
|
|
|
|
|
|
// Save first page at each quality level
|
|
|
|
fs.writeFileSync(
|
|
|
|
path.join(testResultsDir, `jpeg_quality_${quality}_page1.jpg`),
|
|
|
|
Buffer.from(jpegImages[0])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should create JPEG images with max dimensions', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
|
|
|
|
// Create constrained JPEG images
|
|
|
|
const constrainedJpegs = await testSmartPdf.convertPDFToJpegBytes(pdfObject.buffer, {
|
|
|
|
scale: smartpdf.SmartPdf.SCALE_HIGH,
|
|
|
|
quality: 85,
|
|
|
|
maxWidth: 1200,
|
|
|
|
maxHeight: 1200
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(constrainedJpegs.length).toBeGreaterThan(0);
|
|
|
|
console.log('Constrained JPEG sizes:', constrainedJpegs.map(img => img.length));
|
|
|
|
|
|
|
|
// Save constrained JPEG
|
|
|
|
fs.writeFileSync(path.join(testResultsDir, 'jpeg_constrained_page1.jpg'), Buffer.from(constrainedJpegs[0]));
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('should compare file sizes between PNG, WebP, and JPEG', async () => {
|
|
|
|
const pdfObject = await testSmartPdf.readFileToPdfObject('.nogit/3.pdf');
|
|
|
|
|
|
|
|
// Generate all three formats at the same scale
|
|
|
|
const comparisonScale = smartpdf.SmartPdf.SCALE_HIGH; // 3.0 scale
|
|
|
|
|
|
|
|
const pngImages = await testSmartPdf.convertPDFToPngBytes(pdfObject.buffer, {
|
|
|
|
scale: comparisonScale
|
|
|
|
});
|
|
|
|
const webpImages = await testSmartPdf.convertPDFToWebpBytes(pdfObject.buffer, {
|
|
|
|
scale: comparisonScale,
|
|
|
|
quality: 85
|
|
|
|
});
|
|
|
|
const jpegImages = await testSmartPdf.convertPDFToJpegBytes(pdfObject.buffer, {
|
|
|
|
scale: comparisonScale,
|
|
|
|
quality: 85
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(pngImages.length).toEqual(webpImages.length);
|
|
|
|
expect(pngImages.length).toEqual(jpegImages.length);
|
|
|
|
|
|
|
|
// Compare sizes
|
|
|
|
let totalPngSize = 0;
|
|
|
|
let totalWebpSize = 0;
|
|
|
|
let totalJpegSize = 0;
|
|
|
|
|
|
|
|
pngImages.forEach((png, index) => {
|
|
|
|
const pngSize = png.length;
|
|
|
|
const webpSize = webpImages[index].length;
|
|
|
|
const jpegSize = jpegImages[index].length;
|
|
|
|
|
|
|
|
totalPngSize += pngSize;
|
|
|
|
totalWebpSize += webpSize;
|
|
|
|
totalJpegSize += jpegSize;
|
|
|
|
|
|
|
|
const webpReduction = ((pngSize - webpSize) / pngSize * 100).toFixed(1);
|
|
|
|
const jpegReduction = ((pngSize - jpegSize) / pngSize * 100).toFixed(1);
|
|
|
|
|
|
|
|
console.log(`Page ${index + 1}:`);
|
|
|
|
console.log(` PNG: ${pngSize} bytes`);
|
|
|
|
console.log(` WebP: ${webpSize} bytes (${webpReduction}% smaller than PNG)`);
|
|
|
|
console.log(` JPEG: ${jpegSize} bytes (${jpegReduction}% smaller than PNG)`);
|
|
|
|
});
|
|
|
|
|
|
|
|
const totalWebpReduction = ((totalPngSize - totalWebpSize) / totalPngSize * 100).toFixed(1);
|
|
|
|
const totalJpegReduction = ((totalPngSize - totalJpegSize) / totalPngSize * 100).toFixed(1);
|
|
|
|
|
|
|
|
console.log('\nTotal size comparison:');
|
|
|
|
console.log(`PNG: ${totalPngSize} bytes`);
|
|
|
|
console.log(`WebP: ${totalWebpSize} bytes (${totalWebpReduction}% reduction)`);
|
|
|
|
console.log(`JPEG: ${totalJpegSize} bytes (${totalJpegReduction}% reduction)`);
|
|
|
|
|
|
|
|
// JPEG and WebP should both be smaller than PNG
|
|
|
|
expect(totalJpegSize).toBeLessThan(totalPngSize);
|
|
|
|
expect(totalWebpSize).toBeLessThan(totalPngSize);
|
|
|
|
});
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.test('should close the SmartPdf instance properly', async () => {
|
2019-05-29 14:14:02 +02:00
|
|
|
await testSmartPdf.stop();
|
2018-10-06 17:35:26 +02:00
|
|
|
});
|
|
|
|
|
2025-02-25 18:03:27 +00:00
|
|
|
tap.start();
|