diff --git a/package-lock.json b/package-lock.json
index cdfd30c..be5c2a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3187,6 +3187,7 @@
}
}
},
+
"node_modules/@reown/appkit-pay": {
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/@reown/appkit-pay/-/appkit-pay-1.7.8.tgz",
@@ -3544,6 +3545,7 @@
}
}
},
+
"node_modules/@reown/appkit-wallet": {
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.8.tgz",
@@ -3853,6 +3855,7 @@
}
}
},
+
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
@@ -6523,6 +6526,7 @@
}
}
},
+
"node_modules/@walletconnect/window-getters": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.1.tgz",
diff --git a/src/pages/EditPdf/EditPdf.jsx b/src/pages/EditPdf/EditPdf.jsx
index 4c72d7e..66053b7 100644
--- a/src/pages/EditPdf/EditPdf.jsx
+++ b/src/pages/EditPdf/EditPdf.jsx
@@ -16,15 +16,17 @@ import { FREE_LIMITS, mbToBytes } from "../../config/limits";
// ── constants ────────────────────────────────────────────────────────────────
const RENDER_SCALE = 1.5;
const ANN_TOOLS = [
- { id: "draw", label: "Draw" },
- { id: "text", label: "Text" },
- { id: "highlight", label: "Highlight" },
- { id: "rect", label: "Rectangle" },
- { id: "eraser", label: "Eraser" },
+ { id: "draw", label: "Draw", icon: Pencil },
+ { id: "text", label: "Add Text Box", icon: Type },
+ { id: "highlight", label: "Highlight", icon: Highlighter },
+ { id: "rect", label: "Rectangle", icon: Square },
+ { id: "eraser", label: "Eraser", icon: Eraser },
];
const COLORS = ["#ef4444","#f97316","#eab308","#22c55e","#3b82f6","#a855f7","#ec4899","#000000","#ffffff"];
-const HINTS = { draw:"Click and drag freely", text:"Click to place a text box", highlight:"Drag to highlight an area", rect:"Drag to draw a rectangle", eraser:"Click any annotation to remove it" };
+const HINTS = { draw:"Click and drag freely", text:"Click anywhere on the page to place a new text box", highlight:"Drag to highlight an area", rect:"Drag to draw a rectangle", eraser:"Click any annotation to remove it" };
const CURSOR = { draw:"crosshair", text:"text", highlight:"crosshair", rect:"crosshair", eraser:"cell" };
+const TEXTBOX_PADDING_X = 4;
+const TEXTBOX_PADDING_Y = 2;
// ── canvas helpers ────────────────────────────────────────────────────────────
function drawAnn(ctx, ann) {
@@ -48,9 +50,7 @@ function drawAnn(ctx, ann) {
ctx.strokeRect(x, y, Math.abs((ann.x2??ann.x)-ann.x), Math.abs((ann.y2??ann.y)-ann.y)); break;
}
case "text":
- ctx.globalAlpha = ann.opacity ?? 1;
- ctx.font = `${ann.fontSize??18}px sans-serif`;
- ctx.fillText(ann.text, ann.x, ann.y); break;
+ break;
}
ctx.restore();
}
@@ -59,11 +59,14 @@ function hitTest(ann, x, y) {
const R = Math.max(10, (ann.strokeWidth??2)*3);
switch (ann.type) {
case "draw": return ann.points?.some(p => Math.hypot(p.x-x, p.y-y) < R);
- case "text": { const fs = ann.fontSize??18; return x>=ann.x-4 && x<=ann.x+ann.text.length*fs*0.6 && y>=ann.y-fs && y<=ann.y+4; }
+ case "text": { const fs = ann.fontSize??18; return x>=ann.x-4 && x<=ann.x+ann.text.length*fs*0.6 && y>=ann.y-4 && y<=ann.y+fs+4; }
default: return x>=Math.min(ann.x,ann.x2??ann.x)-4&&x<=Math.max(ann.x,ann.x2??ann.x)+4&&y>=Math.min(ann.y,ann.y2??ann.y)-4&&y<=Math.max(ann.y,ann.y2??ann.y)+4;
}
}
function getPos(e, el) { const r = el.getBoundingClientRect(); return { x: e.clientX-r.left, y: e.clientY-r.top }; }
+function isFormTarget(target) {
+ return target instanceof HTMLElement && ["TEXTAREA", "INPUT", "BUTTON"].includes(target.tagName);
+}
// ── component ─────────────────────────────────────────────────────────────────
export function EditPdf() {
@@ -93,6 +96,8 @@ export function EditPdf() {
const canvasRefs = useRef({});
const drawing = useRef(null);
+ const draggingText = useRef(null);
+ const skipTextPlacement = useRef(false);
const { isPremium, isWalletConnected: isConn, hasReachedGlobalLimit, incrementUsage } = useSubscription();
const LIMIT_MB = FREE_LIMITS.editPdf.maxFileSizeMb;
@@ -169,15 +174,45 @@ export function EditPdf() {
});
// ── 2. Draw annotation strokes on top ────────────────────────────────────
- annotations.filter(a => a.pageIndex === pi).forEach(a => drawAnn(ctx, a));
+ annotations.filter(a => a.pageIndex === pi && a.type !== "text").forEach(a => drawAnn(ctx, a));
if (inProgress) drawAnn(ctx, inProgress);
}
// ── annotation mouse handlers ────────────────────────────────────────────────
+ function openTextBox(pi, pos) {
+ setTextBox({
+ pageIndex: pi,
+ x: Math.max(8, pos.x),
+ y: Math.max(8, pos.y),
+ value: "",
+ });
+ }
+ function removeAnnotation(id) {
+ setAnnotations(prev => prev.filter(ann => ann.id !== id));
+ }
+ function startTextDrag(e, ann) {
+ if (mode !== "annotate") return;
+ if (tool === "eraser") {
+ e.stopPropagation();
+ removeAnnotation(ann.id);
+ return;
+ }
+ if (tool !== "text") return;
+ const pos = getPos(e, canvasRefs.current[ann.pageIndex]);
+ draggingText.current = {
+ id: ann.id,
+ pageIndex: ann.pageIndex,
+ offsetX: pos.x - ann.x,
+ offsetY: pos.y - ann.y,
+ };
+ skipTextPlacement.current = true;
+ e.stopPropagation();
+ }
+
function onMouseDown(e, pi) {
if (mode !== "annotate") return;
const pos = getPos(e, canvasRefs.current[pi]);
- if (tool === "text") { setTextBox({ pageIndex: pi, x: pos.x, y: pos.y, value: "" }); return; }
+ if (tool === "text") return;
if (tool === "eraser") {
setAnnotations(prev => {
const idx = [...prev].reverse().findIndex(a => a.pageIndex === pi && hitTest(a, pos.x, pos.y));
@@ -186,7 +221,26 @@ export function EditPdf() {
}
drawing.current = { pageIndex: pi, type: tool, color, strokeWidth: stroke, opacity, x: pos.x, y: pos.y, x2: pos.x, y2: pos.y, points: [pos] };
}
+ function onPageClick(e, pi) {
+ if (skipTextPlacement.current) {
+ skipTextPlacement.current = false;
+ return;
+ }
+ if (mode !== "annotate" || tool !== "text" || textBox || isFormTarget(e.target)) return;
+ const holder = e.currentTarget;
+ openTextBox(pi, getPos(e, holder));
+ }
function onMouseMove(e, pi) {
+ if (draggingText.current?.pageIndex === pi) {
+ const pos = getPos(e, canvasRefs.current[pi]);
+ const { id, offsetX, offsetY } = draggingText.current;
+ setAnnotations(prev => prev.map(ann => (
+ ann.id === id
+ ? { ...ann, x: Math.max(8, pos.x - offsetX), y: Math.max(8, pos.y - offsetY) }
+ : ann
+ )));
+ return;
+ }
if (!drawing.current || drawing.current.pageIndex !== pi) return;
const pos = getPos(e, canvasRefs.current[pi]);
drawing.current.x2 = pos.x; drawing.current.y2 = pos.y;
@@ -194,15 +248,35 @@ export function EditPdf() {
redraw(pi, drawing.current);
}
function onMouseUp(e, pi) {
+ if (draggingText.current?.pageIndex === pi) {
+ draggingText.current = null;
+ return;
+ }
if (!drawing.current || drawing.current.pageIndex !== pi) return;
const d = drawing.current; drawing.current = null;
const ok = d.type === "draw" ? d.points.length >= 2 : Math.abs(d.x2-d.x) > 3 || Math.abs(d.y2-d.y) > 3;
if (ok) setAnnotations(prev => [...prev, { ...d, id: Date.now() }]); else redraw(pi, null);
}
- function commitTextBox() {
- if (!textBox) return;
- if (textBox.value.trim()) setAnnotations(prev => [...prev, { id: Date.now(), type: "text", pageIndex: textBox.pageIndex, x: textBox.x, y: textBox.y + fontSize, text: textBox.value, color, fontSize, opacity, strokeWidth: 1 }]);
- setTextBox(null);
+ function commitTextBox(nextValue) {
+ setTextBox(current => {
+ if (!current) return null;
+ const value = typeof nextValue === "string" ? nextValue : current.value;
+ if (value.trim()) {
+ setAnnotations(prev => [...prev, {
+ id: Date.now(),
+ type: "text",
+ pageIndex: current.pageIndex,
+ x: current.x + TEXTBOX_PADDING_X,
+ y: current.y + TEXTBOX_PADDING_Y,
+ text: value,
+ color,
+ fontSize,
+ opacity,
+ strokeWidth: 1,
+ }]);
+ }
+ return null;
+ });
}
// ── save PDF (applies both annotations and text edits) ──────────────────────
@@ -241,7 +315,7 @@ export function EditPdf() {
Draw, annotate, highlight — or click existing text to edit it directly in the browser.
+Add text boxes, draw, and annotate your PDF pages — or switch modes to edit existing text already in the file.
{error &&