Netko nacrta crni okvir preko imena, ništa ne izravna, otpremi datoteku, a recenzent odabere pravokut i zalijepi ime u e-poštu. PDFiumPas na to odgovara redakcijom na razini operatora: SaveAsRedacted briše samo Unicode skalare čije se kućice znakova dotiču pravokuta redakcije, preživjele gradi izvornim fontom, veličinom, matricom, načinom iscrtavanja i bojom, te obreže putove i slike poravnate s osima umjesto da ih baca cijele
Zašto naslikani pravokut nije redakcija
Operacija crtanja dodana preko toka sadržaja ne sakriva ništa, jer operatorski znakovi za prikaz teksta ispod nje i dalje su u toku i dalje se mapiraju na točke koda. ISO 32000-1 §9.4 definira tekstualni objekt kao niz operatorskih znakova pozicioniranja i prikazivanja unutar BT i ET; puni pravokut nacrtan poslije je samo još jedan operator u istom toku. Izvlačenje prolazi operatore, ne piksele, pa pokriveni niz vraća netaknut. Prava redakcija mora ukloniti operand, a ne zamračiti izlaz
Očita sigurna izvedba je brutalna: nađi svaki objekt stranice čiji se okvir siječe s pravokutom redakcije i izbriši cijeli objekt. To su ranija izdanja PDFiumPasa radila, i točno je, ali skupo. Jedan Tj može nositi cijeli red tablice, pa je zamračivanje jednog broja računa povuklo datum, opis i iznos sa sobom. Pravokutna ispuna koja je slučajno bila traka pune širine nestala je preko cijele stranice. Logotip računa nestao je jer je redakcija odrezala jedan njegov kut. Izdanje 3.101.0 spušta odluku razinu niže, s objekta stranice na operand
Što redakcija na razini operatora stvarno briše?
PDFiumPas briše Unicode skalare, ne tekstualne objekte. Tijekom SaveAsRedacted komponenta gradi mapiranje znakova na objekte stranice iz učitane tekstualne stranice, a zatim za svaki znak u vlasništvu testiranog objekta čita kućicu znaka i provjerava njeno sjecište sa svakim pravokutom redakcije. Znaci koji dotiču pravokut označavaju se za uklanjanje; ostali se označavaju kao preživjeli. Ako se ništa ne siječe, objekt ostaje potpuno na miru. Ako se siječe svaki znak, objekt se uklanja cijeli, točno kao prije. Samo miješani slučaj pokreće rastavljanje
Svaki se preživjeli zatim ponovno ispisuje kao vlastiti tekstualni objekt građen iz izvornog rukovanja fontom, izvorne veličine fonta, matrice teksta po znaku, izvornog načina iscrtavanja teksta te stanja ispune i poteza roditeljskog objekta uključujući širinu poteza, spajanje linija, kraj linije i niz crtica. Ponovno korištenje rukovanja fontom umjesto razrješivanja novog je ono što znakovima čini metrike identičnima, a ponovno korištenje matrice po znaku ono što kerning i razmak riječi drži na mjestu bez ponovnog rasporeda. Cijena je broj objekata: jedan zadržani znak postaje jedan tekstualni objekt, zato TPdfRedactionOptions.MaxSplitObjects postoji kao tvrdi strop nad generiranim fragmentima
procedure RedactDocument(const SourcePdf, TargetPdf: string);
var
Pdf: TPdf;
Options: TPdfRedactionOptions;
Report: TPdfRedactionReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := SourcePdf; // the file already carries /Redact annotations
Pdf.Active := True;
Options := TPdfRedactionOptions.Default;
Options.PreservePartialObjects := True; // operator-level split (the default)
Options.RemoveIntersectingAnnotations := True;
Options.MaxSplitObjects := 20000; // ceiling on generated fragments
if not Pdf.SaveAsRedacted(TargetPdf, Options, Report) then
raise Exception.Create(Report.ErrorMessage); // fail closed, do not ship
finally
Pdf.Free;
end;
end;
Pravokuti se obrezuju, rotirana geometrija ne
Putevi se rastavljaju samo kad PDFiumPas može dokazati da je put pravokut poravnat s osima. Dokaz je namjerno uzak: matrica objekta mora imati oba smična pojma ispod 0.0001, put mora imati četiri do šest segmenata koje počinju MOVETO i nastavljaju samo LINETO, a transformirane točke moraju sletjeti na sva četiri kuta granica objekta u toleranciji 0.01. Put koji prođe tu provjeru smanjuje se uzastopnim oduzimanjem pravokuta, svaki pravokut redakcije izreže skup preživjelih na lijeve, desne, donje i gornje trake, i svaka nastala traka ponovno se stvara s izvornim načinom ispune, zastavom poteza i stanjem boje. Krivulje, trokuti, oblici pod maskom i sve rotirano pada provjeru i cijeli se objekt uklanja
Slike slijede ISO 32000-1 §8.9, gdje uzorci slike zauzimaju jedinični kvadrat mapiran kroz trenutnu transformacijsku matricu. PDFiumPas to mapiranje invertira da svaki preživjeli fragment u prostoru stranice vrati u normalizirane koordinate slike, steže ih u jedinični interval, a zatim pretvara u pikselne indekse zaokruživanjem prema unutra: lijevi i gornji rub prolaze kroz Ceil, desni i donji kroz Floor. Smjer je bitan. Zaokruživanje prema van dopustilo bi da djelomični stupac izvornih piksela s rezane strane preživi na rubu fragmenta. Cjelobrojne pikselne granice zatim se vraćaju u normalizirane koordinate i služe za izvođenje matrice fragmenta, pa izrezana bitmapa sleti točno na pikselnu granicu na kojoj je rezana. Samo rezanje je kopija redova svjesna koraka preko formata Gray, BGR, BGRx i BGRA. Kao kod putova, rotirana ili smicana slika, ili slika čija matrica ima degenerirani pojam mjerila, uklanja se u cijelosti
// After a successful SaveAsRedacted call
Writeln(Format('applied %d redaction(s) on %d page(s)',
[Report.RedactionCount, Report.RedactedPageCount]));
Writeln(Format('scanned %d object(s), removed %d',
[Report.ScannedObjectCount, Report.RemovedObjectCount]));
Writeln(Format('split text/path/image: %d / %d / %d',
[Report.SplitTextObjectCount, Report.SplitPathObjectCount,
Report.SplitImageObjectCount]));
Writeln(Format('preserved %d fragment(s)', [Report.PreservedFragmentCount]));
Writeln(Format('pruned %d resource name(s), swept %d object(s)',
[Report.ResourcePruneReport.RemovedNameCount,
Report.ResourcePruneReport.RemovedObjectCount]));
if Report.PreservedFragmentCount = 0 then
// nothing could be split: every intersecting object was dropped whole
LogWholeObjectFallback(SourcePdf);
Zašto PDFiumPas propada zatvoreno na nemapiranim znakovima?
Jer znak bez reproducibilnog Unicode skalara ne može se pošteno obnoviti. Rekonstrukcija preživjelog znači pozivanje API-ja za postavljanje teksta s nizom, a to traži stabilnu točku koda za svaki zadržani znak. Simbolički podskupni fontovi s pokvarenim ili odsutnim podacima ToUnicode mogu dati prazno mapiranje, a ponovno kodiranje pogađanjem proizvelo bi izlaz koji na ekranu izgleda ispravno dok nosi drugi znak ispod. PDFiumPas odbija: provjera zadržanih znakova podiže iznimku, iznimka se hvata unutar SaveAsRedacted, TPdfRedactionReport.Succeeded vraća se False s porukom u ErrorMessage, a funkcija vraća False. Isto pravilo vrijedi za proračun rastavljanja, koji podiže iznimku umjesto da tiho odreže skup fragmenata. Kad dokument ima fontova kojima ne vjerujete i želite determinističko staro ponašanje, postavite Options.PreservePartialObjects := False i svaki sijecući objekt ode cijeli
Čišćenje resursa kroz dijeljene opsege
Rastavljanje objekata ostavlja siročad iza sebe, a njihovo čišćenje nije jednostavno kao usporedba rječnika /Resources na razini stranice. ISO 32000-1 §7.8.3 dopušta da isti rječnik resursa istovremeno referenciraju više stranica, Form XObjects, uzorci i tokovi izgleda anotacija. Brisanje imena fonta jer ga je jedna stranica prestala koristiti slomi drugu stranicu koja ga još koristi. PruneUnusedPdfResources zato radi po opsegu: razrješuje /Contents bilo da je izravni niz, neizravna referenca na niz ili jedan tok, a zatim prikuplja upotrebu resursa od operatora koji resurse stvarno imenuju — Tf za fontove, Do za XObjects, gs za grafičko stanje, CS, cs, SCN i scn za prostore boja i uzorke, sh za zasjenjenja, BDC i DP za svojstva označenog sadržaja, plus unos /CS ugrađenih slika. Kad jedan rječnik dijeli više opsega, skupovi korištenih imena spajaju se po kategoriji prije nego što se bilo što ukloni
Bacaju se samo imena potvrđeno nereferencirana u svakom opsegu koji pokazuje na rječnik. Opseg koji se ne može pouzdano raščlaniti ostaje netaknut, a to je konzervativan smjer: neočišćena datoteka je samo veća, pogrešno očišćena je pokvarena. Preživjeli rječnici upisuju se natrag kao rijetka inkrementalna dopuna koja nosi točne brojeve generacija, a prepravka dostiživosti zatim pomete objekte koji su postali nedostižni kad su imena nestala. TPdfResourcePruneReport izvještava ScannedScopeCount, UpdatedScopeCount, RemovedNameCount, RemovedObjectCount, brojeve bajtova i zastavicu Succeeded. SaveAsRedacted taj korak pokreće automatski nad očišćenim izlazom, pa ga put redakcije već uključuje, ali je funkcija izvezena na razini tokova za cjevovode koji je žele sami
uses
FPdfCompress;
procedure PruneResourceNames(const SourcePdf, TargetPdf: string);
var
Source, Dest: TFileStream;
Report: TPdfResourcePruneReport;
begin
Source := TFileStream.Create(SourcePdf, fmOpenRead or fmShareDenyWrite);
try
Dest := TFileStream.Create(TargetPdf, fmCreate);
try
// AllowSignedDocument stays False: an incremental rewrite would
// invalidate the byte ranges a signature covers
PruneUnusedPdfResources(Source, Dest, Report);
if not Report.Succeeded then
raise Exception.Create(Report.ErrorMessage);
Writeln(Format('%d name(s) removed from %d scope(s), %d -> %d bytes',
[Report.RemovedNameCount, Report.UpdatedScopeCount,
Report.SourceByteCount, Report.OutputByteCount]));
finally
Dest.Free;
end;
finally
Source.Free;
end;
end;
Uklapanje u cjevovod dokumenata
Put redakcije nikad ne mijenja dokument koji ste učitali. SaveAsRedacted snimi izolirani snimak, tamo primijeni anotacije /Redact, oguli privitke, provede prolaz dezinfekcije koji uklanja otvorenu akciju, akcije kataloga, stabla imena, pridružene datoteke, AcroForm i metapodatke, očisti resurse i tek onda upiše izlazni tok. Ponovno otvaranje tog izlaza kao neovisnog dokumenta i ponovno izvlačenje teksta je korak provjere vrijedan vlastitog testnog skupa, jer je jedina provjera koja odgovara na izvorno pitanje — može li čitatelj još dobiti niz. Jedna posljedica za planiranje: rastavljanje zamjenjuje objekte stranice, pa je svako FPDF_PAGEOBJECT rukovanje koje ste držali mrtvo poslije, ista zamka životnog vijeka opisana u zastarjelim rukovanjima objekata stranice nakon transformacije
Dva susjedna komada dopunjuju tijek rada. Odluka gdje idu pravokuti redakcije obično polazi iz izvučene geometrije, a model blokova i redoslijeda čitanja u strukturiranim tekstualnim blokovima i redoslijedu čitanja je bolji izvor kandidatskih okvira od sirovih nizova znakova. Služenje rezultata recenzentu pripada pravilima očvršćivanja u građenju sigurnog PDF pregleda, gdje ispunjavanje obrasca i JavaScript ostaju po zadanom isključeni. Zajedno pokrivaju petlju koju većina tijekova usklađenosti treba: lociraj, izreži na razini operatora, provjeri ponovnim otvaranjem, pregledaj sigurno. Potpuna API površina, probno preuzimanje i uvjeti licenciranja komponente žive na stranici proizvoda PDFium Delphi komponente