From 017a76ef5d57d06aa9e67b3d688b73ceb892cc88 Mon Sep 17 00:00:00 2001 From: Timur Ercan Date: Sat, 18 Feb 2023 14:37:26 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20=F0=9F=93=91=20pdf=20helpers,=20?= =?UTF-8?q?todos=20for=20signign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/next.config.js | 1 + apps/web/package.json | 2 + apps/web/pages/api/documents/[id]/sign.ts | 7 +- package-lock.json | 105 ++++++++++++++++++++++ package.json | 1 + packages/pdf/index.ts | 2 + packages/pdf/insertImageInPDF.ts | 25 ++++++ packages/pdf/insertTextInPDF.ts | 27 ++++++ packages/pdf/package.json | 7 ++ 9 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 packages/pdf/index.ts create mode 100644 packages/pdf/insertImageInPDF.ts create mode 100644 packages/pdf/insertTextInPDF.ts create mode 100644 packages/pdf/package.json diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 2c0429327..c4b4781ff 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -10,6 +10,7 @@ const withTM = require("next-transpile-modules")([ "@documenso/prisma", "@documenso/lib", "@documenso/ui", + "@documenso/pdf", "@documenso/features", ]); const plugins = []; diff --git a/apps/web/package.json b/apps/web/package.json index 1dde04517..1c283b246 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -10,6 +10,7 @@ "db-studio": "prisma db studio" }, "dependencies": { + "@documenso/pdf": "*", "@documenso/prisma": "*", "@documenso/ui": "*", "@headlessui/react": "^1.7.4", @@ -19,6 +20,7 @@ "@types/filesystem": "^0.0.32", "@types/react-dom": "18.0.9", "avatar-from-initials": "^1.0.3", + "base64-arraybuffer": "^1.0.2", "bcryptjs": "^2.4.3", "dotenv": "^16.0.3", "eslint": "8.27.0", diff --git a/apps/web/pages/api/documents/[id]/sign.ts b/apps/web/pages/api/documents/[id]/sign.ts index b3f76c210..10c110011 100644 --- a/apps/web/pages/api/documents/[id]/sign.ts +++ b/apps/web/pages/api/documents/[id]/sign.ts @@ -8,6 +8,7 @@ import { NextApiRequest, NextApiResponse } from "next"; import { SigningStatus, DocumentStatus } from "@prisma/client"; import { getDocument } from "@documenso/lib/query"; import { Document as PrismaDocument } from "@prisma/client"; +import { insertImageInPDF, insertTextInPDF } from "@documenso/pdf"; async function postHandler(req: NextApiRequest, res: NextApiResponse) { const existingUser = await getUserFromToken(req, res); @@ -33,7 +34,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { if (!document) res.status(404).end(`No document found.`); - // save signature to db for later use + // todo save signature to db for later use await prisma.recipient.update({ where: { @@ -61,7 +62,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }); if (unsignedRecipients.length === 0) { - // if everybody signed insert images and create signature + // todo if everybody signed insert images and create signature await prisma.document.update({ where: { id: recipient.documentId, @@ -70,7 +71,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { status: DocumentStatus.COMPLETED, }, }); - // send notifications + // todo send notifications } return res.status(200).end(); diff --git a/package-lock.json b/package-lock.json index 9ab94e74e..e733ac2e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "next-auth": "^4.18.3", "next-transpile-modules": "^10.0.0", "npm": "^9.1.3", + "pdf-lib": "^1.17.1", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.41.5", @@ -45,6 +46,7 @@ "name": "@documenso/web", "version": "0.1.0", "dependencies": { + "@documenso/pdf": "*", "@documenso/prisma": "*", "@documenso/ui": "*", "@headlessui/react": "^1.7.4", @@ -54,6 +56,7 @@ "@types/filesystem": "^0.0.32", "@types/react-dom": "18.0.9", "avatar-from-initials": "^1.0.3", + "base64-arraybuffer": "^1.0.2", "bcryptjs": "^2.4.3", "dotenv": "^16.0.3", "eslint": "8.27.0", @@ -153,6 +156,10 @@ "resolved": "packages/lib", "link": true }, + "node_modules/@documenso/pdf": { + "resolved": "packages/pdf", + "link": true + }, "node_modules/@documenso/prisma": { "resolved": "packages/prisma", "link": true @@ -560,6 +567,22 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "dependencies": { + "pako": "^1.0.10" + } + }, "node_modules/@prisma/client": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.9.0.tgz", @@ -1491,6 +1514,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -6801,6 +6832,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6849,6 +6885,22 @@ "node": ">=8" } }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/pdfjs-dist": { "version": "2.16.105", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz", @@ -8479,6 +8531,10 @@ "bcryptjs": "^2.4.3" } }, + "packages/pdf": { + "name": "@documenso/pdf", + "version": "0.0.0" + }, "packages/prisma": { "name": "@documenso/prisma", "version": "0.0.0", @@ -8535,6 +8591,9 @@ "bcryptjs": "^2.4.3" } }, + "@documenso/pdf": { + "version": "file:packages/pdf" + }, "@documenso/prisma": { "version": "file:packages/prisma", "requires": { @@ -8562,6 +8621,7 @@ "@documenso/web": { "version": "file:apps/web", "requires": { + "@documenso/pdf": "*", "@documenso/prisma": "*", "@documenso/ui": "*", "@headlessui/react": "^1.7.4", @@ -8578,6 +8638,7 @@ "@types/react-resizable": "^3.0.3", "autoprefixer": "^10.4.13", "avatar-from-initials": "^1.0.3", + "base64-arraybuffer": "^1.0.2", "bcryptjs": "^2.4.3", "dotenv": "^16.0.3", "eslint": "8.27.0", @@ -8854,6 +8915,22 @@ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.0.2.tgz", "integrity": "sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==" }, + "@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "requires": { + "pako": "^1.0.6" + } + }, + "@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "requires": { + "pako": "^1.0.10" + } + }, "@prisma/client": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.9.0.tgz", @@ -9609,6 +9686,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -13271,6 +13353,11 @@ "p-limit": "^3.0.2" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -13304,6 +13391,24 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "requires": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "pdfjs-dist": { "version": "2.16.105", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz", diff --git a/package.json b/package.json index c16cdfb15..ad659b8e7 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "next-auth": "^4.18.3", "next-transpile-modules": "^10.0.0", "npm": "^9.1.3", + "pdf-lib": "^1.17.1", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.41.5", diff --git a/packages/pdf/index.ts b/packages/pdf/index.ts new file mode 100644 index 000000000..574128097 --- /dev/null +++ b/packages/pdf/index.ts @@ -0,0 +1,2 @@ +export { insertTextInPDF } from "./insertTextInPDF"; +export { insertImageInPDF } from "./insertImageInPDF"; diff --git a/packages/pdf/insertImageInPDF.ts b/packages/pdf/insertImageInPDF.ts new file mode 100644 index 000000000..c8ca0ba3e --- /dev/null +++ b/packages/pdf/insertImageInPDF.ts @@ -0,0 +1,25 @@ +import { degrees, PDFDocument, PDFImage, rgb, StandardFonts } from "pdf-lib"; + +export async function insertImageInPDF( + pdfAsBase64: string, + image: string | Uint8Array | ArrayBuffer, + positionX: number, + positionY: number, + page: number = 0 +): Promise { + const existingPdfBytes = pdfAsBase64; + const pdfDoc = await PDFDocument.load(existingPdfBytes); + const pages = pdfDoc.getPages(); + const pdfPage = pages[page]; + const pngImage = await pdfDoc.embedPng(image); + + pdfPage.drawImage(pngImage, { + x: positionX, + y: positionY, + width: pngImage.width, + height: pngImage.height, + }); + + const pdfAsUint8Array = await pdfDoc.save(); + return Buffer.from(pdfAsUint8Array).toString("base64"); +} diff --git a/packages/pdf/insertTextInPDF.ts b/packages/pdf/insertTextInPDF.ts new file mode 100644 index 000000000..99c685413 --- /dev/null +++ b/packages/pdf/insertTextInPDF.ts @@ -0,0 +1,27 @@ +import { degrees, PDFDocument, rgb, StandardFonts } from "pdf-lib"; + +export async function insertTextInPDF( + pdfAsBase64: string, + text: string, + positionX: number, + positionY: number, + page: number = 0 +): Promise { + const existingPdfBytes = pdfAsBase64; + + const pdfDoc = await PDFDocument.load(existingPdfBytes); + const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); + + const pages = pdfDoc.getPages(); + const firstPage = pages[page]; + firstPage.drawText(text, { + x: positionX, + y: positionY, + size: 25, + font: helveticaFont, + color: rgb(0, 0, 0), + }); + + const pdfAsUint8Array = await pdfDoc.save(); + return Buffer.from(pdfAsUint8Array).toString("base64"); +} diff --git a/packages/pdf/package.json b/packages/pdf/package.json new file mode 100644 index 000000000..eabd3e608 --- /dev/null +++ b/packages/pdf/package.json @@ -0,0 +1,7 @@ +{ + "name": "@documenso/pdf", + "version": "0.0.0", + "private": true, + "main": "index.ts", + "dependencies": {} +}