Nekdo naslika črno škatlo čez ime, ničesar ne splošči, odpremi datoteko, recenzent pa izbere pravokotnik in prilepi ime v e-pošto. PDFiumPas se na to odzove z redigiranjem na ravni operatorjev: SaveAsRedacted briše le skalare Unicode, katerih okviri znakov se dotikajo pravokotnika za redigiranje, preživele znova sestavi iz izvirne pisave, velikosti, matrike, načina izrisovanja in barve, osno usmerjene poti in slike pa obreže, namesto da bi jih spustil celotne
Zakaj naslikan pravokotnik ni redigiranje
Risarska operacija, dodana čez tok vsebine, ne skrije ničesar, ker operatorji prikazovanja besedila pod njo še vedno so v toku in se še vedno preslikajo na kodne točke. ISO 32000-1 §9.4 definira besedilni predmet kot zaporedje operatorjev pozicioniranja in prikazovanja znotraj BT in ET; zapolnjen pravokotnik, narisan pozneje, je preprosto še en operator v istem toku. Izvlečenje hodi po operatorjih, ne po pikslih, zato pokriti niz pride nazaj nedotaknjen. Pravo redigiranje mora odstraniti operand, ne zamegliti izhoda
Očitna varna izvedba je brutalna: najdi vsak predmet strani, katerega omejitveno okno seka pravokotnik za redigiranje, in izbriši cel predmet. To so počile prejšnje izdaje PDFiumPas in je pravilno, a drago. En sam Tj lahko nosi celo vrstico tabele, zato je pocrnjenje ene številke računa vzelo s sabo datum, opis in znesek. Pravokotna zapolnitev, ki slučajno je polnoširska pas tabla, je izginila čez celo stran. Logotip računa je izginil, ker je redigiranje odrezalo en kot njega. Različica 3.101.0 premakne odločitev eno raven navzdol, s predmeta strani na operand
Kaj redigiranje na ravni operatorjev dejansko briše?
PDFiumPas briše skalare Unicode, ne besedilnih predmetov. Med SaveAsRedacted komponenta zgradi preslikavo znak-na-predmet-strani iz naložene besedilne strani, nato za vsak znak, ki ga lasti preizkušani predmet, prebere okvir znaka in ta okvir seka proti vsakemu pravokotniku za redigiranje. Znaki, ki se dotikajo pravokotnika, so označeni za odstranitev; ostali so označeni kot preživeli. Če nič ne seka, je predmet popolnoma pri miru puščen. Če se dotakne vsak znak, je predmet odstranjen cel, točno kot prej. Le mešani primer sproži razcep
Vsak preživeli je nato ponovno izdan kot svoj besedilni predmet, zgrajen iz izvirnega ročaja pisave, izvirne velikosti pisave, besedilne matrike na znak, izvirnega načina izrisovanja besedila ter stanja zapolnitve in poteze starševskega predmeta, vključno s širino poteze, spojem vrstic, končnico vrstice in tabelo črtic. Ponovna uporaba ročaja pisave namesto razreševanja novega je tisto, kar ohranja glife metrično enake, ponovna uporaba matrike na znak pa tisto, kar ohrani kerning in razmik besed na mestu brez ponovnega zagona razporeditve. Cena je števec predmetov: en obdržan znak postane en besedilni predmet, zato TPdfRedactionOptions.MaxSplitObjects obstaja kot trdi strop nad ustvarjenimi drobci
procedure RedactDocument(const SourcePdf, TargetPdf: string);
var
Pdf: TPdf;
Options: TPdfRedactionOptions;
Report: TPdfRedactionReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := SourcePdf; // datoteka že nosi anotacije /Redact
Pdf.Active := True;
Options := TPdfRedactionOptions.Default;
Options.PreservePartialObjects := True; // razcep na ravni operatorjev (privzeto)
Options.RemoveIntersectingAnnotations := True;
Options.MaxSplitObjects := 20000; // strop nad ustvarjenimi drobci
if not Pdf.SaveAsRedacted(TargetPdf, Options, Report) then
raise Exception.Create(Report.ErrorMessage); // odpovej zaprto, ne odpremi
finally
Pdf.Free;
end;
end;
Pravokotniki se obrežejo, rotirana geometrija ne
Poti se razcepijo šele, ko PDFiumPas lahko dokaže, da je pot osno usmerjen pravokotnik. Dokaz je namenoma ozek: matrika predmeta mora imeti oba strižna člena pod 0,0001, pot mora sestojati iz štirih do šestih odsekov, ki se začnejo z MOVETO in nadaljujejo le z LINETO, preoblikovane točke pa morajo pristati na vseh štirih kotih omejitev predmeta znotraj tolerance 0,01. Pot, ki to preverjanje opravi, se zmanjša z zaporednim odštevanjem pravokotnikov, vsak pravokotnik za redigiranje izklesa množico preživelih v leve, desne, spodnje in zgornje pasove, vsak nastali pas pa se ponovno ustvari z izvirnim načinom zapolnitve, zastavico poteze in stanjem barvanja. Krivulje, trikotniki, odrezane oblike in vse rotirano spodleti preverjanje in cel predmet se odstrani
Slike sledijo ISO 32000-1 §8.9, kjer vzorci slike zasedajo enotski kvadrat, preslikan skozi trenutno transformacijsko matriko. PDFiumPas obrne to preslikavo, da vsak preživeli drobec v prostoru strani vrne v normalizirane slikovne koordinate, jih pripne na enotski interval in nato pretvori v pikselne indekse z zaokroževanjem navznoter: leve in zgornje robove grejo skozi Ceil, desne in spodnje skozi Floor. Ta smer šteje. Zaokroževanje navzven bi pustilo delni stolpec izvornih pikslov s strani, ki je redigirana, preživeti na robu drobca. Celoštevilčne pikselne meje se nato pretvorijo nazaj v normalizirane koordinate in uporabijo za izpeljavo matrike drobca, zato obrezana slika (bitmap) pristane točno na pikselni meji, pri kateri je bila odrezana. Obrez samo je kopiranje vrst, pozorno na korak, čez formate Gray, BGR, BGRx in BGRA. Kot pri potih se rotirana ali poševna slika ali ena, katere matrika ima degeneriran člen merila, odstrani v celoti
// Po uspelem klicu SaveAsRedacted
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
// nič se ni dalo razcepiti: vsak sekajoč predmet je bil spuščen cel
LogWholeObjectFallback(SourcePdf);
Zakaj PDFiumPas odpove zaprto pri nepreslikanih znakih?
Ker glifa, ki nima reproducibilnega skalara Unicode, ni moč pošteno znova zgraditi. Rekonstrukcija preživelega pomeni klicati API za nastavljanje besedila z nizom, kar zahteva stabilno kodno točko za vsak obdržan znak. Simbolične podnaborne pisave s pokvarjenimi ali manjkajočimi podatki ToUnicode lahko da prazno preslikavo, ponovno kodiranje po ugibanju pa bi dalo izhod, ki na zaslonu izgleda pravilen, medtem ko pod sabo nosi drug znak. PDFiumPas zavrne: preverjanje obdržanih znakov sproži, izjema je ujeta znotraj SaveAsRedacted, TPdfRedactionReport.Succeeded pride nazaj kot False s sporočilom v ErrorMessage, funkcija pa vrne False. Isto pravilo velja za proračun razcepa, ki sproži namesto da bi tiho skrajšal množico drobcev. Ko dokument ima pisave, jim ne zaupate, in želite deterministično staro vedenje, nastavite Options.PreservePartialObjects := False in vsak sekajoč predmet gre stran cel
Pourezdovanje virov čez deljene obsege
Razcepitev predmetov pušča za sabo sirote, njihovo pourezdovanje pa ni tako preprosto kot primerjanje slovarja /Resources na ravni strani. ISO 32000-1 §7.8.3 dovoli, da na isti slovar virov hkrati sklicuje več strani, Form XObjects, vzorci in tokovi videza anotacij. Brisanje imena pisave, ker ga je ena stran prenehala uporabljati, bo pokvarilo drugo stran, ki ga še uporablja. PruneUnusedPdfResources zato deluje na obseg: razreši /Contents, ne glede na to, ali je neposredna tabela, posredni sklic na tabelo ali en sam tok, nato zbere rabo virov iz operatorjev, ki dejansko imenujejo vire — Tf za pisave, Do za XObjects, gs za grafično stanje, CS, cs, SCN in scn za barvne prostore in vzorce, sh za senčenja, BDC in DP za lastnosti označene vsebine, plus vnos /CS vgrajenih slik. Ko en slovar deli več obsegov, se množice rabljenih imen po kategorijah unirajo, preden se kaj odstrani
Samo imena, potrjena nesklicana čez vsak obseg, ki kaže na slovar, se spustijo. Obseg, ki se ga ne da razčleniti z zaupanjem, ostane pri miru, kar je previdna smer: nepourezdana datoteka je le večja, napačno pourezdana pa pokvarjena. Preživeli slovarji se zapišejo nazaj kot redka prirastna posodobitev, ki nosi točne številke generacij, prepis dosegljivosti pa nato pomete predmete, ki so postali nedosegljivi, ko so imena izginila. TPdfResourcePruneReport poroča ScannedScopeCount, UpdatedScopeCount, RemovedNameCount, RemovedObjectCount, števca bajtov in zastavico Succeeded. SaveAsRedacted izvede ta korak samodejno na očiščenem izhodu, tako da pot redigiranja ga že vključuje, funkcija pa je izvožena na ravni tokov za cevovode, ki ga želijo samega
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 ostane False: prirastni prepis bi
// razveljavil bajtne razpone, ki jih pokriva podpis
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;
Priključitev v cevovod dokumentov
Pot redigiranja nikoli ne mutira dokumenta, ki ste ga naložili. SaveAsRedacted zajame izoliran posnetek, tam uporabi anotacije /Redact, odstrani priloge, izvede prehod čiščenja, ki odstrani dejanje odpiranja, dejanja kataloga, drevesa imen, povezane datoteke, AcroForm in metapodatke, pourezduje vire in šele nato zapiše izhodni tok. Ponovno odpiranje tega izhoda kot neodvisen dokument in ponovno izvlečenje besedila je preveritveni korak, vreden ohranitve v vaši preizkusni zbirki, ker je edino preverjanje, ki odgovori na izvirno vprašanje — ali bralec še lahko dobi niz. Ena posledica za načrtovanje: razcepitev zamenja predmete strani, zato je vsak ročaj FPDF_PAGEOBJECT, ki ste ga držali, po tem mrtev, ista življenjska past, opisana v zastarelih ročajih predmetov strani po transformaciji
Dva sosednja kosa naredita delovni tok popoln. Odločitev, kam gredo pravokotniki za redigiranje, se običajno začne iz izvlečene geometrije, model blokov in vrstnega branja v strukturiranih besedilnih blokih in vrstnem redu branja je boljši vir kandidatnih oken kot surovi znakovni teki. Postrežba rezultata recenzentu pripada pravilom utrjevanja v gradnji varnega predogleda PDF, kjer izpolnjevanje obrazcev in JavaScript ostajajo privzeto izklopljeni. Skupaj pokrijejo zanko, ki jo potrebuje večina skladnostnih delovnih tokov: poišči, redigiraj na ravni operatorjev, preveri s ponovnim odpiranjem, varno predogledaj. Celotna površina API, poskusni prenos in licenčni pogoji komponente živijo na strani izdelka PDFium Delphi Component