HotPDF doorloopt elke operator in de content stream van een pagina en geeft elk ervan door aan uw code via VisitLoadedPageContentOperators. Voor elke operator kiest u een actie: behouden, verwijderen, vervangen door uw eigen bytes, de doorloop stoppen, of de hele bewerking afwijzen. Wanneer elke operator wordt behouden, is de herschreven stream byte-voor-byte identiek aan het origineel, wat de eigenschap is die het mechanisme veilig maakt om over documenten te draaien die u niet zelf hebt gemaakt
Content-stream-bewerking is waar PDF-tools meestal fout gaan. Een stream is een reeks operators over een stackmachine, geen documentboom, en de naïeve aanpak om een reguliere expressie over de gedecodeerde bytes te laten lopen, breekt zodra een string-literal iets bevat dat op een operator lijkt, of een inline afbeelding willekeurige binaire data bevat. Een visitor die de stream al heeft getokeniseerd, heeft dat probleem niet
Wat kunt u eigenlijk doen met toegang op operatorniveau?
Drie taakfamilies. Auditing: tellen hoe vaak een pagina een kleur buiten een bekende set instelt, of tekst vinden die met een niet-ingesloten lettertype is getekend. Verwijdering: een watermerk verwijderen dat als gemarkeerde inhoud is getekend, of een specifieke afbeeldingsaanroep strippen. En normalisatie: één operatorvorm vervangen door een equivalente die een downstream-consument beter afhandelt
Elk van die deelt één vereiste: u moet de overige 99 procent van de stream ongemoeid kunnen laten. De syntaxisboom van HotPDF slaat één kopie van de bron plus compacte spans op, en bewerkingen zijn gesorteerde, niet-overlappende overlays. Het schrijven van het resultaat kopieert onaangeraakte bereiken rechtstreeks en geeft alleen vervangende bytes uit waar u iets hebt veranderd, zodat een no-op-rondgang geen drift kan introduceren
Het visitorcontract
Implementeer de interface of ken het event toe, roep dan de visit-methode aan met ApplyChanges ingesteld op false voor een audit of true voor een herschrijving. De eventdata draagt de fase, de operatornaam, het aantal operanden, de bytespan, de huidige nestingstatus en een geleende view om operanden te lezen:
uses
HPDFDoc, HPDFContentStream;
procedure TAuditor.HandleOperator(Sender: TObject;
var EventData: THPDFContentOperatorEventData);
var
Bytes: TBytes;
begin
if EventData.Phase <> covpOperator then
Exit;
// Tel tekst-tonende operators die binnen gemarkeerde inhoud zijn getekend
if (EventData.OperatorName = 'Tj') or
(EventData.OperatorName = 'TJ') then
begin
Inc(FTextOperators);
if EventData.State.MarkedContentDepth > 0 then
Inc(FTextInsideMarkedContent);
end;
// Lees een operand alleen wanneer u hem nodig hebt
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); // alleen audit
Writeln(Format('%d operators visited, max q/Q depth %d',
[Stats.VisitedOperatorCount, Stats.MaximumGraphicsStateDepth]));
finally
Pdf.Free;
end;
end;
Waarom is de view geleend in plaats van muteerbaar?
De callback ontvangt nooit een bewerkbare syntaxisboom. Ze ontvangt een view die op aanvraag de bytes van de huidige bewerking of een specifieke operand kan ophalen, en dat is alles. Elke verandering loopt in plaats daarvan via de actie- en vervangingsvelden
Die omweg is geen formaliteit. Directe boommutatie zou een visitor in staat stellen het vervangingsbudget en de controle op overlappende bewerkingen te omzeilen, en overlappende bewerkingen in een byte-span-overlaymodel produceren output die stilzwijgend fout is in plaats van luid kapot. Veranderingen via het actieveld leiden betekent dat elke bewerking op dezelfde manier wordt gevalideerd, ongeacht wat de visitor bedoelde
Op aanvraag lezen houdt het gewone geval ook goedkoop. Een audit die alleen operatornamen telt, materialiseert operandbytes helemaal nooit, wat er toe doet bij een content stream met honderdduizenden bewerkingen
Het statusveld beschrijft altijd de invoer
Elk event draagt de diepte van de grafische status, de diepte van het tekstobject, de diepte van gemarkeerde inhoud en de diepte van de compatibiliteitssectie zoals die stonden in de oorspronkelijke invoer onmiddellijk vóór het uitvoeren van die operator. Dat geldt zelfs als uw visitor eerdere operators heeft verwijderd of vervangen
Status verankeren aan de oorspronkelijke invoer in plaats van aan de bewerkte output is een bewuste keuze met een praktisch voordeel: dezelfde audit twee keer over hetzelfde document uitvoeren levert identieke resultaten op, en een audit die naast bewerkingen draait, verschuift zijn eigen referentiekader niet. Als u de diepte in de output moet weten, weet u wat u hebt verwijderd, dus kunt u die berekenen; het omgekeerde, stabiele invoerposities reconstrueren uit een bewegende output, is niet mogelijk
Verwijderen en vervangen, met de regels die u beschermen
Een vervanging bestaat uit rauwe content-stream-bytes, en u bent verantwoordelijk voor hun syntaxis. De waarborgen eromheen zijn het budget op totale vervangende bytes, de niet-overlappingsvereiste, en de optionele outputvalidatie die het resultaat opnieuw parseert voordat het wordt geaccepteerd:
procedure TWatermarkStripper.HandleOperator(Sender: TObject;
var EventData: THPDFContentOperatorEventData);
begin
EventData.Action := coaKeep;
if EventData.Phase <> covpOperator then
Exit;
// Laat de operators vallen die het watermerkformulier tekenen
if (EventData.OperatorName = 'Do') and OperandIsWatermark(EventData) then
EventData.Action := coaDelete
// Normaliseer een afwijkende kleur naar het huisgrijs
else if EventData.OperatorName = 'rg' then
begin
EventData.Replacement := TEncoding.ANSI.GetBytes('0.2 0.2 0.2 rg');
EventData.Action := coaReplace;
end
// Wijs de hele bewerking af als we iets tegenkomen dat we niet begrijpen
else if EventData.OperatorName = 'BI' then
EventData.Action := coaReject;
end;
var
Options: THPDFContentOperatorVisitOptions;
begin
Options := THPDFContentOperatorVisitOptions.Default;
Options.ValidateOutput := True; // opnieuw parseren voordat het wordt geaccepteerd
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;
De actieset bevat om een reden twee uitgangen. coaStop beëindigt de doorloop en behoudt wat tot dan toe is beslist, wat juist is wanneer u hebt gevonden wat u zocht. coaReject geeft de hele bewerking op en laat de pagina ongemoeid, wat juist is wanneer u een constructie tegenkomt die uw herschrijfregels niet dekken. Inline afbeeldingen zijn het klassieke voorbeeld: hun binaire payload is niet iets waar een kleurnormaliserende visitor omheen zou moeten bewerken
De statistieken lezen, en waar de doorloop stopt
Het statusveld onderscheidt voltooiing van stoppen, afwijzing, ongeldige invoer, een overschreden limiet, ongeldige output en een visitorfout. Dat zijn verschillende problemen: ongeldige output betekent dat uw vervangende bytes niet opnieuw parseerden, terwijl ongeldige invoer betekent dat de bronstream al misvormd was en er geen bewerking is geprobeerd
De tellers maken een audit zelfcontrolerend. VisitedOperatorCount tegenover KeptOperatorCount, DeletedOperatorCount en ReplacedOperatorCount moeten optellen, en de maximale dieptecijfers vertellen u of de pagina diep genoeg genest is om een nadere blik waard te zijn. SourceBytes tegenover OutputBytes geeft het grootte-effect van uw bewerking in één getal
Voor de gewone taken bestaan er routes op hoger niveau die geen visitor vereisen om te schrijven. Inhoud permanent verwijderen, met de resource-opruiming die echte redactie nodig heeft, wordt behandeld in redactie en het samenvoegen van pagina's op geladen documenten. Zichtbare tekst vervangen wordt behandeld in zoeken en vervangen in een bestaande PDF. Grijp naar de operatorvisitor wanneer uw regel structureel is in plaats van tekstueel, of wanneer u een audit nodig hebt die geen bestaand rapport beantwoordt
Content-stream-visiting, extractie en herschrijving draaien op hetzelfde geladen documentmodel in Delphi en C++Builder; de volledige functielijst staat op de HotPDF Delphi PDF component-pagina