Riktig PDF-redigering i Delphi innebär att man tar bort de underliggande byten, inte att man målar över dem. losLab PDF Library drar den gränsen med två API:er: RedactRegion redigerar sidans innehållsström så att den borttagna texten, vektorerna och bilderna fysiskt försvinner, och SanitizeDocument rensar metadata, skript och åtgärder där redigerad information också gömmer sig. Om du gör fel på något av detta skickar du en fil som ser censurerad ut på skärmen men som lämnar tillbaka hemligheten till alla som kopierar texten eller öppnar Dokumentegenskaper
Detta felscenario är välkänt. Tidningar, domstolar och statliga myndigheter har alla släppt PDF-filer med en svart rektangel lagd över ett namn eller ett nummer, bara för att en läsare ska kunna markera texten därunder, kopiera den och klistra in den påstått censurerade strängen helt synligt. Rektangeln var en ritning. Texten fanns fortfarande kvar i innehållsströmmen ett lager under färgen, helt intakt och avkodningsbar. losLab PDF Library, PDF-motorn för Delphi och C++Builder som exponeras via klassen TPDFlib, behandlar den skillnaden som hela syftet med redigeringen snarare än en eftertanke
Varför är det inte riktig redigering att rita en svart ruta över PDF-text?
losLab PDF Library exponerar MaskRect för de fall där övertäckning verkligen är vad du vill ha: en vattenstämpel, en visuell blackout, en stämpel. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) lägger till en rektangel i sidans innehållsström och målar den med sökbaneoperatören re följt av fyllningsoperatören f (ISO 32000-1 §8.5). Varje byte som redan fansw på sidan — tecknen i namnet du täckte över, vektorlinjerna, den inbäddade bilden — stannar kvar exakt där den var, en instruktion tidigare i samma ström. En kompatibel läsare ritar den svarta rutan sist, så att den vinner visuellt. Övertäckningen ändrar ingenting därunder
Vem som helst kan återställa det ursprungliga innehållet på tre sätt: markera och kopiera texten rakt igenom den opaka rutan, köra en textextraherare över sidan eller ta bort den täckande rektangeln från innehållsströmmen. MaskRect är ärlig med detta i sitt eget namn — den maskerar, den tar inte bort. Resultatet är som ett klistermärke över ett ord som du fortfarande kan läsa genom att lyfta bort klistermärket
Hur RedactRegion tar bort innehåll på instruktionsnivå
RedactRegion(Page, Left, Top, Width, Height, Options) fungerar ett lager under färgen. losLab PDF Library analyserar varje innehållslager på sidan till en TPDFContentProgram, går igenom instruktionslistan med en CTM-spårande analysator (FindInstructionsInRect i innehållsmodellen) och markerar varje textvisnings-, sökbanemålnings- och bildoperatör (Do) vars målade område skär din rektangel. Dessa instruktioner tas bort med TPDFContentProgram.Delete, och det redigerade lagret skrivs tillbaka med WritePageContentLayer. Byten hamnar inte bakom ett skydd — de lämnar strömmen. En textextraktion som körs efteråt hittar ingenting eftersom det inte finns något att hitta
Två implementeringsdetaljer är värda att ta med i dina egna överväganden. RedactRegion tar en rektangel uppe till vänster i det aktuella koordinatsystemet och enheterna och konverterar den internt till det nedre vänstra användarutrymme som innehållsströmmen använder, så att du anger regionen som du ser den på sidan. När en rektangel täcker flera instruktioner tar RedactRegion bort dem i fallande indexordning, eftersom att ta bort instruktion 4 före instruktion 7 skulle förskjuta alla senare index och korrumpera intervallet. Skicka Options = 0 så stämplar losLab PDF Library också en solid svart ruta över den rensade regionen som en synlig markör — men den markören är kosmetisk, applicerad efter att det verkliga innehållet redan är borta
I praktiken hårdkodar du sällan rektangeln. Du söker igenom sidan efter den känsliga strängen och läser av dess begränsningsruta — samma sökning och elementuppräkning som beskrivs i hitta text och sidselement i Delphi — och skickar sedan den rektangeln till RedactRegion:
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('contract.pdf', '') = 1 then
begin
// Delete everything painted inside a 220 x 14 pt box on page 1.
// Coordinates are top-left, in the current origin and units.
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // Options=0 stamps a black marker
PDF.SaveToFile('contract-redacted.pdf');
end;
finally
PDF.Free;
end;
end;
Var döljer sig fortfarande redigerad information i en PDF?
Redigering som stannar vid det synliga lagret lämnar spår eftersom en PDF lagrar information på platser som en läsare aldrig ser. losLab PDF Library grupperar dessa läckor i kanaler som är värda att nämnas. Dokumentinformationsordboken (Document Information Dictionary) och XMP-metadataströmmen innehåller rutinmässigt en författare (Author), en producent (Producer) eller en sökordssträng (Keywords) som namnger just den person eller det fall du tog bort från brödtexten. JavaScript på dokumentnivå och katalogens OpenAction kan köras vid öppning och nå data som du trodde var borta. Varje anteckning bär på en valfri /A-åtgärd, och en anteckning i sig — en kommentar, ett gammalt formulärfält, en länk — kan ligga direkt över det redigerade området med sin egen kopia av texten. Inget av detta berörs av kryptering; en krypterad fil med ett XMP-paket i klartext läcker fortfarande sin titel till sökrobotar, en lucka som är värd att undersöka i revision av kryptering och behörigheter för sig. Försvarbar sanering måste besöka var och en av dessa gömställen, inte bara sidan du tittade på
Rensa dokumentet med SanitizeDocument
losLab PDF Library möter denna spridning av läckor med SanitizeDocument(Flags), ett samordnat anrop som kör en uppsättning oberoende rensningar valda med en bitmask och returnerar antalet rensningar som utförts. Flaggorna kan kombineras. SANITIZE_JAVASCRIPT tar bort varje JavaScript-paket på dokumentnivå och alla OpenAction-skript. SANITIZE_ACTIONS rensar åtgärdsordboken /A från varje anteckning, via det nya TPDFAnnots.RemoveAnnotAction, och rensar katalogens OpenAction. SANITIZE_METADATA tömmer Author, Subject, Keywords, Creator och Producer i både dokumentinformationsordboken (ISO 32000-1 §14.3.3) och XMP-metadataströmmen (§14.3.2). SANITIZE_ANNOTATIONS tar bort varje anteckning i hela dokumentet, och SANITIZE_OPEN_ACTION tar bort katalogposten /OpenAction. SANITIZE_ALL är den bitvisa unionen av alla fem
Indexkonventionerna inuti dessa rensningar är precis en sådan sak som tyst kan förstöra en manuellt skriven loop, vilket är en stor anledning till att SanitizeDocument existerar som ett enda anrop. GlobalJavaScriptPackageName är 0-baserat, och RemoveGlobalJavaScript minskar listan allteftersom den körs, så JavaScript-rensningen läser alltid paketindex 0 och tar bort tills antalet når noll. DeleteAnnotation är däremot 1-baserat. AnnotationCount rapporterar mot den valda sidan, så anteckningsrensningarna väljer varje sida i tur och ordning innan de räknar. losLab PDF Library hanterar dessa gränser rätt internt, så du skickar en flagginställning och får ett antal tillbaka istället för att behöva reproducera fem olika iterationsregler
var
Applied: Integer;
begin
// Strip metadata, scripts, and actions but keep the annotations:
Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);
// Or clear every hidden channel, annotations included:
Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;
Det fullständiga arbetsflödet för redigering och sanering
Slå ihop de två och hela proceduren blir kort. losLab PDF Library tar bort det synliga innehållet med RedactRegion, sida för sida, och rensar sedan varje dold kanal med SanitizeDocument(SANITIZE_ALL) innan filen skrivs. Eftersom redigering nästan alltid körs på dokument som kommit från källor utanför din kontroll, är det värt att läsa in dem via en härdad bana — samma defensiva hållning som beskrivs i säker analys av opålitliga PDF-filer — så att ett felaktigt indata misslyckas rent istället för halvvägs genom redigeringen
var
PDF: TPDFlib;
Applied: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('incoming.pdf', '') = 1 then
begin
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // delete the sensitive line
Applied := PDF.SanitizeDocument(SANITIZE_ALL); // clear metadata, JS, actions, annots
PDF.SaveToFile('published.pdf');
end;
finally
PDF.Free;
end;
end;
Var redigering i innehållsströmmen upphör
RedactRegion fungerar på innehållsströmmen, vilket är precis där digitalt skapad text lever och precis där skannad text inte gör det. När en sida är en skannad bild är orden pixlar inuti ett bild-XObject, inte textoperatörer, och losLab PDF Library kan inte skära ut ett namn ur mitten av ett raster på samma sätt som det tar bort ett tecken. Dess bildhantering är medvetet konservativ: alla bilder vars placering skär rektangeln tas bort helt, så en redigeringsruta på en skannad sida tar med sig hela skanningen snarare än bara en del av den. Att redigera en region inuti en bild innebär rasterisering, målning och återinbäddning på pixel-nivå, vilket är en annan operation än borttagning i innehållsströmmen. Samma försiktighet gäller för ett OCR-textlager över en skannad sida: RedactRegion tar bort de textoperatörer som det kan se, men bilden därunder förblir orörd om inte dess bild-XObject också skär rutan
Två finare detaljer håller resultaten ärliga. Skärningstestet uppskattar bredden för varje textkörning utifrån dess teckenantal och typsnittsstorlek snarare än att mäta exakta teckenmått, så ett mycket kort ord skrivet i ett proportionellt typsnitt kan slinka förbi en snävt ritad rektangel — ge rutan en liten marginal. Borttagningen sker också på instruktionsnivå: RedactRegion tar bort hela textvisningsoperatörer, så en rektangel som bara klipper en del av en körning kan ta med sig intilliggande tecken som delade samma operatör med den. Ingen av dessa begränsningar är en anledning att inte lita på mekanismen, bara en anledning att avgränsa rektangeln noggrant och kontrollera resultatet
Disciplinen som avslutar varje redigeringsarbetsflöde är verifiering, inte tillit. Redigera regionen, sanera dokumentet, spara till en ny fil, öppna den igen och försök att hämta tillbaka både texten du tog bort och metadata du rensade; när båda kommer tillbaka tomma höll redigeringen. API:erna RedactRegion och SanitizeDocument som visas här levereras med losLab PDF Library för Delphi och C++Builder, tillsammans med den fullständiga referensen för innehållsmodell och sanering