HotPDF går igenom varje operator i en sidas innehållsström och lämnar över var och en till din kod via VisitLoadedPageContentOperators. För varje operator väljer du en åtgärd: behåll den, ta bort den, ersätt den med dina egna byte, avbryt genomgången, eller avvisa hela redigeringen. När varje operator behålls är den omskrivna strömmen byte-för-byte identisk med originalet, vilket är egenskapen som gör mekanismen säker att köra över dokument du inte skapat
Redigering av innehållsströmmar är där PDF-verktyg vanligtvis går fel. En ström är en sekvens av operatorer över en stackmaskin, inte ett dokumentträd, och den naiva metoden att köra ett reguljärt uttryck över de avkodade byten går sönder så fort en stränglitteral innehåller något som liknar en operator, eller en inline-bild innehåller godtycklig binärdata. En besökare som redan har tokeniserat strömmen har inte det problemet
Vad kan du egentligen göra med åtkomst på operatornivå?
Tre familjer av uppgifter. Granskning: räkna hur många gånger en sida ställer in en färg utanför en känd uppsättning, eller hitta text ritad med ett typsnitt som inte är inbäddat. Borttagning: ta bort en vattenstämpel som ritades som markerat innehåll, eller ta bort ett specifikt bildanrop. Och normalisering: ersätt en operatorform med en likvärdig som en nedströms konsument hanterar bättre
Var och en av dessa delar ett krav: du måste kunna lämna de andra 99 procenten av strömmen orörda. HotPDF:s syntaxträd lagrar en kopia av källan plus kompakta intervall, och redigeringar är sorterade icke-överlappande överlägg. Att skriva resultatet kopierar orörda intervall direkt och avger endast ersättningsbyte där du ändrat något, så en no-op-rundtur kan inte introducera avvikelser
Besökarkontraktet
Implementera gränssnittet eller tilldela händelsen, anropa sedan besöksmetoden med ApplyChanges satt till false för en granskning eller true för en omskrivning. Händelsedatan bär fasen, operatornamnet, dess operandantal, dess byteintervall, det aktuella nästlingstillståndet och en lånad vy för att läsa operander:
uses
HPDFDoc, HPDFContentStream;
procedure TAuditor.HandleOperator(Sender: TObject;
var EventData: THPDFContentOperatorEventData);
var
Bytes: TBytes;
begin
if EventData.Phase <> covpOperator then
Exit;
// Räkna textvisande operatorer ritade inuti markerat innehåll
if (EventData.OperatorName = 'Tj') or
(EventData.OperatorName = 'TJ') then
begin
Inc(FTextOperators);
if EventData.State.MarkedContentDepth > 0 then
Inc(FTextInsideMarkedContent);
end;
// Läs en operand bara när du behöver 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); // enbart granskning
Writeln(Format('%d operators visited, max q/Q depth %d',
[Stats.VisitedOperatorCount, Stats.MaximumGraphicsStateDepth]));
finally
Pdf.Free;
end;
end;
Varför är vyn lånad istället för muterbar?
Callbacken tar aldrig emot ett redigerbart syntaxträd. Den tar emot en vy som kan hämta den aktuella operationens byte eller en specifik operand på begäran, och det är allt. Varje ändring går genom fälten action och replacement istället
Den indirektionen är ingen formalitet. Direkt trädmutation skulle låta en besökare kringgå ersättningsbudgeten och kontrollen för överlappande redigeringar, och överlappande redigeringar i en byteintervallöverlägg-modell ger utdata som är tyst fel snarare än högljutt trasig. Att kanalisera ändringar genom action-fältet betyder att varje redigering valideras på samma sätt oavsett vad besökaren avsåg
Att läsa på begäran håller också det vanliga fallet billigt. En granskning som bara räknar operatornamn materialiserar aldrig operandbyte alls, vilket spelar roll på en innehållsström med hundratusentals operationer
State-fältet beskriver alltid indata
Varje händelse bär grafiktillståndsdjupet, textobjektsdjupet, det markerade innehållsdjupet och kompatibilitetssektionsdjupet som de stod i den ursprungliga indatan omedelbart innan den operatorn kördes. Det gäller även om din besökare tagit bort eller ersatt tidigare operatorer
Att förankra tillståndet till den ursprungliga indatan snarare än till den redigerade utdatan är ett medvetet val med en praktisk vinst: att köra samma granskning två gånger över samma dokument ger identiska resultat, och en granskning som körs jämsides med redigeringar förskjuter inte sin egen referensram. Om du behöver veta djupet i utdatan vet du vad du tagit bort, så du kan beräkna det; det omvända, att rekonstruera stabila indatapositioner från en rörlig utdata, är inte möjligt
Att ta bort och ersätta, med reglerna som skyddar dig
En ersättning är rå innehållsströmsbyte, och du ansvarar för deras syntax. Skyddsmekanismerna kring dem är budgeten för totala ersättningsbyte, kravet på icke-överlappning, och den valfria utdatavalideringen som tolkar om resultatet innan det accepteras:
procedure TWatermarkStripper.HandleOperator(Sender: TObject;
var EventData: THPDFContentOperatorEventData);
begin
EventData.Action := coaKeep;
if EventData.Phase <> covpOperator then
Exit;
// Ta bort operatorerna som ritar vattenstämpelformuläret
if (EventData.OperatorName = 'Do') and OperandIsWatermark(EventData) then
EventData.Action := coaDelete
// Normalisera en avvikande färg till 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
// Avvisa hela redigeringen om vi stöter på något vi inte förstår
else if EventData.OperatorName = 'BI' then
EventData.Action := coaReject;
end;
var
Options: THPDFContentOperatorVisitOptions;
begin
Options := THPDFContentOperatorVisitOptions.Default;
Options.ValidateOutput := True; // tolka om innan det accepteras
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;
Åtgärdsuppsättningen innehåller två utgångar av en anledning. coaStop avslutar genomgången och behåller vad som beslutats hittills, vilket är rätt när du hittat det du letade efter. coaReject överger hela besöket och lämnar sidan orörd, vilket är rätt när du stöter på en konstruktion dina omskrivningsregler inte täcker. Inline-bilder är det klassiska exemplet: deras binära last är inte något en färgnormaliserande besökare bör redigera runt
Att läsa statistiken, och var genomgången stannar
Statusfältet skiljer mellan slutförande, avbrytning, avvisning, ogiltig indata, en överskriden gräns, ogiltig utdata och ett besökarfel. Det är olika problem: ogiltig utdata betyder att dina ersättningsbyte inte gick att tolka om, medan ogiltig indata betyder att källströmmen redan var felformad och ingen redigering försöktes
Räknarna gör en granskning självkontrollerande. VisitedOperatorCount mot KeptOperatorCount, DeletedOperatorCount och ReplacedOperatorCount måste summeras korrekt, och de maximala djupvärdena talar om huruvida sidan nästlar tillräckligt djupt för att vara värd en närmare titt. SourceBytes mot OutputBytes ger storlekseffekten av din redigering i en enda siffra
För de vanliga uppgifterna finns det routes på högre nivå som inte kräver att skriva en besökare alls. Att ta bort innehåll permanent, med den resursuppstädning riktig redigering behöver, täcks i redigering och sidsammanfogning på inlästa dokument. Att ersätta synlig text täcks i sök och ersätt text i en befintlig PDF. Ta till operatorbesökaren när din regel är strukturell snarare än textmässig, eller när du behöver en granskning ingen befintlig rapport besvarar
Genomgång, extraktion och omskrivning av innehållsströmmar körs mot samma inlästa dokumentmodell i Delphi och C++Builder; den fullständiga funktionslistan finns på HotPDF Delphi PDF-komponentsidan