diff --git a/packages/draft/src/pdf.ts b/packages/draft/src/pdf.ts index ae3cc71c7..6a1afe127 100644 --- a/packages/draft/src/pdf.ts +++ b/packages/draft/src/pdf.ts @@ -2,7 +2,7 @@ import async from 'async'; import { ReadableStream } from 'node:stream/web'; import * as handlebars from 'handlebars'; import path from 'node:path'; -import { PDFDocument, PDFImage } from 'pdf-lib'; +import { PDFDocument, PDFEmbeddedPage, PDFImage } from 'pdf-lib'; import puppeteer from 'puppeteer'; import { match } from 'ts-pattern'; import { Logger } from '@zerologementvacant/utils'; @@ -89,13 +89,17 @@ function createTransformer(opts: TransformerOptions) { width: width, height: height, x: x, - y: y + y: y, + type: (image as Element)?.classList.contains('header__image') ? 'logo' : 'signature' }; }); }); // Retrieve images from the HTML only once if (images.length === 0) { - images.push(...elements); + images.push(...elements.map(element => ({ + ...element, + type: element.type as 'logo' | 'signature' + }))); } // Save image positions elements.forEach((element) => { @@ -147,10 +151,29 @@ function createTransformer(opts: TransformerOptions) { /** * Embed an image into the PDF. * @param pdf - * @param image A JPEG or PNG image encoded in base64 + * @param image A PDF, JPEG or PNG image encoded in base64 */ - async embed(pdf: PDFDocument, image: Image): Promise { + async embed(pdf: PDFDocument, image: Image): Promise { return match(image) + .when( + (image) => image.content.startsWith('data:application/pdf'), + async (image) => { + const content = image.content.substring( + image.content.indexOf('JVBER') + ); + + const binaryString = atob(content); + const len = binaryString.length; + const bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + const [embeddedPage] = await pdf.embedPdf(bytes.buffer, [0]); + return embeddedPage; + } + ) .when( (image) => image.content.startsWith('data:image/jpeg'), async (image) => { @@ -180,22 +203,47 @@ function createTransformer(opts: TransformerOptions) { * @param pdf */ async save(pdf: PDFDocument): Promise { + const firstImageHeight = { 'logo': 0, 'signature': 0 }; + const firstImageWidth = { 'logo': 0, 'signature': 0 }; + // Embed images into the PDF await async.forEach(images, async (image) => { const embed = await this.embed(pdf, image); + + let imageHeight = 0; + if (embed instanceof PDFImage) { + imageHeight = image.height; + } else if (embed instanceof PDFEmbeddedPage) { + // PDFs are vector images, so we use the max width defined for the header__image CSS class + imageHeight = 140 / (embed.width / embed.height); // Keep the aspect ratio as image.height is incorrect + } + pdf.getPages().forEach((page, i) => { const position = imagePositions.get(image.id)?.at(i); if (!position) { throw new Error('Image position not found'); } - page.drawImage(embed, { - x: toPoints(position.x), - // The Y-axis is inverted in the PDF specification - y: page.getHeight() - toPoints(position.y) - toPoints(image.height), - width: toPoints(image.width), - height: toPoints(image.height) - }); + if (embed instanceof PDFImage) { + page.drawImage(embed, { + x: toPoints(position.x), + // The Y-axis is inverted in the PDF specification + y: page.getHeight() - toPoints(image.height) - (image.type === 'signature' ? toPoints(position.y) : toPoints(40) + toPoints(firstImageHeight[image.type])), + width: toPoints(image.width), + height: toPoints(image.height) + }); + } else if (embed instanceof PDFEmbeddedPage) { + page.drawPage(embed, { + x: toPoints(position.x) - (image.type === 'signature' ? toPoints(40) : 0), + // The Y-axis is inverted in the PDF specification + y: page.getHeight() - toPoints(imageHeight) - (image.type === 'signature' ? toPoints(position.y) : toPoints(40) + toPoints(firstImageHeight[image.type])), + width: toPoints(140), + height: toPoints(imageHeight) + }); + + } }); + firstImageHeight[image.type] = firstImageHeight[image.type] === 0 ? imageHeight : 0; + firstImageWidth[image.type] = firstImageWidth[image.type] === 0 ? image.width : 0; }); const final = await pdf.save(); return Buffer.from(final); @@ -222,6 +270,7 @@ interface Image { content: string; width: number; height: number; + type: 'logo' | 'signature'; } interface Position { diff --git a/packages/draft/src/templates/draft/draft.css b/packages/draft/src/templates/draft/draft.css index 80a495b27..57f35b9cd 100644 --- a/packages/draft/src/templates/draft/draft.css +++ b/packages/draft/src/templates/draft/draft.css @@ -96,7 +96,7 @@ footer { .footer__signature { margin-left: 4rem; - width: 200px; + width: 280px; } .footer__signature p {