Skip to content

Commit ae4e2c5

Browse files
committed
feat(editor): v1.20.0 — Fill & Sign mode with reusable account signatures
Adobe-style Fill & Sign: a toolbar button puts the document in fill mode (form fields clickable, forms panel -> fill), and a capture dialog inserts a signature or initials created by Draw (freehand, ink-trimmed), Type (handwriting font) or Upload, dropped as a movable image element (existing addImage path). Signatures can be saved to the account once and reused in one click. Backend (built + adversarially reviewed via workflow): - Prisma model UserSignature (user_signatures, additive; no FK/relation so `prisma db push` stays non-breaking on the shared Alembic DB). - /api/user/signatures GET/POST/DELETE, all requireSession() + ownership-scoped (GET where userId; DELETE deleteMany{id,userId}) => no IDOR. Validation: base64 raster png/jpeg/webp only, decoded <=1.5MB + Content-Length pre-check <=3MB, dims positive int <=4000. Resilient to the not-yet-created table (P2021 -> GET []/POST 503). dataUrl never logged. 20 vitest cases. Integration: Tool union += 'fill_sign'; toolbar buttons (Fill & Sign + Insert signature) + i18n fr/en; page.tsx wiring (fill mode + placement + save). NOTE: account-save needs a one-time additive `prisma db push` on the server to create user_signatures; until then sign+insert works and saving degrades gracefully.
1 parent 4828d04 commit ae4e2c5

12 files changed

Lines changed: 1395 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ All notable changes to GigaPDF are documented here.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.20.0] - 2026-07-02
9+
10+
### Added — "Fill & Sign" mode with reusable signatures (Adobe-style)
11+
12+
- **A new "Fill & Sign" toolbar button** puts the document in fill mode — the form
13+
fields become clickable and the forms panel switches to filling — so you can
14+
complete a form without leaving the page.
15+
- **Insert a signature or initials** from a capture dialog offering three ways to
16+
create one, exactly like Adobe: **Draw** (freehand, trimmed to the ink),
17+
**Type** (a handwriting font), or **Upload** an image. The signature drops onto
18+
the page as a movable, resizable stamp.
19+
- **Save signatures to your account, once.** A "Save to my account" option stores
20+
the signature (or initials) so it appears in the dialog for one-click reuse on
21+
any future document; each saved item can be deleted. Stored per user, isolated
22+
from other accounts.
23+
- All saved-signature endpoints authenticate the caller and are ownership-scoped
24+
(you can only ever see or delete your own). The image is validated (raster PNG/
25+
JPEG/WebP, size-capped) and never logged.
26+
827
## [1.19.3] - 2026-07-02
928

1029
### Fixed — document fills the canvas on open; form-field names accept spaces

apps/web/messages/en.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,26 @@
19041904
"appliedTitle": "Document signed",
19051905
"appliedDescription": "The digital signature has been applied to the document."
19061906
},
1907+
"signature": {
1908+
"title": "Add a signature",
1909+
"subtitle": "Draw, type or upload your signature, then insert it into the document.",
1910+
"kindSignature": "Signature",
1911+
"kindInitials": "Initials",
1912+
"tabDraw": "Draw",
1913+
"tabType": "Type",
1914+
"tabUpload": "Upload",
1915+
"clear": "Clear",
1916+
"typePlaceholder": "Type your name",
1917+
"uploadHint": "PNG, JPEG or SVG, 3 MB maximum.",
1918+
"uploadTooLarge": "The image exceeds the 3 MB limit.",
1919+
"saveToAccount": "Save to my account",
1920+
"savedTitle": "Saved signatures",
1921+
"savedEmpty": "No saved signatures yet.",
1922+
"delete": "Delete",
1923+
"insert": "Insert",
1924+
"cancel": "Cancel",
1925+
"drawHint": "Draw your signature in the box below."
1926+
},
19071927
"compress": {
19081928
"title": "Compress PDF",
19091929
"currentSize": "Current size: {size}",
@@ -2046,6 +2066,8 @@
20462066
"save": "Save",
20472067
"undo": "Undo",
20482068
"redo": "Redo",
2069+
"fillSign": "Fill & Sign",
2070+
"insertSignature": "Insert a signature or initials",
20492071
"zoomIn": "Zoom in",
20502072
"zoomOut": "Zoom out",
20512073
"fitWidth": "Fit width",

apps/web/messages/fr.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,26 @@
19041904
"appliedTitle": "Document signé",
19051905
"appliedDescription": "La signature numérique a été appliquée au document."
19061906
},
1907+
"signature": {
1908+
"title": "Ajouter une signature",
1909+
"subtitle": "Dessinez, saisissez ou importez votre signature, puis insérez-la dans le document.",
1910+
"kindSignature": "Signature",
1911+
"kindInitials": "Initiales",
1912+
"tabDraw": "Dessiner",
1913+
"tabType": "Saisir",
1914+
"tabUpload": "Importer",
1915+
"clear": "Effacer",
1916+
"typePlaceholder": "Saisissez votre nom",
1917+
"uploadHint": "PNG, JPEG ou SVG, 3 Mo maximum.",
1918+
"uploadTooLarge": "L'image dépasse la limite de 3 Mo.",
1919+
"saveToAccount": "Enregistrer dans mon compte",
1920+
"savedTitle": "Signatures enregistrées",
1921+
"savedEmpty": "Aucune signature enregistrée pour le moment.",
1922+
"delete": "Supprimer",
1923+
"insert": "Insérer",
1924+
"cancel": "Annuler",
1925+
"drawHint": "Dessinez votre signature dans le cadre ci-dessous."
1926+
},
19071927
"compress": {
19081928
"title": "Compresser le PDF",
19091929
"currentSize": "Taille actuelle : {size}",
@@ -2046,6 +2066,8 @@
20462066
"save": "Enregistrer",
20472067
"undo": "Annuler",
20482068
"redo": "Rétablir",
2069+
"fillSign": "Remplir et signer",
2070+
"insertSignature": "Insérer une signature ou un paraphe",
20492071
"zoomIn": "Zoom avant",
20502072
"zoomOut": "Zoom arrière",
20512073
"fitWidth": "Ajuster à la largeur",

apps/web/prisma/schema.prisma

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,26 @@ model font_cache {
506506
507507
@@index([last_used_at], map: "idx_font_cache_last_used")
508508
}
509+
510+
// ─────────────────────────────────────────────────────────────────────────────
511+
// Saved signatures — reusable "Fill & Sign" stamps (signature + initials)
512+
//
513+
// Each user keeps a small set of hand-drawn / typed signature images, stored as
514+
// data: URLs so they can be dropped straight onto a page without a round-trip to
515+
// object storage. Rows are scoped per user (bare `user_id`, matching the
516+
// text-based owner_id convention used across the app — no FK to `users` so this
517+
// table can be added additively via `prisma db push` without touching auth).
518+
// ─────────────────────────────────────────────────────────────────────────────
519+
model UserSignature {
520+
id String @id @default(uuid())
521+
userId String @map("user_id")
522+
kind String @default("signature")
523+
dataUrl String @map("data_url")
524+
width Int
525+
height Int
526+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
527+
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
528+
529+
@@index([userId], map: "idx_user_signatures_user_id")
530+
@@map("user_signatures")
531+
}

apps/web/src/app/(app)/editor/[id]/page.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ import {
199199
import { bakeOutline } from "@/components/editor/lib/outline-bake";
200200
import { ResizePageDialog } from "@/components/editor/resize-page-dialog";
201201
import { RedactPiiDialog } from "@/components/editor/redact-pii-dialog";
202+
import { SignatureCaptureDialog } from "@/components/editor/signature-capture-dialog";
202203
import type { ExportFormat } from "@/components/editor/lib/export-formats";
203204
import type { HeaderFooterSpec } from "@qrcommunication/gigapdf-lib";
204205
import { clientLogger } from "@/lib/client-logger";
@@ -4172,6 +4173,8 @@ export default function EditorPage() {
41724173

41734174
// --- PII auto-detect redaction --------------------------------------------
41744175
const [showRedactPiiDialog, setShowRedactPiiDialog] = useState(false);
4176+
// Fill & Sign — signature/initials capture dialog visibility.
4177+
const [signatureDialogOpen, setSignatureDialogOpen] = useState(false);
41754178

41764179
// Redact every auto-detected PII region (whole text runs) across the whole
41774180
// document. Reuses the manual redaction baking path: build per-page geometry,
@@ -4658,6 +4661,35 @@ export default function EditorPage() {
46584661
fileInputRef.current?.click();
46594662
}, []);
46604663

4664+
// Fill & Sign (mode Adobe) : passe le document en mode remplissage — le panneau
4665+
// formulaire bascule en « fill » et l'overlay des champs devient cliquable.
4666+
const handleFillSign = useCallback(() => {
4667+
setFormsMode("fill");
4668+
if (!showFormsPanel) toggleFormsPanel();
4669+
}, [setFormsMode, showFormsPanel, toggleFormsPanel]);
4670+
4671+
// Ouvre le dialog de capture (dessin / texte / import) signature ou paraphe.
4672+
const handleInsertSignature = useCallback(() => {
4673+
setSignatureDialogOpen(true);
4674+
}, []);
4675+
4676+
// Insère la signature/paraphe capturé comme élément image, déplaçable et
4677+
// redimensionnable, exactement comme un ajout d'image (même chemin de save).
4678+
const handleSignatureInsert = useCallback(
4679+
(sig: {
4680+
dataUrl: string;
4681+
width: number;
4682+
height: number;
4683+
kind: "signature" | "initials";
4684+
}) => {
4685+
canvasHandle?.addImage(sig.dataUrl, sig.width, sig.height);
4686+
setSignatureDialogOpen(false);
4687+
setDirty(true);
4688+
saveWithPriority("immediate");
4689+
},
4690+
[canvasHandle, setDirty, saveWithPriority],
4691+
);
4692+
46614693
// Handler pour le chargement de l'image
46624694
const handleImageFileChange = useCallback(
46634695
(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -5288,6 +5320,8 @@ export default function EditorPage() {
52885320
onBringToFront={handleBringToFront}
52895321
onSendToBack={handleSendToBack}
52905322
onAddImage={handleAddImage}
5323+
onFillSign={handleFillSign}
5324+
onInsertSignature={handleInsertSignature}
52915325
onInsertTable={handleInsertTable}
52925326
onInsertLink={handleInsertLink}
52935327
onRemoveLink={handleRemoveLink}
@@ -5733,6 +5767,13 @@ export default function EditorPage() {
57335767
onConfirm={handleRedactPiiAuto}
57345768
/>
57355769

5770+
{/* Remplir & Signer — capture (dessin / texte / import) + signatures du compte */}
5771+
<SignatureCaptureDialog
5772+
open={signatureDialogOpen}
5773+
onClose={() => setSignatureDialogOpen(false)}
5774+
onInsert={handleSignatureInsert}
5775+
/>
5776+
57365777
{/* Status bar */}
57375778
<footer className="flex items-center justify-between border-t px-4 py-1.5 text-xs text-muted-foreground">
57385779
<div className="flex items-center gap-4">

apps/web/src/app/(site)/[locale]/(legal)/changelog/changelog-content.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ interface ChangelogEntry {
1414
}
1515

1616
const changelog: ChangelogEntry[] = [
17+
{
18+
version: "1.20.0",
19+
date: "2026-07-02",
20+
type: "minor",
21+
changes: [
22+
{ type: "feature", description: "Fill & Sign mode (Adobe-style): a new toolbar button puts the document in fill mode — form fields become clickable — so you can complete a form in place." },
23+
{ type: "feature", description: "Insert a signature or initials from a capture dialog with three ways to create one: Draw (freehand, trimmed to the ink), Type (a handwriting font), or Upload an image. It drops onto the page as a movable, resizable stamp." },
24+
{ type: "feature", description: "Save signatures to your account once: a saved signature or initials reappears in the dialog for one-click reuse on any future document, and can be deleted. Stored per user, isolated from other accounts, image validated and never logged." },
25+
],
26+
},
1727
{
1828
version: "1.19.3",
1929
date: "2026-07-02",

0 commit comments

Comments
 (0)