- {recipientName} signed a document by using one of your direct links
+ {recipientName} {action} a document by using one of your direct links
diff --git a/packages/lib/client-only/download-pdf.ts b/packages/lib/client-only/download-pdf.ts
index 2e450de0c..830e3428a 100644
--- a/packages/lib/client-only/download-pdf.ts
+++ b/packages/lib/client-only/download-pdf.ts
@@ -18,7 +18,7 @@ export const downloadPDF = async ({ documentData, fileName }: DownloadPDFProps)
const baseTitle = (fileName ?? 'document').replace(/\.pdf$/, '');
downloadFile({
- filename: `${baseTitle}.pdf`,
+ filename: `${baseTitle}_signed.pdf`,
data: blob,
});
};
diff --git a/packages/lib/constants/billing.ts b/packages/lib/constants/billing.ts
index 0d8dee6e2..17178662d 100644
--- a/packages/lib/constants/billing.ts
+++ b/packages/lib/constants/billing.ts
@@ -4,6 +4,7 @@ export enum STRIPE_CUSTOMER_TYPE {
}
export enum STRIPE_PLAN_TYPE {
+ REGULAR = 'regular',
TEAM = 'team',
COMMUNITY = 'community',
ENTERPRISE = 'enterprise',
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,
+ };
+};
diff --git a/packages/lib/server-only/template/create-document-from-direct-template.ts b/packages/lib/server-only/template/create-document-from-direct-template.ts
index eeb639bb8..229851729 100644
--- a/packages/lib/server-only/template/create-document-from-direct-template.ts
+++ b/packages/lib/server-only/template/create-document-from-direct-template.ts
@@ -480,6 +480,7 @@ export const createDocumentFromDirectTemplate = async ({
// Send email to template owner.
const emailTemplate = createElement(DocumentCreatedFromDirectTemplateEmailTemplate, {
recipientName: directRecipientEmail,
+ recipientRole: directTemplateRecipient.role,
documentLink: `${formatDocumentsPath(document.team?.url)}/${document.id}`,
documentName: document.title,
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',