Ein Schwärzungs-Mark, ohne /IC-Farbe gezeichnet, kann aus dem Apply-Schritt als transparente Lücke oder hohler Umriss herauskommen, während der Kommentar des Reviewers dazu in der Datei bleibt. PDFlibPas wendet Redact-Annotationen mit ApplyRedactions an: der abgedeckte Inhalt geht zuerst, das Overlay kommt aus /RO oder aus /IC mit Schwarz als Fallback, und das Dictionary des Marks wird geleert
Markieren ist umkehrbar, Anwenden nicht
ISO 32000-1 §12.5.6.23 spaltet Schwärzung in Inhaltsidentifikation und Inhaltsentfernung. Während der Identifikation ist eine Redact-Annotation nur ein Vorschlag, den Nutzer noch sehen, verschieben und neu definieren können, was eine Review-Runde überhaupt erst möglich macht. Die Entfernung bleibt der Anwendung überlassen, mit festen Anforderungen: alles, was die Annotation identifiziert, geht, die Annotation geht, und der abgedeckte Teil jedes Bildes bekommt seine Daten zerstört, denn ein Clipping-Pfad oder eine Bildmaske über den Pixeln qualifiziert nicht. Die Schlüssel sind in Tabelle 192 von ISO 32000-1 aufgelistet (Tabelle 195 in ISO 32000-2), und die meisten beschreiben, wie die Seite nach der Entfernung aussehen soll, statt was zu entfernen ist
| Schlüssel | Rolle in der Spezifikation | Was ApplyRedactions damit macht |
|---|---|---|
QuadPoints | Zu entfernende Region als n Vierecke; ersetzt Rect als Region, wenn vorhanden | Schrubbt die Bounding Box jedes Vierecks getrennt; ein Array, dessen Länge kein Vielfaches von 8 ist, fällt auf Rect zurück |
Rect | Zu entfernende Region, wenn QuadPoints fehlt | Benutzt, mit umgekehrten Ecken normalisiert |
IC | DeviceRGB-Füllung nach der Entfernung; fehlend heißt transparent; ignoriert, wenn RO existiert | Behalten, wenn vorhanden, als Schwarz ergänzt, wenn fehlend |
RO | Form-XObject-Overlay; geht IC, OverlayText, DA und Q vor | Als normale Darstellung installiert und auf die Seite geflattet |
OverlayText, Repeat | Label über der Region gezeichnet, optional gekachelt | In das erzeugte Overlay gezeichnet, gekachelt, wenn Repeat true ist |
DA, Q | Font, Größe und Farbe des Labels; Ausrichtung | Größe und Farbe aus DA gelesen, Ausrichtung aus Q |
Marks mit AddRedactAnnotation erzeugen
TPDFlib.AddRedactAnnotation(Left, Top, Width, Height, FillR, FillG, FillB, OverlayText, RepeatOverlay) fügt der gewählten Seite ein Mark hinzu und gibt die neue Annotationszahl der Seite zurück, die auch der 1-basierte Index des Marks ist, oder 0, wenn nichts hinzugefügt wurde. Koordinaten folgen dem aktuellen Ursprung und den Einheiten, also schaltet das Beispiel auf den Ursprung oben links um, wo Top die Oberkante ist. Der Aufruf schreibt /Rect, ein /IC aus den Füllkomponenten und ein festes /DA von /F0 10 Tf 1 1 1 rg, weißen Text auf 10 Punkt; ein nicht-leeres OverlayText ergänzt /OverlayText, /Repeat und /Q 1 für ein zentriertes Label. Redact-Annotationen sind ein PDF-1.7-Feature, also wird ein älterer Header auf 1.7 angehoben. Noch ist kein Appearance-Stream geschrieben; das ausstehende Mark sieht so aus, wie die Anwendung des Reviewers es zeichnet. Bei weiß fixiertem Label paaren Sie OverlayText mit einer dunklen Füllung, denn auf blassgelb verschwindet das Label. Marks lassen sich in jeder PDFlibPas-Edition erzeugen
uses
System.SysUtils, PDFlibrary;
procedure MarkForReview(const SourceFile, ReviewFile: string);
var
Pdf: TPDFlib;
Index: Integer;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile(SourceFile, '') <> 1 then
raise Exception.CreateFmt('Cannot open %s', [SourceFile]);
Pdf.SetOrigin(1); // Ursprung oben links: Top ist die Oberkante
Pdf.SetMeasurementUnits(0); // Punkte
if Pdf.SelectPage(1) <> 1 then
raise Exception.Create('Page 1 not found');
// Zeugenname: schwarze Füllung, zentriertes weißes Label
Index := Pdf.AddRedactAnnotation(72, 180, 220, 14, 0, 0, 0, 'REDACTED');
if Index = 0 then
raise Exception.Create('Mark was not added');
// Konto-Block: den Freistellungscode über eine höhere Box kacheln
Pdf.AddRedactAnnotation(72, 410, 300, 40, 0, 0, 0, '(b)(6)', 1);
// Nur Marks; jedes Byte des Originalinhalts ist noch vorhanden
Pdf.SaveToFile(ReviewFile);
finally
Pdf.Free;
end;
end;
QuadPoints entscheiden die Entfernungsfläche; Antworten behalten ihren Text
Trägt ein Mark /QuadPoints, entscheiden diese Vierecke, was ApplyRedactions entfernt, und Rect begrenzt sie nur für die Anzeige. Mit Textauswahl-Werkzeugen gemachte Marks haben meist ein Viereck pro gewählter Zeile, also verschiebt das Vergrößern solcher Marks mit SetAnnotRect nur den Rahmen auf dem Bildschirm und lässt die Entfernungsfläche, wo sie war; SetAnnotQuadPoints ist der Aufruf, der die Region verschiebt, und GetAnnotQuadCount gibt 0 zurück für ein Mark, das allein auf Rect baut. Der Text, der an einem Mark hängt, folgt einer anderen Regel. Review-Werkzeuge halten die Notiz in /Contents und den Autor in /T, und eine Notiz an einem Schwärzungs-Mark beschreibt tendenziell genau das, was entfernt wird („SSN des Mandanten, mit der Rechtsabteilung klären“). ApplyRedactions leert das eigene Dictionary des Marks, aber Antworten, die über /IRT darunter eingereiht sind, sind eigene Annotationen, gelöscht nur, wenn ihr Rechteck eine Schwärzungsregion überlappt, also behält eine im Rand geparkte Antwort ihren Text. Die Auflistung unten druckt jedes Mark mit seiner Viereckzahl und den darunter eingereihten Antworten
procedure ListPendingMarks(Pdf: TPDFlib);
var
P, I, Parent: Integer;
begin
for P := 1 to Pdf.PageCount do
begin
if Pdf.SelectPage(P) <> 1 then
Continue;
for I := 1 to Pdf.AnnotationCount do
if Pdf.GetAnnotType(I) = 'Redact' then
Writeln(Format('p%d #%d by %s, quads=%d: %s',
[P, I, Pdf.GetAnnotTitle(I), Pdf.GetAnnotQuadCount(I),
Pdf.GetAnnotContentsEx(I)]))
else
begin
Parent := Pdf.GetAnnotReplyTo(I); // 0, wenn /IRT fehlt
if (Parent > 0) and (Pdf.GetAnnotType(Parent) = 'Redact') then
Writeln(Format(' reply #%d to #%d: %s',
[I, Parent, Pdf.GetAnnotContentsEx(I)]));
end;
end;
end;
Welches Overlay erscheint nach ApplyRedactions?
Ein /RO-Eintrag gewinnt, wann immer er vorhanden ist, wie die Vorrangregel in Tabelle 192 verlangt. ApplyRedactions installiert dieses Form XObject als die normale Darstellung der Annotation (/AP /N), wirft jeden Appearance-State weg und flattet ihn, also haben /IC, /OverlayText, /DA und /Q keine Wirkung auf dieses Mark. Die Platzierung ist der eine subtile Teil. Die Spezifikation sagt, der Ursprung des Overlays fluchtet mit der unteren linken Ecke von Rect, während das Flattening die BBox des Forms durch seine Matrix auf Rect abbildet, dieselbe BBox- und Matrix-Platzierung, die das Flattening auf Formularfeld-Widgets anwendet. Ein Overlay, dessen BBox [0 0 w h] ist, auf das Rechteck dimensioniert, mit einer identischen Matrix sieht unter beiden Lesarten gleich aus, also verlangen Sie diese Form, wenn Ihre eigenen Werkzeuge /RO-Marks erzeugen
Ohne /RO normalisiert ApplyRedactions das Dictionary und baut die Darstellung selbst. Es löscht /C und /BS und setzt /Border auf [0 0 0]: diese Einträge beschreiben das ausstehende Mark, meist einen farbigen Umriss, und es in die finale Seite einzubrennen würde eine fertige Schwärzung unfertig aussehen lassen. Ohne den Rand deckt die Füllung das ganze Rechteck. Das Label wird im erzeugten Form gezeichnet, mit Größe und Farbe aus /DA, Ausrichtung aus /Q, Kachelung aus /Repeat, und der Fontname F0 ist in den eigenen Ressourcen dieses Forms an Standard-Helvetica gebunden. Diese Bindung zählt, wenn Dateien zwischen Werkzeugen reisen: ISO 32000-1 §12.7.3.3 definiert einen DA-Fontnamen als Eintrag in den Default-Ressourcen des interaktiven Formulars (/DR), ein Dokument ohne Formularfelder hat keine, und eine andere Anwendung, die dasselbe Mark anwendet, kann einen Fallback-Font einsetzen. PDFlibPas überspringt diese Nachschlage, und mit Helvetica passt das Label zu Lateinschrift-Text wie REDACTED oder einem Freistellungscode
Ein fehlendes /IC wird eine schwarze Box
Der Default der Spezifikation für ein fehlendes /IC ist ein transparenter Innenraum, und PDFlibPas weicht davon absichtlich ab. Nach der Entfernung zeigt ein transparenter Innenraum das Papier, eine weiße Lücke, wo früher ein Name war, und diese Lücke sieht genauso aus wie ein Layout, das nie etwas dort hatte. Ein Reader kann Geschwärztes nicht von Leerem unterscheiden, und ein Reviewer kann die Schwärzung nicht bestätigen, ohne extrahierten Text zu vergleichen. Ein hohler Rahmen, was ein Apply-Schritt erzeugt, wenn er die ausstehende Darstellung eines nur-Umriss-Marks flattet, scheitert anders: er liest sich als Vorschlag, auf den niemand agiert hat. Wir haben abgewogen, dem Default wörtlich zu folgen, und die schwarze Box gewählt, denn das sichtbare Zeichen, dass etwas zurückgehalten wurde, ist Teil dessen, was ein geschwärztes Dokument seinem Leser sagt, und die Wahl betrifft nur Marks, deren Autor nie eine Füllung angab. Ein /IC, bereits im Dictionary, wird benutzt, welchen Wert es auch hat, also bekommt ein Producer, der Weiß verlangte, Weiß
Was weg ist, bevor das Overlay gezeichnet wird
ApplyRedactions arbeitet Seite für Seite und schließt jede Entfernung ab, bevor es irgendein Overlay flattet, also kann ein Overlay nie von seiner eigenen Region erwischt werden. Pro Seite sammelt es ein Rechteck pro Viereck, oder das Rect von Marks ohne Vierecke, und schrubbt jede Inhalts-Ebene gegen jedes davon:
- Text wird Glyph für Glyph geschnitten, gemessen aus den Seiten-Fonts und dem vollen Textzustand, und der Rest einer teilweise abgedeckten Zeichenkette bleibt an seiner Originalposition
- Vektorpfade, Inline-Bilder und Shadings, die in der Region malen, werden aus dem Content-Stream gelöscht
- Form XObjects werden rekursiv betreten; ein Form, das andere Seiten oder Platzierungen ebenfalls zeichnen, wird über eine private Kopie geschwärzt, also behalten diese Seiten das Original
- Bild-XObjects verlieren nur die abgedeckten Pixel, überschrieben mit Schwarz (volle K-Farbe für CMYK); JPEG-, JPEG 2000-, JBIG2- und CCITT-Bilder werden dekodiert und als verlustfreie Flate-Streams zurückgespeichert
- In getaggtem PDF werden
/Altund/ActualText, die an entferntem Marked Content hängen, im Seiteninhalt und im Strukturbaum geleert - Andere Annotationen, deren Rechteck die Region trifft, werden gelöscht, und Formularfelder, deren Widgets dort sitzen, verlieren ihre Werte
Der Pixelschritt ist die Bildklausel der Spezifikation in der Praxis. Ein schwarzes Rechteck über einem gescannten Pass beruhigt das Auge und scheitert trotzdem an §12.5.6.23, denn die Originalsamples dekodieren, sobald jemand das Bild extrahiert. Dieselbe Engine sitzt hinter RedactRegion, das Inhalt aus einem Rechteck entfernt, das Sie direkt übergeben, und sich mit SanitizeDocument paart, ohne irgendeine Annotation zu lesen. Ist der Inhalt weg, wird jedes Mark geflattet und sein Dictionary bis auf /Type und /Subtype geleert. Das Flattening entfernt die Annotation aus dem /Annots-Array der Seite, aber das Dictionary ist weiterhin ein Objekt im Dokument, und ein Save, das es schreibt, würde /Contents, /RC, /T und den Rest der Review-Spur dem Empfänger aushändigen
Speichern ohne die ungeschwärzte Revision
Ein inkrementelles Save macht alles Obige zunichte. Nach §7.5.6 (dieselbe Nummer in ISO 32000-1 und ISO 32000-2) hängt ein inkrementelles Update geänderte Objekte ans Dateiende an und lässt die Originalbytes intakt, also bleibt die Originalrevision, mit ihrem Seiteninhalt, ihren Bildern und den markierten Schwärzungen, lesbar für jeden, der die Datei am früheren %%EOF abschneidet; ExtractDocumentRevision in der PDFlibPas Small Business Edition macht das in einem Aufruf. Nach ApplyRedactions schreiben Sie eine neue Datei mit SaveToFile, nie SaveChanges, das anhängt. Vor dem Speichern fegt GarbageCollectObjects die Objekttabelle und wirft Objekte weg, die nichts mehr referenzieren, etwa die Appearance-Streams, die die Marks vor dem Apply-Schritt trugen; ein vollständiges Save überspringt diesen Ausflug, außer SetGarbageCollect schaltet ihn ein, oder das GarbageCollect-Feld des TPDFlibSaveOptions-Records, den Sie SaveToFileOptions übergeben, ist gesetzt. Metadaten sind die restliche Lücke. SanitizeDocument(SANITIZE_METADATA or SANITIZE_XMP_PACKET) leert Author, Subject, Keywords, Creator und Producer und entfernt den XMP-Stream, das Title überlässt es Ihnen zur Prüfung. Meiden Sie SANITIZE_ALL hier: es schließt SANITIZE_ANNOTATIONS ein und löscht jede Annotation, die im Dokument übrig ist, Notizen, die Sie behalten wollten, eingeschlossen
ApplyRedactions ist wie RedactRegion in der Enterprise Edition verfügbar. Auf einem schlichten TPDFlib gibt es 0 zurück und setzt LastErrorCode auf PDFLIB_ERROR_ENTERPRISE_REQUIRED (900), also benutzt der Freigabe-Schritt TPDFlibEnterprise aus der Unit PDFlibEnterprise
uses
System.SysUtils, PDFlibrary, PDFlibEnterprise;
procedure ApplyAndRelease(const ReviewFile, ReleaseFile: string);
var
Pdf: TPDFlibEnterprise;
Applied, Orphans: Integer;
begin
Pdf := TPDFlibEnterprise.Create;
try
if Pdf.LoadFromFile(ReviewFile, '') <> 1 then
raise Exception.CreateFmt('Cannot open %s', [ReviewFile]);
Applied := Pdf.ApplyRedactions; // jedes Redact-Mark auf jeder Seite
if Applied = 0 then
raise Exception.CreateFmt('No marks applied (error %d)',
[Pdf.LastErrorCode]);
// Info und XMP können noch benennen, was die Seiten nicht mehr zeigen
Pdf.SanitizeDocument(SANITIZE_METADATA or SANITIZE_XMP_PACKET);
Orphans := Pdf.GarbageCollectObjects;
// Vollständige Neuschreibung in eine neue Datei; nie SaveChanges nach einer Schwärzung
if Pdf.SaveToFile(ReleaseFile) <> 1 then
raise Exception.Create('Save failed');
Writeln(Format('%d marks applied, %d unreferenced objects dropped',
[Applied, Orphans]));
finally
Pdf.Free;
end;
end;
Erwartet: ApplyRedactions gibt die Anzahl der Redact-Marks zurück, die es über alle Seiten geflattet hat, 2 für die von MarkForReview geschriebene Review-Kopie, und GarbageCollectObjects gibt zurück, wie viele nicht mehr referenzierte Objekte der Ausflug fallen ließ
Welches Werkzeug auch immer ein Mark gezeichnet hat, seine /RO- und /IC-Einträge entscheiden, was der Empfänger auf der Seite sieht, während der Apply-Schritt in PDFlibPas Enterprise for Delphi entscheidet, was darunter weg ist