Teknisk artikel

Ægte PDF-redigering i Delphi: Slet og rens

Ægte PDF-redigering i Delphi betyder at slette de underliggende bytes, ikke at male hen over dem. losLab PDF Library trækker denne linje med to API'er: RedactRegion redigerer sideindholdsstrømmen, så den fjernede tekst, vektorer og billeder fysisk er væk, og SanitizeDocument rydder metadata, scripts og handlinger, hvor redigerede oplysninger også gemmer sig. Hvis du tager fejl af en af delene, sender du en fil, der ser censureret ud på skærmen, men giver hemmeligheden tilbage til enhver, der kopierer teksten eller åbner Dokumentegenskaber

Fejltilstanden er offentligt kendt. Aviser, domstole og offentlige myndigheder har alle frigivet PDF-filer med et sort rektangel lagt over un navn eller et nummer, blot for at en læser kan vælge teksten nedenunder, kopiere den og indsætte den formodede redigerede streng i fuldt synligt format. Rektanglet var en tegning. Teksten var der stadig, siddende i indholdsstrømmen et lag under malingen, fuldt intakt og fuldt valgbar. losLab PDF Library, PDF-motoren til Delphi og C++Builder eksponeret via klassen TPDFlib, behandler den skelnen som hele pointen med redigering frem for en eftertanke

Hvorfor er det ikke ægte redigering at tegne en sort boks over PDF-tekst?

losLab PDF Library eksponerer MaskRect til de tilfælde, hvor overdækning reelt er det, du ønsker: Et vandmærke, en visuel blackout, et korrekturstempel. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) tilføjer et rektangel til sidens indholdsstrøm og maler det med sti-konstruktionsoperatoren re efterfulgt af udfyldningsoperatoren f (ISO 32000-1 §8.5). Hver byte, der allerede var på siden — glyferne for det navn, du dækkede, vektorstregerne, det indlejrede billede — forbliver præcis, hvor det var, én instruktion tidligere i den samme strøm. En overensstemmende fremviser tegner den sorte boks til sidst, så den vinder visuelt. Overdækningen ændrer intet nedenunder

Enhver kan hente det oprindelige indhold på tre måder: Vælg og kopier teksten direkte gennem den uigennemsigtige boks, kør en tekstudtrækker over siden, eller slet det dækkende rektangel fra indholdsstrømmen. MaskRect er ærlig om dette i sit eget navn — den maskerer, den fjerner ikke. Resultatet er et klistermærke over et ord, du stadig kan læse ved at løfte klistermærket af

Hvordan sletter RedactRegion indhold på instruktionsniveau?

RedactRegion(Page, Left, Top, Width, Height, Options) arbejder et lag under malingen. losLab PDF Library fortolker hvert indholdslag på siden til et TPDFContentProgram, gennemgår instruktionslisten med en CTM-sporende analysator (FindInstructionsInRect i indholdsmodellen) og markerer enhver tekstvisnings-, stimalings- og billed-Do-operator, hvis malede område skærer dit rektangel. Disse instruktioner fjernes med TPDFContentProgram.Delete, og det redigerede lag skrives tilbage med WritePageContentLayer. Bytes glider ikke om bag en overdækning — de forlader strømmen. En tekstudtrækker, der køres bagefter, finder intet, da der ikke er noget at finde

To implementeringsdetaljer er værd at tage med i dine egne overvejelser. RedactRegion tager et øverste-venstre rektangel i det aktuelle udgangspunkt og enheder og konverterer det internt til det nederste-venstre brugerområde, som indholdsstrømmen bruger, så du angiver området på den måde, du ser det på siden. Når et rektangel dækker flere instruktioner, sletter RedactRegion dem i faldende indeksrækkefølge, fordi fjernelse af instruktion 4 før instruktion 7 ville forskyde ethvert senere indeks og ødelægge området. Angiv Options = 0, og losLab PDF Library stempler også en solid sort boks over det ryddede område som en synlig markør — men denne markør er kosmetisk, påført efter at det virkelige indhold allerede er væk

I praksis hårdkoder du sjældent rektanglet. Du søger på siden efter den følsomme streng og læser dens grænseboks — den samme sidesøgning og elementopregning, der er dækket i lokalisering af tekst og sideelementer i Delphi — og overdrager derefter dette rektangel til 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;

Hvor gemmer redigerede oplysninger sig stadig i en PDF?

Redigering, der stopper ved det synlige lag, efterlader spor, fordi en PDF gemmer information på steder, en læser aldrig kigger. losLab PDF Library grupperer lækagerne i kanaler, der er værd at navngive. Dokumentinformationens ordbog og XMP-metadatastrømmen bærer rutinemæssigt en streng for forfatter, producent eller søgeord, der navngiver netop den person eller sag, du fjernede fra brødteksten. JavaScript på dokumentniveau og katalogets OpenAction kan køre ved åbning og nå data, du troede var væk. Enhver annotation bærer en valgfri /A-handling, og selve annotationen — en selvklæbende note, et forældet formularfelt, et link — kan sidde direkte over det redigerede sted med sin egen kopi af teksten. Intet af dette berøres af kryptering; en krypteret fil med en XMP-pakke i klartekst lækker stadig sin titel til enhver indeksør, et hul, der er værd at foretage en audit af kryptering og tilladelser på alene. Forsvarlig rensning skal besøge hvert af disse gemmesteder, ikke kun den side, du kiggede på

Rensning af dokumentet med SanitizeDocument

losLab PDF Library besvarer denne spredning af lækager med SanitizeDocument(Flags), et orkestreringskald, der kører et sæt uafhængige strips valgt af en bitmaske og returnerer antallet af strips, det anvendte. Flagene kan kombineres. SANITIZE_JAVASCRIPT fjerner alle JavaScript-pakker på dokumentniveau og ethvert OpenAction-script. SANITIZE_ACTIONS fjerner /A-handlingsordbogen fra enhver annotation via den nye TPDFAnnots.RemoveAnnotAction og rydder katalogets OpenAction. SANITIZE_METADATA blanker forfatter, emne, søgeord, opretter og producent i både dokumentinformationens ordbog (ISO 32000-1 §14.3.3) og XMP-metadatastrømmen (§14.3.2). SANITIZE_ANNOTATIONS sletter alle annotationer i hele dokumentet, og SANITIZE_OPEN_ACTION fjerner katalogets /OpenAction-post. SANITIZE_ALL er den bitvise sammenslutning af alle fem

Indekskonventionerne i disse strips er præcis den slags, der lydløst ødelægger en manuelt opbygget løkke, hvilket er en stor del af årsagen til, at SanitizeDocument eksisterer som et enkelt kald. GlobalJavaScriptPackageName is 0-baseret, og RemoveGlobalJavaScript krymper listen, mens den kører, så JavaScript-strippen altid læser pakkeindeks 0 og fjerner, indtil antallet når nul. DeleteAnnotation er derimod 1-baseret. AnnotationCount rapporterer mod den aktuelt valgte side, så annotations-strips vælger hver side efter tur før optælling. losLab PDF Library rammer disse grænser rigtigt internt, så du sender ét flagsæt og læser ét antal tilbage i stedet for at reproducere fem forskellige 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 komplette workflow for redigering og rensning

Sæt de to ting sammen, og den fulde procedure er kort. losLab PDF Library fjerner det synlige indhold med RedactRegion, side for side, og rydder derefter enhver skjult kanal med SanitizeDocument(SANITIZE_ALL), før filen skrives. Da redigering næsten altid kører på dokumenter, der ankommer uden for din kontrol, er det værd at indlæse dem via en hærdet sti — den samme defensive holdning beskrevet i sikker fortolkning af utilsigtede PDF-filer — så et forkert udformet input fejler rent i stedet for halvvejs gennem redigeringsrunden

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;

Hvor indholdsstrøms-redigering ophører

RedactRegion fungerer på indholdsstrømmen, hvilket er præcis, hvor digitalt født tekst lever, og præcis, hvor scannet tekst ikke gør. Når en side er et scannet billede, er ordene pixels inde i et billed-XObject, ikke tekstoperatorer, og losLab PDF Library kan ikke skære et navn ud af midten af et rasterbillede på den måde, det sletter en glyf. Dets billedhåndtering er bevidst konservativ: Ethvert billede, hvis placering skærer rektanglet, fjernes helt, så en redigeringsboks på en scannet side tager hele scanningen med sig i stedet for en del af den. Redigering af et område inde i et billede betyder rasterisering, maling og genindlejring på pixel-niveau, hvilket er en anden operation end sletning af indholdsstrømmen. Den samme forsigtighed gælder for et OCR-tekstlag over en scannet side: RedactRegion fjerner de tekstoperatorer, den kan se, men billedet nedenunder forbliver uberørt, medmindre dets billed-XObject også skærer boksen

To finere detaljer holdet resultaterne ærlige. Skæringstesten estimerer bredden af hver tekstkørsel ud fra dens tegnantal og skrifttypestørrelse snarere end at måle nøjagtige glyf-metrikker, så et meget kort ord sat i en proportional skrifttype kan slippe forbi et stramt tegnet rektangel — giv boksen en lille margen. Sletning er også instruktions-granulær: RedactRegion fjerner hele tekstvisnings-operatorer, så a rektangel, der kun klipper en del af en kørsel, kan fjerne tilstødende tegn, der delte den samme operator med det. Ingen af grænserne er en grund til at mistro mekanismen, kun en grund til at afgrænse rektanglet omhyggeligt og kontrollere resultatet

Disciplinen, der afslutter ethvert redigeringsworkflow, er verifikation, ikke tillid. Rediger området, rens dokumentet, gem i en ny fil, genåbn den, og prøv at hente både den tekst, du fjernede, og de metadata, du ryddede; når begge kommer tomme tilbage, holdt redigeringen. API'erne RedactRegion og SanitizeDocument vist her leveres med losLab PDF Library til Delphi og C++Builder, sammen med den fulde reference til indholdsmodel og rensning