From 9fbc61a04d3d55f12d74e2c3a30abdd65169973a Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 11 Jun 2024 19:04:55 +1000 Subject: [PATCH] fix: insert fields correctly for rotated pdfs --- .../server-only/pdf/insert-field-in-pdf.ts | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/lib/server-only/pdf/insert-field-in-pdf.ts b/packages/lib/server-only/pdf/insert-field-in-pdf.ts index 8ff575d65..7b37c367f 100644 --- a/packages/lib/server-only/pdf/insert-field-in-pdf.ts +++ b/packages/lib/server-only/pdf/insert-field-in-pdf.ts @@ -1,6 +1,7 @@ // https://github.com/Hopding/pdf-lib/issues/20#issuecomment-412852821 import fontkit from '@pdf-lib/fontkit'; -import { PDFDocument } from 'pdf-lib'; +import { PDFDocument, RotationTypes, degrees, radiansToDegrees } from 'pdf-lib'; +import { match } from 'ts-pattern'; import { DEFAULT_HANDWRITING_FONT_SIZE, @@ -37,7 +38,25 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu throw new Error(`Page ${field.page} does not exist`); } - const { width: pageWidth, height: pageHeight } = page.getSize(); + const pageRotation = page.getRotation(); + + let pageRotationInDegrees = match(pageRotation.type) + .with(RotationTypes.Degrees, () => pageRotation.angle) + .with(RotationTypes.Radians, () => radiansToDegrees(pageRotation.angle)) + .exhaustive(); + + // Round to the closest multiple of 90 degrees. + pageRotationInDegrees = Math.round(pageRotationInDegrees / 90) * 90; + + const isPageRotatedToLandscape = pageRotationInDegrees === 90 || pageRotationInDegrees === 270; + + let { width: pageWidth, height: pageHeight } = page.getSize(); + + // For landscape pages, swap the width and height so we can calculate item position the same way + // the user placed it in the editor. + if (isPageRotatedToLandscape) { + [pageWidth, pageHeight] = [pageHeight, pageWidth]; + } const fieldWidth = pageWidth * (Number(field.width) / 100); const fieldHeight = pageHeight * (Number(field.height) / 100); @@ -65,17 +84,31 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu imageWidth = imageWidth * scalingFactor; imageHeight = imageHeight * scalingFactor; - const imageX = fieldX + (fieldWidth - imageWidth) / 2; + let imageX = fieldX + (fieldWidth - imageWidth) / 2; let imageY = fieldY + (fieldHeight - imageHeight) / 2; // Invert the Y axis since PDFs use a bottom-left coordinate system imageY = pageHeight - imageY - imageHeight; + if (pageRotationInDegrees !== 0) { + const adjustedPosition = adjustPositionForRotation( + pageWidth, + pageHeight, + imageX, + imageY, + pageRotationInDegrees, + ); + + imageX = adjustedPosition.xPos; + imageY = adjustedPosition.yPos; + } + page.drawImage(image, { x: imageX, y: imageY, width: imageWidth, height: imageHeight, + rotate: degrees(pageRotationInDegrees), }); } else { const longestLineInTextForWidth = field.customText @@ -90,17 +123,31 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize); textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize); - const textX = fieldX + (fieldWidth - textWidth) / 2; + let textX = fieldX + (fieldWidth - textWidth) / 2; let textY = fieldY + (fieldHeight - textHeight) / 2; // Invert the Y axis since PDFs use a bottom-left coordinate system textY = pageHeight - textY - textHeight; + if (pageRotationInDegrees !== 0) { + const adjustedPosition = adjustPositionForRotation( + pageWidth, + pageHeight, + textX, + textY, + pageRotationInDegrees, + ); + + textX = adjustedPosition.xPos; + textY = adjustedPosition.yPos; + } + page.drawText(field.customText, { x: textX, y: textY, size: fontSize, font, + rotate: degrees(pageRotationInDegrees), }); } @@ -117,3 +164,33 @@ export const insertFieldInPDFBytes = async ( return await pdfDoc.save(); }; + +const adjustPositionForRotation = ( + pageWidth: number, + pageHeight: number, + xPos: number, + yPos: number, + pageRotationInDegrees: number, +) => { + if (pageRotationInDegrees === 270) { + xPos = pageWidth - xPos; + + [xPos, yPos] = [yPos, xPos]; + } + + if (pageRotationInDegrees === 90) { + yPos = pageHeight - yPos; + + [xPos, yPos] = [yPos, xPos]; + } + + if (pageRotationInDegrees === 180) { + xPos = pageWidth - xPos; + yPos = pageHeight - yPos; + } + + return { + xPos, + yPos, + }; +};