Een redactiemark getekend zonder een /IC-kleur kan uit de apply-stap komen als een transparant gat of een holle omlijning, terwijl de opmerking van de reviewer erop in het bestand blijft. PDFlibPas past Redact-annotaties toe met ApplyRedactions: de gedekte content gaat eerst weg, de overlay komt uit /RO of uit /IC met zwart als fallback, en het dictionary van de mark wordt geleegd
Markeren is omkeerbaar, toepassen niet
ISO 32000-1 §12.5.6.23 splitst redactie in contentidentificatie en contentverwijdering. Tijdens de identificatie is een Redact-annotatie slechts een voorstel dat gebruikers nog kunnen zien, verplaatsen en herdefiniëren, en dat is wat een reviewronde mogelijk maakt. De verwijdering is aan de applicatie, met harde eisen: alles wat de annotatie identificeert gaat weg, de annotatie gaat weg, en het gedekte deel van elke afbeelding krijgt zijn data vernietigd, want een clipping path of image mask over de pixels volstaat niet. De sleutels staan in Table 192 van ISO 32000-1 (Table 195 in ISO 32000-2), en de meeste beschrijven hoe de pagina er na de verwijdering uit hoort te zien in plaats van wat er weg moet
| Sleutel | Rol in de specificatie | Wat ApplyRedactions ermee doet |
|---|---|---|
QuadPoints | Te verwijderen gebied als n vierhoeken; vervangt Rect als gebied wanneer aanwezig | Scrubt de bounding box van elke vierhoek apart; een array waarvan de lengte geen veelvoud van 8 is valt terug op Rect |
Rect | Te verwijderen gebied wanneer QuadPoints ontbreekt | Gebruikt met omgekeerde hoeken genormaliseerd |
IC | DeviceRGB-vulling na verwijdering; afwezig betekent transparant; genegeerd wanneer RO bestaat | Behouden wanneer aanwezig, als zwart toegevoegd wanneer afwezig |
RO | Form XObject-overlay; gaat boven IC, OverlayText, DA en Q | Geïnstalleerd als de normal appearance en op de pagina afgevlakt |
OverlayText, Repeat | Label boven het gebied getekend, optioneel getegeld | In de gegenereerde overlay getekend, getegeld wanneer Repeat true is |
DA, Q | Font, grootte en kleur van het label; uitlijning | Grootte en kleur uit DA gelezen, uitlijning uit Q |
Marks aanmaken met AddRedactAnnotation
TPDFlib.AddRedactAnnotation(Left, Top, Width, Height, FillR, FillG, FillB, OverlayText, RepeatOverlay) voegt een mark toe aan de geselecteerde pagina en geeft het nieuwe annotatieaantal van de pagina terug, wat ook de 1-based index van de mark is, of 0 wanneer er niets is toegevoegd. Coördinaten volgen de huidige oorsprong en eenheden, dus het voorbeeld schakelt naar de oorsprong linksboven, waar Top de bovenrand is. De aanroep schrijft /Rect, een /IC uit de vulcomponenten en een vaste /DA van /F0 10 Tf 1 1 1 rg, witte tekst op 10 points; een niet-lege OverlayText voegt /OverlayText, /Repeat en /Q 1 toe voor een gecentreerd label. Redact-annotaties zijn een PDF 1.7-feature, dus een oudere header wordt verhoogd naar 1.7. Er wordt nog geen appearance stream geschreven; de mark in behandeling ziet eruit zoals de applicatie van de reviewer hem tekent. Met het label vast op wit paart u OverlayText aan een donkere vulling, want op bleek geel verdwijnt het label. Marks kunnen in elke PDFlibPas-editie worden aangemaakt
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); // oorsprong linksboven: Top is de bovenrand
Pdf.SetMeasurementUnits(0); // points
if Pdf.SelectPage(1) <> 1 then
raise Exception.Create('Page 1 not found');
// Getuigenaam: zwarte vulling, gecentreerd wit label
Index := Pdf.AddRedactAnnotation(72, 180, 220, 14, 0, 0, 0, 'REDACTED');
if Index = 0 then
raise Exception.Create('Mark was not added');
// Accountblok: tegel de vrijstellingscode over een hoger kader
Pdf.AddRedactAnnotation(72, 410, 300, 40, 0, 0, 0, '(b)(6)', 1);
// Alleen marks; elke byte van de originele content is nog aanwezig
Pdf.SaveToFile(ReviewFile);
finally
Pdf.Free;
end;
end;
QuadPoints bepalen het verwijdergebied; replies houden hun tekst
Draagt een mark eenmaal /QuadPoints, dan bepalen die vierhoeken wat ApplyRedactions verwijdert, en Rect omringt ze alleen voor de weergave. Marks gemaakt met tekstselectietools hebben meestal één vierhoek per geselecteerde regel, dus het verschuiven van het kader van zo een mark met SetAnnotRect verplaatst alleen zijn kader op het scherm en laat het verwijdergebied waar het was; SetAnnotQuadPoints is de aanroep die het gebied verplaatst, en GetAnnotQuadCount geeft 0 terug voor een mark die uitsluitend op Rect leunt. De tekst die aan een mark hangt volgt een andere regel. Reviewtools bewaren de notitie in /Contents en de auteur in /T, en een notitie op een redactiemark beschrijft vaak precies wat er wordt verwijderd ("SSN van de klant, even afstemmen met legal"). ApplyRedactions leegt het eigen dictionary van de mark, maar replies die via /IRT eronder hangen zijn aparte annotaties, alleen verwijderd wanneer hun rechthoek een redactiegebied overlapt, dus een reply geparkeerd in de marge houdt zijn tekst. De lijst hieronder drukt elke mark af met zijn aantal vierhoeken en de eronder hangende replies
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 wanneer /IRT ontbreekt
if (Parent > 0) and (Pdf.GetAnnotType(Parent) = 'Redact') then
Writeln(Format(' reply #%d to #%d: %s',
[I, Parent, Pdf.GetAnnotContentsEx(I)]));
end;
end;
end;
Welke overlay verschijnt na ApplyRedactions?
Een /RO-entry wint zodra hij aanwezig is, zoals de voorrangsregel in Table 192 vereist. ApplyRedactions installeert die Form XObject als de normal appearance van de annotatie (/AP /N), gooit elke appearance state weg en vlakt hem af, dus /IC, /OverlayText, /DA en /Q hebben op die mark geen effect. Plaatsing is het ene subtiele punt. De specificatie zegt dat de oorsprong van de overlay aansluit op de linkerbenedenhoek van Rect, terwijl het afvlakken de BBox van de Form via zijn Matrix op Rect mapt, dezelfde BBox- en Matrix-plaatsing die afvlakken toepast op widgets van formuliervelden. Een overlay met een BBox van [0 0 w h], op de rechthoek gemaakt, en een identiteits-Matrix ziet er onder beide lezingen hetzelfde uit, dus vraag om die vorm wanneer uw eigen tools /RO-marks genereren
Zonder /RO normaliseert ApplyRedactions het dictionary en bouwt de appearance zelf. Hij verwijdert /C en /BS en zet /Border op [0 0 0]: die entries beschrijven de mark in behandeling, meestal een gekleurde omlijning, en die in de definitieve pagina branden zou een afgeronde redactie onafgewerkt laten lijken. Zonder rand dekt de vulling de hele rechthoek. Het label wordt in de gegenereerde Form getekend, met grootte en kleur uit /DA, uitlijning uit /Q, tegeling uit /Repeat, en de fontnaam F0 gebind in de eigen resources van die Form aan standaard Helvetica. Die binding doet ertoe wanneer bestanden tussen tools reizen: ISO 32000-1 §12.7.3.3 definieert een DA-fontnaam als een entry in de default resources (/DR) van het interactieve formulier, een document zonder formuliervelden heeft die niet, en een andere applicatie die dezelfde mark toepast kan een fallback-font substitueren. PDFlibPas slaat die opslagactie over, en met Helvetica past het label bij Latijnse tekst zoals REDACTED of een vrijstellingscode
Een ontbrekende /IC wordt een zwart vlak
De default van de specificatie voor een afwezige /IC is een transparant binnenvlak, en PDFlibPas wijkt daar opzettelijk van af. Na de verwijdering laat een transparant binnenvlak het papier zien, een wit gat waar vroeger een naam stond, en dat gat ziet er hetzelfde uit als een layout waar nooit iets stond. Een reader kan geredigeerd niet van leeg onderscheiden, en een reviewer kan niet bevestigen dat de redactie heeft plaatsgevonden zonder geëxtraheerde tekst te vergelijken. Een hol kader, wat een apply-stap oplevert wanneer het de pending appearance van een mark met alleen een omlijning afvlakt, faalt anders: het leest als een voorstel waar niemand op handelde. We hebben de default letterlijk volgen tegen het zwarte vlak afgewogen en kozen voor het zwarte vlak, want het zichtbare teken dat iets is achtergehouden hoort bij wat een geredigeerd document zijn lezer vertelt, en de keuze raakt alleen marks waarvan de auteur nooit een vulling opgaf. Een /IC die al in het dictionary zit wordt gebruikt, welke waarde ook, dus een producent die om wit vroeg krijgt wit
Wat is al weg voordat de overlay wordt getekend
ApplyRedactions werkt pagina voor pagina en voltooit alle verwijdering voordat het enige overlay afvlakt, dus een overlay kan nooit door zijn eigen gebied worden gepakt. Per pagina verzamelt het één rechthoek per vierhoek, of de Rect van marks zonder vierhoeken, en schrobt elke contentlaag tegen elk ervan:
- Tekst wordt glyph voor glyph weggeknipt, gemeten vanuit de pagefonts en de volledige text state, en de rest van een deels gedekte string blijft op zijn oorspronkelijke positie
- Vectorpaden, inline afbeeldingen en shadings die binnen het gebied tekenen worden uit de contentstream verwijderd
- Form XObjects worden recursief betreden; een Form die ook andere pagina's of plaatsingen tekenen wordt via een privé-kopie geredigeerd, zodat die pagina's het origineel houden
- Image XObjects verliezen alleen de gedekte pixels, overschreven met zwart (volle K-inkt voor CMYK); JPEG-, JPEG 2000-, JBIG2- en CCITT-afbeeldingen worden gedecodeerd en als verliesvrije Flate-streams terug opgeslagen
- In getagde PDF worden
/Alten/ActualTextdie aan verwijderde marked content hangen gewist in de paginacontent en in de structuurboom - Andere annotaties waarvan de rechthoek het gebied raakt worden verwijderd, en formuliervelden waarvan de widgets er zitten verliezen hun waarden
De pixelstap is de image-clausule van de specificatie in de praktijk. Een zwart kader over een gescand paspoort voldoet voor het oog en zakt nog steeds voor §12.5.6.23, want de originele samples worden gedecodeerd zodra iemand de afbeelding extraheert. Dezelfde engine zit achter RedactRegion, dat content uit een rechthoek verwijdert die u direct doorgeeft en samenwerkt met SanitizeDocument, zonder annotaties te lezen. Als de content eenmaal weg is, wordt elke mark afgevlakt en zijn dictionary gewist tot op /Type en /Subtype. Afvlakken verwijdert de annotatie uit de /Annots-array van de pagina, maar het dictionary blijft een object in het document, en een save die het wegschrijft zou /Contents, /RC, /T en de rest van het reviewspoor aan de ontvanger overhandigen
Opslaan zonder de niet-geredigeerde revisie
Een incrementele save maakt alles hierboven ongedaan. Onder §7.5.6 (hetzelfde nummer in ISO 32000-1 en ISO 32000-2) voegt een incrementele update gewijzigde objecten achteraan in het bestand toe en laat de originele bytes intact, dus de originele revisie, met zijn paginacontent, afbeeldingen en geannoteerde marks, blijft leesbaar voor iedereen die het bestand knipt op de eerdere %%EOF; ExtractDocumentRevision in de PDFlibPas Small Business-editie doet dat in één aanroep. Na ApplyRedactions schrijft u een nieuw bestand met SaveToFile, nooit SaveChanges, dat toevoegt. Vóór het opslaan veegt GarbageCollectObjects de objecttabel schoon en laat objecten vallen waarnaar niets meer verwijst, zoals de appearance streams die de marks vóór de apply-stap droegen; een volledige save slaat die veegbeurt over tenzij SetGarbageCollect hem aanzet, of het veld GarbageCollect van het TPDFlibSaveOptions-record dat u aan SaveToFileOptions doorgeeft is gezet. Metadata blijft het resterende gat. SanitizeDocument(SANITIZE_METADATA or SANITIZE_XMP_PACKET) maakt Author, Subject, Keywords, Creator en Producer leeg en verwijdert de XMP-stream, en laat de Title over om zelf na te kijken. Vermijd SANITIZE_ALL hier: het omvat SANITIZE_ANNOTATIONS en verwijdert elke annotatie die nog in het document zit, notities die u had willen houden inbegrepen
ApplyRedactions is, net als RedactRegion, beschikbaar in de Enterprise-editie. Op een kale TPDFlib geeft het 0 terug en zet het LastErrorCode op PDFLIB_ERROR_ENTERPRISE_REQUIRED (900), dus de releasestap gebruikt TPDFlibEnterprise uit de 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; // elke Redact-mark op elke pagina
if Applied = 0 then
raise Exception.CreateFmt('No marks applied (error %d)',
[Pdf.LastErrorCode]);
// Info en XMP kunnen nog benoemen wat de pagina's niet meer tonen
Pdf.SanitizeDocument(SANITIZE_METADATA or SANITIZE_XMP_PACKET);
Orphans := Pdf.GarbageCollectObjects;
// Volledige herschrijving naar een nieuw bestand; nooit SaveChanges na redactie
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;
Verwacht: ApplyRedactions geeft het aantal Redact-marks terug dat het over alle pagina's heeft afgevlakt, 2 voor de reviewkopie geschreven door MarkForReview, en GarbageCollectObjects geeft terug hoeveel niet-gerefereerde objecten de veegbeurt heeft laten vallen
Welke tool ook een mark tekende, zijn /RO- en /IC-entries bepalen wat de ontvanger op de pagina ziet, terwijl de apply-stap in PDFlibPas Enterprise for Delphi bepaalt wat eronder is verdwenen