Une marque de caviardage dessinée sans couleur /IC peut sortir de l'étape d'application comme un trou transparent ou un contour creux, pendant que le commentaire du relecteur posé sur la marque reste dans le fichier. PDFlibPas applique les annotations Redact avec ApplyRedactions : le contenu couvert part d'abord, le recouvrement vient de /RO ou de /IC avec le noir en repli, et le dictionnaire de la marque est vidé
Marquer est réversible, appliquer ne l'est pas
ISO 32000-1 §12.5.6.23 partage le caviardage entre identification de contenu et suppression de contenu. Pendant l'identification, une annotation Redact n'est qu'une proposition que les utilisateurs peuvent encore voir, déplacer et redéfinir, et c'est ce qui rend possible un tour de relecture. La suppression est laissée à l'application, avec des exigences fermes : tout ce que l'annotation identifie part, l'annotation part, et la partie couverte de toute image voit ses données détruites, car un chemin de découpage ou un masque d'image par-dessus les pixels ne qualifie pas. Les clés sont listées dans la Table 192 de ISO 32000-1 (Table 195 en ISO 32000-2), et la plupart décrivent ce à quoi la page doit ressembler après suppression plutôt que quoi supprimer
| Clé | Rôle dans la spécification | Ce que ApplyRedactions en fait |
|---|---|---|
QuadPoints | Région à supprimer comme n quadrilatères ; remplace Rect comme région quand présent | Décape la boîte englobante de chaque quadrilatère séparément ; un tableau dont la longueur n'est pas un multiple de 8 retombe sur Rect |
Rect | Région à supprimer quand QuadPoints est absent | Utilisé avec les coins inversés normalisés |
IC | Remplissage DeviceRGB après suppression ; absent signifie transparent ; ignoré quand RO existe | Conservé quand présent, ajouté en noir quand absent |
RO | Recouvrement Form XObject ; prime sur IC, OverlayText, DA et Q | Installé comme apparence normale et aplati sur la page |
OverlayText, Repeat | Étiquette dessinée par-dessus la région, éventuellement en pavage | Dessinée dans le recouvrement généré, en pavage quand Repeat est true |
DA, Q | Police, taille et couleur de l'étiquette ; justification | Taille et couleur lues dans DA, alignement depuis Q |
Créer des marques avec AddRedactAnnotation
TPDFlib.AddRedactAnnotation(Left, Top, Width, Height, FillR, FillG, FillB, OverlayText, RepeatOverlay) ajoute une marque à la page sélectionnée et renvoie le nouveau nombre d'annotations de la page, qui est aussi l'index base 1 de la marque, ou 0 quand rien n'a été ajouté. Les coordonnées suivent l'origine et les unités courantes, si bien que l'exemple bascule vers l'origine en haut à gauche, où Top est le bord supérieur. L'appel écrit un /Rect, un /IC depuis les composantes de remplissage et un /DA fixe de /F0 10 Tf 1 1 1 rg, du texte blanc 10 points ; un OverlayText non vide ajoute /OverlayText, /Repeat et /Q 1 pour une étiquette centrée. Les annotations Redact sont une fonctionnalité PDF 1.7, si bien qu'un en-tête plus ancien est monté à 1.7. Aucun flux d'apparence n'est encore écrit ; la marque en attente a l'aspect que l'application du relecteur lui dessine. Avec l'étiquette fixée au blanc, associez OverlayText à un remplissage sombre, car sur jaune pâle l'étiquette disparaît. Les marques peuvent être créées dans chaque édition de PDFlibPas
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); // origine en haut à gauche : Top est le bord supérieur
Pdf.SetMeasurementUnits(0); // points
if Pdf.SelectPage(1) <> 1 then
raise Exception.Create('Page 1 not found');
// Nom du témoin : remplissage noir, étiquette blanche centrée
Index := Pdf.AddRedactAnnotation(72, 180, 220, 14, 0, 0, 0, 'REDACTED');
if Index = 0 then
raise Exception.Create('Mark was not added');
// Bloc compte : paver le code d'exemption sur une boîte plus haute
Pdf.AddRedactAnnotation(72, 410, 300, 40, 0, 0, 0, '(b)(6)', 1);
// Marques seulement ; chaque octet du contenu original est toujours présent
Pdf.SaveToFile(ReviewFile);
finally
Pdf.Free;
end;
end;
Les QuadPoints décident de la zone de suppression ; les réponses gardent leur texte
Dès qu'une marque porte /QuadPoints, ces quadrilatères décident de ce que ApplyRedactions supprime, et le Rect ne fait que les borner pour l'affichage. Les marques faites avec des outils de sélection de texte ont d'ordinaire un quadrilatère par ligne sélectionnée, si bien que redimensionner une telle marque avec SetAnnotRect déplace son cadre à l'écran et laisse la zone de suppression où elle était ; SetAnnotQuadPoints est l'appel qui déplace la région, et GetAnnotQuadCount renvoie 0 pour une marque qui ne compte que sur son Rect. Le texte attaché à une marque suit une autre règle. Les outils de relecture gardent la note dans /Contents et l'auteur dans /T, et une note sur une marque de caviardage tend à décrire précisément la chose en train d'être supprimée (« numéro de sécurité sociale du client, à confirmer avec le juriste »). ApplyRedactions vide le dictionnaire propre de la marque, mais les réponses enfilées sous elle au travers de /IRT sont des annotations séparées, supprimées seulement quand leur rectangle chevauche une région de caviardage, si bien qu'une réponse garée dans la marge garde son texte. Le listing ci-dessous imprime chaque marque avec son nombre de quadrilatères et les réponses enfilées sous elle
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 quand /IRT est absent
if (Parent > 0) and (Pdf.GetAnnotType(Parent) = 'Redact') then
Writeln(Format(' reply #%d to #%d: %s',
[I, Parent, Pdf.GetAnnotContentsEx(I)]));
end;
end;
end;
Quel recouvrement apparaît après ApplyRedactions ?
Une entrée /RO gagne dès qu'elle est présente, comme l'exige la règle de précédence de la Table 192. ApplyRedactions installe ce Form XObject comme apparence normale de l'annotation (/AP /N), abandonne tout état d'apparence et l'aplatit, si bien que /IC, /OverlayText, /DA et /Q n'ont aucun effet sur cette marque. Le placement est la seule partie subtile. La spécification dit que l'origine du recouvrement s'aligne sur le coin inférieur gauche du Rect, tandis que l'aplatissement mappe le BBox du Form au travers de son Matrix sur le Rect, le même placement BBox et Matrix que l'aplatissement applique aux widgets de champs de formulaire. Un recouvrement dont le BBox vaut [0 0 w h], dimensionné sur le rectangle, avec un Matrix identité, se ressemble sous les deux lectures, donc demandez cette forme quand vos propres outils génèrent des marques /RO
Sans /RO, ApplyRedactions normalise le dictionnaire et construit l'apparence lui-même. Il supprime /C et /BS et pose /Border à [0 0 0] : ces entrées décrivent la marque en attente, d'ordinaire un contour coloré, et le brûler dans la page finale ferait passer un caviardage achevé pour inachevé. La bordure partie, le remplissage couvre tout le rectangle. L'étiquette est dessinée à l'intérieur du Form généré, avec la taille et la couleur de /DA, la justification de /Q, le pavage de /Repeat, et le nom de police F0 lié dans les ressources propres de ce Form à la Helvetica standard. Cette liaison compte quand les fichiers voyagent entre outils : ISO 32000-1 §12.7.3.3 définit un nom de police DA comme une entrée dans les ressources par défaut du formulaire interactif (/DR), un document sans champs de formulaire n'en a aucune, et une autre application appliquant la même marque peut substituer une police de repli. PDFlibPas saute cette recherche, et avec Helvetica l'étiquette convient au texte en écriture latine tel que REDACTED ou un code d'exemption
Un /IC manquant devient une boîte noire
Le défaut de la spécification pour un /IC absent est un intérieur transparent, et PDFlibPas s'en écarte à dessein. Après suppression, un intérieur transparent montre le papier, un blanc à l'endroit où un nom se trouvait, et ce blanc se confond avec une mise en page qui n'y a jamais rien eu. Un lecteur ne peut pas distinguer caviardé de vide, et un relecteur ne peut pas confirmer que le caviardage a eu lieu sans comparer le texte extrait. Un cadre creux, ce que produit une étape d'application quand elle aplatit l'apparence en attente d'une marque à contour seul, échoue autrement : il se lit comme une proposition sur laquelle personne n'a agi. Nous avons pesé le suivi littéral du défaut et choisi la boîte noire, car le signe visible que quelque chose a été retiré fait partie de ce qu'un document caviardé dit à son lecteur, et le choix ne touche que les marques dont l'auteur n'a jamais énoncé de remplissage. Un /IC déjà dans le dictionnaire est utilisé quelle que soit sa valeur, si bien qu'un producteur qui a demandé du blanc obtient du blanc
Ce qui a disparu avant que le recouvrement soit dessiné
ApplyRedactions travaille page par page et achève toute la suppression avant d'aplatir un recouvrement quelconque, si bien qu'un recouvrement ne peut jamais être attrapé par sa propre région. Pour chaque page il ramasse un rectangle par quadrilatère, ou le Rect des marques sans quadrilatères, et décape chaque couche de contenu contre chacun d'eux :
- Le texte est coupé glyphe par glyphe, mesuré depuis les polices de la page et l'état de texte complet, et le reste d'une chaîne partiellement couverte reste à sa position d'origine
- Les tracés vectoriels, les images en ligne et les shadings qui peignent à l'intérieur de la région sont supprimés du flux de contenu
- Les Form XObjects sont entrés récursivement ; un Form que d'autres pages ou placements dessinent aussi est caviardé au travers d'une copie privée, si bien que ces pages gardent l'original
- Les image XObjects ne perdent que les pixels couverts, écrasés en noir (encre K pleine pour le CMYK) ; les images JPEG, JPEG 2000, JBIG2 et CCITT sont décodées et re-stockées comme flux Flate sans perte
- Dans le PDF balisé,
/Altet/ActualTextattachés au contenu marqué supprimé sont effacés dans le contenu de page et dans l'arbre de structure - Les autres annotations dont le rectangle rencontre la région sont supprimées, et les champs de formulaire dont les widgets se tiennent là perdent leurs valeurs
L'étape des pixels est la clause image de la spécification en pratique. Un rectangle noir peint par-dessus un passeport scanné satisfait l'œil et échoue quand même à la §12.5.6.23, car les échantillons originaux se décodent dès que quelqu'un extrait l'image. Le même moteur se cache derrière RedactRegion, qui supprime le contenu d'un rectangle que vous passez directement et fait couple avec SanitizeDocument, sans lire aucune annotation. Une fois le contenu parti, chaque marque est aplatie et son dictionnaire vidé jusqu'à /Type et /Subtype. L'aplatissement retire l'annotation du tableau /Annots de la page, mais le dictionnaire reste un objet dans le document, et une sauvegarde qui l'écrirait remettrait /Contents, /RC, /T et le reste de la piste de relecture au destinataire
Sauvegarder sans la révision non caviardée
Une sauvegarde incrémentale défait tout ce qui précède. Sous §7.5.6 (le même numéro en ISO 32000-1 et ISO 32000-2), une mise à jour incrémentale ajoute les objets modifiés à la fin du fichier et laisse les octets originaux intacts, si bien que la révision originale, avec son contenu de page, ses images et ses marques annotées, reste lisible par quiconque coupe le fichier au %%EOF antérieur ; ExtractDocumentRevision de l'édition Small Business de PDFlibPas fait cela en un appel. Après ApplyRedactions, écrivez un nouveau fichier avec SaveToFile, jamais SaveChanges, qui ajoute. Avant la sauvegarde, GarbageCollectObjects balaie la table d'objets et abandonne les objets que plus rien ne référence, tels les flux d'apparence que les marques portaient avant l'étape d'application ; une sauvegarde complète saute ce balayage à moins que SetGarbageCollect ne l'active, ou que le champ GarbageCollect de l'enregistrement TPDFlibSaveOptions que vous passez à SaveToFileOptions ne soit posé. Les métadonnées sont le trou restant. SanitizeDocument(SANITIZE_METADATA or SANITIZE_XMP_PACKET) blanchit Author, Subject, Keywords, Creator et Producer et retire le flux XMP, en laissant le Title à vérifier pour vous. Évitez SANITIZE_ALL ici : il inclut SANITIZE_ANNOTATIONS et supprime chaque annotation restée dans le document, les notes que vous vouliez garder comprises
ApplyRedactions, comme RedactRegion, est disponible dans l'édition Enterprise. Sur un TPDFlib simple il renvoie 0 et pose LastErrorCode à PDFLIB_ERROR_ENTERPRISE_REQUIRED (900), si bien que l'étape de publication utilise TPDFlibEnterprise de l'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; // chaque marque Redact sur chaque page
if Applied = 0 then
raise Exception.CreateFmt('No marks applied (error %d)',
[Pdf.LastErrorCode]);
// Info et XMP peuvent encore nommer ce que les pages ne montrent plus
Pdf.SanitizeDocument(SANITIZE_METADATA or SANITIZE_XMP_PACKET);
Orphans := Pdf.GarbageCollectObjects;
// Réécriture complète vers un nouveau fichier ; jamais SaveChanges après caviardage
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;
Attendu : ApplyRedactions renvoie le nombre de marques Redact qu'il a aplaties sur toutes les pages, 2 pour la copie de relecture écrite par MarkForReview, et GarbageCollectObjects renvoie combien d'objets non référencés le balayage a abandonnés
Quel que soit l'outil qui a dessiné une marque, ses entrées /RO et /IC règlent ce que le destinataire voit sur la page, tandis que l'étape d'application dans PDFlibPas Enterprise for Delphi règle ce qui a disparu dessous