qus-node-pdf-generator
Version:
client api for generating pdfs in nestjs
156 lines (139 loc) • 4.98 kB
text/typescript
import { Injectable } from '@nestjs/common';
import * as Handlebars from 'handlebars';
import chromium from '@sparticuz/chromium';
import puppeteer, { Browser, Page } from 'puppeteer';
()
export class PdfService {
/**
* Renders a single template into a PDF.
*
* @param template
* @param data
* @param format
* @param landscape
*/
async render(template: string, data: any, format: PdfFormat, landscape: boolean = false): Promise<Uint8Array> {
const compiledTemplate: HandlebarsTemplateDelegate = Handlebars.compile(template);
const htmlContent: string = compiledTemplate(data);
console.log("chrome path " + await chromium.executablePath());
const browser: Browser = await puppeteer.launch({
executablePath: await chromium.executablePath(),
args: chromium.args,
headless: chromium.headless,
});
const page: Page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const pdfBuffer: Uint8Array = await page.pdf({ format, landscape });
await browser.close();
return pdfBuffer;
}
/**
* Renders multiple templates into a single PDF.
*
* @param templates
* @param data
* @param format
* @param addPageBreaks
* @param styles
* @param landscape
*/
async renderMultiple(
templates: string[],
data: any[],
format: PdfFormat,
addPageBreaks: boolean = true,
styles?: string,
landscape: boolean = false
): Promise<Uint8Array> {
const defaultStyles = this.getDefaultCss(addPageBreaks);
const appliedStyles: string = styles || defaultStyles;
let htmlContent: string = `<body>${appliedStyles}`;
for (let i = 0; i < templates.length; i++) {
const compiledTemplate: HandlebarsTemplateDelegate = Handlebars.compile(templates[i]);
const renderedContent = compiledTemplate(data[i]);
htmlContent += `<div class="page">${renderedContent}</div>`;
}
htmlContent += `</body>`;
console.log('Chromium Path:', await chromium.executablePath())
const browser: Browser = await puppeteer.launch({ headless: 'shell' });
const page: Page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const pdfBuffer: Uint8Array = await page.pdf({ format, landscape, printBackground: true });
await browser.close();
return pdfBuffer;
}
/**
* Generates HTML content from multiple templates.
*
* @param templates
* @param data
* @param addPageBreaks
* @param styles
*/
async generateHtml(templates: string[], data: any[], addPageBreaks: boolean = true, styles?: string): Promise<string> {
// Default styles if none are provided
const defaultStyles = `
<style>
body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden; /* Prevent horizontal scrolling */
}
.page {
width: 100%;
max-width: 800px; /* Constrain content width */
margin: 0 auto; /* Center content */
padding: 20px; /* Add padding for better readability */
box-sizing: border-box;
${addPageBreaks ? 'page-break-after: always;' : ''} /* Optional page break */
}
.page:last-child {
page-break-after: auto; /* Avoid unnecessary blank page at the end */
}
</style>
`;
// Use provided styles or fallback to default
const appliedStyles: string = styles || defaultStyles;
// Combine all templates into a single HTML string
let htmlContent: string = `<body>${appliedStyles}`;
for (let i = 0; i < templates.length; i++) {
const compiledTemplate: HandlebarsTemplateDelegate = Handlebars.compile(templates[i]);
const renderedContent = compiledTemplate(data[i]);
htmlContent += `<div class="page">${renderedContent}</div>`;
}
htmlContent += `</body>`;
return htmlContent;
}
private getDefaultCss(addPageBreaks: boolean): string {
return `
<style>
body {
margin: 0;
padding: 0;
width: 100%;
overflow-x: hidden;
}
.page {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
${addPageBreaks ? 'page-break-after: always;' : ''}
}
.page:last-child {
page-break-after: auto;
}
</style>
`;
}
}
// Enum for PDF formats
export enum PdfFormat {
A3 = 'A3',
A4 = 'A4',
A5 = 'A5',
Letter = 'letter',
Legal = 'legal'
}