6.3 KiB
6.3 KiB
Plan: PDF-Metadaten — anzeigen und bereinigen
Goal
Add a new tool "Metadaten" (/tools/metadata) with two jobs:
- Inspect a PDF: show document metadata (title, author, subject, keywords, creator, producer, creation/modification dates), page count, file size, and encryption state — lawyers need to know what a document reveals before forwarding it.
- Clean a PDF: strip the metadata fields and re-save, so the cleaned file can be shared without leaking software, author names, or edit history.
No rasterization is involved — this tool works directly with pdf-lib and preserves the document fully (vector text stays selectable). No new dependencies.
Open decisions (defaults marked ✅)
D1 — Cleaning strategy
- ✅ A. Neutralize fields + re-save with pdf-lib. Set title/author/subject/keywords to empty strings, creator/producer to neutral values, reset creation/modification dates to the cleaning moment. Saving through pdf-lib rebuilds the file without unreferenced objects. Simple and dependency-free.
- B. Also attempt to remove embedded files/attachments. pdf-lib has no reliable public API to enumerate or remove all embedded-file entries; defer to D2.
D2 — Embedded-file detection
- ✅ A. Best-effort heuristic warning. Scan the raw bytes for
/EmbeddedFileand/EmbeddedFilesmarkers; if found, show "Die Datei enthält möglicherweise eingebettete Anlagen. Diese werden beim Bereinigen nicht entfernt." No removal in v1. - B. No detection at all. Rejected: silent ignorance is worse than an honest warning.
D3 — Encrypted PDFs
- ✅ A. Friendly rejection. pdf-lib cannot load encrypted files; show a German hint pointing
to
tools/decryptfirst. No silent failure.
UX
- Route
/tools/metadata, new entry insrc/lib/tools.ts:{ slug: 'metadata', title: 'Metadaten', description: 'Zeigen Sie Metadaten eines PDFs an und entfernen Sie sie.', icon: 'info' }- icon mapping on the start page.
- Flow:
PdfDropzone(single PDF) → results card with:- Info table (label/value rows, empty values shown as "–"): Titel, Autor, Betreff, Schlüsselwörter, Erstellt mit (creator), PDF-Erzeuger (producer), Erstellt am, Geändert am, Seiten, Größe, Verschlüsselung (Ja/Nein — from load success/failure is insufficient; use pdf-lib's load error for encrypted detection and show it as a state, or report "Unbekannt" when not determinable).
- Embedded-file warning block when the heuristic fires (amber styling consistent with existing warning patterns).
- Quality note: "Beim Bereinigen bleibt der Inhalt vollständig erhalten (kein Qualitätsverlust)."
- Actions:
- "Bereinigt herunterladen" (primary, disabled for encrypted files) → cleaned PDF named
{baseName}_bereinigt.pdf. - Secondary: "Original herunterladen" is not offered (the user already has the original).
- "Bereinigt herunterladen" (primary, disabled for encrypted files) → cleaned PDF named
- German inline error area for corrupt/encrypted files, matching existing tool pages.
Technical approach
New route src/routes/tools/metadata/+page.svelte plus a small lib module
src/lib/pdf-metadata.ts:
export type PdfMetadataInfo = {
title: string;
author: string;
subject: string;
keywords: string[];
creator: string;
producer: string;
creationDate: Date | null;
modificationDate: Date | null;
pageCount: number;
embeddedFileSuspected: boolean;
};
export const readPdfMetadata = async (bytes: Uint8Array): Promise<PdfMetadataInfo>;
export const stripPdfMetadata = async (bytes: Uint8Array): Promise<Uint8Array>;
readPdfMetadata:PDFDocument.load(bytes)(with{ ignoreEncryption: false }default so encrypted files throw), then thegetTitle()/getAuthor()/getSubject()/getKeywords()/getCreator()/getProducer()/getCreationDate()/getModificationDate()/getPageCount()accessors. Encryption detection: catch the pdf-libEncryptedPDFErrorand return a distinct{ encrypted: true }result shape (discriminated union) so the UI can branch.stripPdfMetadata: load, apply setters (setTitle(''),setAuthor(''),setSubject(''),setKeywords([]),setCreator(''),setProducer('beA-Edit'),setCreationDate(new Date()),setModificationDate(new Date())),save().- Embedded-file heuristic: decode bytes with
TextDecoderonce and search for the two marker strings; cheap, runs before pdf-lib load. - Reading metadata does not need pdfjs or thumbnails — no rasterization limits apply; large files are fine because pdf-lib parses lazily enough for this purpose.
Files changed
src/lib/pdf-metadata.ts— new.src/routes/tools/metadata/+page.svelte— new route.src/lib/tools.ts,src/routes/+page.svelte— registry entry + icon map.tests/e2e/toolbox.spec.ts— new spec block.agents.md— structure update.
Implementation phases
pdf-metadata.tswith read + strip + heuristic.- Route UI: info table, warning block, clean action, encrypted-file branch.
- Registry entry + icon.
- Playwright tests: PDF created with
createPdf+ set metadata fields → fields displayed; clean download loads with pdf-lib and has empty author/title; encrypted-PDF rejection hint (reuse the encrypt tool to produce a fixture inline). - Polish, German copy review,
pnpm check/lint/build/test:e2e.
Verification
- Metadata of a known PDF renders completely; empty fields show "–".
- Cleaned PDF: open in viewer, metadata fields empty; content and page count identical; file still selectable/searchable (no rasterization happened).
- Encrypted input shows the decrypt hint instead of crashing.
/EmbeddedFile-containing fixture triggers the amber warning.- Standard command gauntlet passes.
Risks
- XMP metadata: pdf-lib's setters touch the Info dict; a separate XMP metadata stream can survive. Mitigation: note in the UI copy that cleaning covers standard document properties. (Full XMP removal could be a follow-up by deleting the metadata stream object.)
- Producer fingerprint: setting
beA-Editas producer is itself a small fingerprint; neutral but acceptable — alternative is an empty string; pick empty string if reviewers prefer.
Out of scope
- Removing embedded files/attachments (see D2).
- XMP stream removal (follow-up).
- Metadata editing (rename title, etc.) — inspection + removal only.
- Hooking metadata cleaning into the beA export pipeline.