Skip to content

Commit

Permalink
Merge pull request #1009 from MTES-MCT/campaign-pdf-upload
Browse files Browse the repository at this point in the history
fix: allow PDF upload for logo on campaign
  • Loading branch information
loicguillois authored Nov 22, 2024
2 parents f64479d + 5722316 commit 036a573
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 13 deletions.
73 changes: 61 additions & 12 deletions packages/draft/src/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<PDFImage> {
async embed(pdf: PDFDocument, image: Image): Promise<PDFImage | PDFEmbeddedPage> {
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) => {
Expand Down Expand Up @@ -180,22 +203,47 @@ function createTransformer(opts: TransformerOptions) {
* @param pdf
*/
async save(pdf: PDFDocument): Promise<Buffer> {
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);
Expand All @@ -222,6 +270,7 @@ interface Image {
content: string;
width: number;
height: number;
type: 'logo' | 'signature';
}

interface Position {
Expand Down
2 changes: 1 addition & 1 deletion packages/draft/src/templates/draft/draft.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ footer {

.footer__signature {
margin-left: 4rem;
width: 200px;
width: 280px;
}

.footer__signature p {
Expand Down

0 comments on commit 036a573

Please sign in to comment.