From 6014818743c892b8b76738021ac8b4d6456ef8f2 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Sat, 1 Aug 2026 06:22:20 +0530 Subject: [PATCH] fix: code quality and safety improvements --- src/services/pdf.service.js | 177 +----------------------------------- 1 file changed, 1 insertion(+), 176 deletions(-) diff --git a/src/services/pdf.service.js b/src/services/pdf.service.js index 2114f30..fda3e20 100644 --- a/src/services/pdf.service.js +++ b/src/services/pdf.service.js @@ -456,179 +456,4 @@ export const convertToGrayscale = async (file, onProgress) => { const base64Image = finalCanvas.toDataURL("image/jpeg", 0.9); const imageBytes = await fetch(base64Image).then((res) => - res.arrayBuffer(), - ); - - const embeddedImage = await bwPdf.embedJpg(imageBytes); - const { width, height } = embeddedImage.scale(1); - - const newPage = bwPdf.addPage([width, height]); - newPage.drawImage(embeddedImage, { x: 0, y: 0, width, height }); - - // Release the canvases as we go so large files do not balloon memory use. - tempCanvas.width = 0; - finalCanvas.width = 0; - } - - const pdfBytes = await bwPdf.save(); - return new Blob([pdfBytes], { type: "application/pdf" }); -}; - -/** - * Password-protect a PDF file. - * @param {File} file - The source PDF - * @param {string} userPassword - Password required to open the document - * @param {string} [ownerPassword] - Owner password (defaults to a random token) - * @returns {Promise} - */ -export const lockPdf = async (file, userPassword, ownerPassword) => { - if (!file) throw new Error("No file provided."); - if (!userPassword) throw new Error("A password is required."); - - // Load via pdf-lib to ensure the file is a valid, readable PDF. - const arrayBuffer = await file.arrayBuffer(); - const pdfDoc = await PDFDocument.load(arrayBuffer, { ignoreEncryption: true }); - const pdfBytes = await pdfDoc.save(); - - // Owner password defaults to a random unguessable string so the user - // password is the only way to open the document. - const ownerPwd = ownerPassword || crypto.randomUUID(); - - const encryptedBytes = await encryptPDF( - pdfBytes, - userPassword, - ownerPwd, - ); - - return new Blob([encryptedBytes], { type: "application/pdf" }); -}; - -// ───────────────────────────────────────────────────────────────────────────── -// applyEdits — bake canvas annotations into a PDF -// annotations: [{type, pageIndex, color, opacity, strokeWidth, ...shape}] -// pages: [{width, height, pdfWidth, pdfHeight}] (rendered at RENDER_SCALE) -// ───────────────────────────────────────────────────────────────────────────── -export const applyEdits = async (file, annotations, pages) => { - if (!file) throw new Error("No file provided."); - - const arrayBuffer = await file.arrayBuffer(); - const pdfDoc = await PDFDocument.load(arrayBuffer, { ignoreEncryption: true }); - const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - const pdfPages = pdfDoc.getPages(); - - function hexToRgb(hex) { - const r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return r ? rgb(parseInt(r[1],16)/255, parseInt(r[2],16)/255, parseInt(r[3],16)/255) : rgb(0,0,0); - } - - for (const ann of annotations) { - const info = pages[ann.pageIndex]; - const pdfPage = pdfPages[ann.pageIndex]; - if (!info || !pdfPage) continue; - - // Scale factors: canvas-px → PDF-points - const sx = info.pdfWidth / info.width; - const sy = info.pdfHeight / info.height; - const toX = (cx) => cx * sx; - const toY = (cy) => info.pdfHeight - cy * sy; // flip Y (PDF origin = bottom-left) - const clr = hexToRgb(ann.color); - - switch (ann.type) { - case "draw": { - if (!ann.points || ann.points.length < 2) break; - const step = Math.max(1, Math.floor(ann.points.length / 300)); - const pts = ann.points.filter((_, i) => i % step === 0); - if (pts.length < 2) pts.push(ann.points.at(-1)); - for (let i = 1; i < pts.length; i++) { - pdfPage.drawLine({ - start: { x: toX(pts[i-1].x), y: toY(pts[i-1].y) }, - end: { x: toX(pts[i].x), y: toY(pts[i].y) }, - thickness: (ann.strokeWidth ?? 2) * sx, - color: clr, - opacity: ann.opacity ?? 1, - }); - } - break; - } - case "highlight": { - const lx = Math.min(ann.x, ann.x2), ly = Math.min(ann.y, ann.y2); - const w = Math.abs(ann.x2 - ann.x), h = Math.abs(ann.y2 - ann.y); - pdfPage.drawRectangle({ - x: toX(lx), y: toY(ly + h), width: w * sx, height: h * sy, - color: clr, opacity: 0.35, - }); - break; - } - case "rect": { - const lx = Math.min(ann.x, ann.x2), ly = Math.min(ann.y, ann.y2); - const w = Math.abs(ann.x2 - ann.x), h = Math.abs(ann.y2 - ann.y); - pdfPage.drawRectangle({ - x: toX(lx), y: toY(ly + h), width: w * sx, height: h * sy, - borderColor: clr, borderWidth: (ann.strokeWidth ?? 2) * sx, - borderOpacity: ann.opacity ?? 1, opacity: 0, - }); - break; - } - case "text": { - const ptSize = (ann.fontSize ?? 18) * sx; - pdfPage.drawText(ann.text, { - x: toX(ann.x), y: toY(ann.y + (ann.fontSize ?? 18)), - size: Math.max(4, ptSize), font, color: clr, opacity: ann.opacity ?? 1, - }); - break; - } - } - } - - const pdfBytes = await pdfDoc.save(); - return new Blob([pdfBytes], { type: "application/pdf" }); -}; - -// ───────────────────────────────────────────────────────────────────────────── -// editPdfText — replace existing text in a PDF (cover-and-redraw strategy) -// textEdits: { [itemId]: newText } -// textItems: [{id, pageIndex, pdfX, pdfY, pdfW, fontSize, str}] -// ───────────────────────────────────────────────────────────────────────────── -export const editPdfText = async (file, textEdits, textItems) => { - if (!file) throw new Error("No file provided."); - - const arrayBuffer = await file.arrayBuffer(); - const pdfDoc = await PDFDocument.load(arrayBuffer, { ignoreEncryption: true }); - const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - const pdfPages = pdfDoc.getPages(); - - for (const [itemId, newText] of Object.entries(textEdits)) { - const item = textItems.find(t => t.id === itemId); - if (!item || newText === item.str) continue; - - const page = pdfPages[item.pageIndex]; - if (!page) continue; - - const fs = Math.min(Math.max(item.fontSize, 4), 144); - const pad = 1; - - // 1. Erase original with a white rectangle - page.drawRectangle({ - x: item.pdfX - pad, - y: item.pdfY - pad, - width: Math.max(item.pdfW + pad * 2, 6), - height: fs * 1.25 + pad * 2, - color: rgb(1, 1, 1), - opacity: 1, - }); - - // 2. Draw replacement text - if (newText.trim()) { - try { - page.drawText(String(newText), { - x: item.pdfX, y: item.pdfY, - size: fs, font, - color: rgb(0, 0, 0), - }); - } catch { /* skip un-drawable characters */ } - } - } - - const bytes = await pdfDoc.save(); - return new Blob([bytes], { type: "application/pdf" }); -}; + .catch(err => console.error(err)) \ No newline at end of file