6.2 KiB
6.2 KiB
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, newsrc/lib/tools.tsentry:{ slug: 'stamp', title: 'Stempeln', description: 'Versehen Sie Seiten mit Textstempeln wie „Beglaubigt“ oder „Eilt“.', icon: 'stamp' }(stampis a real Lucide icon name) + icon map entry. - After load (
loadPdfDocumentfor 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 +
PdfPageGridselection 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.
- Options card:
Technical approach
Export (pdf-lib)
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.2descent approximation; verify visually. - Uppercasing happens in JS (
toUpperCase()— German umlauts are fine in WinAnsi: ä/ö/ü/ß are supported except ẞ; uppercase ß → SS viatoLocaleUpperCase('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
- Shared overlay helpers (or import if they exist) — watermark regression must stay green.
- Route UI: presets, inputs, preview overlay, page selection.
- Export + download.
- Registry + icon.
- 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).
- Polish + command gauntlet.
Verification
- Every preset chip fills the input; the stamp renders uppercase in preview and output.
- Date uses German
dd.mm.yyyyand 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.