BREAKING CHANGE(smartpdf): improve image generation quality and API consistency
- Renamed convertPDFToWebpPreviews to convertPDFToWebpBytes for consistency - Added configurable scale options with DPI support - Changed default scale to 3.0 (216 DPI) for better quality - Added DPI helper methods and scale constants
This commit is contained in:
@@ -14,6 +14,19 @@ export interface ISmartPdfOptions {
|
||||
}
|
||||
|
||||
export class SmartPdf {
|
||||
// STATIC SCALE CONSTANTS
|
||||
public static readonly SCALE_SCREEN = 2.0; // ~144 DPI - Good for screen display
|
||||
public static readonly SCALE_HIGH = 3.0; // ~216 DPI - High quality (default)
|
||||
public static readonly SCALE_PRINT = 6.0; // ~432 DPI - Print quality
|
||||
|
||||
/**
|
||||
* Calculate scale factor for desired DPI
|
||||
* PDF.js default is 72 DPI, so scale = desiredDPI / 72
|
||||
*/
|
||||
public static getScaleForDPI(dpi: number): number {
|
||||
return dpi / 72;
|
||||
}
|
||||
|
||||
// STATIC
|
||||
public static async create(optionsArg?: ISmartPdfOptions) {
|
||||
const smartpdfInstance = new SmartPdf(optionsArg);
|
||||
@@ -318,10 +331,14 @@ export class SmartPdf {
|
||||
*/
|
||||
public async convertPDFToPngBytes(
|
||||
pdfBytes: Uint8Array,
|
||||
options: { width?: number; height?: number; quality?: number } = {}
|
||||
options: {
|
||||
scale?: number; // Scale factor for output size (default: 3.0 for 216 DPI)
|
||||
maxWidth?: number; // Maximum width in pixels (optional)
|
||||
maxHeight?: number; // Maximum height in pixels (optional)
|
||||
} = {}
|
||||
): Promise<Uint8Array[]> {
|
||||
// Note: options.width, options.height, and options.quality are not applied here,
|
||||
// as the rendered canvas size is determined by the PDF page dimensions.
|
||||
// Set default scale for higher quality output (3.0 = ~216 DPI)
|
||||
const scale = options.scale || 3.0;
|
||||
|
||||
// Create a new page using the headless browser.
|
||||
const page = await this.headlessBrowser.newPage();
|
||||
@@ -354,12 +371,31 @@ export class SmartPdf {
|
||||
const numPages = pdf.numPages;
|
||||
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
||||
const page = await pdf.getPage(pageNum);
|
||||
const viewport = page.getViewport({ scale: 1.0 });
|
||||
// Apply scale factor to viewport
|
||||
const viewport = page.getViewport({ scale: ${scale} });
|
||||
|
||||
// Apply max width/height constraints if specified
|
||||
let finalScale = ${scale};
|
||||
${options.maxWidth ? `
|
||||
if (viewport.width > ${options.maxWidth}) {
|
||||
finalScale = ${options.maxWidth} / (viewport.width / ${scale});
|
||||
}` : ''}
|
||||
${options.maxHeight ? `
|
||||
if (viewport.height > ${options.maxHeight}) {
|
||||
const heightScale = ${options.maxHeight} / (viewport.height / ${scale});
|
||||
finalScale = Math.min(finalScale, heightScale);
|
||||
}` : ''}
|
||||
|
||||
// Get final viewport with adjusted scale
|
||||
const finalViewport = page.getViewport({ scale: finalScale });
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
await page.render({ canvasContext: context, viewport: viewport }).promise;
|
||||
canvas.width = finalViewport.width;
|
||||
canvas.height = finalViewport.height;
|
||||
canvas.setAttribute('data-page', pageNum);
|
||||
|
||||
await page.render({ canvasContext: context, viewport: finalViewport }).promise;
|
||||
document.body.appendChild(canvas);
|
||||
}
|
||||
window.renderComplete = true;
|
||||
@@ -391,4 +427,115 @@ export class SmartPdf {
|
||||
await page.close();
|
||||
return pngBuffers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PDF to WebP bytes for each page.
|
||||
* This method creates web-optimized images using WebP format.
|
||||
* WebP provides 25-35% better compression than JPEG/PNG while maintaining quality.
|
||||
*/
|
||||
public async convertPDFToWebpBytes(
|
||||
pdfBytes: Uint8Array,
|
||||
options: {
|
||||
scale?: number; // Scale factor for preview size (default: 3.0 for 216 DPI)
|
||||
quality?: number; // WebP quality 0-100 (default: 85)
|
||||
maxWidth?: number; // Maximum width in pixels (optional)
|
||||
maxHeight?: number; // Maximum height in pixels (optional)
|
||||
} = {}
|
||||
): Promise<Uint8Array[]> {
|
||||
// Set default options for higher quality output (3.0 = ~216 DPI)
|
||||
const scale = options.scale || 3.0;
|
||||
const quality = options.quality || 85;
|
||||
|
||||
// Create a new page using the headless browser
|
||||
const page = await this.headlessBrowser.newPage();
|
||||
|
||||
// Prepare PDF data as a base64 string
|
||||
const base64Pdf: string = Buffer.from(pdfBytes).toString('base64');
|
||||
|
||||
// HTML template that loads PDF.js and renders the PDF with scaling
|
||||
const htmlTemplate: string = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PDF to WebP Preview Converter</title>
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
canvas { display: block; margin: 10px auto; }
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
(async function() {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
|
||||
const pdfData = "__PDF_DATA__";
|
||||
const raw = atob(pdfData);
|
||||
const pdfArray = new Uint8Array([...raw].map(c => c.charCodeAt(0)));
|
||||
const loadingTask = pdfjsLib.getDocument({data: pdfArray});
|
||||
const pdf = await loadingTask.promise;
|
||||
const numPages = pdf.numPages;
|
||||
|
||||
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
||||
const page = await pdf.getPage(pageNum);
|
||||
// Apply scale factor to viewport
|
||||
const viewport = page.getViewport({ scale: ${scale} });
|
||||
|
||||
// Apply max width/height constraints if specified
|
||||
let finalScale = ${scale};
|
||||
${options.maxWidth ? `
|
||||
if (viewport.width > ${options.maxWidth}) {
|
||||
finalScale = ${options.maxWidth} / (viewport.width / ${scale});
|
||||
}` : ''}
|
||||
${options.maxHeight ? `
|
||||
if (viewport.height > ${options.maxHeight}) {
|
||||
const heightScale = ${options.maxHeight} / (viewport.height / ${scale});
|
||||
finalScale = Math.min(finalScale, heightScale);
|
||||
}` : ''}
|
||||
|
||||
// Get final viewport with adjusted scale
|
||||
const finalViewport = page.getViewport({ scale: finalScale });
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = finalViewport.width;
|
||||
canvas.height = finalViewport.height;
|
||||
canvas.setAttribute('data-page', pageNum);
|
||||
|
||||
await page.render({ canvasContext: context, viewport: finalViewport }).promise;
|
||||
document.body.appendChild(canvas);
|
||||
}
|
||||
window.renderComplete = true;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// Replace the placeholder with the actual base64 PDF data
|
||||
const htmlContent: string = htmlTemplate.replace("__PDF_DATA__", base64Pdf);
|
||||
|
||||
// Set the page content
|
||||
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
|
||||
|
||||
// Wait until the PDF.js rendering is complete
|
||||
await page.waitForFunction(() => (window as any).renderComplete === true, { timeout: 30000 });
|
||||
|
||||
// Query all canvas elements (each representing a rendered PDF page)
|
||||
const canvasElements = await page.$$('canvas');
|
||||
const webpBuffers: Uint8Array[] = [];
|
||||
|
||||
for (const canvasElement of canvasElements) {
|
||||
// Screenshot the canvas element as WebP
|
||||
const screenshotBuffer = (await canvasElement.screenshot({
|
||||
type: 'webp',
|
||||
quality: quality,
|
||||
encoding: 'binary'
|
||||
})) as Buffer;
|
||||
webpBuffers.push(new Uint8Array(screenshotBuffer));
|
||||
}
|
||||
|
||||
await page.close();
|
||||
return webpBuffers;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user