Files
bea-edit/plan/done/metadata-plan.md
T
2026-10-06 22:52:35 +02:00

6.3 KiB
Raw Blame History

Plan: PDF-Metadaten — anzeigen und bereinigen

Goal

Add a new tool "Metadaten" (/tools/metadata) with two jobs:

  1. 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.
  2. 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 /EmbeddedFile and /EmbeddedFiles markers; 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/decrypt first. No silent failure.

UX

  • Route /tools/metadata, new entry in src/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).
  • 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 the getTitle()/getAuthor()/getSubject()/getKeywords()/ getCreator()/getProducer()/getCreationDate()/getModificationDate()/getPageCount() accessors. Encryption detection: catch the pdf-lib EncryptedPDFError and 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 TextDecoder once 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

  1. pdf-metadata.ts with read + strip + heuristic.
  2. Route UI: info table, warning block, clean action, encrypted-file branch.
  3. Registry entry + icon.
  4. 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).
  5. 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-Edit as 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.