Valaki fekete dobozt fest egy név fölé, semmit nem lelapít, kiküldi a fájlt, és a lektor kijelöli a téglalapot, majd beilleszti a nevet egy e-mailbe. A PDFiumPas operátorszintű kitakarással válaszol: a SaveAsRedacted csak azokat a Unicode skalárokat törli, amelyek karakterdobozai hozzáérnek egy kitakaró téglalaphoz, a túlélőket az eredeti betűtípusból, méretből, mátrixból, megjelenítési módból és színből építi újra, és a tengelypárhuzamos útvonalakat és képeket nem egészben ejti, hanem körbevágja
Miért nem kitakarás egy festett téglalap
A rajzolási művelet, amelyet egy tartalomstream tetejére adnak, semmit sem rejt el, mert alatta a szövegmegjelenítő operátorok továbbra is a streamben vannak, és továbbra is kódpontokra képeződnek le. Az ISO 32000-1 §9.4 a szövegobjektumot a BT és ET közötti pozicionáló és megjelenítő operátorok sorozataként definiálja; egy utána rajzolt kitöltött téglalap egyszerűen egy újabb operátor ugyanabban a streamben. A kinyerés az operátorokat járja, nem a pixeleket, így a takart sztring épségben jön vissza. A valódi kitakarásnak az operandust kell eltávolítania, nem a kimenetet elrejtenie
A kézenfekvő biztonságos megvalósítás kíméletlen: megtalálja minden oldalobjektumot, amelynek befoglaló doboza metsz egy kitakaró téglalapot, és egészben törli az objektumot. Ezt tették a korábbi PDFiumPas kiadások, és ez helyes, de drága. Egyetlen Tj egy teljes táblázatsort is hordozhat, így egy számlaszám feketévé tétele magával vitte a dátumot, a leírást és az összeget. Egy véletlenül teljes szélességű táblázatsávot adó téglalap kitöltés az egész oldalon eltűnt. Egy számlalogó azért tűnt el, mert a kitakarás az egyik sarkát metszte. A 3.101.0-s verzió a döntést egy szinttel lejjebb viszi, az oldalobjektumtól az operandusig
Mit töröl valójában az operátorszintű kitakarás?
A PDFiumPas Unicode skalárokat töröl, nem szövegobjektumokat. A SaveAsRedacted alatt a komponens karakter–oldalobjektum hozzárendelést épít a betöltött szöveges oldalból, majd a vizsgált objektum tulajdonában lévő minden karakterhez beolvassa a karakterdobozt, és azt metszi minden kitakaró téglalappal. A téglalaphoz hozzáérő karakterek törlésre jelölődnek; a többi túlélőként. Ha semmi nem metsz, az objektum teljesen érintetlen marad. Ha minden karakter metsz, az objektum egészben törlődik, pontosan mint korábban. Csak a vegyes eset vált ki bontást
Minden túlélő ezután saját szövegobjektumként bocsátódik ki, amely az eredeti betűtípus-handle-ből, az eredeti betűméretből, a karakterenkénti szövegmátrixból, az eredeti szövegmegjelenítési módból és a szülőobjektum kitöltési és körvonalállapotából épül, ideértve a körvonalvastagságot, a vonalcsatlakozást, a vonalvéget és a szaggatott mintát. A betűtípus-handle újrafelhasználása új feloldás helyett az, ami a glyphokat metrikailag azonosvá teszi, és a karakterenkénti mátrix újrafelhasználása az, ami a kerninget és a szóközözést a helyén tartja az elrendezés újbóli futtatása nélkül. Az ára az objektumszám: egy megtartott karakter egy szövegobjektummá válik, ezért létezik a TPdfRedactionOptions.MaxSplitObjects mint kemény felső határ a generált töredékekre
procedure RedactDocument(const SourcePdf, TargetPdf: string);
var
Pdf: TPdf;
Options: TPdfRedactionOptions;
Report: TPdfRedactionReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := SourcePdf; // a fájl már tartalmaz /Redact annotációkat
Pdf.Active := True;
Options := TPdfRedactionOptions.Default;
Options.PreservePartialObjects := True; // operátorszintű bontás (az alapértelmezett)
Options.RemoveIntersectingAnnotations := True;
Options.MaxSplitObjects := 20000; // felső határ a generált töredékekre
if not Pdf.SaveAsRedacted(TargetPdf, Options, Report) then
raise Exception.Create(Report.ErrorMessage); // hibázzon zártan, ne küldje ki
finally
Pdf.Free;
end;
end;
A téglalapok körbevágódnak, a forgatott geometria nem
Az útvonalak csak akkor bonthatók, ha a PDFiumPas be tudja bizonyítani, hogy az útvonal tengelypárhuzamos téglalap. A bizonyítás szándékosan szűk: az objektummátrixnak mindkét nyírási tagja 0.0001 alatt kell legyen, az útvonalnak négy-hat szegmensből kell állnia, amely MOVETO-val kezdődik és csak LINETO-val folytatódik, és a transzformált pontoknak az objektumhatárok mind a négy sarkára kell esniük 0.01 tűrésen belül. A vizsgálaton átmenő útvonal egymást követő téglalapkivonással szűkül, minden kitakaró téglalap a túlélő halmazt bal, jobb, alatta és felette sávokra faragja, és minden keletkező sáv az eredeti kitöltési móddal, körvonaljelzővel és festési állapottal jön létre újra. A görbék, háromszögek, kivágott alakzatok és bármi, ami forgatva van, megbukik a vizsgálaton, és az objektum egészben törlődik
A képek az ISO 32000-1 §8.9-et követik, ahol a képminták az aktuális transzformációs mátrix által képezett egységnégyzetet foglalják el. A PDFiumPas megfordítja azt a leképezést, hogy minden túlélő oldalterület-töredéket visszaalakítson normalizált képkoordinátákba, azokat az egységintervallumra szorítja, majd pixelindexekké alakítja befelé kerekítéssel: a bal és felső él a Ceil-ön megy át, a jobb és alsó a Floor-ön. Ez az irány számít. Kifelé kerekítve a takart oldalról származó részleges forráspixel-oszlop túlélné a töredék szélét. Az egész pixelhatárokat ezután visszaalakítják normalizált koordinátákba, és abból származtatják a töredékmátrixot, így a levágott bitkép pontosan arra a pixelhatárra esik, amelynél elvágták. Maga a kivágás stride-tudatos sormásolás a Gray, BGR, BGRx és BGRA formátumokban. Az útvonalakhoz hasonlóan egy forgatott vagy nyírt kép, vagy amelynek mátrixában degenerált skáltag van, egészben törlődik
// Egy sikeres SaveAsRedacted hívás után
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
// semmi nem bontható: minden metsző objektum egészben eldobódott
LogWholeObjectFallback(SourcePdf);
Miért hibázik zártan a PDFiumPas a leképezetlen karaktereknél?
Mert egy glyph, amelynek nincs reprodukálható Unicode skalárja, nem építhető újra becsületesen. Egy túlélő rekonstruálása azt jelenti, hogy a szövegbeállító API-t sztringgel hívja meg, ami minden megtartott karakterhez stabil kódpontot igényel. A törött vagy hiányzó ToUnicode adatú szimbolikus részhalmaz betűtípusok üres leképezést adhatnak, és találgatás alapú újrakódolás olyan kimenetet adna, amely a képernyőn helyesnek néz ki, miközben mást hordoz alatta. A PDFiumPas visszautasítja: a megtartott karakterekre vonatkozó vizsgálat kivételt dob, a kivétel a SaveAsRedacted belül elkapásra kerül, a TPdfRedactionReport.Succeeded False-dal tér vissza az üzenettel az ErrorMessage-ban, és a függvény False-ot ad. Ugyanaz a szabály vonatkozik a bontási keretre, amely kivételt dob, nem pedig csendben csonkolja a töredékhalmazt. Amikor egy dokumentumban olyan betűtípusok vannak, amelyekben nem bízik, és a determinisztikus régi viselkedést szeretné, állítsa Options.PreservePartialObjects := False-ra, és minden metsző objektum egészben távozik
Erőforrás-tisztítás megosztott hatókörökben
Az objektumok bontása árvákat hagy maga után, és azok kitisztítása nem olyan egyszerű, mint a lap szintű /Resources szótár átvizsgálása. Az ISO 32000-1 §7.8.3 engedi, hogy ugyanaz az erőforrásszótár egyszerre hivatkozzon több oldal, Form XObjectek, mintázatok és annotáció megjelenítési streamek által. Egy betűtípusnév törlése azért, mert egy lap már nem használja, megtöri a másik lapot, amely még igen. A PruneUnusedPdfResources ezért hatókörönként dolgozik: feloldja a /Contents-t, legyen az közvetlen tömb, tömbre mutató indirekt hivatkozás vagy egyetlen stream, majd erőforráshasználatot gyűjt azokból az operátorokból, amelyek ténylegesen neveznek erőforrásokat — Tf betűtípusokra, Do XObjectekre, gs grafikai állapotra, CS, cs, SCN és scn színterekre és mintázatokra, sh árnyalatokra, BDC és DP megjelölt tartalom tulajdonságokra, plusz a beágyazott képek /CS bejegyzése. Amikor egy szótárt több hatókör oszt meg, a használt-név halmazok kategóriánként unióra kerülnek, mielőtt bármi törlődne
Csak azok a nevek törlődnek, amelyeket minden, a szótárra mutató hatókör megerősítetten nem hivatkozik. A hatókör, amely nem értelmezhető biztosan, érintetlen marad, ami a konzervatív irány: egy tisztítatlan fájl csupán nagyobb, egy tévesen tisztított sérült. A túlélő szótárak pontos generációs számokkal, ritka inkrementális frissítés formájában íródnak vissza, és egy elérhetőségi átírás ezután kisöpri az objektumokat, amelyek a nevek eltűnése után elérhetetlenné váltak. A TPdfResourcePruneReport jelent ScannedScopeCount-ot, UpdatedScopeCount-ot, RemovedNameCount-ot, RemovedObjectCount-ot, a bájtszámokat és egy Succeeded jelzőt. A SaveAsRedacted ezt a lépést automatikusan lefuttatja a fertőtlenített kimeneten, így a kitakarási út már tartalmazza, de a függvény stream szinten exportált azoknak a folyamatoknak, amelyek önállóan akarják
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
// Az AllowSignedDocument False marad: egy inkrementális újraírás
// érvénytelenítené az aláírás által lefedett bájtartományokat
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;
Bekötése egy dokumentumfolyamatba
A kitakarási út soha nem módosítja a betöltött dokumentumot. A SaveAsRedacted izolált pillanatképet készít, ott alkalmazza a /Redact annotációkat, eltávolítja a mellékleteket, lefuttatja a fertőtlenítő lépést, amely eltávolítja a nyitó műveletet, a katalógusműveleteket, a névfákat, a társított fájlokat, az AcroFormot és a metaadatokat, megtisztítja az erőforrásokat, és csak ezután írja a kimeneti streamet. Annak a kimenetnek az újranyitása független dokumentumként és a szöveg újra kinyerése az az ellenőrzési lépés, amelyet érdemes a saját tesztkészletében tartani, mert ez az egyetlen vizsgálat, amely megválaszolja az eredeti kérdést — tudja-e még egy megjelenítő megszerezni a sztringet. Egy következmény, amire számítani kell: a bontás kicseréli az oldalobjektumokat, így minden FPDF_PAGEOBJECT handle, amelyet tartott, utána halott, ugyanaz az élettartam-csapda, amelyet a elavult oldalobjektum handle-ek transzformáció után cikk ír le
Két szomszédos darab teszi teljessé a munkafolyamatot. Annak eldöntése, hova kerülnek a kitakaró téglalapok, általában kinyert geometriából indul, és a strukturált szövegblokkok és olvasási sorrend blokk- és olvasásirend-modell jobb forrás a jelölt dobozokra, mint a nyers karakterfutamok. Az eredmény lektorhoz juttatása a biztonságos PDF-előnézet építése keményítési szabályaihoz tartozik, ahol az űrlapkitöltés és a JavaScript alapból kikapcsolva marad. Együtt lefedik a legtöbb megfelelőségi munkafolyamat által igényelt hurkot: megtalálás, operátorszintű kitakarás, ellenőrzés újranyitással, biztonságos előnézet. A teljes API-felület, a próbaverzió letöltése és a komponens licencfeltételei a PDFium Delphi Component termékoldalon élnek