Techninis straipsnis

TrueType šriftų poaibio kūrimas naudojant fontsub.dll Delphi

HotXLS, Delphi ir C++Builder Excel komponentas, sumažina įterptųjų PDF šriftų dydį naudodamas TrueType šriftų poaibio kūrimą: eksportuojant PDF jis iškviečia Windows sistemos bibliotekos fontsub.dll funkciją CreateFontPackage, kad atkurtų įterptąjį TrueType šriftą naudodamas tik tas Unicode kodo vietas, kurias faktiškai naudojo darbalapis, užuot įtraukęs visą šrifto failą. Ataskaitai su dviem šimtais eilučių kiniškų produktų pavadinimų gali reikėti vos kelių šimtų skirtingų Han rašmenų, tačiau Windows tiekiami CJK šriftai paprastai užima nuo 5 iki 20 MB. Įterpus visą šriftą, vien jis gali būti didesnis už visus kitus PDF objektus kartu

fontsub.dll nėra biblioteka, apie kurią būtų girdėję dauguma Delphi kūrėjų, ir tam yra priežastis: Microsoft ją tiekia kaip nedidelę, menkai dokumentuotą pagalbinę DLL, o ne kaip pagrindinę Win32 API dalį. HotXLS ją traktuoja kaip pasirenkamą galimybę, o ne griežtą priklausomybę, todėl tai, kaip eksportuotojas ją įkelia, iškviečia ir veikia, kai jos nėra, pasako tiek apie atsargų Windows programavimą, tiek apie šriftų formatus, ir abi šios istorijos dalis verta nuosekliai apžvelgti

Kodėl Unicode tekstas taip išpučia HotXLS PDF eksportą

HotXLS PDF eksportuotojas įterptojo TrueType šrifto imasi tik tada, kai darbalapio tekstas nepatenka į WinAnsi, o kitu metu lieka su integruota Helvetica šeima – tai numatytasis kelias, išsamiai aprašytas darbalapio eksportavimo į PDF apžvalgoje. WinAnsi pakankamai gerai apima Vakarų Europos tekstą, todėl daugybė darbaknygių apskritai nesukelia šrifto įterpimo: PDF tiesiog nurodo Helvetica pavadinimu, o skaitytuvas ją pateikia vietoje, tad failas išlieka mažas. Kai tik langelyje atsiranda tai, ko WinAnsi negali pavaizduoti – kiniškas produkto pavadinimas, korėjietiška pastaba ar atsitiktinis komentaro simbolis – eksportuotojas turi įterpti tikrą šrifto programą, nes PDF skaitytuvas neturi atsarginio glifo šaltinio simboliams, nepatenkantiems į standartinius 14 šriftų

HotXLS automatiškai nustato šį šriftą, Windows Fonts aplanke ieškodama trumpo įdiegtų kandidatų sąrašo, įskaitant CJK tinkamus šriftus, kuriuos Windows tiekia kinų ir korėjiečių atvaizdavimui, nebent eksportuotojo savybė UnicodeFontFile jau nurodo konkretų failą, o pasirinktas šriftas prieš pradedant poaibio kūrimą įterpiamas visas. Šis įterpimo reikalavimas būdingas PDF: HotXLS RTF ir HTML eksportavimo keliai išlaiko Unicode tekstą nepakitusį, išvengdami kodo vietas baitų sraute, o ne pateikdami šrifto programą, todėl šiame straipsnyje aptariama dydžio problema tiems dviem formatams neturi atitikmens

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;

Kas yra fontsub.dll ir kodėl neparašius poaibio kūrimo priemonės nuo nulio

fontsub.dll yra nedidelė Windows sistemos biblioteka, tiekiama nuo Windows XP, kuri atskleidžia vieną čia aktualią funkciją: CreateFontPackage. Pateikite jai šaltinio TrueType šrifto baitus ir saugomų Unicode kodo vietų sąrašą, o ji grąžins minimalų šriftą, vis dar atitinkantį visus šrifto formato apribojimus: glifų indeksai pernumeruojami, glyf ir loca atkuriami tik pagal išlaikytus kontūrus, o hmtx ir cmap perrašomi taip, kad sutaptų. HotXLS funkcijos rodyklės tipą deklaruoja tiesiogiai pagal šią sutartį

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;

Ranka įgyvendinti CreateFontPackage darbą, užuot jį iškvietus, reikštų sukurti teisingą TrueType poaibio kūrimo priemonę: pereiti sudėtinius glifus ir įtraukti kiekvieną komponento glifą, kurį nurodytas išlaikytas glifas naudoja, iš naujo sukurti loca poslinkius pašalinus kontūrus, paisyti šrifto OS/2 lentelėje esančių įterpimo leidimo bitų ir visa tai tinkamai atlikti su bet kokiais neįprastais šriftais, įdiegtais kliento kompiuteryje. Microsoft šią problemą jau išsprendė ir pateikia sprendimą kartu su pačia Windows, todėl iškvietus sistemos DLL, kurią ji prižiūri, testuoja su savo šriftų atvaizdavimo rinkiniu ir nemokamai platina kiekviename kompiuteryje, HotXLS tenka dinaminis įkėlimas ir funkcijos rodyklė; iš naujo įgyvendinus tą pačią logiką tektų prižiūrėti dešimtmečius kauptų kraštinių atvejų turinčio dvejetainio formato analizatorių dėl funkcijos, svarbios tik tada, kai šriftas būna didelis

Išlaikytinų glifų sąrašo kūrimas pagal faktiškai atvaizduotus glifus

HotXLS poaibio kūrimo išlaikytinų glifų sąrašą sudaro iš žemėlapio, kurį jau prižiūrėjo dėl kitos priežasties, todėl apskaita nieko papildomai nekainuoja. Kaskart, kai puslapio atvaizdavimo kodas nupiešia simbolį, kuriam reikia įterptojo Unicode šrifto, jis suranda to simbolio glifo indeksą ir įrašo porą į FUnicodeGlyphMap – glifo ir kodo vietos lentelę, kuri taip pat valdo PDF ToUnicode CMap, kad kopijuojant ir įklijuojant iš baigto dokumento būtų grąžintas pradinis tekstas, o ne neapdoroti glifų ID. Baigus puslapio turinio srautus, šiame žemėlapyje jau tiksliai pateiktas dokumento naudotų Unicode kodo vietų rinkinys – nei daugiau, nei mažiau

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;

Užbaigimo metu HotXLS antrą kartą pereina tą patį žemėlapį ir sukuria CreateFontPackage reikalingą išlaikytinų glifų sąrašą – paprastą išlaikytinų Unicode kodo vietų masyvą API reikalaujamu 16 bitų formatu. Kadangi tas argumentas yra 16 bitų žodžių masyvas, jis tinkamai apima Pagrindinę daugiakalbę plokštumą, kurioje be papildomų sunkumų telpa įprastas CJK, kirilicos, graikų ir arabų kalbų tekstas; darbalapis, kuriame naudojami papildomos plokštumos simboliai, tam tikri jaustukai ar reti istoriniai raštai, nepatenka į tai, ką galima tiesiogiai įvardyti vienu išlaikytinų glifų sąrašo įrašu, ir tai yra riba, kurią verta žinoti, o ne trūkumas, nes didžioji dauguma daug Unicode naudojančių verslo skaičiuoklių tos plokštumos apskritai nepasiekia

Kas nutinka, kai nėra fontsub.dll

HotXLS niekada nedaro prielaidos, kad fontsub.dll yra, ir PDF eksportas niekada nesugenda vien dėl to, kad jos nėra. Biblioteka dinamiškai įkeliama tuo metu, kai prireikia kurti poaibį, naudojant SafeLoadLibrary ir GetProcAddress, o ne statinį importą, būtent todėl, kad fontsub.dll nėra dokumentuota ir garantuotai prieinama viešoji API, kaip kernel32.dll: tai yra kartu su Windows tiekiamas šriftų įterpimo įrankis, o Microsoft sutartyje nepažadėta, kad jis išliks kiekviename SKU, kiekvienoje aptarnavimo šakoje ar kiekviename suderinamumo sluoksnyje, bandančiame emuliuoti 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;

Kiekvienas nesėkmės kelias grįžta į tą pačią baigtį. Jei trūksta DLL, eksporto, grąžinamas ne nulinis kodas arba šrifto OS/2 lentelė draudžia kurti poaibį naudodama įterpimo leidimo bitus, HotXLS tiesiog palieka visą jau įterptą šriftą ir tęsia darbą. Nieko neišmetama, eksportas nenutraukiamas, o iškviečiančiam kodui nereikia savo išimčių tvarkymo apgaubti šrifto optimizavimo; eksportuotas PDF bet kuriuo atveju galioja, skiriasi tik tai, ar jis bus mažas, ar šiek tiek didesnis

Kiek mažesnis iš tikrųjų tampa PDF

HotXLS TrueType šriftų poaibio kūrimas paprastai sumažina daug Unicode naudojančio darbalapio eksportuoto PDF dydį iki maždaug dvidešimtosios–aštuntosios neapdoroto šrifto dydžio, taigi sumažina 8–20 kartų; mastą lemia tai, kokią viso šrifto dalį konkretus dokumentas iš tikrųjų naudoja: pirkimo užsakymas, sudarytas iš kelių šimtų skirtingų kiniškų simbolių, pasilieka tik tuos kelis šimtus glifų iš dešimčių tūkstančių, kuriuos turi CJK šriftas, o lapas, apimantis įvairesnį simbolių derinį, proporcingai pasilieka daugiau. HotXLS prieš įrašydama poaibio šrifto baitus į PDF /FontFile2 srautą papildomai taiko Flate glaudinimą, tokį patį, kuris taikomas likusiems dokumento turinio srautams, ir iškviečiančiam kodui nereikia nieko papildomai daryti: darbalapis, niekada neperžengiantis WinAnsi ribų, šio kelio nepasiekia ir toliau eksportuojamas naudojant paprastą Helvetica, o darbalapiui, kuris suaktyvina Unicode šrifto kelią, poaibis sukuriamas automatiškai, nenustatant jokios savybės ir neatliekant atskiro iškvietimo; vienintelė susijusi savybė UnicodeFontFile tik parenka įterpiamą ir į poaibį įtraukiamą šriftą, bet nenustato, ar poaibis bus kuriamas

Šriftų poaibio kūrimas yra tik viena platesnio HotXLS Delphi Excel Component PDF eksportavimo paviršiaus detalių greta puslapių numeravimo, darbalapio spausdinimo metaduomenų ir kartu tiekiamų CSV, HTML bei RTF eksportavimo kelių