Tehnični članak

Podmnožice pisav TrueType prek fontsub.dll v Delphiju

HotXLS, Excelova komponenta za Delphi in C++Builder, zmanjša velikost vdelane pisave PDF s podmnoženjem pisave TrueType: ob izvozu PDF pokliče funkcijo CreateFontPackage sistemske knjižnice Windows fontsub.dll, da vdelano pisavo TrueType znova zgradi samo okoli kodnih točk Unicode, ki jih je preglednica dejansko uporabila, namesto da bi priložil celotno datoteko pisave. Poročilo z dvesto vrsticami kitajskih imen izdelkov morda potrebuje le nekaj sto različnih hanovskih znakov, vendar pisave CJK, ki jih dobavlja Windows, pogosto merijo od 5 do 20 MB vsaka. Če vdelate eno celotno, lahko sama pisava preseže skupno velikost vseh drugih objektov v PDF

fontsub.dll ni knjižnica, za katero bi slišala večina razvijalcev Delphi, in za to obstaja razlog: Microsoft jo dobavlja kot majhen, skopo dokumentiran pomožni DLL namesto kot izpostavljen Win32 API. HotXLS jo obravnava kot neobvezno zmogljivost, ne kot trdo odvisnost, zato način, kako jo izvoznik naloži, pokliče in se vrne nazaj, ko manjka, pove toliko o obrambnem programiranju sistema Windows kot o oblikah pisav, oba dela zgodbe pa je vredno pregledati

Zakaj besedilo Unicode poveča izvoz PDF iz HotXLS?

Izvoznik PDF v HotXLS poseže po vdelani pisavi TrueType samo, ko besedilo preglednice presega WinAnsi, sicer pa ostane pri vgrajeni družini Helvetica, kar je privzeta pot, podrobno opisana v vodniku za izvoz preglednice v PDF. WinAnsi dovolj dobro pokriva zahodnoevropsko besedilo, zato številni delovni zvezki sploh ne sprožijo vdelave pisave: PDF se samo sklicuje na Helvetica po imenu, bralnik pa jo zagotovi lokalno, zato datoteka ostane majhna. Ko celica vsebuje nekaj, česar WinAnsi ne more predstaviti, kitajsko ime izdelka, korejsko opombo ali osamljen simbol v komentarju, mora izvoznik vdelati dejanski program pisave, ker bralnik PDF nima vira nadomestnih glifov za znake zunaj standardnih 14 pisav

HotXLS pisavo samodejno poišče tako, da pregleda mapo Windows Fonts za kratek seznam nameščenih kandidatov, vključno s pisavami, zmožnimi za CJK, ki jih Windows dobavlja za izris kitajščine in korejščine, razen če lastnost UnicodeFontFile izvoznika že kaže na določeno datoteko, izbrana pisava pa se v celoti vdelana, preden se sploh začne podmnoženje. Ta zahteva po vdelavi je posebna za PDF: poti izvoza RTF in HTML ohranijo besedilo Unicode nedotaknjeno tako, da kodne točke ubežijo v bajtni tok, namesto da bi pošiljale program pisave, zato težava z velikostjo, ki jo obravnava ta članek, v teh dveh oblikah nima ustreznice

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;

Kaj je fontsub.dll in zakaj podmnoževalnika ne napišemo sami?

fontsub.dll je majhna sistemska knjižnica Windows, dobavljena od sistema Windows XP naprej, ki izpostavlja eno tukaj pomembno funkcijo: CreateFontPackage. Če ji podate bajte izvorne pisave TrueType in seznam kodnih točk Unicode, ki jih je treba obdržati, vrne minimalno pisavo, ki še vedno izpolnjuje vse omejitve oblike pisave: indeksi glifov so preštevilčeni, glyf in loca sta znova zgrajena samo okoli ohranjenih obrisov, hmtx in cmap pa sta prepisana tako, da se ujemata. HotXLS vrsto kazalca funkcije deklarira neposredno glede na to pogodbo

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;

Če bi delo funkcije CreateFontPackage napisali sami, namesto da jo pokličete, bi morali izdelati pravilen podmnoževalnik TrueType: prehoditi sestavljene glife in vključiti vsak sestavni glif, na katerega se ohranjeni glif sklicuje, znova zgraditi odmike loca, potem ko so obrisi odstranjeni, upoštevati bite dovoljenj za vdelavo v tabeli OS/2 pisave in vse to pravilno izvesti za poljubne nenavadne pisave, ki jih ima nameščen uporabnikov računalnik. Microsoft je to težavo že rešil in rešitev dobavlja kot del sistema Windows, zato klic sistemskega DLL-ja, ki ga vzdržuje in preizkuša glede na lastni sklad za izris pisav ter ga brezplačno razdeli vsakemu računalniku, HotXLS stane le dinamično nalaganje in kazalec funkcije; ponovna izvedba iste logike bi pomenila lastništvo razčlenjevalnika binarne oblike z desetletji robnih primerov za funkcijo, ki je pomembna samo, ko je pisava velika

Izdelava seznama za ohranitev iz dejansko izrisanih glifov

HotXLS seznam za ohranitev izdela iz preslikave, ki jo je že vzdrževal iz drugega razloga, zato obračun ne stane nič dodatnega. Vsakič, ko koda za izris strani nariše znak, ki potrebuje vdelano pisavo Unicode, poišče indeks glifa tega znaka in zabeleži povezavo v FUnicodeGlyphMap, tabelo glifov in kodnih točk, ki poganja tudi PDF CMap ToUnicode, zato kopiranje in lepljenje iz končnega dokumenta vrne izvirno besedilo namesto surovih ID-jev glifov. Ko so tokovi vsebine strani dokončani, ta preslikava že vsebuje natanko množico kodnih točk Unicode, ki jih je dokument uporabil, nič več in nič manj

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;

Ob zaključevanju HotXLS še enkrat prehodi isto preslikavo in izdela seznam za ohranitev, ki ga pričakuje CreateFontPackage, torej navadno polje kodnih točk Unicode, ki jih je treba obdržati, v 16-bitni obliki, ki jo zahteva argument seznama API-ja. Ker je ta argument polje 16-bitnih besed, čisto naslavlja osnovno večjezično ravnino, ki brez zapletov pokriva običajno besedilo CJK, cirilico, grščino in arabščino; preglednica, ki uporablja znake zunaj osnovne ravnine, na primer določene emodžije ali redke zgodovinske pisave, presega mejo, ki jo lahko neposredno poimenuje en vnos seznama za ohranitev, kar je meja, ki jo je dobro poznati, ne pa napaka, saj velika večina poslovnih preglednic z veliko Unicode besedila te ravnine sploh ne doseže

Kaj se zgodi, ko fontsub.dll manjka?

HotXLS nikoli ne predpostavi, da je fontsub.dll prisoten, in izvoz PDF nikoli ne spodleti zato, ker ga ni. Knjižnica se naloži dinamično šele, ko je potrebna podmnožica, z uporabo SafeLoadLibrary in GetProcAddress namesto statičnega uvoza, prav zato, ker fontsub.dll ni dokumentiran javni API, za katerega bi bila zagotovljena prisotnost, kot je kernel32.dll: gre za orodje za vdelavo pisav, pogodba Microsofta pa ne obljublja, da bo preživelo na vsaki izdaji sistema, v vsaki veji servisiranja ali v vsaki združljivostni plasti, ki poskuša posnemati 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;

Vsaka pot napake se vrne k istemu izidu. Če DLL manjka, če manjka izvožena funkcija, če je vrnjena neničelna koda ali če tabela OS/2 pisave prek bitov dovoljenj za vdelavo prepoveduje podmnoženje, HotXLS preprosto obdrži celotno pisavo, ki jo je že vdelal, in nadaljuje. Ni izjem, izvoz se ne prekine in klicna koda ne potrebuje lastne obravnave izjem okoli optimizacije pisave; izvoženi PDF je v obeh primerih veljaven, spremenljivka je samo njegova velikost

Koliko manjši je PDF v resnici?

Podmnoženje pisave TrueType v HotXLS običajno skrči izvoženi PDF preglednice z veliko Unicode besedila na nekje med dvajsetino in osmino njegove nepodmnožene velikosti, torej za 8- do 20-krat, pri čemer je obseg odvisen od tega, kolikšen del celotne pisave dokument dejansko uporablja: naročilnica, zgrajena okoli nekaj sto različnih kitajskih znakov, obdrži samo teh nekaj sto glifov od več deset tisoč, ki jih dobavlja pisava CJK, medtem ko list z obsežnejšo mešanico znakov obdrži sorazmerno več. HotXLS na bajte podmnožene pisave pred zapisom v tok PDF /FontFile2 uporabi še stiskanje Flate, enako stiskanje, skozi katero že gredo drugi tokovi vsebine dokumenta, pri čemer klicna koda ne potrebuje ničesar dodatnega: preglednica, ki nikoli ne zapusti WinAnsi, se tej poti ne približa in se še naprej izvaža prek navadne Helvetica, preglednica, ki pot Unicode pisave sproži, pa samodejno dobi podmnoženje brez nastavljanja lastnosti in brez ločenega klica, pri čemer edina povezana lastnost, UnicodeFontFile, izbere samo pisavo, ki bo vdelana in podmnožena, ne pa tudi tega, ali se podmnoženje izvede

Podmnoženje pisav je ena podrobnost širše površine izvoza PDF komponente HotXLS Delphi Excel Component, skupaj s prelomi strani, metapodatki za tiskanje delovnih listov ter potmi izvoza CSV, HTML in RTF, ki jih dobavlja