Teknik Makale

Yazı Tipi Alt Kümeleme ve HotPDF EndDoc Sonlandırma Sırası

Bir rapor oluşturun, bir TrueType yazı tipi gömün; çıktı denediğiniz her görüntüleyicide doğru şekilde açılır. Karakterler doğrudur, metin seçilebilirdir, dosya geçerlidir. Yanlış olan tek şey boyuttur. Birkaç düzine Latin karakteri kullanan bir belge, 350 KB'lık yazı tipinin tamamını taşır. Çince bir paragraf yazdıran bir belge, ihtiyaç duyması gereken yarım megabaytlık dilim yerine 14 MB'lık bir CJK yazı tipini taşır. Hiçbir istisna oluşturulmadı, hiçbir uyarı günlüğe kaydedilmedi ve dosya doğrulamadan geçti. Bu, dışarıdan bakıldığında yanlış sıralanmış bir sonlandırma adımının nasıl göründüğüdür: hiçbir şey başarısız olmaz ve tek kanıt çok büyük bir sayıdır

Bunu bir ders olarak yazmaya değer, çünkü hatanın biçimi geneldir. Her belge motorunda, nesneleri yazmadan hemen önce değiştiren bir sonlandırma aşaması vardır ve o aşamanın doğruluğu, adımlarının serileştirmeye göre sırasına tümüyle bağlıdır. Tek bir adımı yazmanın yanlış tarafına alırsanız hiçbir şey yapmaz — sessizce

Yazı tipi alt kümelemenin ne yapması gerekir

Bir alt küme yazı tipi, bir belgenin gerçekten kullandığı bir TrueType dosyasının parçasıdır. ISO 32000-1 §9.9, gömülü bir yazı tipi programının yazı tipi tanımlayıcısı tarafından başvurulan bir akışta nasıl yer aldığını açıklar ve bir TrueType programı için bu akış, sıkıştırılmamış bayt sayısını veren bir /Length1 ile birlikte /FontFile2'dir. Alt kümeleme, glyf ve loca tablolarını yeniden yazarak yalnızca belgenin başvurduğu glifleri içermelerini sağlar, glif tanımlayıcılarını yeniden numaralandırır ve yazı tipini bir alt küme olarak işaretlemek için spesifikasyonun gerektirdiği şekilde /BaseFont adının önüne ABCDEF+ gibi altı harfli bir etiket ekler. On veya on beş kilobayta düşürülen Latin yazı tipi, yalın bir PDF ile tek bir başlık uğruna tüm bir yazı tipini gönderen bir PDF arasındaki farktır

Bunun gerçekleştiği nokta önemlidir. Alt kümeleme, diskte zaten bulunan baytlara uyguladığınız bir dönüştürme değildir. Bellekteki nesne grafiğini düzenler: /FontFile2 akış içeriğini küçültür, /Length1 değerini düzeltir ve /BaseFont dizesini yeniden yazar. Serileştirici grafiği dolaşırken ve baytları yayarken tüm bunların yerinde olması gerekir. Düzenlemeler baytlar yazıldıktan sonra gerçekleşirse, hiç kimsenin okumayacağı nesneleri güncellerler

HotPDF: Yazı tipi alt kümeleme, tam bir TrueType yüzünü kullanılan-glif kapanışından geçirir ve bellekte FontFile2, Length1 ve BaseFont nesnelerini güncellemeden önce glyf ile loca'yı yeniden yazar
Alt kümeleme, glyf ve loca'yı bir belgenin gerçekten kullandığı glifler etrafında yeniden kurar; sonra bellekte /FontFile2, /Length1 ve /BaseFont önekini günceller. Bunların tamamı, tek bir bayt yazılmadan önce yerinde olmalıdır

Belirti ve neden hiçbir şeyin şikayet etmediği

Alt kümeleme yazmanın yanlış tarafına düştüğünde belirti, herhangi bir teşhis olmaksızın çıktıda tam yazı tipleridir. Bir Unicode TrueType yazı tipi kaydedin, normal bir belge üretin; gömülü yazı tipi nesnesi kaynak .ttf dosyasıyla aynı uzunlukta çıkar ve /BaseFont adında altı harfli alt küme öneki yoktur. On glif kullanan çalıştırmalarla on bin glif kullanan çalıştırmalar arasında çıktı hiç küçülmez

Herhangi bir hatanın olmaması, bu hata sınıfını pahalı kılan kısımdır. Yanlış zamanda çalışan bir alt kümeleme yordamı yine de çalışır. Birikmiş kod noktası kullanımını gezer, tümüyle doğru bir alt küme oluşturur ve bellekteki nesne grafiğine uygular. İçeride iş bitmiştir ve çağrı temiz döner. Yanlış olan tek şey, düzenlediği nesne grafiğinin artık yazılan şey olmamasıdır, çünkü yazıcı işini çoktan bitirmiştir. Çağıranın gözünden belge sorunsuz üretilip kaydedilmiştir; sessiz bir başarısızlığın verdiği izlenim tam olarak da budur

Alt kümeleme, sonlandırma sırasında nerede durur

HotPDF'te kapatma işlemi EndDoc içinde gerçekleşir. Alt kümeleme adımı, BuildAndApplyUnicodeFontSubset adında dahili bir rutindir. Metin yayma yolunun glifler gösterildikçe doldurduğu bir bit eşleminde tutulan belge başına kullanılan kod noktaları kümesini okur, kullanılan her kod noktasını önbelleğe alınmış kod noktasından glife tablosu aracılığıyla gerçek bir glif tanımlayıcısına eşler ve yazı tipi programını bu kapsam çevresinde yeniden yazar. Bir Unicode TrueType yazı tipi kaydedildiğinde, yayma yolu çizdiği her karakter için kullanılan kod noktaları kümesinde bir bit ayarlar, böylece belge kapandığında motor alt kümenin hangi glifleri tutması gerektiğini tam olarak bilir

Tuzak, o çağrının konumudur. BuildAndApplyUnicodeFontSubset, SaveToStream ya da SaveToFile belgeyi çoktan serileştirdikten sonra çağrılırsa, alt kümeleyicinin /FontFile2 üzerindeki düzenlemeleri, düzelttiği /Length1 ve altı harfli /BaseFont öneki, baytlara çoktan dönüştürülmüş bir nesne grafiğine göre hesaplanır. Doğru sıra önce alt kümeleyiciyi çalıştırır, serileştirme sonra gelir; böylece yazıcı özgün fontu değil alt kümelemiş fontu yazar. Doğru yapmak tek bir çağrının konumu meselesidir, fazlası değildir

HotPDF timeline comparing an EndDoc order where BuildAndApplyUnicodeFontSubset runs after SaveToStream with the correct order where subsetting runs ahead of serialization
Tek bir çağrıyı yazmanın önüne almak, çağıran kodda hiçbir şeyi değiştirmediği hâlde kaydedilen baytların tam bir font mu gerçek bir alt küme mi tutacağına karar verir. Serileştirmenin yanlış tarafındaki bir adım temiz döner ve hiçbir şey yapmaz
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansSC-Regular.ttf');
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Noto Sans SC', [], 12);
    Pdf.CurrentPage.TextOut(72, 760, 0, '报表标题 Report Heading');
    Pdf.EndDoc;                 // alt kümeleme burada yazmadan önce çalışır
    Pdf.SaveToFile('Report.pdf');
  finally
    Pdf.Free;
  end;
end;

Bunların hiçbiri çağıran kodda görünmez. Bir Unicode TrueType font kaydedildikten sonra alt kümeleme varsayılan olarak açıktır. Fontu kaydeder, belgeyi başlatır, çizim yapar ve bitirirsiniz; alt küme, baytlar bellekten çıkmadan önce kullandığınız gliflerden oluşturulur

Yanlış yerleştirilmiş tek bir adım neden bütün bir hata sınıfıdır

Bunun bir dipnot yerine bir ders niteliğinde olmasının nedeni, EndDoc işlevinin bir dizi kapatma adımı yayması ve bunlardan her birinin yazma işlemine göre konumuna duyarlı olmasıdır. Yazı tipi alt kümeleme bunlardan biridir. PDF/A çıktısı, alt kümede bulunan glif tanımlayıcılarını tam olarak listeleyen bir /CIDSet akışı gerektirir; bu, bir doğrulayıcının gömülü programın yazı tipi tanımlayıcısının iddia ettiği şeyle eşleştiğini onaylayabilmesi için ISO 19005'in getirdiği bir kısıtlamadır; bu akış aynı sonlandırma penceresinde yayılır ve önce alt kümenin oluşturulmuş olmasına bağlıdır. PDF/UA-1, ISO 14289-1 §7.18.3 uyarınca, açıklama taşıyan her sayfanın /S değerine sahip /Tabs bildirmesini gerektirir ve EnsurePDFUATabsOnAnnotatedPages adlı dahili bir rutin aynı aşamada bu anahtarı damgalar. Çıktı amacı kontrolleri de burada çalışır

Alt kümelemeyi devre dışı bırakan aynı sıralama hatası, açıklamalı sayfalarda PDF/UA sekme sırası anahtarının düşmesine de yol açardı, çünkü o adım aynı pencerede durur. veraPDF ve PAC, eksik bir /Tabs /Syi Matterhorn protokolü 21-001 kontrol maddesinin ihlali olarak raporlar. Yani yanlış yerleştirilmiş tek bir çağrı yalnızca dosya boyutunu şişirmez; aynı zamanda erişilebilirlik uyumluluk gereksinimini de sessizce bozar ve yine aynı hatasızlıkla. Sonlandırma aşamasının tehlikesi budur: adımları ortak bir ön koşulu paylaşır ve tek bir sıralama hatası, her çağrı başarı döndürürken birkaçını birden devre dışı bırakabilir

HotPDF'nin EndDoc içinde kuyruğa aldığı dört sonlandırma adımı: yazı tipi alt kümeleme, CIDSet üretimi ve PDF/UA Tabs damgalama dahil; herhangi bir bayt var olmadan önce tümü serileştiriciyi besler
Alt kümeleme, /CIDSet üretimi ve /Tabs /S damgası, serileştirmeden önceki tek bir bitirme yuvasını paylaşır. Yanlış yere konmuş tek bir çağrı, her yordam hâlâ başarı bildirirken hem yazı tipi alt kümesini hem de bir erişilebilirlik gereksinimini düşürebilir

Sessiz bir yayma hatası gerçekte nasıl yakalanır

İstisna fırlatmayan bir başarısızlık, program çalıştırılarak yakalanmaz. Çıktının incelenmesi ve girdinin ne üretmesi gerekiyorsa onunla karşılaştırılmasıyla yakalanır. Font alt kümeleme için denetimler somuttur. Çıktı dosyası boyutunu kaba bir beklentiyle karşılaştırın: birkaç glife dokunan bir belge, tam bir yazı tipi boyutunda olmamalıdır. Gömülü font nesnesini açın ve bayt uzunluğunu okuyun; Latin bir yüz için alt kümelemiş bir /FontFile2, kaynak dosyanın küçük bir kesridir. /BaseFont adını okuyun ve altı harfli önekin orada olduğunu teyit edin; yokluğu, hiç alt küme uygulanmadığının doğrudan sinyalidir

var
  Pdf: THotPDF;
  Output: TMemoryStream;
begin
  Output := TMemoryStream.Create;
  try
    Pdf := THotPDF.Create(nil);
    try
      Pdf.RegisterUnicodeTTF('C:\Fonts\DejaVuSans.ttf');
      Pdf.BeginDoc;
      Pdf.CurrentPage.SetFont('DejaVu Sans', [], 11);
      Pdf.CurrentPage.TextOut(72, 760, 0, 'Subset me');
      Pdf.EndDoc;
      Pdf.SaveToStream(Output);
    finally
      Pdf.Free;
    end;
    // ~700 KB'lik bir yüzden birkaç glif, yüzlerce KB'lik bir akış üretmemeli
    if Output.Size > 100 * 1024 then
      raise Exception.Create('Font subset did not shrink the output');
  finally
    Output.Free;
  end;
end;

PDF/A çıktısı için kontrol daha da keskindir, çünkü bir doğrulayıcı işi sizin yerinize yapar. Uyumluluk seviyesini ayarlayın ve sonucu veraPDF aracılığıyla çalıştırın: eksik bir /CIDSet veya tanımlayıcıyla eşleşmeyen bir alt küme, gözle fark etmeniz için bırakılmak yerine başarısız bir madde olarak bildirilir. Bu sonlandırma çalışmasını yönlendiren uyumluluk anahtarları belgedeki özelliklerdir. PDFACompliance, PDF/A-2 Seviye B için '2B' gibi bir dize alır ve PDFUACompliance, etiketli PDF ve sekme sırası gereksinimlerini etkinleştiren bir boole değeridir

Pdf := THotPDF.Create(nil);
try
  Pdf.PDFACompliance := '2B';     // PDF/A-2 Level B, drives /CIDSet emission
  Pdf.PDFUACompliance := True;    // stamps /Tabs /S on annotated pages
  Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansSC-Regular.ttf');
  Pdf.BeginDoc;
  Pdf.CurrentPage.SetFont('Noto Sans SC', [], 12);
  Pdf.CurrentPage.TextOut(72, 760, 0, '合规报告');
  Pdf.EndDoc;
  Pdf.SaveToFile('Report_PDFA.pdf');
finally
  Pdf.Free;
end;

Mühendislik dersi

Bundan iki kural çıkar. Birincisi, nesneleri değiştiren her sonlandırma adımı, o nesneler serileştirilmeden önce çalışmak zorundadır ve bir belge motorunun kapanış aşaması, serileştirmenin son eylem olduğu sıralı bir hat olarak okunmalıdır; birkaç eylemden biri değil. İkincisi unutması en kolay olanıdır: bir yayma adımı için hatasızlık, başarının kanıtı değildir. Doğru alt kümeyi oluşturup yanlış, çoktan yazılmış grafiğe uygulayan bir yordam, kendi perspektifinden yanlış bir şey olmadığı için yanlış bir şey raporlamaz. Doğrulamanın geri dönüş koduna değil esere bakması gerekir. Çıktı boyutunu kontrol edin, gömülü fontun bayt uzunluğunu ve /BaseFont önekini okuyun; eksik bir /CIDSet'in sessiz bir eksiği adlandırılmış bir başarısızlığa çevirdiği PDF/A çıktısını veraPDF'e yargılatın

Yazı tipi işlemenin üretici tarafı, yazı tiplerinin rapor çıktısı için nasıl kaydedildiği ve gömüldüğü, rapor çıktısında yazı tipleri ve resimler hakkındaki makalemizde ele alınmıştır. Bu sonlandırma adımlarının standartlara göre kontrol edildiği doğrulama tarafı, PDF/A ve PDF/UA doğrulaması hakkındaki kılavuzda ele alınmıştır. Her ikisi de, bu blogun başka yerlerinde ele alınan yükleme, düzenleme, şifreleme ve imzalama API'lerinin yanı sıra Delphi ve C++Builder için HotPDF Bileşeni'nin bir parçası olarak sunulan burada açıklanan alt kümeleme ve uyumluluk çalışmasıyla eşleşir