HotPDF gennemløber hver operator i en sides content stream og overdrager hver enkelt til din kode via VisitLoadedPageContentOperators. For hver operator vælger du en handling: behold den, slet den, erstat den med dine egne bytes, stop gennemløbet, eller afvis hele redigeringen. Når alle operatorer beholdes, er den omskrevne stream byte for byte identisk med originalen, hvilket er den egenskab, der gør mekanismen sikker at køre over dokumenter, du ikke selv har oprettet
Redigering af content streams er der, hvor PDF-værktøjer som regel går galt. En stream er en sekvens af operatorer over en stakmaskine, ikke et dokumenttræ, og den naive tilgang med at køre et regulært udtryk over de afkodede bytes går i stykker, i det øjeblik et string-literal indeholder noget, der ligner en operator, eller et inline-billede indeholder vilkårlig binærdata. En visitor, der allerede har tokeniseret streamen, har ikke det problem
Hvad kan du egentlig gøre med adgang på operatorniveau?
Tre familier af opgaver. Auditering: tæl, hvor mange gange en side sætter en farve uden for et kendt sæt, eller find tekst tegnet med en skrifttype, der ikke er indlejret. Fjernelse: slet et vandmærke, der blev tegnet som marked content, eller fjern en bestemt billedpåkaldelse. Og normalisering: erstat én operatorform med en tilsvarende, som en efterfølgende forbruger håndterer bedre
Hver af disse deler ét krav: du skal kunne lade de øvrige 99 procent af streamen være urørte. HotPDF's syntakstræ gemmer én kopi af kilden plus kompakte spans, og redigeringer er sorterede, ikke-overlappende overlejringer. Skrivning af resultatet kopierer urørte intervaller direkte og udsender kun erstatningsbytes, hvor du har ændret noget, så en no-op-tur frem og tilbage ikke kan indføre drift
Visitor-kontrakten
Implementér interfacet eller tildel eventet, og kald derefter visit-metoden med ApplyChanges sat til false for en auditering eller true for en omskrivning. Event-dataene bærer fasen, operatorens navn, dens operandantal, dens byte-span, den aktuelle nesting-tilstand og en lånt view til at læse operander:
uses
HPDFDoc, HPDFContentStream;
procedure TAuditor.HandleOperator(Sender: TObject;
var EventData: THPDFContentOperatorEventData);
var
Bytes: TBytes;
begin
if EventData.Phase <> covpOperator then
Exit;
// Tæl tekstvisende operatorer tegnet inde i marked content
if (EventData.OperatorName = 'Tj') or
(EventData.OperatorName = 'TJ') then
begin
Inc(FTextOperators);
if EventData.State.MarkedContentDepth > 0 then
Inc(FTextInsideMarkedContent);
end;
// Læs kun en operand, når du har brug for den
if EventData.OperatorName = 'Do' then
if EventData.View.GetOperandBytes(0, Bytes) then
NoteXObjectUse(TEncoding.ANSI.GetString(Bytes));
EventData.Action := coaKeep;
end;
procedure TAuditor.Run(const FileName: string);
var
Pdf: THotPDF;
Stats: THPDFContentOperatorVisitStatistics;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile(FileName) <= 0 then
Exit;
Pdf.OnContentOperator := HandleOperator;
for I := 0 to Pdf.LoadedPageCount - 1 do
Pdf.VisitLoadedPageContentOperators(I, False, Stats); // kun auditering
Writeln(Format('%d operators visited, max q/Q depth %d',
[Stats.VisitedOperatorCount, Stats.MaximumGraphicsStateDepth]));
finally
Pdf.Free;
end;
end;
Hvorfor er viewet lånt frem for muterbart?
Callbacket modtager aldrig et ændringsbart syntakstræ. Det modtager et view, der kan hente den aktuelle operations bytes eller en specifik operand efter behov, og det er alt. Enhver ændring går i stedet gennem action- og replacement-felterne
Denne indirekthed er ikke ceremoni. Direkte trædmutation ville lade en visitor omgå erstatningsbudgettet og tjekket for overlappende redigeringer, og overlappende redigeringer i en byte-span-overlejringsmodel giver output, der er stiltiende forkert frem for tydeligt ødelagt. At lede ændringer gennem action-feltet betyder, at hver redigering valideres på samme måde, uanset hvad visitoren havde til hensigt
At læse efter behov holder også det almindelige tilfælde billigt. En auditering, der kun tæller operatornavne, materialiserer aldrig operandbytes overhovedet, hvilket betyder noget på en content stream med hundredtusindvis af operationer
State-feltet beskriver altid inputtet
Hvert event bærer grafiktilstands-dybden, tekstobjekt-dybden, marked content-dybden og kompatibilitetssektion-dybden, som de stod i det oprindelige input umiddelbart før den operator blev udført. Det gælder, selv hvis din visitor slettede eller erstattede tidligere operatorer
At forankre tilstanden til det oprindelige input frem for til det redigerede output er et bevidst valg med en praktisk gevinst: at køre den samme auditering to gange over det samme dokument giver identiske resultater, og en auditering, der kører sideløbende med redigeringer, forskyder ikke sin egen referenceramme. Hvis du har brug for at kende dybden i outputtet, ved du, hvad du slettede, så du kan beregne det; det omvendte, at rekonstruere stabile inputpositioner ud fra et output i bevægelse, er ikke muligt
Sletning og erstatning, med de regler der beskytter dig
En erstatning er rå content-stream-bytes, og du er ansvarlig for deres syntaks. Beskyttelsesforanstaltningerne omkring dem er budgettet for de samlede erstatningsbytes, kravet om ikke-overlap og den valgfrie output-validering, der genparser resultatet, før det accepteres:
procedure TWatermarkStripper.HandleOperator(Sender: TObject;
var EventData: THPDFContentOperatorEventData);
begin
EventData.Action := coaKeep;
if EventData.Phase <> covpOperator then
Exit;
// Drop de operatorer, der tegner vandmærke-formularen
if (EventData.OperatorName = 'Do') and OperandIsWatermark(EventData) then
EventData.Action := coaDelete
// Normalisér en vildfaren farve til husets grå
else if EventData.OperatorName = 'rg' then
begin
EventData.Replacement := TEncoding.ANSI.GetBytes('0.2 0.2 0.2 rg');
EventData.Action := coaReplace;
end
// Afvis hele redigeringen, hvis vi støder på noget, vi ikke forstår
else if EventData.OperatorName = 'BI' then
EventData.Action := coaReject;
end;
var
Options: THPDFContentOperatorVisitOptions;
begin
Options := THPDFContentOperatorVisitOptions.Default;
Options.ValidateOutput := True; // genpars før accept
Options.MaxReplacementBytes := 64 * 1024;
Pdf.OnContentOperator := HandleOperator;
if Pdf.VisitLoadedPageContentOperators(PageIndex, True, Options, nil, Stats) then
if Stats.Modified then
Pdf.SaveLoadedDocument('cleaned.pdf');
end;
Handlingssættet indeholder to udgange af en grund. coaStop afslutter gennemløbet og beholder, hvad der er besluttet indtil da, hvilket er rigtigt, når du har fundet det, du ledte efter. coaReject opgiver hele besøget og lader siden være urørt, hvilket er rigtigt, når du støder på en konstruktion, dine omskrivningsregler ikke dækker. Inline-billeder er det klassiske eksempel: deres binære payload er ikke noget, en farve-normaliserende visitor bør redigere omkring
Aflæsning af statistikken, og hvor gennemløbet stopper
Status-feltet adskiller fuldførelse fra stop, afvisning, ugyldigt input, en overskredet grænse, ugyldigt output og en visitor-fejl. Det er forskellige problemer: ugyldigt output betyder, at dine erstatningsbytes ikke lod sig genparse, mens ugyldigt input betyder, at kildestreamen allerede var fejlformet, og ingen redigering blev forsøgt
Tællerne gør en auditering selvkontrollerende. VisitedOperatorCount sammenholdt med KeptOperatorCount, DeletedOperatorCount og ReplacedOperatorCount skal gå op, og maksimum-dybdetallene fortæller dig, om siden nester dybt nok til at være et nærmere kig værd. SourceBytes sammenholdt med OutputBytes giver størrelseseffekten af din redigering i ét tal
For de almindelige opgaver findes der ruter på et højere niveau, som slet ikke kræver, at du skriver en visitor. Permanent fjernelse af indhold, med den ressourceoprydning ægte redigering af følsomme oplysninger kræver, er dækket i redigering af følsomme oplysninger og sidesammensyning på indlæste dokumenter. Erstatning af synlig tekst er dækket i søg og erstat i en eksisterende PDF. Grib til operator-visitoren, når din regel er strukturel frem for tekstuel, eller når du har brug for en auditering, som ingen eksisterende rapport besvarer
Content stream-besøg, ekstraktion og omskrivning kører alle mod den samme indlæste dokumentmodel i Delphi og C++Builder; den komplette funktionsliste findes på HotPDF Delphi PDF component-siden