# Plan: Stempeln — Textstempel auf PDFs ## Goal Add a new tool **"Stempeln"** (`/tools/stamp`) that places opaque, framed text stamps on PDF pages — "BEGLAUBIGT", "EILT", "ENTWURF", "KOPIE", "AUSFERTIGUNG", plus free text with an optional date, or an image. This is a staple of German legal/case-file work and is deliberately separate from the watermark tool: - Watermark = semi-transparent, decorative/repetitive, many positions. - Stamp = fully opaque, framed, authoritative marking, usually one per page. Pure pdf-lib drawing (text + rectangle border): lossless, instant. No new dependencies. --- ## Open decisions (defaults marked ✅) ### D1 — Stamp look - ✅ **A. Uppercase bold text inside a plain rectangle border** (2 pt border, padding 8 pt, default color red `#cc1a1a`, matching the existing brand red used by the watermark default). pdf-lib has no rounded-rectangle primitive; plain rectangle is the honest option. - B. Borderless colored text. Possible but reads as a watermark, not a stamp. ### D2 — Presets - ✅ **Editable preset chips + free text.** Chips: BEGLAUBIGT, AUSFERTIGUNG, KOPIE, EILT, ENTWURF, ERLEDIGT. Selecting a chip fills the text input; the input stays editable. Optional checkbox "Datum anhängen" appends `dd.mm.yyyy` (German format). - B. Fixed presets only. Rejected: courts/firms love their own wording. ### D3 — Relation to the watermark tool - ✅ **Separate tool**, sharing extracted helpers from `src/lib/pdf-overlay.ts` (position coordinate computation + hex color parsing — see page-numbers-plan; whichever plan is implemented first creates the module). Keeps discovery simple (one tile per job) and avoids overloading the watermark UI with a mode switch. --- ## UX - Route `/tools/stamp`, new `src/lib/tools.ts` entry: `{ slug: 'stamp', title: 'Stempeln', description: 'Versehen Sie Seiten mit Textstempeln wie „Beglaubigt“ oder „Eilt“.', icon: 'stamp' }` (`stamp` is a real Lucide icon name) + icon map entry. - After load (`loadPdfDocument` for thumbnails): - **Options card**: - Preset chip row (buttons; selected chip highlighted). - Text input (bound, default "BEGLAUBIGT"), "Datum anhängen" checkbox. - Schriftgröße slider (12–48 pt, default 24). - Farbe color input (default `#cc1a1a`). - Position select — same six positions + center as watermark (reuses shared position helper; no "diagonal" for stamps). - Rotation slider (−45°…45°, default 0) — stamps are often applied slightly tilted. - Apply-to: "Auf alle Seiten anwenden" checkbox + `PdfPageGrid` selection when off (watermark pattern). - **Live preview**: rendered current page (pdfjs) with an absolutely-positioned overlay div styled like the stamp (border, uppercase, color, rotation) at the chosen position; page navigation. Approximation is fine, same approach as page-numbers-plan. - Primary button **"Stempel anwenden & herunterladen"** → `{baseName}_stempel.pdf`. ## Technical approach ### Export (pdf-lib) ```ts const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold); const stampText = `${textInput.trim().toUpperCase()}${withDate ? ` ${formatGermanDate(new Date())}` : ''}`; const textWidth = font.widthOfTextAtSize(stampText, size); const boxWidth = textWidth + 2 * padding; const boxHeight = font.heightAtSize(size) + 2 * padding; for (const pageIndex of targetPages) { const { width, height } = pages[pageIndex].getSize(); const { x, y } = getPositionCoords(width, height, boxWidth, boxHeight, position); // shared helper pages[pageIndex].drawRectangle({ x, y, width: boxWidth, height: boxHeight, borderColor: parseHexColor(colorValue), borderWidth: 2 }); pages[pageIndex].drawText(stampText, { x: x + padding, y: y + padding + /* baseline adjust */, size, font, color: parseHexColor(colorValue), rotate: degrees(rotationValue) }); } ``` Details: - Rotation is applied to the text; the border rectangle stays axis-aligned for v1 (rotated rectangles need manual path drawing — follow-up if users care). - Baseline adjustment: use `font.heightAtSize(size) * 0.2` descent approximation; verify visually. - Uppercasing happens in JS (`toUpperCase()` — German umlauts are fine in WinAnsi: ä/ö/ü/ß are supported except ẞ; uppercase ß → SS via `toLocaleUpperCase('de')` acceptable). - Encrypted input → German error + decrypt hint (shared pattern). ### Shared helpers Depends on `src/lib/pdf-overlay.ts` (position presets + `parseHexColor`). If page-numbers or stamp ships first, it creates the module and rewires watermark; the second consumer just imports. ## Files changed - `src/routes/tools/stamp/+page.svelte` — new route. - `src/lib/pdf-overlay.ts` — shared helpers (created here if not already present). - `src/lib/tools.ts`, `src/routes/+page.svelte` — registry + icon map. - `tests/e2e/toolbox.spec.ts` — spec block. - `agents.md` — structure update. ## Implementation phases 1. Shared overlay helpers (or import if they exist) — watermark regression must stay green. 2. Route UI: presets, inputs, preview overlay, page selection. 3. Export + download. 4. Registry + icon. 5. Playwright: preset "EILT" on all pages of a 3-page PDF → output valid, 3 pages; custom text - date checkbox adds text; selection-only mode leaves other pages unmodified (compare page content-stream sizes via pdf-lib if practical, else file-level validity + count). 6. Polish + command gauntlet. ## Verification - Every preset chip fills the input; the stamp renders uppercase in preview and output. - Date uses German `dd.mm.yyyy` and updates on the export day (not cached). - Rotation visually matches preview within tolerance. - Watermark tool unchanged (regression). ## Risks - **Text width overflow** on small pages with long custom text + huge font: clamp computed box to page size minus margins and shrink font if needed (simple guard loop), with a German hint. - Preview overlay approximation may differ a few points from export — acceptable, labeled. ## Out of scope - Image stamps (logo stamps) — users can route through the watermark image mode for that. - Rotated border rectangles, multi-stamps per page in one run (run the tool twice; output can be re-loaded). - Stamp "libraries" persisted across sessions.