Kažkas nupiešia juodą langelį ant vardo, nieko nesulieja, išsiunčia failą, o recenzentas pasirenka stačiakampį ir įklijuoja vardą į el. laišką. PDFiumPas į tai atsako operatorių lygio redagavimu: SaveAsRedacted šalina tik tuos Unicode skaliarus, kurių simbolių langeliai liečia redagavimo stačiakampį, išgyvenusius atkuria iš originalaus šrifto, dydžio, matricos, atvaizdavimo režimo ir spalvos, o ašims lygiagrečius kelius ir vaizdus apkarpo, vietoj to, kad mestų juos visus
Kodėl nupieštas stačiakampis nėra redagavimas
Piešimo operacija, pridėta ant turinio srauto viršaus, nieko neslepia, nes po ja esantys teksto rodymo operatoriai tebėra sraute ir tebemapuoja į kodinius taškus. ISO 32000-1 §9.4 apibrėžia teksto objektą kaip pozicionavimo ir rodymo operatorių seką tarp BT ir ET; vėliau nupieštas užpildytas stačiakampis yra tiesiog kita operacija tame pačiame sraute. Ištrauka eina per operatorius, o ne per pikselius, todėl padengta eilutė grįžta sveika. Tikras redagavimas turi pašalinti operandą, o ne uždengti išvestį
Akivaizdi saugi realizacija yra žiauri: rasti kiekvieną puslapio objektą, kurio ribinis langelis kertasi su redagavimo stačiakampiu, ir ištrinti visą objektą. Tą darė ankstesnės PDFiumPas versijos, ir tai teisinga, bet brangu. Vienas Tj gali nešti visą lentelės eilutę, todėl vieno sąskaitos numerio uždengimas nusinešė datą, aprašymą ir sumą. Stačiakampis užpildas, pasirodo, buvo viso pločio lentelės juosta, ir jis dingo visame puslapyje. Sąskaitos logotipas dingo, nes redagavimas apkirpo jo vieną kampą. Versija 3.101.0 nulemia sprendimą lygiu žemiau — nuo puslapio objekto prie operando
Ką operatorių lygio redagavimas iš tikrųjų šalina?
PDFiumPas šalina Unicode skaliarus, o ne teksto objektus. SaveAsRedacted metu komponentas iš įkelto teksto puslapio suformuoja simbolių ir puslapio objektų susiejimą, tada kiekvienam tiriamo objekto simboliui skaito simbolio langelį ir jį susikerta su kiekvieno redagavimo stačiakampiu. Simboliai, liečiantys stačiakampį, pažymimi šalinimui; kiti pažymimi kaip išgyvenę. Jei niekas nekertasi, objektas paliekamas visiškai ramybėje. Jei kertasi kiekvienas simbolis, objektas šalinamas visas — lygiai kaip anksčiau. Tik mišrus atvejis sukelia skaidymą
Kiekvienas išgyvenęs tada iš naujo išduodamas kaip savas teksto objektas, sukurtas iš originalios šrifto rankenos, originalaus šrifto dydžio, simbolio teksto matricos, originalaus teksto atvaizdavimo režimo ir tėvinio objekto užpildymo bei kontūravimo būsenos, įskaitant kontūro plotį, linijos sujungimą, linijos galą ir brūkšnių masyvą. Tas, kad naudojama ta pati šrifto rankena, o ne išrandama nauja, išlaiko glifus metriškai identiškus, o simbolio matricos pakartotinis naudojimas išlaiko kerningą ir tarpus tarp žodžių vietoje be pakartotinio išdėstymo. Kaina — objektų skaičius: vienas paliktas simbolis tampa vienu teksto objektu, todėl TPdfRedactionOptions.MaxSplitObjects egzistuoja kaip griežta generuojamų fragmentų riba
procedure RedactDocument(const SourcePdf, TargetPdf: string);
var
Pdf: TPdf;
Options: TPdfRedactionOptions;
Report: TPdfRedactionReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := SourcePdf; // faile jau yra /Redact anotacijų
Pdf.Active := True;
Options := TPdfRedactionOptions.Default;
Options.PreservePartialObjects := True; // operatorių lygio skaidymas (numatytasis)
Options.RemoveIntersectingAnnotations := True;
Options.MaxSplitObjects := 20000; // generuojamų fragmentų riba
if not Pdf.SaveAsRedacted(TargetPdf, Options, Report) then
raise Exception.Create(Report.ErrorMessage); // fail closed, neišsiųsti
finally
Pdf.Free;
end;
end;
Stačiakampiai apkarpo, pasukta geometrija ne
Keliai skaidomi tik tada, kai PDFiumPas gali įrodyti, kad kelias yra ašims lygiagretus stačiakampis. Įrodymas sąmoningai ankštas: objekto matrica turi turėti abu šlyties narius žemiau 0.0001, kelias turi sudaryti iš keturių iki šešių segmentų, prasidedančių MOVETO ir tęsiamų tik LINETO, o transformuoti taškai turi pataikyti į visus keturis objekto ribų kampus 0.01 leistinoje paklaidoje. Patikrinimą praėjęs kelias mažinamas nuosekliu stačiakampių atėmimu — kiekvienas redagavimo stačiakampis išgyvenusią aibę skaido į kairiąją, dešiniąją, apatinę ir viršutinę juostas, o kiekviena gauta juosta sukuriami iš naujo su originaliu užpildymo režimu, kontūro žyma ir dažymo būsena. Kreivės, trikampiai, apkirptos formos ir visa, kas pasukta, nepereina patikrinimo, ir visas objektas šalinamas
Vaizdai seka ISO 32000-1 §8.9, kur vaizdo ėminiai užima vienetinį kvadratą, atvaizduotą per dabartinę transformacijos matricą. PDFiumPas apverčia tą atvaizdavimą, kad kiekvienas išgyvenęs puslapio erdvės fragmentas vėl taptų normalizuotomis vaizdo koordinatėmis, jas suvaržo į vienetinį intervalą ir tada konvertuoja į pikselių indeksus apvalindamas į vidų: kairysis ir viršutinis kraštai eina per Ceil, dešinysis ir apatinis — per Floor. Ta kryptis svarbi. Apvalinimas į išorę leistų dalinei šaltinio pikselių stulpelio daliai iš redaguotos pusės išgyventi fragmento krašte. Sveikieji pikselių rėžiai tada vėl konvertuojami į normalizuotas koordinates ir naudojami fragmento matricai išvesti, tad apkarpytas bitmap nusileidžia tiksliai ant to pikselių krašto, kuriuo buvo pjaustas. Pats apkarpymas yra stride žinanti eilučių kopija per Gray, BGR, BGRx ir BGRA formatus. Kaip ir keliai, pasukta ar pasvirusi nuotrauka arba tokia, kurios matrica turi degraduotą mastelio narį, šalinama visa
// Po sėkmingo SaveAsRedacted kvietimo
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
// skaidyti nieko nepavyko: kiekvienas kertantis objektas mestas visas
LogWholeObjectFallback(SourcePdf);
Kodėl PDFiumPas dėl neatvaizduotų simbolių elgiasi fail closed?
Nes glifo, neturinčio atkuriamo Unicode skaliaro, negalima sąžiningai atkurti. Išgyvenusiojo atkūrimas reiškia teksto nustatymo API kvietimą su eilute, o tam reikia stabilaus kodinio taško kiekvienam paliktam simboliui. Simboliniai pogrupio šriftai su sugadintais arba nebūtais ToUnicode duomenimis gali duoti tuščią atvaizdavimą, o pakartotinis kodavimas spėliojant duotų išvestį, kuri ekrane atrodo teisinga, bet po ja neša kitokį simbolį. PDFiumPas atsisako: paliktų simbolių patikrinimas kelia išimtį, ji pagaujama SaveAsRedacted viduje, TPdfRedactionReport.Succeeded grįžta False su žinute ErrorMessage, ir funkcija grąžina False. Ta pati taisyklė galioja skaidymo biudžetui, kuris kelia išimtį vietoj tylio fragmentų aibės nukirpimo. Kai dokumente yra šriftai, kurių nepasitikite, ir norite seno deterministinio elgesio, nustatykite Options.PreservePartialObjects := False, ir kiekvienas kertantis objektas dingsta visas
Resursų genėjimas per bendrinamas apimtis
Objektų skaidymas palieka našlaičius, o jų genėjimas nėra toks paprastas kaip puslapio lygio /Resources žodyno lyginimas. ISO 32000-1 §7.8.3 leidžia tą patį resursų žodyną vienu metu naudoti keliems puslapiams, Form XObjects, patterns ir anotacijų appearance srautams. Šrifto vardo šalinimas todėl, kad vienas puslapis nustojo jį naudoti, sulaužys kitą puslapį, kuris tebenaudoja. PruneUnusedPdfResources todėl dirba per apimtį: jis išsprendžia /Contents — ar tai tiesioginis masyvas, netiesioginė nuoroda į masyvą, ar vienas srautas, tada surenka resursų naudojimą iš operatorių, kurie iš tikrųjų įvardija resursus — Tf šriftams, Do XObjects, gs grafikos būsenai, CS, cs, SCN ir scn spalvų erdvėms ir patterns, sh shadings, BDC ir DP marked-content savybėms, plius /CS įrašą inline vaizduose. Kai vieną žodyną bendrina kelios apimtys, naudojamų vardų aibės sujungiamos per kategoriją prieš šalinant ką nors
Metami tik vardai, patvirtinti kaip nenaudojami kiekvienoje apimityje, rodančioje į žodyną. Apimtis, kurios negalima užtikrintai išanalizuoti, paliekama neliesta — tai konservatyvi kryptis: negenėtas failas tik didesnis, o klaidingai genėtas — sugadintas. Išgyvenę žodynai rašomi atgal kaip retas papildomas atnaujinimas su tiksliais kartų numeriais, o pasiekiamumo perrašymas paskui nušluoja objektus, tapusius nepasiekiamais, kai vardai dingo. TPdfResourcePruneReport praneša ScannedScopeCount, UpdatedScopeCount, RemovedNameCount, RemovedObjectCount, baitų skaičius ir Succeeded žymą. SaveAsRedacted šį žingsnį vykdo automatiškai ant sanitizuotos išvesties, tad redagavimo kelias jo jau turi, bet funkcija išvežama srauto lygiu pipelines, kurie to nori atskirai
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 lieka False: papildomas perrašymas
// panaikintų baitų diapazonus, kuriuos dengia parašas
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;
Įjungimas į dokumento pipeline
Redagavimo kelias niekada nemutuoja jūsų įkelto dokumento. SaveAsRedacted užfiksuoja izoliuotą momentinę kopiją, ten pritaiko /Redact anotacijas, nuplėšia priedus, vykdo sanitizacijos perėjimą, kuris šalina atidarymo veiksmą, katalogo veiksmus, vardų medžius, susietus failus, AcroForm ir metaduomenis, genėja resursus ir tik tada rašo išvesties srautą. To output vėl atidarymas kaip nepriklausomo dokumento ir teksto pakartotinė ištrauka yra patikrinimo žingsnis, vertas jūsų testų rinkinio, nes tai vienintelis patikrinimas, atsakantis į pradinį klausimą — ar skaitytojas vis dar gali gauti eilutę. Viena pasekmė, į kurią reikia planuoti: skaidymas pakeičia puslapio objektus, tad bet kuri FPDF_PAGEOBJECT rankena, kurią laikėte, po to mirusi — tas pats gyvavimo laiko spąstas, aprašytas straipsnyje pasenusios puslapio objektų rankenos po transformacijos
Du gretimi gabalai užbaigia darbo eigą. Sprendžiant, kur dėti redagavimo stačiakampius, paprastai pradedama nuo ištrauktos geometrijos, o blokų ir skaitymo tvarkos modelis straipsnyje struktūrizuoti teksto blokai ir skaitymo tvarka yra geresnis kandidatų langelių šaltinis nei žaliavos simbolių sekos. Rezultato pateikimas recenzentui priklauso prie sukietinimo taisyklių straipsnyje saugios PDF peržiūros kūrimas, kur formų užpildymas ir JavaScript pagal numatytuosius lieka išjungti. Kartu jie dengia ciklą, kurio daugiausia reikia compliance darbo eigoms: surasti, redaguoti operatorių lygiu, patikrinti atidarant iš naujo, saugiai peržiūrėti. Pilnas API paviršius, bandomasis atsisiuntimas ir komponento licencijavimo sąlygos gyvena PDFium Delphi Component produkto puslapyje