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..964556c83 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,32 @@ 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(); + + // PDFs can have pages that are rotated, which are correctly rendered in the frontend. + // However when we load the PDF in the backend, the rotation is applied. + // + // To account for this, we swap the width and height for pages that are rotated by 90/270 + // degrees. This is so we can calculate the virtual position the field was placed if it + // was correctly oriented in the frontend. + // + // Then when we insert the fields, we apply a transformation to the position of the field + // so it is rotated correctly. + if (isPageRotatedToLandscape) { + [pageWidth, pageHeight] = [pageHeight, pageWidth]; + } const fieldWidth = pageWidth * (Number(field.width) / 100); const fieldHeight = pageHeight * (Number(field.height) / 100); @@ -65,17 +91,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 +130,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 +171,32 @@ 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]; + } + + // Invert all the positions since it's rotated by 180 degrees. + if (pageRotationInDegrees === 180) { + xPos = pageWidth - xPos; + yPos = pageHeight - yPos; + } + + return { + xPos, + yPos, + }; +};