HotXLS, de Delphi- en C++Builder-Excel-component, verkleint ingesloten PDF-lettertypen via TrueType-lettertypesubsetting: op het moment van PDF-export roept het de functie CreateFontPackage van de Windows-systeembibliotheek fontsub.dll aan om een ingesloten TrueType-lettertype opnieuw op te bouwen rond alleen de Unicode-codepunten die een werkblad daadwerkelijk gebruikte, in plaats van het hele lettertypebestand mee te leveren. Een rapport met tweehonderd rijen Chinese productnamen heeft mogelijk maar een paar honderd unieke Han-tekens nodig, terwijl de CJK-lettertypen die Windows meelevert doorgaans 5 tot 20 MB per stuk beslaan. Sluit er één helemaal in, en het lettertype alleen al kan zwaarder wegen dan elk ander object in de PDF samen
fontsub.dll is geen bibliotheek waar de meeste Delphi-ontwikkelaars ooit van hebben gehoord, en daar is een reden voor: Microsoft levert het als een kleine, spaarzaam gedocumenteerde hulpprogramma-DLL in plaats van een prominente Win32-API. HotXLS behandelt het als een optionele mogelijkheid, geen harde afhankelijkheid, dus hoe de exporter het laadt, aanroept, en terugvalt wanneer het ontbreekt, zegt net zoveel over defensief Windows-programmeren als over lettertypeformaten, en beide helften van dat verhaal zijn de moeite waard om te doorlopen
Waarom laat Unicode-tekst een HotXLS PDF-export opblazen?
HotXLS's PDF-exporter grijpt alleen naar een ingesloten TrueType-lettertype wanneer werkbladtekst buiten WinAnsi valt, en blijft de rest van de tijd op de ingebouwde Helvetica-familie, het standaardpad dat de wandeling door werkblad-naar-PDF-export uitgebreid behandelt. WinAnsi dekt West-Europese tekst goed genoeg dat veel werkboeken nooit een lettertype-inbedding activeren: de PDF verwijst gewoon naar Helvetica bij naam en de lezer levert het lokaal, dus het bestand blijft klein. Zodra een cel iets bevat dat WinAnsi niet kan weergeven, een Chinese productnaam, een Koreaanse notitie, een verdwaald symbool in een opmerking, moet de exporter een echt lettertypeprogramma inbedden, omdat een PDF-lezer geen fallback-glyphbron heeft voor tekens buiten de 14 standaardlettertypen
HotXLS vindt dat lettertype automatisch, door de map Windows Fonts te doorzoeken naar een korte lijst geïnstalleerde kandidaten, inclusief de CJK-geschikte lettertypen die Windows meelevert voor Chinese en Koreaanse weergave, tenzij de UnicodeFontFile-eigenschap van de exporter al naar een specifiek bestand wijst, en welk lettertype het ook wordt, dat wordt volledig ingesloten voordat subsetting ooit draait. Die inbeddingsvereiste is specifiek voor PDF: HotXLS's RTF- en HTML-exportpaden houden Unicode-tekst intact door codepunten in de bytestream te escapen in plaats van een lettertypeprogramma mee te leveren, wat verklaart waarom het grootteprobleem dat dit artikel behandelt geen equivalent heeft op die twee formaten
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;
Wat is fontsub.dll, en waarom niet een subsetter vanaf nul schrijven?
fontsub.dll is een kleine Windows-systeembibliotheek, meegeleverd sinds Windows XP, die één hier relevante functie ontsluit: CreateFontPackage. Geef het de bytes van een bron-TrueType-lettertype en een lijst met te behouden Unicode-codepunten, en het geeft een minimaal lettertype terug dat nog steeds aan elke lettertypeformaat-beperking voldoet: glyph-indexen hernummerd, glyf en loca herbouwd rond alleen de behouden contouren, hmtx en cmap herschreven om overeen te komen. HotXLS declareert het functiepointertype rechtstreeks tegen dat contract
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;
Het werk van CreateFontPackage met de hand schrijven in plaats van het aan te roepen, zou betekenen dat er een correcte TrueType-subsetter moet worden geïmplementeerd: samengestelde glyphs doorlopen om elke componentglyph binnen te halen waarnaar een behouden glyph verwijst, loca-offsets herbouwen nadat contouren zijn verwijderd, de inbeddingstoestemmingsbits in de OS/2-tabel van een lettertype respecteren, en dat alles correct krijgen over welke rare lettertypen een klant zijn machine ook toevallig heeft geïnstalleerd. Microsoft heeft dat probleem al opgelost en levert de oplossing als onderdeel van Windows zelf, dus het aanroepen van een systeem-DLL die het onderhoudt, test tegen zijn eigen lettertype-renderingstack, en gratis aan elke machine distribueert, kost HotXLS een dynamische laadactie en een functiepointer; hetzelfde logica opnieuw implementeren zou betekenen dat men een parser bezit voor een binair formaat met decennia aan edge cases, voor een functie die alleen ertoe doet wanneer een lettertype toevallig groot is
De behoudslijst opbouwen uit daadwerkelijk gerenderde glyphs
HotXLS bouwt de subsetting-behoudslijst op uit een kaart die het om een andere reden al bijhield, zodat de boekhouding niets extra kost. Elke keer dat de paginarendercode een teken tekent dat het ingesloten Unicode-lettertype nodig heeft, zoekt het de glyph-index van dat teken op en registreert de koppeling in FUnicodeGlyphMap, een glyph-naar-codepunt-tabel die ook de PDF-ToUnicode-CMap aanstuurt, zodat kopiëren en plakken uit het voltooide document de oorspronkelijke tekst teruggeeft in plaats van ruwe glyph-ID's. Tegen de tijd dat de paginainhoudsstromen klaar zijn, bevat die kaart al precies de set Unicode-codepunten die het document gebruikte, niet meer en niet minder
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;
Op het moment van afronden doorloopt HotXLS diezelfde kaart een tweede keer om de behoudslijst op te bouwen die CreateFontPackage verwacht, een gewone array van de te behouden Unicode-codepunten in de 16-bits vorm die het behoudslijst-argument van de API vereist. Omdat dat argument een array van 16-bits woorden is, adresseert het het Basic Multilingual Plane netjes, wat gewone CJK-, Cyrillische, Griekse, en Arabische tekst dekt zonder complicaties; een werkblad dat leunt op tekens uit een aanvullend vlak, bepaalde emoji of zeldzame historische schriften, valt buiten wat één behoudslijstvermelding rechtstreeks kan benoemen, wat een grens is die de moeite waard is om te kennen in plaats van een defect, aangezien de overgrote meerderheid van Unicode-zware bedrijfsspreadsheets dat vlak nooit in de buurt komt
Wat gebeurt er wanneer fontsub.dll ontbreekt?
HotXLS neemt nooit aan dat fontsub.dll aanwezig is, en de PDF-export faalt nooit omdat het ontbreekt. De bibliotheek wordt dynamisch geladen op het moment dat een subset nodig is, met SafeLoadLibrary en GetProcAddress in plaats van een statische import, precies omdat fontsub.dll geen gedocumenteerde, gegarandeerd aanwezige publieke API is zoals kernel32.dll: het is gebundelde lettertype-inbeddingstooling, en niets in Microsofts contract belooft dat het overleeft op elke SKU, elke servicingtak, of elke compatibiliteitslaag die probeert Windows te emuleren
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;
Elk faalpad vouwt terug naar dezelfde uitkomst. Een ontbrekende DLL, een ontbrekende export, een niet-nul retourcode, of een lettertype waarvan de OS/2-tabel subsetting verbiedt via zijn inbeddingstoestemmingsbits, HotXLS houdt gewoon het volledige lettertype dat het al had ingesloten en gaat verder. Niets werpt een uitzondering op, niets breekt de export af, en de aanroepende code hoeft nooit een lettertype-optimalisatie in zijn eigen uitzonderingsafhandeling te wikkelen; de geëxporteerde PDF is in beide gevallen geldig, en de enige variabele is of het bestand klein of iets groter uitvalt
Hoeveel kleiner wordt de PDF daadwerkelijk?
HotXLS's TrueType-lettertypesubsetting verkleint een Unicode-zwaar werkblads geëxporteerde PDF doorgaans tot ergens tussen een twintigste en een achtste van zijn niet-gesubsette grootte, een 8-tot-20-voudige verkleining waarvan de schaal meebeweegt met hoeveel van een volledig lettertype een gegeven document daadwerkelijk aanraakt: een inkooporder opgebouwd rond een paar honderd unieke Chinese tekens behoudt slechts die paar honderd glyphs van de tienduizenden die een CJK-lettertype meelevert, terwijl een blad dat een bredere mix van tekens beslaat proportioneel meer behoudt. HotXLS voegt daarbovenop een extra Flate-compressiepas toe aan de gesubsette lettertypebytes voordat deze in de /FontFile2-stroom van de PDF worden geschreven, dezelfde compressie die de rest van de inhoudsstromen van het document al ondergaat, en niets daarvan vraagt iets extra's van de aanroepende code: een werkblad dat WinAnsi nooit verlaat, raakt dit pad nooit aan en blijft exporteren via gewoon Helvetica, terwijl een werkblad dat het Unicode-lettertypepad wel activeert automatisch subsetting krijgt, zonder eigenschap om in te stellen en zonder aparte aanroep om te maken, en de ene betrokken eigenschap, UnicodeFontFile, kiest alleen welk lettertype wordt ingesloten en gesubset, niet of subsetting plaatsvindt
Lettertypesubsetting is één detail binnen het bredere PDF-exportoppervlak van de HotXLS Delphi Excel-component, naast paginering, werkblad-afdrukmetagegevens, en de CSV-, HTML-, en RTF-exportpaden die het meelevert