Tekninen artikkeli

TrueType-fonttien osajoukkojen muodostus fontsub.dll:llä Delphissä

HotXLS, Delphi- ja C++Builder-Excel-komponentti, pienentää upotetun PDF-fontin kokoa TrueType-fonttien osajoukkojen muodostuksella: PDF-vientihetkellä se kutsuu Windowsin järjestelmäkirjaston fontsub.dll:n CreateFontPackage-funktiota rakentaakseen upotetun TrueType-fontin uudelleen vain niiden Unicode-koodipisteiden ympärille, joita työarkki todella käytti, sen sijaan että toimittaisi koko fonttitiedoston. Raportti, jossa on kaksisataa riviä kiinalaisia tuotenimiä, saattaa tarvita vain muutaman sadan erillisen han-merkin, kuitenkin Windowsin mukana tulevat CJK-fontit ovat rutiininomaisesti 5–20 Mt kappaleelta. Upota yksi kokonaisena, ja pelkkä fontti voi painaa enemmän kuin kaikki muut PDF:n oliot yhteensä

fontsub.dll ei ole kirjasto, josta useimmat Delphi-kehittäjät ovat koskaan kuulleet, ja tähän on syy: Microsoft toimittaa sen pienenä, niukasti dokumentoituna apuohjelma-DLL:nä eikä otsikoita saavana Win32-API:na. HotXLS kohtelee sitä valinnaisena ominaisuutena, ei kovana riippuvuutena, joten se, miten viejä lataa sen, kutsuu sitä ja palautuu varakeinoon, kun se puuttuu, kertoo yhtä paljon puolustavasta Windows-ohjelmoinnista kuin fonttimuodoista, ja molemmat puolet tästä tarinasta kannattaa käydä läpi

Miksi Unicode-teksti paisuttaa HotXLS-PDF-vientiä?

HotXLS:n PDF-viejä turvautuu upotettuun TrueType-fonttiin vain, kun työarkin teksti putoaa WinAnsin ulkopuolelle, ja pysyy sisäänrakennetussa Helvetica-perheessä muun ajan, oletuspolku, jonka työarkki-PDF-vienti-läpikäynti käsittelee perusteellisesti. WinAnsi kattaa länsieurooppalaisen tekstin riittävän hyvin, ettei moni työkirja koskaan laukaise fontin upotusta lainkaan: PDF vain viittaa Helveticaan nimellä, ja lukija toimittaa sen paikallisesti, joten tiedosto pysyy pienenä. Heti kun solu sisältää jotain, mitä WinAnsi ei voi esittää, kiinalaisen tuotenimen, korealaisen huomautuksen, eksyneen symbolin kommentissa, viejän on upotettava todellinen fonttiohjelma, koska PDF-lukijalla ei ole varaglyfilähdettä merkeille standardin 14 fontin ulkopuolella

HotXLS paikantaa tuon fontin automaattisesti, skannaamalla Windowsin Fonts-kansiota lyhyen listan asennettuja ehdokkaita varten, mukaan lukien CJK-kykyiset kirjasintyypit, jotka Windows toimittaa kiinan- ja koreankielistä renderöintiä varten, ellei viejän UnicodeFontFile-ominaisuus jo osoita tiettyyn tiedostoon, ja mikä tahansa fontti, johon se päätyy, upotetaan kokonaisena ennen kuin osajoukon muodostus koskaan ajetaan. Tuo upotusvaatimus on PDF:lle ominainen: HotXLS:n RTF- ja HTML-vientipolut pitävät Unicode-tekstin ehjänä koodaamalla koodipisteet tavuvirtaan fonttiohjelman toimittamisen sijaan, minkä vuoksi tämän artikkelin käsittelemällä kokoongelmalla ei ole vastinetta näissä kahdessa muodossa

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;

Mikä on fontsub.dll, ja miksei kirjoittaa osajoukonmuodostajaa alusta?

fontsub.dll on pieni Windows-järjestelmäkirjasto, toimitettu Windows XP:stä lähtien, joka paljastaa yhden tähän liittyvän funktion: CreateFontPackage. Anna sille lähde-TrueType-fontin tavut ja lista säilytettäviä Unicode-koodipisteitä, ja se antaa takaisin minimaalisen fontin, joka silti täyttää jokaisen fonttimuodon rajoitteen: glyfi-indeksit uudelleennumeroitu, glyf ja loca rakennettu uudelleen vain säilytettyjen ääriviivojen ympärille, hmtx ja cmap kirjoitettu uudelleen täsmäämään. HotXLS ilmoittaa funktio-osoitintyypin suoraan tuota sopimusta vasten

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;

CreateFontPackagen tehtävän kirjoittaminen käsin sen kutsumisen sijaan tarkoittaisi oikean TrueType-osajoukonmuodostajan toteuttamista: yhdistelmäglyfien läpikäymistä, jotta jokainen komponenttiglyfi, johon säilytetty glyfi viittaa, otettaisiin mukaan, loca-siirtymien uudelleenrakentamista sen jälkeen, kun ääriviivoja on pudotettu, fontin OS/2-taulukon upotuslupabittien kunnioittamista ja kaiken tämän oikein saamista minkä tahansa erikoisen fontin yli, jonka asiakkaan koneelle sattuu olemaan asennettuna. Microsoft on jo ratkaissut tuon ongelman ja toimittaa ratkaisun osana itse Windowsia, joten järjestelmä-DLL:n kutsuminen, jota se ylläpitää, testaa omaa fontinrenderöintipinoaan vasten ja jakaa ilmaiseksi jokaiselle koneelle, maksaa HotXLS:lle dynaamisen latauksen ja funktio-osoittimen; saman logiikan uudelleentoteuttaminen tarkoittaisi jäsentimen omistamista binäärimuodolle, jolla on vuosikymmenten reunatapaukset, ominaisuudelle, joka on merkityksellinen vain, kun fontti sattuu olemaan suuri

Säilytyslistan rakentaminen todella renderöidyistä glyfeistä

HotXLS rakentaa osajoukon säilytyslistan kartasta, jota se jo ylläpiti eri syystä, joten kirjanpito ei maksa mitään ylimääräistä. Aina kun sivunrenderöintikoodi piirtää merkin, joka tarvitsee upotetun Unicode-fontin, se hakee kyseisen merkin glyfi-indeksin ja kirjaa parin kenttään FUnicodeGlyphMap, glyfi-koodipiste-taulukkoon, joka myös ohjaa PDF:n ToUnicode CMapia, jotta kopiointi ja liittäminen valmiista asiakirjasta palauttaa alkuperäisen tekstin raakojen glyfi-ID:iden sijaan. Siihen mennessä, kun sivun sisältövirrat on valmiiksi rakennettu, tuo kartta jo listaa täsmälleen sen joukon Unicode-koodipisteitä, joita asiakirja käytti, ei enempää eikä vähempää

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;

Viimeistelyhetkellä HotXLS käy läpi saman kartan toisen kerran rakentaakseen säilytyslistan, jota CreateFontPackage odottaa, tavallisen taulukon säilytettävistä Unicode-koodipisteistä 16-bittisessä muodossa, jota API:n säilytyslista-argumentti vaatii. Koska tuo argumentti on 16-bittisten sanojen taulukko, se osoittaa Basic Multilingual Plane -tason puhtaasti, mikä kattaa tavallisen CJK-, kyrillisen, kreikkalaisen ja arabialaisen tekstin ilman komplikaatioita; työarkki, joka nojaa lisätasojen merkkeihin, tiettyihin emojeihin tai harvinaisiin historiallisiin kirjoitusjärjestelmiin, on sen ulkopuolella, mitä yksi säilytyslistan merkintä voi nimetä suoraan, mikä on tunnettava raja eikä vika, koska valtaosa Unicode-painotteisista liiketoimintalaskentataulukoista ei koskaan mene lähellekään tuota tasoa ylipäätään

Mitä tapahtuu, kun fontsub.dll puuttuu?

HotXLS ei koskaan oleta fontsub.dll:n olevan läsnä, eikä PDF-vienti koskaan epäonnistu sen puuttumisen vuoksi. Kirjasto ladataan dynaamisesti sillä hetkellä, kun osajoukkoa tarvitaan, SafeLoadLibrary- ja GetProcAddress-funktioilla staattisen tuonnin sijaan, nimenomaan koska fontsub.dll ei ole dokumentoitu, taatusti läsnä oleva julkinen API samalla tavalla kuin kernel32.dll: se on paketoitua fontinupotustyökalua, eikä mikään Microsoftin sopimuksessa lupaa sen säilyvän jokaisessa SKU:ssa, jokaisessa huoltohaarassa tai jokaisessa yhteensopivuuskerroksessa, joka yrittää emuloida Windowsia

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;

Jokainen epäonnistumispolku taittuu takaisin samaan lopputulokseen. Puuttuva DLL, puuttuva vienti, nollasta poikkeava paluukoodi tai fontti, jonka OS/2-taulukko kieltää osajoukon muodostuksen upotuslupabiteillään, HotXLS vain pitää koko fontin, jonka se oli jo upottanut, ja jatkaa eteenpäin. Mikään ei nosta poikkeusta, mikään ei keskeytä vientiä, eikä kutsuvan koodin koskaan tarvitse kääriä fonttioptimointia omaan poikkeuskäsittelyynsä; viety PDF on pätevä joka tapauksessa, ja ainoa muuttuja on, päätyykö se pieneksi vai jonkin verran suuremmaksi

Kuinka paljon pienemmäksi PDF todella tulee?

HotXLS:n TrueType-fonttien osajoukkojen muodostus tyypillisesti kutistaa Unicode-painotteisen työarkin viedyn PDF:n johonkin kahdenkymmenesosan ja kahdeksasosan väliin sen osajoukkoa muodostamattomasta koosta, 8–20-kertaisen pienennyksen, jonka mittakaava seuraa sitä, kuinka suurta osaa täydestä fontista tietty asiakirja todella koskettaa: ostotilaus, joka on rakennettu muutaman sadan erillisen kiinalaisen merkin ympärille, säilyttää vain nuo muutama sata glyfiä kymmenistä tuhansista, joita CJK-kirjasintyyppi toimittaa, kun taas arkki, joka kattaa laajemman merkkien sekoituksen, säilyttää suhteessa enemmän. HotXLS kerrostaa lisäksi Flate-pakkauskierroksen osajoukon fonttitavujen päälle ennen niiden kirjoittamista PDF:n /FontFile2-virtaan, samaa pakkausta, jonka asiakirjan muut sisältövirrat jo käyvät läpi, eikä mikään tästä pyydä kutsuvalta koodilta mitään ylimääräistä: työarkki, joka ei koskaan poistu WinAnsista, ei koskaan kosketa tätä polkua ja jatkaa viemistä tavallisen Helvetican kautta, kun taas työarkki, joka todella laukaisee Unicode-fonttipolun, saa osajoukon muodostuksen automaattisesti, ilman asetettavaa ominaisuutta ja ilman erillistä kutsua tehtäväksi, ja ainoa mukana oleva ominaisuus, UnicodeFontFile, valitsee vain, mikä fontti upotetaan ja josta muodostetaan osajoukko, ei sitä, tapahtuuko osajoukon muodostus

Fonttien osajoukon muodostus on yksi yksityiskohta HotXLS Delphi Excel -komponentin laajemmassa PDF-vientipinnassa, yhdessä sivutuksen, työarkin tulostusmetatiedon sekä CSV-, HTML- ja RTF-vientipolkujen kanssa, joiden kanssa se toimitetaan