HotXLS, Excel-komponenten for Delphi og C++Builder, kutter innebygd PDF-skrifttypestørrelse gjennom TrueType-delmengdedannelse: ved PDF-eksporttidspunktet kaller den Windows-systembiblioteket fontsub.dlls CreateFontPackage-funksjon for å gjenoppbygge en innebygd TrueType-skrifttype rundt bare de Unicode-kodepunktene et regneark faktisk brukte, i stedet for å sende med hele skriftfilen. En rapport med to hundre rader kinesiske produktnavn trenger kanskje bare noen få hundre distinkte Han-tegn, men CJK-skrifttypene Windows leverer med, kjører rutinemessig 5 til 20 MB hver. Bygg inn én hel, og skrifttypen alene kan veie mer enn alle andre objekter i PDF-en til sammen
fontsub.dll er ikke et bibliotek de fleste Delphi-utviklere noensinne har hørt om, og det er en grunn til det: Microsoft leverer det som en liten, sparsomt dokumentert verktøy-DLL snarere enn en fremtredende Win32-API. HotXLS behandler det som en valgfri mulighet, ikke en hard avhengighet, så hvordan eksportøren laster den, kaller den, og faller tilbake når den mangler, sier like mye om defensiv Windows-programmering som det gjør om skrifttypeformater, og begge halvdeler av den historien er verdt å gå gjennom
Hvorfor blåser Unicode-tekst opp en HotXLS PDF-eksport?
HotXLS' PDF-eksportør griper til en innebygd TrueType-skrifttype bare når regnearkteksten faller utenfor WinAnsi, og holder seg på den innebygde Helvetica-familien resten av tiden, standardveien gjennomgangen av regneark-til-PDF-eksport dekker i dybden. WinAnsi dekker vesteuropeisk tekst godt nok til at mange arbeidsbøker aldri utløser en skrifttype-innbygging i det hele tatt: PDF-en refererer bare til Helvetica ved navn, og leseren leverer den lokalt, slik at filen forblir liten. I det øyeblikket en celle inneholder noe WinAnsi ikke kan representere, et kinesisk produktnavn, en koreansk notis, et streift symbol i en kommentar, må eksportøren bygge inn et faktisk skrifttypeprogram, fordi en PDF-leser ikke har noen reserve-glyfkilde for tegn utenfor de 14 standardskrifttypene
HotXLS finner den skrifttypen automatisk, ved å skanne Windows' Fonts-mappe for en kort liste med installerte kandidater, inkludert de CJK-kapable skrifttypene Windows leverer for kinesisk og koreansk gjengivelse, med mindre eksportørens UnicodeFontFile-egenskap allerede peker på en spesifikk fil, og hvilken skrifttype den enn lander på, bygges inn hel før delmengdedannelse noensinne kjører. Det innbyggingskravet er spesifikt for PDF: HotXLS' RTF- og HTML-eksportveier holder Unicode-tekst intakt ved å escape-kode kodepunkter inn i bytestrømmen i stedet for å sende med et skrifttypeprogram, noe som er grunnen til at størrelsesproblemet denne artikkelen dekker, ikke har noe motstykke på de to formatene
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;
Hva er fontsub.dll, og hvorfor ikke skrive en delmengde-danner fra bunnen av?
fontsub.dll er et lite Windows-systembibliotek, levert siden Windows XP, som eksponerer én funksjon relevant her: CreateFontPackage. Gi den bytene til en kilde-TrueType-skrifttype og en liste med Unicode-kodepunkter å beholde, og den overleverer en minimal skrifttype som fortsatt tilfredsstiller hver skrifttypeformat-begrensning: glyfindekser nummerert på nytt, glyf og loca gjenoppbygd rundt bare de beholdte konturene, hmtx og cmap skrevet om for å matche. HotXLS deklarerer funksjonspekertypen direkte mot den kontrakten
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;
Å skrive CreateFontPackages jobb for hånd i stedet for å kalle den, ville betydd å implementere en korrekt TrueType-delmengde-danner: å gå gjennom sammensatte glyfer for å trekke inn hver komponentglyf en beholdt glyf refererer til, gjenoppbygge loca-forskyvninger etter at konturer droppes, respektere innbyggings-tillatelses-bitene i en skrifttypes OS/2-tabell, og få alt riktig på tvers av hvilke som helst rare skrifttyper en kundes maskin tilfeldigvis har installert. Microsoft har allerede løst det problemet og leverer løsningen som en del av Windows selv, så å kalle en system-DLL de vedlikeholder, tester mot sin egen skrifttype-gjengivelsesstakk, og distribuerer til hver maskin gratis, koster HotXLS en dynamisk innlasting og en funksjonspeker; å reimplementere den samme logikken ville betydd å eie en parser for et binærformat med tiår av kantsaker, for en funksjon som bare betyr noe når en skrifttype tilfeldigvis er stor
Å bygge beholde-listen fra glyfer faktisk gjengitt
HotXLS bygger delmengde-beholde-listen fra et kart den allerede vedlikeholdt av en annen grunn, så regnskapsføringen koster ingenting ekstra. Hver gang sidegjengivelseskoden tegner et tegn som trenger den innebygde Unicode-skrifttypen, slår den opp det tegnets glyfindeks og registrerer paringen i FUnicodeGlyphMap, en glyf-til-kodepunkt-tabell som også driver PDF-ToUnicode-CMap-en slik at kopier-og-lim-inn ut av det ferdige dokumentet returnerer den opprinnelige teksten i stedet for rå glyf-ID-er. Innen sideinnholdsstrømmene er ferdige, lister det kartet allerede opp nøyaktig settet med Unicode-kodepunkter dokumentet brukte, verken mer eller mindre
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;
Ved finalize-tidspunktet går HotXLS gjennom det samme kartet en andre gang for å bygge beholde-listen CreateFontPackage forventer, et enkelt array med Unicode-kodepunktene å beholde i den 16-bit formen API-ets beholde-liste-argument krever. Fordi det argumentet er et array av 16-bit ord, adresserer det Basic Multilingual Plane rent, noe som dekker vanlig CJK-, kyrillisk-, gresk-, og arabisk tekst uten komplikasjon; et regneark som lener seg på supplerende-plan-tegn, visse emoji eller sjeldne historiske skriftsystemer, ligger utenfor det en enkelt beholde-liste-oppføring kan navngi direkte, noe som er en avgrensning verdt å kjenne til snarere enn en defekt, ettersom det store flertallet av Unicode-tunge forretningsregneark aldri kommer i nærheten av det planet i utgangspunktet
Hva skjer når fontsub.dll mangler?
HotXLS antar aldri at fontsub.dll er til stede, og PDF-eksporten feiler aldri fordi den ikke er det. Biblioteket lastes dynamisk i det øyeblikket en delmengde trengs, med SafeLoadLibrary og GetProcAddress i stedet for en statisk import, nettopp fordi fontsub.dll ikke er et dokumentert, garantert-tilstedeværende offentlig API slik kernel32.dll er: det er medfølgende skrifttype-innbyggingsverktøy, og ingenting i Microsofts kontrakt lover at det overlever på hver SKU, hver vedlikeholdsgren, eller hvert kompatibilitetslag som prøver å emulere 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;
Hver feilvei folder tilbake til det samme utfallet. En manglende DLL, en manglende eksport, en ikke-null returkode, eller en skrifttype hvis OS/2-tabell forbyr delmengdedannelse gjennom sine innbyggings-tillatelses-biter, HotXLS beholder bare den fulle skrifttypen den allerede hadde bygget inn og går videre. Ingenting kaster et unntak, ingenting avbryter eksporten, og den kallende koden trenger aldri å pakke en skrifttypeoptimalisering inn i sin egen unntaksbehandling; den eksporterte PDF-en er gyldig uansett, og den eneste variabelen er om den ender opp liten eller noe større
Hvor mye mindre blir PDF-en faktisk?
HotXLS' TrueType-skrifttype-delmengdedannelse krymper typisk et Unicode-tungt regnearks eksporterte PDF til et sted mellom en tjuendedel og en åttendedel av dens ikke-delmengdegjorte størrelse, en 8-til-20-gangers reduksjon hvis skala følger hvor mye av en full skrifttype et gitt dokument faktisk rører: en innkjøpsordre bygget rundt noen få hundre distinkte kinesiske tegn beholder bare de få hundre glyfene av de titusenvis en CJK-skrifttype leverer med, mens et ark som spenner over en bredere blanding av tegn, beholder proporsjonalt flere. HotXLS legger et videre Flate-komprimeringspass på toppen av delmengde-skrifttype-bytene før den skriver dem inn i PDF-ens /FontFile2-strøm, den samme komprimeringen resten av dokumentets innholdsstrømmer allerede går gjennom, og ingenting av det ber om noe ekstra fra den kallende koden: et regneark som aldri forlater WinAnsi, rører aldri denne veien og fortsetter å eksportere gjennom vanlig Helvetica, mens et regneark som utløser Unicode-skrifttypeveien, får delmengdedannelse automatisk, uten noen egenskap å sette og intet separat kall å gjøre, og den ene egenskapen som er involvert, UnicodeFontFile, velger bare hvilken skrifttype som bygges inn og lages delmengde av, ikke om delmengdedannelse skjer
Skrifttype-delmengdedannelse er én detalj inne i den bredere PDF-eksport-overflaten til HotXLS Delphi Excel-komponenten, sammen med paginering, regneark-utskriftsmetadata, og CSV-, HTML-, og RTF-eksportveiene den leveres med