React 및 Puppeteer: PDF 생성(pdf 생성 API)
PDF 생성
목표:
1 단계
일부 도우미를 만들 수 있습니다.
글꼴을 등록하고 pdf 본문을 보간하기 위해 전체 html 문서를 렌더링하려면 도우미가 필요합니다.
// file: apps/pdf-server/src/pdf/helpers/render-pdf-document.ts
import { Font, PDFData } from '@pdf-generation/constants';
const renderFontFaces = (font: Font) => {
return (
font?.fontFaces?.map((fontFace) => {
const family = font.family ? `font-family: ${font.family};` : '';
const style = fontFace.style ? `font-style: ${fontFace.style};` : '';
const weight = fontFace.weight ? `font-weight: ${fontFace.weight};` : '';
const format = fontFace.format ? `format(${fontFace.format})` : '';
return `
@font-face {
${family}
${style}
${weight}
src: url(${fontFace.src}) ${format};
}
`;
}) || ''
);
};
export const renderPdfDocument = (data: PDFData, body: string) => {
const font = renderFontFaces(data.font);
return `
<html>
<head>
<meta charset="utf-8" />
<title>${data.metadata.title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
${font}
body {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
print-color-adjust: exact !important;
}
</style>
</head>
<body>
${body}
</body>
</html>
`;
};
이제 PDF 파일의 메타데이터를 설정하는 도우미가 필요합니다. 불행하게도 Puppeteer는 필요할 수 있는 메타데이터를 지정할 방법이 없습니다. 따라서 아래에서
pdf-lib
를 사용하여 이 메타데이터를 설정하고 추가 사용을 위해 Buffer
를 반환합니다.import { PDFDocument } from 'pdf-lib';
const metadataSetters = {
title: 'setTitle',
author: 'setAuthor',
creator: 'setCreator',
producer: 'setProducer',
subject: 'setSubject',
};
export async function setPdfMetadata(pdf: Buffer, metadata) {
const setterEntries = Object.entries(metadataSetters);
if (!setterEntries.some(([key]) => !!metadata[key])) return pdf;
const document = await PDFDocument.load(pdf);
setterEntries.forEach(([key, setter]) => {
const value = metadata[key];
if (value) document[setter](value);
});
const pdfBytes = await document.save();
return Buffer.from(pdfBytes);
}
사용하려는 글꼴을 정의할 수도 있습니다.
export const DEFAULT_PDF_FONT = {
family: 'Open Sans',
familyDisplay: "'Open Sans', sans-serif",
fontFaces: [
{
src: 'https://fonts.gstatic.com/s/opensans/v18/mem5YaGs126MiZpBA-UN_r8-VeJoCqeDjg.ttf',
weight: 300,
},
{
src: 'https://fonts.gstatic.com/s/opensans/v18/mem8YaGs126MiZpBA-U1UpcaXcl0Aw.ttf',
},
{
src: 'https://fonts.gstatic.com/s/opensans/v18/mem6YaGs126MiZpBA-UFUJ0ef8xkA76a.ttf',
style: 'italic',
},
{
src: 'https://fonts.gstatic.com/s/opensans/v18/mem5YaGs126MiZpBA-UNirk-VeJoCqeDjg.ttf',
weight: 600,
},
{
src: 'https://fonts.gstatic.com/s/opensans/v18/memnYaGs126MiZpBA-UFUKXGUehsKKKTjrPW.ttf',
weight: 600,
style: 'italic',
},
],
};
2 단계
작업을 수행할 서비스를 만들 수 있습니다.
// file: apps/pdf-server/src/pdf/pdf.service.ts
import { Injectable } from '@nestjs/common';
import * as puppeteer from 'puppeteer';
import { writeFile } from 'fs/promises';
import { PDFDocumentData } from '@pdf-generation/constants';
import { PdfDocProps, renderPdfDoc } from '@pdf-generation/pdf-doc';
import { environment } from '../environments/environment';
import { renderPdfDocument, setPdfMetadata, DEFAULT_PDF_FONT } from './helpers';
@Injectable()
export class PdfService {
async generatePdf(data: PDFDocumentData<PdfDocProps>) {
const browser = await puppeteer.launch({
/**
* Adding `--no-sandbox` here is for easier deployment. Please see puppeteer docs about
* this argument and why you may not want to use it.
*/
args: [`--no-sandbox`],
});
const pdfData = {
...data,
font: data.font ?? DEFAULT_PDF_FONT,
};
const documentBody = renderPdfDoc(pdfData.document, pdfData.font);
const content = renderPdfDocument(pdfData, documentBody);
const pdfOptions = pdfData?.options || {};
const page = await browser.newPage();
await page.setContent(content, {
waitUntil: 'networkidle2',
});
const pdf = await page.pdf({
format: 'a4',
printBackground: true,
preferCSSPageSize: true,
...pdfOptions,
margin: {
left: '40px',
right: '40px',
top: '40px',
bottom: '40px',
...(pdfOptions?.margin || {}),
},
});
// Give the buffer to pdf-lib
const result = await setPdfMetadata(pdf, pdfData?.metadata);
// write to disk for debugging
if (!environment.production) {
const now = Date.now();
const filePath = `${environment.tmpFolder}/${now}-tmp-file.pdf`;
await page.screenshot({
fullPage: true,
path: `${environment.tmpFolder}/${now}-tmp-file.png`,
});
await writeFile(filePath, result);
}
await browser.close();
return result;
}
}
지금 여기서 무슨 일이 일어나고 있나요?
renderPdfDoc
renderPdfDocument
로 렌더링합니다.setPdfMetadata
로 설정합니다Buffer
를 반환합니다.3단계
이제
Controller
를 업데이트하여 액세스할 수 있도록 서비스를 연결해야 합니다.// file: apps/pdf-server/src/pdf/pdf.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { PdfService } from './pdf.service';
@Controller('pdf')
export class PdfController {
constructor(private service: PdfService) {}
@Post('/generate')
async generatePdf(@Body() data) {
return this.service.generatePdf(data);
}
}
4단계
이제 마지막으로 PDF 모듈을 앱에 연결합니다.
apps/pdf-server/src/app/app.module.ts
에서PdfModule
배열에 imports
추가@Module({
imports: [PdfModule], // <== here
controllers: [AppController],
providers: [AppService],
})
그게 다야!
이제 API를 시작하여 테스트할 수 있습니다.
pnpm nx run pdf-server:serve
curl --location --request POST 'localhost:3333/api/pdf/generate' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": "sample",
"metadata": {
"title": "Some random pdf",
"subject": "Some radom pdf",
"author": "Statale",
"producer": "pdf-generation-server",
"creator": "My awesome creator"
},
"document": {
"images": [
{
"id": 0,
"url": "https://source.unsplash.com/category/technology/1600x900"
},
{
"id": 1,
"url": "https://source.unsplash.com/category/current-events/1600x900"
}
],
"title": "Some random pdf",
"description": "<p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat,...</p>"
}
}'
Reference
이 문제에 관하여(React 및 Puppeteer: PDF 생성(pdf 생성 API)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/lwhiteley/react-and-puppeteer-pdf-generation-pdf-generation-api-c7o텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)