Teknik Makale

Delphi'de fontsub.dll ile TrueType Yazı Tipi Alt Kümeleme

Delphi ve C++Builder Excel bileşeni olan HotXLS, gömülü PDF yazı tipi boyutunu TrueType yazı tipi alt kümeleme yoluyla azaltır: PDF dışa aktarım anında, tüm yazı tipi dosyasını göndermek yerine, gömülü bir TrueType yazı tipini yalnızca bir çalışma sayfasının gerçekte kullandığı Unicode kod noktaları etrafında yeniden inşa etmek için Windows sistem kütüphanesi fontsub.dll'nin CreateFontPackage fonksiyonunu çağırır. İki yüz satır Çince ürün adı içeren bir rapor, yalnızca birkaç yüz farklı Han karakterine ihtiyaç duyabilir, oysa Windows'un gönderdiği CJK yazı tipleri rutin olarak her biri 5 ila 20 MB çalışır. Bir tanesini tamamen gömün ve yalnızca yazı tipi, PDF'deki diğer her nesnenin toplamından daha ağır basabilir

fontsub.dll, çoğu Delphi geliştiricisinin hiç duymadığı bir kütüphanedir ve bunun bir nedeni vardır: Microsoft onu manşet bir Win32 API'si yerine küçük, seyrek belgelenmiş bir yardımcı program DLL'i olarak gönderir. HotXLS onu sert bir bağımlılık yerine isteğe bağlı bir yetenek olarak ele alır; bu yüzden dışa aktarıcının onu nasıl yüklediği, çağırdığı ve eksik olduğunda nasıl geri döndüğü, yazı tipi biçimleri kadar savunmacı Windows programlama hakkında da bir şeyler söyler ve hikayenin her iki yarısı da gezmeye değer

Unicode metni neden bir HotXLS PDF dışa aktarımını şişiriyor?

HotXLS'in PDF dışa aktarıcısı, yalnızca çalışma sayfası metni WinAnsi'nin dışına düştüğünde gömülü bir TrueType yazı tipine başvurur ve geri kalan zamanda yerleşik Helvetica ailesinde kalır; bu, çalışma sayfasından PDF'e dışa aktarım anlatımının derinlemesine ele aldığı varsayılan yoldur. WinAnsi, Batı Avrupa metnini yeterince iyi kapsar; bu yüzden pek çok çalışma kitabı hiçbir zaman bir yazı tipi gömme tetiklemez: PDF yalnızca Helvetica'ya adıyla referans verir ve okuyucu onu yerel olarak sağlar; bu yüzden dosya küçük kalır. Bir hücre WinAnsi'nin temsil edemeyeceği bir şey tuttuğu anda — bir Çince ürün adı, bir Korece not, bir yorumdaki başıboş bir simge — dışa aktarıcının gerçek bir yazı tipi programını gömmesi gerekir, çünkü bir PDF okuyucunun standart 14 yazı tipinin dışındaki karakterler için yedek bir glif kaynağı yoktur

HotXLS, dışa aktarıcının UnicodeFontFile özelliği zaten belirli bir dosyaya işaret etmedikçe, Windows Yazı Tipleri klasörünü kısa bir yüklü aday listesi için tarayarak — Çince ve Korece oluşturma için Windows'un gönderdiği CJK özellikli yazı tipleri dahil — o yazı tipini otomatik olarak bulur ve hangi yazı tipine inerse insin, alt kümeleme hiç çalışmadan önce tamamen gömülür. Bu gömme gereksinimi PDF'e özgüdür: HotXLS'in RTF ve HTML dışa aktarım yolları, bir yazı tipi programı göndermek yerine kod noktalarını byte akışına kaçırarak Unicode metnini bozulmadan tutar; bu makalenin ele aldığı boyut sorununun bu iki biçimde eşdeğeri olmamasının nedeni budur

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;

fontsub.dll nedir ve neden sıfırdan bir alt kümeleyici yazmıyoruz?

fontsub.dll, Windows XP'den beri gönderilen ve burada ilgili tek bir fonksiyonu ortaya çıkaran küçük bir Windows sistem kütüphanesidir: CreateFontPackage. Ona bir kaynak TrueType yazı tipinin byte'larını ve tutulacak bir Unicode kod noktaları listesini verin, o da her yazı tipi biçimi kısıtlamasını hâlâ karşılayan minimal bir yazı tipini geri verir: glif indeksleri yeniden numaralandırılır, glyf ve loca yalnızca tutulan ana hatlar etrafında yeniden inşa edilir, hmtx ve cmap eşleşecek şekilde yeniden yazılır. HotXLS fonksiyon işaretçisi türünü doğrudan bu sözleşmeye karşı bildirir

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;

CreateFontPackage'ın işini onu çağırmak yerine elle yazmak, doğru bir TrueType alt kümeleyici uygulamak anlamına gelirdi: tutulan bir glifin referans verdiği her bileşen glifi çekmek için bileşik glifleri gezmek, ana hatlar bırakıldıktan sonra loca ofsetlerini yeniden inşa etmek, bir yazı tipinin OS/2 tablosundaki gömme izin bitlerine saygı göstermek ve bir müşterinin makinesinde kurulu olabilecek her türlü tuhaf yazı tipinde bunların hepsini doğru yapmak. Microsoft bu sorunu zaten çözdü ve çözümü Windows'un kendisinin bir parçası olarak gönderiyor; bu yüzden bakımını yaptığı, kendi yazı tipi oluşturma yığınına karşı test ettiği ve her makineye ücretsiz dağıttığı bir sistem DLL'ini çağırmak HotXLS'e dinamik bir yükleme ve bir fonksiyon işaretçisi maliyeti çıkarır; aynı mantığı yeniden uygulamak, yalnızca bir yazı tipi büyük olduğunda önem taşıyan bir özellik için, on yıllardır süren uç durumları olan ikili bir biçim için bir ayrıştırıcıya sahip olmak anlamına gelirdi

Tutma listesini gerçekte oluşturulan gliflerden inşa etmek

HotXLS, alt kümeleme tutma listesini zaten başka bir nedenle sürdürdüğü bir haritadan inşa eder; bu yüzden muhasebe ekstra hiçbir şeye mal olmaz. Sayfa oluşturma kodu, gömülü Unicode yazı tipine ihtiyaç duyan bir karakteri her çizdiğinde, o karakterin glif indeksini arar ve eşleşmeyi, bitmiş belgeden kopyala-yapıştırın ham glif kimlikleri yerine orijinal metni döndürmesi için PDF ToUnicode CMap'ini de yönlendiren bir glif-kod noktası tablosu olan FUnicodeGlyphMap'e kaydeder. Sayfa içerik akışları bittiğinde, o harita zaten belgenin kullandığı Unicode kod noktaları kümesini, ne fazla ne eksik, tam olarak listeler

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;

Sonlandırma anında, HotXLS aynı haritayı ikinci kez gezerek CreateFontPackage'ın beklediği tutma listesini inşa eder — API'nin tutma listesi argümanının gerektirdiği 16 bit biçimde tutulacak Unicode kod noktalarının sade bir dizisi. Bu argüman 16 bit kelimelerden oluşan bir dizi olduğu için, Temel Çok Dilli Düzlem'i temiz bir şekilde ele alır; bu da komplikasyon olmadan sıradan CJK, Kiril, Yunanca ve Arapça metni kapsar; ek düzlem karakterlerine, belirli emojilere veya nadir tarihi yazılara dayanan bir çalışma sayfası, tek bir tutma listesi girdisinin doğrudan adlandırabileceğinin dışında oturur; bu bir kusur değil bilinmeye değer bir sınırdır, çünkü Unicode ağırlıklı iş elektronik tablolarının büyük çoğunluğu ilk etapta o düzlemin yakınına hiç gitmez

fontsub.dll eksik olduğunda ne olur?

HotXLS fontsub.dll'in var olduğunu asla varsaymaz ve PDF dışa aktarımı, var olmadığı için asla başarısız olmaz. Kütüphane, tam olarak fontsub.dll'in kernel32.dll'in olduğu gibi belgelenmiş, varlığı garanti edilmiş bir genel API olmadığı için — paketlenmiş yazı tipi gömme aracıdır ve Microsoft'un sözleşmesinde hiçbir şey onun her SKU'da, her servis dalında veya Windows'u taklit etmeye çalışan her uyumluluk katmanında hayatta kaldığını vaat etmez — statik bir import yerine SafeLoadLibrary ve GetProcAddress ile bir alt küme gerektiği anda dinamik olarak yüklenir

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;

Her başarısızlık yolu aynı sonuca katlanır. Eksik bir DLL, eksik bir dışa aktarım, sıfır olmayan bir dönüş kodu veya OS/2 tablosu gömme izin bitleri aracılığıyla alt kümelemeyi yasaklayan bir yazı tipi — HotXLS yalnızca zaten gömdüğü tam yazı tipini tutar ve devam eder. Hiçbir şey fırlatmaz, hiçbir şey dışa aktarımı iptal etmez ve çağıran kodun bir yazı tipi optimizasyonunu kendi istisna işlemesine sarmasına asla gerek kalmaz; dışa aktarılan PDF her iki durumda da geçerlidir ve tek değişken sonucun küçük mü yoksa biraz daha büyük mü çıktığıdır

PDF gerçekte ne kadar küçülür?

HotXLS'in TrueType yazı tipi alt kümelemesi, tipik olarak Unicode ağırlıklı bir çalışma sayfasının dışa aktarılan PDF'ini alt kümelenmemiş boyutunun yirmide biri ile sekizde biri arasına küçültür; ölçeği belirli bir belgenin tam bir yazı tipinin ne kadarına gerçekte dokunduğunu izleyen 8 ila 20 kat bir azalma: birkaç yüz farklı Çince karakter etrafında inşa edilmiş bir satın alma siparişi, bir CJK yazı tipinin gönderdiği on binlercesinden yalnızca bu birkaç yüz glifi tutar, oysa daha geniş bir karakter karışımına yayılan bir sayfa orantılı olarak daha fazlasını tutar. HotXLS, alt küme yazı tipi byte'larını PDF'in /FontFile2 akışına yazmadan önce üzerlerine ek bir Flate sıkıştırma geçişi katmanlar — belgenin geri kalan içerik akışlarının zaten geçtiği aynı sıkıştırma — ve hiçbiri çağıran koddan ekstra bir şey istemez: hiç WinAnsi'nin dışına çıkmayan bir çalışma sayfası bu yola hiç dokunmaz ve sade Helvetica üzerinden dışa aktarmaya devam eder, oysa Unicode yazı tipi yolunu gerçekten tetikleyen bir çalışma sayfası, ayarlanacak bir özellik veya yapılacak ayrı bir çağrı olmadan otomatik olarak alt kümelemeyi alır; işin içindeki tek özellik olan UnicodeFontFile, yalnızca hangi yazı tipinin gömüleceğini ve alt kümeleneceğini seçer, alt kümelemenin gerçekleşip gerçekleşmediğini değil

Yazı tipi alt kümeleme, birlikte gönderdiği sayfalama, çalışma sayfası yazdırma meta verisi ve CSV, HTML ve RTF dışa aktarım yollarının yanı sıra, HotXLS Delphi Excel Bileşeni'nin daha geniş PDF dışa aktarım yüzeyinin içindeki bir detaydır