A HotXLS, a Delphi és C++Builder Excel-komponens, TrueType betűkészlet-részhalmazolással (subsetting) csökkenti a beágyazott PDF-betűkészlet méretét: PDF-exportáláskor meghívja a Windows rendszerkönyvtár fontsub.dll CreateFontPackage függvényét, hogy egy beágyazott TrueType betűkészletet csak azon Unicode-kódpontok köré építsen újra, amelyeket egy munkalap ténylegesen használt, ahelyett hogy a teljes betűtípus-fájlt szállítaná. Egy jelentés kétszáz sornyi kínai terméknévvel talán csak néhány száz különálló han-karaktert igényel, mégis a Windows által szállított CJK-betűkészletek rutinszerűen 5-20 MB-osak darabonként. Ágyazz be egyet egészben, és a betűkészlet önmagában felülmúlhatja a PDF minden más objektumát együttvéve
A fontsub.dll nem olyan könyvtár, amelyről a legtöbb Delphi-fejlesztő valaha is hallott, és ennek megvan az oka: a Microsoft egy kis, ritkásan dokumentált segédprogram-DLL-ként szállítja, nem pedig egy vezető Win32 API-ként. A HotXLS opcionális képességként kezeli, nem kemény függőségként, így az, hogyan tölti be az exportáló, hogyan hívja meg, és hogyan esik vissza, ha hiányzik, ugyanannyit elárul a defenzív Windows-programozásról, mint a betűtípus-formátumokról, és mindkét felét érdemes végigjárni
Miért duzzasztja fel az Unicode-szöveg egy HotXLS PDF-exportot?
A HotXLS PDF-exportáló csak akkor nyúl egy beágyazott TrueType betűkészlethez, amikor a munkalap-szöveg a WinAnsi-n kívülre esik, és egyébként a beépített Helvetica családnál marad, ez az alapértelmezett útvonal, amelyet a munkalap-PDF exportbemutató mélyen tárgyal. A WinAnsi eléggé lefedi a nyugat-európai szöveget ahhoz, hogy sok munkafüzet soha ne váltson ki betűkészlet-beágyazást: a PDF egyszerűen névvel hivatkozik a Helveticára, és az olvasó helyben biztosítja azt, így a fájl kicsi marad. Abban a pillanatban, amikor egy cella olyasmit tartalmaz, amit a WinAnsi nem tud ábrázolni, egy kínai terméknevet, egy koreai megjegyzést, egy elszórt szimbólumot egy megjegyzésben, az exportálónak tényleges betűkészlet-programot kell beágyaznia, mert egy PDF-olvasónak nincs tartalék glyph-forrása a szabványos 14 betűkészleten kívüli karakterekhez
A HotXLS automatikusan megtalálja ezt a betűkészletet, végigpásztázva a Windows Betűkészletek mappáját néhány telepített jelölt után kutatva, beleértve a CJK-képes betűtípusokat, amelyeket a Windows kínai és koreai renderelésre szállít, hacsak az exportáló UnicodeFontFile tulajdonsága már nem mutat egy konkrét fájlra, és bármelyik betűkészleten is köt ki, az teljes egészében beágyazódik, mielőtt a részhalmazolás egyáltalán lefutna. Ez a beágyazási követelmény a PDF-re jellemző: a HotXLS RTF- és HTML-exportútvonalai érintetlenül tartják az Unicode-szöveget azáltal, hogy a kódpontokat a bájtfolyamba menekítik ahelyett, hogy egy betűkészlet-programot szállítanának, ezért nincs megfelelője e két formátumon a méretproblémának, amit ez a cikk tárgyal
uses
lxHandle, lxPDF;
var
Book: TXLSWorkbook;
Exporter: TXLSPDFExport;
begin
Book := TXLSWorkbook.Create;
try
Book.Open('catalog-cn.xlsx');
Exporter := TXLSPDFExport.Create;
try
// Optional: pin a specific CJK-capable font instead of the
// exporter's automatic Windows\Fonts scan.
Exporter.UnicodeFontFile := 'C:\Windows\Fonts\simhei.ttf';
Exporter.SaveAsPDF(Book.ActiveSheet, 'catalog-cn.pdf');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
Mi az a fontsub.dll, és miért nem érdemes egy részhalmazolót nulláról megírni?
A fontsub.dll egy kis Windows rendszerkönyvtár, amelyet a Windows XP óta szállítanak, és amely egyetlen, itt releváns függvényt tesz elérhetővé: a CreateFontPackage-et. Add át neki egy forrás TrueType betűkészlet bájtjait és egy megtartandó Unicode-kódpontok listáját, és visszaad egy minimális betűkészletet, amely még mindig kielégít minden betűtípus-formátumbeli megszorítást: a glyph-indexek átszámozva, a glyf és loca újraépítve csak a megtartott körvonalak köré, a hmtx és cmap átírva, hogy egyezzen. A HotXLS közvetlenül e szerződés ellen deklarálja a függvénymutató-típust
const
TTFCFP_FLAGS_SUBSET = 1;
TTFMFP_SUBSET = 0;
TTFCFP_MS_PLATFORMID = 3;
TTFCFP_UNICODE_CHAR_SET = 1;
type
TCreateFontPackage = function(puchSrcBuffer: Pointer; ulSrcBufferSize: Cardinal;
var puchFontPackageBuffer: PAnsiChar; var pulFontPackageBufferSize: Cardinal;
var pulBytesWritten: Cardinal; usFlags, usTTCIndex, usSubsetFormat,
usSubsetLanguage, usSubsetPlatform, usSubsetEncoding: Word;
pusSubsetKeepList: PWordArray; usSubsetKeepListCount: Word;
lpfnAllocate, lpfnReAllocate, lpfnFree, reserved: Pointer): Cardinal; cdecl;
A CreateFontPackage feladatának kézzel történő megírása ahelyett, hogy meghívnánk, egy helyes TrueType-részhalmazoló megvalósítását jelentené: kompozit glyph-ek bejárását, hogy behúzzunk minden komponens-glyph-et, amelyre egy megtartott glyph hivatkozik, a loca eltolások újraépítését, miután körvonalakat eldobtunk, egy betűkészlet OS/2 táblájában lévő beágyazási-engedély bitek tiszteletben tartását, és mindezt helyesen elvégezni bármilyen furcsa betűkészleten, amit egy ügyfél gépe véletlenül telepítve tart. A Microsoft már megoldotta ezt a problémát, és a megoldást magának a Windowsnak a részeként szállítja, így egy rendszer-DLL meghívása, amelyet ő tart karban, ő tesztel a saját betűkészlet-renderelő verme ellen, és ő terjeszt minden géphez ingyen, a HotXLS-nek egy dinamikus betöltésébe és egy függvénymutatóba kerül; ugyanennek a logikának az újravalósítása azt jelentené, hogy birtokolnunk kellene egy elemzőt egy bináris formátumhoz, amelynek évtizedes szélsőesetei vannak, egy olyan funkcióért, amely csak akkor számít, amikor egy betűkészlet véletlenül nagy
A megtartási lista felépítése a ténylegesen renderelt glyph-ekből
A HotXLS a részhalmazolási megtartási listát egy olyan térképből építi fel, amelyet már amúgy is más okból fenntartott, így a könyvelés semmi extrát nem kerül. Valahányszor az oldal-renderelő kód lerajzol egy karaktert, amelynek szüksége van a beágyazott Unicode-betűkészletre, felkeresi annak a karakternek a glyph-indexét, és rögzíti a párosítást az FUnicodeGlyphMap-ben, egy glyph-kódpont táblázatban, amely a PDF ToUnicode CMap-ját is meghajtja, így a kész dokumentumból történő kimásolás és beillesztés az eredeti szöveget adja vissza, nem nyers glyph-azonosítókat. Mire az oldal tartalomfolyamai elkészülnek, ez a térkép már pontosan azt a Unicode-kódpont-halmazt listázza, amit a dokumentum használt, se többet, se kevesebbet
var
keepList: array of Word;
keepCount, i: Integer;
codePoint: LongWord;
begin
SetLength(keepList, FUnicodeGlyphMap.Count);
keepCount := 0;
for i := 0 to FUnicodeGlyphMap.Count - 1 do
begin
codePoint := LongWord(StrToIntDef('$' + FUnicodeGlyphMap.ValueFromIndex[i], 0));
if codePoint > 0 then
begin
keepList[keepCount] := Word(codePoint);
Inc(keepCount);
end;
end;
end;
A véglegesítéskor a HotXLS másodszor is bejárja ugyanazt a térképet, hogy felépítse a CreateFontPackage által elvárt megtartási listát, egy egyszerű tömböt a megtartandó Unicode-kódpontokból, az API megtartásilista-argumentuma által megkövetelt 16 bites formában. Mivel ez az argumentum 16 bites szavak tömbje, tisztán megcímzi az Alapvető Többnyelvű Síkot (Basic Multilingual Plane), amely bonyodalom nélkül lefedi a közönséges CJK-, cirill, görög, és arab szöveget; egy munkalap, amely kiegészítő síkú karakterekre támaszkodik, bizonyos emojikra vagy ritka történelmi írásokra, azon kívül esik, amit egyetlen megtartásilista-bejegyzés közvetlenül meg tud nevezni, ami egy határ, amit érdemes ismerni, nem pedig egy hiba, mivel az Unicode-nehéz üzleti táblázatok túlnyomó többsége soha nem is közelíti meg azt a síkot
Mi történik, ha a fontsub.dll hiányzik?
A HotXLS soha nem feltételezi, hogy a fontsub.dll jelen van, és a PDF-export soha nem bukik el azért, mert nincs ott. A könyvtár dinamikusan töltődik be abban a pillanatban, amikor egy részhalmazra szükség van, SafeLoadLibrary-vel és GetProcAddress-szel, nem pedig egy statikus importtal, pontosan azért, mert a fontsub.dll nem egy dokumentált, garantáltan jelen lévő nyilvános API úgy, ahogyan a kernel32.dll az: ez egy csomagolt betűkészlet-beágyazási eszköz, és semmi a Microsoft szerződésében nem ígéri, hogy túléli minden SKU-n, minden szervizelési ágon, vagy minden kompatibilitási rétegen, amely megpróbálja emulálni a Windowst
var
hFontSub: HMODULE;
CreateFontPackage: TCreateFontPackage;
begin
hFontSub := SafeLoadLibrary('FontSub.dll');
if hFontSub = 0 then
Exit; // no subsetting available - keep the full embedded font
try
@CreateFontPackage := GetProcAddress(hFontSub, 'CreateFontPackage');
if not Assigned(CreateFontPackage) then
Exit;
// ... call CreateFontPackage, check its return code ...
finally
FreeLibrary(hFontSub);
end;
end;
Minden hibaútvonal ugyanarra az eredményre hajtódik vissza. Egy hiányzó DLL, egy hiányzó export, egy nemnulla visszatérési kód, vagy egy betűkészlet, amelynek OS/2 táblája tiltja a részhalmazolást a beágyazási-engedély bitjein keresztül, a HotXLS egyszerűen megtartja azt a teljes betűkészletet, amit már beágyazott, és halad tovább. Semmi nem dob kivételt, semmi nem szakítja meg az exportot, és a hívó kódnak soha nem kell egy betűkészlet-optimalizálást a saját kivételkezelésébe csomagolnia; az exportált PDF mindkét esetben érvényes, és az egyetlen változó az, hogy kicsi lesz-e, vagy kissé nagyobb
Mennyivel lesz ténylegesen kisebb a PDF?
A HotXLS TrueType betűkészlet-részhalmazolása jellemzően egy huszad és egy nyolcad közötti méretre zsugorítja egy Unicode-nehéz munkalap exportált PDF-jét a részhalmazolatlan mérethez képest, egy 8-20-szoros csökkentés, amelynek mértéke azt követi, mennyit érint ténylegesen egy adott dokumentum egy teljes betűkészletből: egy néhány száz különálló kínai karakterre épített megrendelés csak azt a néhány száz glyph-et tartja meg a tízezrekből, amiket egy CJK-betűtípus szállít, míg egy szélesebb karakterkeverést átfogó lap arányosan többet tart meg. A HotXLS egy további Flate-tömörítési átfutást rétegez a részhalmaz-betűkészlet bájtjaira, mielőtt beírná őket a PDF /FontFile2 streamjébe, ugyanazt a tömörítést, amin a dokumentum tartalomfolyamainak többi része már átmegy, és mindebből semmi nem kér semmi extrát a hívó kódtól: egy munkalap, amely soha nem hagyja el a WinAnsi-t, soha nem érinti ezt az útvonalat, és továbbra is egyszerű Helveticán keresztül exportál, míg egy munkalap, amely kiváltja az Unicode-betűkészlet útvonalat, automatikusan megkapja a részhalmazolást, tulajdonság beállítása és külön hívás nélkül, és az egyetlen érintett tulajdonság, az UnicodeFontFile, csak azt választja meg, melyik betűkészlet kerül beágyazásra és részhalmazolásra, nem azt, hogy a részhalmazolás megtörténik-e
A betűkészlet-részhalmazolás egy részlet a Delphihez és C++Builderhez készült HotXLS Delphi Excel Komponens szélesebb PDF-export felületén belül, a lapozás, a munkalap-nyomtatási metaadatok, és a vele szállított CSV-, HTML-, és RTF-exportútvonalak mellett