Generáljon egy jelentést, ágyazott TrueType betűtípussal, és a kimenet helyesen nyílik meg minden kiértékelt megjelenítőben. A glifák helyesek, a szöveg kijelölhető, a fájl érvényes. Az egyetlen probléma a méret. Egy olyan dokumentum, amely néhány tucat latin karaktert használt, hordozza a teljes 350 KB-os betűtípust. Egy kínai bekezdést nyomtató dokumentum 14 MB-os CJK betűtípust tartalmaz a szükséges fél megabájtos szelet helyett. Nem történt kivétel, nem naplóztak figyelmeztetést, és a fájl átment az ellenőrzésen. Kívülről így néz ki egy hibásan ütemezett véglegesítési lépés: semmi sem hibásodik meg, az egyetlen bizonyíték egy túl nagy szám.
Az ezt okozó hiba egyetlen kiadás erejéig élt a HotPDF-ben, és azóta javításra került. Nem hibaértesítőként, hanem tanulságként érdemes leírni, mert a hiba jellege általános. Minden dokumentum-motor rendelkezik egy véglegesítési szakasszal, amely közvetlenül az írás előtt módosítja az objektumokat, és ennek a szakasznak a helyessége teljesen a lépések szerializációhoz viszonyított sorrendjétől függ. Ha egy lépés a kiírás rossz oldalára kerül, csendben nem csinál semmit.
Mit kellene tennie a betűtípus-részhalmazok készítésének
A részhalmaz betűtípus (subset font) a TrueType fájlnak az a része, amelyet a dokumentum ténylegesen használ. Az ISO 32000-1 szabvány 9.9. szakasza leírja, hogyan utazik egy beágyazott betűtípus-program a betűleíró (font descriptor) által hivatkozott adatfolyamban (stream), és egy TrueType program esetében ez a folyam a /FontFile2, amelynek a tömörítetlen bájtok számát megadó /Length1 értéke van. A részhalmazkészítés átírja a glyf és loca táblákat, hogy azok csak a dokumentum által hivatkozott glifákat tartalmazzák, újraszámozza a glifa-azonosítókat, és a specifikáció követelményeinek megfelelően egy hatbetűs címkével (például ABCDEF+) látja el a /BaseFont nevét, jelezve, hogy a betűtípus egy részhalmaz. Egy latin betűtípus, amely tíz vagy tizenöt kilobájtra csökken, jelenti a különbséget a karcsú PDF és az között, amely egyetlen címsor kedvéért egy egész betűcsaládot hordoz.
Az a pont, ahol ez megtörténik, számít. A részhalmazkészítés nem egy olyan transzformáció, amelyet a már lemezen lévő bájtokra alkalmazunk. A memóriában lévő objektumgráfot módosítja: zsugorítja a /FontFile2 adatfolyam tartalmát, javítja a /Length1 értéket, és átírja a /BaseFont karakterláncot. Mindezeknek a helyükön kell lenniük, amikor a szerializáló bejárja a gráfot és kiírja a bájtokat. Ha a módosítások a bájtok kiírása után történnek meg, olyan objektumokat frissítenek, amelyeket soha senki nem fog olvasni.
A tünet, és miért nem érkezett panasz
A bejelentett viselkedés a kimenetben lévő teljes betűtípusok megléte volt, diagnosztika nélkül. Az a felhasználó, aki regisztrált egy Unicode TrueType betűtípust és létrehozott egy normál dokumentumot, azt tapasztalta, hogy a beágyazott betűtípus-objektum hossza megegyezik a forrásként szolgáló .ttf fájl hosszával, és a /BaseFont név nem hordozott hatbetűs részhalmaz-előtagot. A kimenet mérete soha nem csökkent a tíz glifát használó futások és a tízezer glifát használó futások között.
A hiba hiánya az a rész, ami ezt a fajta hibát drágává teszi. A részhalmazkészítő rutin elindul, még ha rossz időben is. Bejárja az összegyűjtött kódpont-használatot, felépít egy tökéletesen helyes részhalmazt, és alkalmazza azt a memóriában lévő objektumgráfra. Belsőleg a munka elkészült, és a hívás tisztán visszatért. Az egyetlen probléma az, hogy az általa módosított objektumgráf már nem az a dolog, amit kiírnak, mert az író már végzett. A hívó szempontjából a dokumentum incidens nélkül elkészült és mentésre került, ami pontosan a csendes hiba benyomását kelti.
A kiváltó ok a véglegesítési sorrend volt
A HotPDF-ben a lezárási munka az EndDoc metóduson belül történik. A részhalmazkészítési lépés egy BuildAndApplyUnicodeFontSubset nevű belső rutin. Beolvassa a dokumentumonként használt kódpontok halmazát, amelyet egy bitképben tárol, amelyet a szövegkiírási útvonal tölt fel a glifák megjelenítésekor, leképezi a használt kódpontokat a gyorsítótárazott kódpont-glifa táblán keresztül egy valós glifa-azonosítóra, és újraírja a betűtípus-programot ezen lezárás körül. Amikor regisztrálnak egy Unicode TrueType betűtípust, a kiírási útvonal beállít egy bitt a használt kódpontok halmazában minden egyes kirajzolt karakterhez, így mire a dokumentum lezárul, a motor pontosan tudja, mely glifákat kell megtartania a részhalmaznak.
A hiba az volt, hogy a BuildAndApplyUnicodeFontSubset azután került meghívásra, hogy a SaveToStream vagy a SaveToFile már szerializálta a dokumentumot. A részhalmazkészítő módosításai a /FontFile2-n, a javított /Length1-en és a hatbetűs /BaseFont előtagon mind egy olyan objektumgráfon lettek kiszámítva, amely már bájtokká lett alakítva. A javítás egy egykvarcos átrendezés volt: a részhalmaz-hívást a szerializáció elé kellett mozgatni, hogy az író a részhalmazra bontott betűtípust írja ki az eredeti helyett. A javított szekvencia először a részhalmazkészítőt futtatja, és csak utána szerializál.
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansSC-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Noto Sans SC', [], 12);
Pdf.CurrentPage.TextOut(72, 760, 0, '报表标题 Report Heading');
Pdf.EndDoc; // subsetting runs here, before the write
Pdf.SaveToFile('Report.pdf');
finally
Pdf.Free;
end;
end;
A sorrend javítása után a hívó kód semmit sem változik. A részhalmazkészítés alapértelmezés szerint be van kapcsolva, amint egy Unicode TrueType betűtípust regisztráltak. Regisztrálja a betűtípust, elindítja a dokumentumot, rajzol, majd befejezi, és a részhalmaz az Ön által használt glifákból épül fel, még mielőtt a bájtok elhagynák a memóriát.
Miért jelent egyetlen rossz helyre tett lépés egy egész kategóriát
Amiért ez megér egy leckét ahelyett, hogy lábjegyzet lenne, az az, hogy az EndDoc a lezáró lépések listáját bocsátja ki, és ezek mindegyike érzékeny a kiíráshoz viszonyított pozíciójára. A betűtípus-részhalmazkészítés az egyik. A PDF/A kimenetnek szüksége van egy /CIDSet adatfolyamra, amely pontosan felsorolja a részhalmazben jelen lévő glifa-azonosítókat, ezt a korlátozást az ISO 19005 írja elő, hogy az ellenőrző meg tudja erősíteni, hogy a beágyazott program megegyezik azzal, amit a betűleíró állít; ez az adatfolyam ugyanabban a véglegesítési ablakban kerül kibocsátásra, és függ attól, hogy a részhalmaz először épüljön fel. A PDF/UA-1 megköveteli az ISO 14289-1 §7.18.3 szerint, hogy minden annotációt hordozó oldal deklarálja a /Tabs kulcsot /S értékkel, és egy EnsurePDFUATabsOnAnnotatedPages nevű belső rutin ugyanebben a szakaszban bélyegzi le ezt a kulcsot. A kimeneti szándék (output intent) ellenőrzései szintén itt futnak.
Ugyanaz a rendezési hiba, amely letiltotta a részhalmazkészítést, eldobta a PDF/UA tabulátorsorrend kulcsát is az annotált oldalakon, mivel az a lépés a kiírás ugyanazon rossz oldalán helyezkedett el. A veraPDF és a PAC a hiányzó /Tabs /S kulcsot a Matterhorn protokoll 21-001-es ellenőrzőpontjának megsértéseként jelzi. Így egyetlen rossz helyre tett hívás nemcsak a fájlméretet növelte meg; egyúttal csendben megsértett egy akadálymentességi megfelelőségi követelményt is, szintén mindenféle hibaüzenet nélkül. Ez a véglegesítési szakasz veszélye: a lépései közös előfeltételeken osztoznak, és egyetlen rendezési hiba egyszerre többet is kiüthet közülük, miközben minden hívás sikeresnek tűnik.
Hogyan lehet ténylegesen elcsípni egy csendes kiírási hibát
Azt a hibát, amely nem vált ki kivételt, nem a program futtatásával lehet elcsípni. Úgy csíphető el, ha megvizsgáljuk a kimenetet, és összehasonlítjuk azzal, amit a bemenetnek kellett volna előállítania. A betűtípus-részhalmazok esetében az ellenőrzések konkrétak. Hasonlítsa össze a kimeneti fájl méretét a durva várakozással: egy olyan dokumentum, amely csak maroknyi glifát érintett, nem szabadna, hogy egy teljes betűtípus méretű legyen. Nyissa meg a beágyazott betűtípus-objektumot, és olvassa le a bájthosszát; egy latin betűtípus részhalmazára bontott /FontFile2-je a forrásfájl kis töredéke. Olvassa el a /BaseFont nevét, és ellenőrizze, hogy jelen van-e a hatbetűs előtag, mivel annak hiánya közvetlen jele annak, hogy nem történt részhalmazkészítés.
var
Pdf: THotPDF;
Output: TMemoryStream;
begin
Output := TMemoryStream.Create;
try
Pdf := THotPDF.Create(nil);
try
Pdf.RegisterUnicodeTTF('C:\Fonts\DejaVuSans.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('DejaVu Sans', [], 11);
Pdf.CurrentPage.TextOut(72, 760, 0, 'Subset me');
Pdf.EndDoc;
Pdf.SaveToStream(Output);
finally
Pdf.Free;
end;
// A few glyphs from a ~700 KB face must not yield a multi-hundred-KB stream.
if Output.Size > 100 * 1024 then
raise Exception.Create('Font subset did not shrink the output');
finally
Output.Free;
end;
end;
A PDF/A kimenet esetében az ellenőrzés még élesebb, mert egy érvényesítő (validator) elvégzi a munkát Ön helyett. Állítsa be a megfelelőségi szintet, és futtassa át az eredményt a veraPDF-en: a hiányzó /CIDSet-et vagy a leírónak meg nem felelő részhalmazt hibás feltételként jelenti a rendszer, ahelyett, hogy szemmel kellene észrevennie. A véglegesítési munkát vezérlő megfelelőségi kapcsolók a dokumentum tulajdonságai. A PDFACompliance egy karakterláncot vár, például '2B' a PDF/A-2 Level B esetében, a PDFUACompliance pedig egy logikai érték, amely bekapcsolja a címkézett PDF (tagged-PDF) és a tabulátorsorrend követelményeit.
Pdf := THotPDF.Create(nil);
try
Pdf.PDFACompliance := '2B'; // PDF/A-2 Level B, drives /CIDSet emission
Pdf.PDFUACompliance := True; // stamps /Tabs /S on annotated pages
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansSC-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Noto Sans SC', [], 12);
Pdf.CurrentPage.TextOut(72, 760, 0, '合规报告');
Pdf.EndDoc;
Pdf.SaveToFile('Report_PDFA.pdf');
finally
Pdf.Free;
end;
A mérnöki tanulság
Ebből két szabály következik. Az első az, hogy minden olyan véglegesítési lépésnek, amely objektumokat módosít, le kell futnia az objektumok szerializációja előtt, és a dokumentum-motor lezáró szakaszát egy olyan rendezett csővezetékként kell értelmezni, ahol a szerializáció az utolsó művelet, nem pedig egy művelet a sok közül. A második az, amely a legtöbb időt felemésztette itt: egy kibocsátási lépésnél a hiba hiánya nem a siker bizonyítéka. Egy olyan rutin, amely felépíti a megfelelő részhalmazt, de a hibás, már kiírt gráfon alkalmazza azt, semmi rosszat nem jelent, mert a saját szemszögéből minden rendben volt. Az ellenőrzésnek a létrejött kimenetet kell vizsgálnia, nem a visszatérési kódot. Ellenőrizze a kimenet méretét, olvassa le a beágyazott betűtípus bájthosszát és a /BaseFont előtagját, és hagyja, hogy a veraPDF ítélje meg a PDF/A kimenetet, ahol a hiányzó /CIDSet a csendes hiányosságot nevesített hibává változtatja.
A betűtípus-kezelés előállítói oldala, a betűtípusok regisztrálása és beágyazása a jelentések kimenetéhez a betűtípusokról és képekről szóló cikkünkben található. A validációs oldal, ahol ezeket a véglegesítési lépéseket ellenőrzik a szabványok szerint, a PDF/A és PDF/UA ellenőrzésről szóló útmutatóban érhető el. Mindkettő kapcsolódik az itt leírt részhalmaz-készítési és megfelelőségi munkához, amely a Delphihez és C++Builderhez készült HotPDF Component része, a blogon máshol tárgyalt betöltési, szerkesztési, titkosítási és aláírási API-k mellett.