HotXLS, Excel komponenta za Delphi i C++Builder, smanjuje veličinu ugrađenih PDF fontova podskupom fonta TrueType: pri izvozu PDF-a poziva funkciju CreateFontPackage Windowsove sistemske biblioteke fontsub.dll kako bi ponovno izgradio ugrađeni font TrueType samo oko Unicode kodnih točaka koje je radni list stvarno upotrijebio, umjesto da isporuči cijelu datoteku fonta. Izvještaju s dvjesto redaka kineskih naziva proizvoda možda treba samo nekoliko stotina različitih han znakova, dok CJK fontovi koje Windows isporučuje redovito zauzimaju od 5 do 20 MB svaki. Ugradnja jednog cijelog fonta može učiniti da samo font bude veći od svih ostalih objekata u PDF-u zajedno
fontsub.dll nije biblioteka za koju je većina razvojnih programera u Delphiju ikada čula, a za to postoji razlog: Microsoft je isporučuje kao malu, oskudno dokumentiranu pomoćnu DLL biblioteku, a ne kao istaknuti Win32 API. HotXLS je tretira kao dodatnu mogućnost, a ne kao čvrstu ovisnost, pa način na koji je izvoznik učitava, poziva i prelazi na rezervni put kada nedostaje govori koliko o obrambenom programiranju u Windowsu toliko i o formatima fontova, a oba dijela te priče vrijedi proći
Zašto Unicode tekst toliko povećava PDF izvoz iz HotXLS-a
Izvoznik PDF-a u HotXLS-u poseže za ugrađenim fontom TrueType samo kada tekst radnog lista izlazi izvan WinAnsi skupa, a u svim ostalim slučajevima ostaje na ugrađenoj obitelji Helvetica, što je zadani put detaljno opisan u uputama za izvoz radnog lista u PDF. WinAnsi dovoljno dobro pokriva zapadnoeuropski tekst pa mnoge radne knjige uopće ne pokrenu ugradnju fonta: PDF samo navodi Helveticu po imenu, a čitač je lokalno osigurava, tako da datoteka ostaje mala. Čim ćelija sadrži nešto što WinAnsi ne može prikazati, primjerice kineski naziv proizvoda, korejsku bilješku ili slučajni simbol u komentaru, izvoznik mora ugraditi stvarni program fonta jer PDF čitač nema rezervni izvor glifova za znakove izvan standardnih 14 fontova
HotXLS automatski pronalazi taj font pretraživanjem mape Windows Fonts za kratki popis instaliranih kandidata, uključujući fontove sposobne za CJK prikaz koje Windows isporučuje za kinesko i korejsko iscrtavanje, osim ako svojstvo izvoznika UnicodeFontFile već pokazuje na određenu datoteku, a font koji pronađe u cijelosti se ugrađuje prije nego što se podskup uopće pokrene. Taj je zahtjev za ugradnjom poseban za PDF: putevi izvoza u RTF i HTML u HotXLS-u čuvaju Unicode tekst izbjegavanjem kodnih točaka u tok bajtova umjesto slanja programa fonta, zbog čega problem veličine obrađen u ovom članku nema istovjetan oblik u ta dva formata
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;
Što je fontsub.dll i zašto ne napisati vlastiti alat za podskupove
fontsub.dll mala je Windowsova sistemska biblioteka koja se isporučuje od sustava Windows XP i ovdje izlaže jednu relevantnu funkciju: CreateFontPackage. Predate li joj bajtove izvornog fonta TrueType i popis Unicode kodnih točaka koje treba zadržati, vratit će minimalni font koji i dalje zadovoljava sva ograničenja formata fonta: indeksi glifova ponovno se numeriraju, glyf i loca ponovno se grade samo oko zadržanih obrisa, a hmtx i cmap ponovno se zapisuju tako da im odgovaraju. HotXLS izravno deklarira vrstu pokazivača na funkciju prema tom ugovoru
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;
Ručno pisanje posla koji obavlja CreateFontPackage umjesto njegova pozivanja značilo bi implementirati ispravan alat za podskup TrueType fonta: proći kroz složene glifove i uključiti svaki sastavni glif na koji se zadržani glif poziva, ponovno izgraditi pomake loca nakon uklanjanja obrisa, poštovati bitove dopuštenja za ugradnju u tablici OS/2 fonta i sve to ispravno izvesti na neobičnim fontovima koji se slučajno nalaze na računalu korisnika. Microsoft je taj problem već riješio i rješenje isporučuje kao dio samog Windowsa, pa pozivanje sistemske DLL biblioteke koju održava, testira uz vlastiti sustav za iscrtavanje fontova i besplatno distribuira na svako računalo HotXLS-u donosi samo dinamičko učitavanje i pokazivač na funkciju; ponovna implementacija iste logike značila bi preuzimanje parsera binarnog formata s desetljećima rubnih slučajeva za mogućnost koja je važna samo kada je font velik
Izrada popisa za zadržavanje iz stvarno iscrtanih glifova
HotXLS gradi popis za podskup iz mape koju je već održavao iz drugog razloga, pa obračun ne donosi dodatni trošak. Svaki put kada kôd za iscrtavanje stranice nacrta znak kojemu treba ugrađeni Unicode font, pronalazi indeks glifa tog znaka i bilježi par u FUnicodeGlyphMap, tablicu glifova i kodnih točaka koja ujedno pokreće PDF-ov CMap ToUnicode kako bi kopiranje i lijepljenje iz dovršenog dokumenta vratilo izvorni tekst umjesto sirovih ID-ova glifova. Kada se tokovi sadržaja stranice dovrše, ta mapa već navodi točan skup Unicode kodnih točaka koje je dokument koristio, ni više ni manje
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;
Pri završnoj obradi HotXLS još jednom prolazi kroz istu mapu kako bi izgradio popis za zadržavanje koji očekuje CreateFontPackage, odnosno običan niz Unicode kodnih točaka koje treba zadržati u obliku 16-bitnih vrijednosti koji zahtijeva argument API-ja za taj popis. Budući da je taj argument niz 16-bitnih riječi, uredno pokriva Osnovnu višejezičnu ravninu, koja bez poteškoća obuhvaća uobičajeni CJK, ćirilicu, grčki i arapski tekst; radni list koji se oslanja na znakove iz dopunske ravnine, poput određenih emotikona ili rijetkih povijesnih pisama, izlazi izravno iz onoga što jedan unos popisa može imenovati, što je granica koju vrijedi poznavati, a ne nedostatak, jer velika većina poslovnih tablica s mnogo Unicode teksta tu ravninu nikada ne koristi
Što se događa kada nedostaje fontsub.dll
HotXLS nikada ne pretpostavlja da je fontsub.dll prisutan i izvoz PDF-a nikada ne prestaje raditi samo zato što nije prisutan. Biblioteka se dinamički učitava u trenutku kada je potreban podskup, pomoću SafeLoadLibrary i GetProcAddress, a ne statičkim uvozom, upravo zato što fontsub.dll nije dokumentiran i zajamčeno prisutan javni API poput kernel32.dll: riječ je o alatu za ugrađivanje fontova koji dolazi u paketu, a Microsoftov ugovor ne obećava da će preživjeti na svakoj SKU varijanti, svakoj grani servisiranja ili svakom sloju kompatibilnosti koji pokušava oponašati Windows
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;
Svaki put pogreške vraća se na isti ishod. Ako nedostaje DLL, ako nedostaje izvoz, ako je povratni kôd različit od nule ili ako font kroz bitove dopuštenja za ugradnju u tablici OS/2 zabranjuje izradu podskupa, HotXLS jednostavno zadržava cijeli font koji je već ugradio i nastavlja. Ništa ne izbacuje iznimku, ništa ne prekida izvoz i pozivajući kôd ne mora vlastitom obradom iznimki omotati optimizaciju fonta; izvezeni PDF valjan je u oba slučaja, a jedina je razlika hoće li završiti malen ili nešto veći
Koliko se PDF doista smanjuje
Podskup fonta TrueType u HotXLS-u obično smanji izvezeni PDF radnog lista s mnogo Unicode teksta na između dvadesetine i osmine izvorne veličine bez podskupa, odnosno za 8 do 20 puta, a razmjer ovisi o tome koliki dio cijelog fonta određeni dokument doista koristi: narudžbenica s nekoliko stotina različitih kineskih znakova zadržava samo tih nekoliko stotina glifova od deseci tisuća koje isporučuje CJK pismo, dok list s raznolikijom mješavinom znakova zadržava razmjerno više. HotXLS na bajtove fonta s podskupom primjenjuje još jedan prolaz Flate kompresije prije zapisivanja u PDF tok /FontFile2, istu kompresiju kroz koju već prolaze ostali tokovi sadržaja dokumenta, a pozivajućem kodu za to nije potrebno ništa dodatno: radni list koji nikada ne napusti WinAnsi ne dodiruje ovaj put i nastavlja se izvoziti običnom Helveticom, dok se na radnom listu koji pokrene put Unicode fonta podskup izrađuje automatski, bez svojstva koje treba postaviti i bez zasebnog poziva, a jedino uključeno svojstvo, UnicodeFontFile, samo odabire koji će se font ugraditi i podskupiti, a ne hoće li se podskup izrađivati
Podskup fonta samo je jedan detalj šireg sustava izvoza PDF-a komponente HotXLS Excel komponenta za Delphi, uz prijelome stranica, metapodatke za ispis radnog lista te putove izvoza u CSV, HTML i RTF koje isporučuje