Teknik Makale

PDF Rendering'de Composite Glyph ve CFF seac Aksanları

HotPDF, gömülü fontlardan gelen aksanlı harfleri, bir glifi diğerlerinden inşa eden iki font mekanizmasını hallederek doğru render eder: bileşen kaymaları glif-cihaz matrisinden önce glif birimlerinde uygulanması gereken TrueType composite glyph'leri ve bir temel harf ile bir aksanı StandardEncoding koduyla birleştiren endchar'ın CFF seac biçimi. Herhangi birini yanlış yaparsanız é, Å, ü ya da kıvrımlı çift tırnak bir parçası eksik çıkar; çevresindeki düz harfler ise kusursuz görünür

Son ayrıntı, bu hataları pahalı yapan şeydir. Bir rapor render olur, gövde metni iyidir ve yalnızca "München" içinde bir delik vardır; ya da bir alıntı tek tırnak ve bir boşlukla açılır. Kimse render motorundan şüphelenmez. İlk kuram daima hasarlı bir font ya da kötü bir üreticidir ve kayıt bir hafta boyunca elden ele dolaşır. Bu yazı her mekanizmada gerçekte neyin bozulduğunu, HotPDF Delphi bileşeninin v2.768.10 ve v2.768.11'den beri bunu nasıl hallettiğini ve HotPDF fontları okumak yerine yazarken composite glyph'leri bütün tutan v2.770.83'teki ilgili subsetting düzeltmesini anlatır

Bir PDF glifinden neden yalnızca aksan kaybolur?

Aksan, glifin sıfır olmayan kayma taşıyan tek parçası olduğu için kaybolur. TrueType ve CFF fontları da dış çizimleri yeniden kullanarak yer kazanır: é yeni bir şekil olarak değil, "e glifi artı yukarı ve sağa kaydırılmış acute glifi" olarak çizilir. Temel harf neredeyse her zaman (0, 0) kaymasında durur; bu yüzden kaymaların uygulanmasındaki herhangi bir hata ona dokunmaz. Hareket eden bileşen akandır; yanlış yere varan ya da hiç çizilmeyen bileşen de odur

Belirti deseni başlı başına bir tanıdır. Aksanlı karakterlerin temel harfleri render olup diakritikler olmuyorsa ya da “ ile ” gibi çift noktalama tek işaret olarak görünüyorsa font dosyasına bakmayı bırakın. Bozuk bir font genellikle bütün glifleri bozar ve onları her görüntüleyici için bozar. Acrobat sayfayı düzgün gösterirken bir render motorunda oluşturulmuş gliflerin eksik parçaları, bileşenleri birleştiren kodu işaret eder

TrueType composite glyph'ler bileşenlerini nasıl yerleştirir?

Bir TrueType composite glyph her bileşeni, font birimlerinde ifade edilen kendi küçük affine dönüşümüyle yerleştirir ve o dönüşüm, glif-cihaz matrisinden önce bileşen dış çizimine etki etmek zorundadır. OpenType glyf tablosunda bir composite glyph negatif bir kontur sayısı taşır ve ardından bileşen kayıtlarının bir listesi gelir. Her kayıt bir flags kelimesi, bileşen glif indeksi, bayt ya da word olan iki argüman (ARG_1_AND_2_ARE_WORDS, 0x0001) ve isteğe bağlı bir ölçek tutar: WE_HAVE_A_SCALE (0x0008) için bir F2Dot14 değeri, WE_HAVE_AN_X_AND_Y_SCALE (0x0040) için iki değer ya da WE_HAVE_A_TWO_BY_TWO (0x0080) için tam bir 2x2 matris. ARGS_ARE_XY_VALUES (0x0002) ayarlıysa iki argüman, font birimlerinde bir dx/dy kaymasıdır. MORE_COMPONENTS (0x0020), başka bir kaydın izlediğini söyler

Her bileşen için ölçek kısmından ve kaymadan bir bileşen matrisi CM kurar, sonra onu font birimlerini cihaz piksellerine haritalayan M matrisiyle birleştirirsiniz. O birleşimin sırası hataların tamamıdır. HotPDF'nin iç matris çarpımı sütun vektör kuralını kullanır; böylece A ve B çarpımı önce B'yi sonra A'yı uygular. Doğru bileşim önce CM'yi, glif uzayında, sonra M'yi uygular. Ters yazıldığında M önce çalışır ve CM'nin kayması ardından eklenir; artık cihaz pikselleri olarak ve asla ölçeklenmeden

// Yalnızca illüstrasyon: sütun vektör kuralıyla 2B bir affine kaydı,
// Mul(A, B) bir noktayı önce B'den sonra A'dan geçirir
type
  TAffine = record
    a, b, c, d, e, f: Double;
  end;

function Mul(const A, B: TAffine): TAffine;
begin
  Result.a := A.a * B.a + A.c * B.b;
  Result.b := A.b * B.a + A.d * B.b;
  Result.c := A.a * B.c + A.c * B.d;
  Result.d := A.b * B.c + A.d * B.d;
  Result.e := A.a * B.e + A.c * B.f + A.e;
  Result.f := A.b * B.e + A.d * B.f + A.f;
end;

// M  : font birimleri -> cihaz pikselleri (küçük ölçek, y çevik)
// CM : bileşen dönüşümü; e/f font birimlerinde dx/dy tutar
Sub := Mul(M, CM);   // doğru: kayma dış çizimle birlikte ölçeklenir
// Sub := Mul(CM, M); // yanlış: dx/dy ham cihaz pikselleri olarak düşer

Sayılar, bir kez gördüğünüzde başarısızlığı açık eder. Arial 2048 birimlik bir em kullanır ve é'nin aksan bileşeni kaymasını o font birimlerinde taşır. Doğru sırayla o kayma, dış çizimle birlikte küçülür ve normal zoomda birkaç piksel olur. Ters sırayla ham font birimi değeri cihaz pikselleri olarak eklenir ve HotPDF test render'ında Arial'ın é'sinin aksanı harfinden 243 piksel uzakta kaldı; glifin çok dışında ve genellikle görünen hiçbir şeyin dışında. Arial'daki sol çift tırnak, tek tırnak şeklinin ikincisi kaydırılmış iki kopyasıdır; bu yüzden boş bir alanın ardından yalnız bir ‘ olarak render oldu

Doğrusal kısım hatayı nadiren ele verir. Aksan bileşenleri çoğu zaman hiç ölçek taşımaz ve düzgün bir ölçek zaten M ile yer değiştirebilir; bu yüzden ters çevrilmiş çarpımı yalnızca öteleme ele verir. Düz Latin metniyle yapılan hızlı bir testin geçmesinin nedeni de budur: terslik, v2.768.10 onu düzeltene kadar birçok sürüm boyunca kodda oturuyordu ve onu yalnızca kaymalı bileşenler gösteriyordu

Özyineleme ve döngüler

Composite glyph'ler başka composite glyph'lere referans verebilir; bu yüzden bir render motoru onları özyinelemeli gezinir ve kendini savunmak zorundadır. HotPDF yuvalama derinliğini 5 seviyeyle, glif başına toplam bileşen sayısını 1024 ile sınırlar, kendi glif indeksini adlandıran bir bileşeni atlar ve nadiren kullanılan nokta eşleştirme modunu (kayma değil nokta numarası olan argümanlar) sıfır kayma gibi ele alır. Düşmanca ya da bozuk bir font böylece bir bileşen kaybedebilir ama sayfa render'ını asılı tutamaz

CFF endchar'ın seac biçimi nedir?

endchar'ın seac biçimi, bir CFF Type 2 charstring'in "bu glif bir temel harf artı bir akandır" demesinin terk edilmiş bir yoludur: endchar dört argüman taşıdığında bunlar adx ady bchar achar olur. Adobe Technical Note #5177, onu Type 1 ile uyumluluk ekinde anlatır. Ad, Type 1 seac operatöründen (standard encoding accented character) gelir ve Type 1 fontlardan dönüştürülmüş CFF subsetleri aksanlı Latin harfleri için onu hâlâ yoğun biçimde kullanır

Üç ayrıntı uygulayıcıyı sendeletir. Birincisi, bchar ile achar glif ID'si değildir; Adobe StandardEncoding'deki kodlardır. Render motoru her kodu bir glif adına eşler ve sonra GID'yi bulmak için o adı fontun charset'i üzerinden arar. İkincisi, Type 2'de Type 1 seac'in aksine asb argümanı yoktur; Type 1 seac ise asb adx ady bchar achar alır. Type 2'de akann kökeni, temel glifin kökenine göre doğrudan (adx, ady)'ye gider; yan boşluk düzeltmesi yoktur. Üçüncüsü, CID anahtarlı CFF fontları seac'i hiç kullanmayabilir; çünkü StandardEncoding kodlarının çözüleceği bir glif adlı charset yoktur

Sonra genişlik meselesi var. Her Type 2 charstring, ilk yığın temizleyen operatörünü isteğe bağlı bir advance genişliği argümanıyla başlatabilir ve endchar o operatörlerden biridir. Bir argümanlı düz bir endchar genişlik taşır; beş argümanlı bir seac endchar, dört seac değerinin ardından geldiği bir genişlik taşır. Kural, argümanları beklenen sabit sayıya, seac için dördüne karşı saymaktır. v2.768.11 öncesi HotPDF her endchar'ı "genişliği al ve dur" gibi ele alıyordu; seac glifleri bu yüzden boş bir dış çizim üretiyordu: temel yok, aksan yok, yalnızca satırda doğru boyutlu bir boşluk

// Bir Type 2 yorumlayıcısında endchar kararının illüstrasyonu.
// Stack, endchar'dan önceki operandları tutar; Count kaç tanesi olduğudur.
if (Count >= 4) and not IsCIDFont then
begin
  // 5 operand: ilki advance genişliği, gerisi seac
  if Count = 5 then
    TakeWidthAndShift;                 // genişliği at, 4 operandı tut
  if StackDepth = 4 then
  begin
    BaseGID   := GIDForName(StdEncName(Round(Stack[2])));  // bchar
    AccentGID := GIDForName(StdEncName(Round(Stack[3])));  // achar
    DrawGlyph(BaseGID, Identity);
    DrawGlyph(AccentGID, Translate(Stack[0], Stack[1]));   // adx, ady
    Exit;
  end;
end;
// değilse: sıradan endchar, isteğe bağlı genişlik, yolu kapat

Gerçek yorumlayıcıda her iki bileşen glif de aynı charstring motoruyla çalıştırılır; aksanın matrisi, geçerli glif matrisinin (adx, ady)'ye bir ötelemeyle bileşimidir ve matris sonradan geri yüklenir, böylece satırdaki sonraki glifler etkilenmez. İsimlerden herhangi biri bir GID'ye çözülmezse o bileşen, bütün glifi düşürmek yerine atlanır; bir subset biraz fazla agresif kırpıldığında isteyeceğiniz affedici davranış budur

Düzeltmeyi kendi Delphi kodunuzdan denetlemek

Composite glyph ve seac işleyişini doğrulamanın en hızlı yolu, gömülü fontlardan aksanlı metin içeren bir sayfayı iki genel çıktı yoluyla render etmektir: raster çıktı için THotPDF.RenderLoadedPageToBitmap ve vektör çıktı için EmbedFonts değeri True yapılmış THotPDF.BuildLoadedPageSVG. İkisi de aynı glif dış çizim kodunu paylaşır; bu yüzden ikisi de v2.768.10 ve v2.768.11 düzeltmelerini aldı. Hattın bütününe HotPDF'in gömülü TrueType, CFF ve Type 3 glifleri nasıl render ettiği yazısı, bitmap seçeneklerine Delphi'de PDF sayfalarını bitmap'lere render etme yazısı değinir

uses
  System.Classes, Vcl.Graphics, HPDFDoc;

procedure RenderAccentProbe(const FileName: string);
var
  Pdf: THotPDF;
  Bmp: TBitmap;
  Svg: AnsiString;
  Lines: TStringList;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile(FileName) <= 0 then
      Exit;

    // Raster yol: 144 DPI yanlış yerdeki aksanları kolayca görünür kılar
    Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);
    if Bmp <> nil then
    try
      Bmp.SaveToFile('accent-probe.bmp');
    finally
      Bmp.Free;
    end;

    // Vektör yol: EmbedFonts = True glif dış çizimlerini yazar; böylece
    // composite ve seac birleşimi SVG path'lerinin kendisinde görünür
    Svg := Pdf.BuildLoadedPageSVG(0, True);
    if Svg <> '' then
    begin
      Lines := TStringList.Create;
      try
        Lines.Text := string(Svg);
        Lines.SaveToFile('accent-probe.svg');
      finally
        Lines.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Test belgelerini bilinçli seçin. İstediğiniz şey, é, Å ve kıvrımlı çift tırnaklar içeren gömülü bir TrueType subset'li bir PDF ile Type 1 fonttan üretilmiş gömülü bir CFF (FontFile3, Type1C) subset'li bir PDF'tir; seac'in hâlâ yaşadığı yer genellikle eski dizgi çıktıları ve bazı baskı iş akışlarıdır. Üreticinin gömmediği fontlarla dizilmiş metin burada size hiçbir şey söylemez; çünkü o glifler, ISO 32000-1 §9.9'un anlattığı gömülü font programından değil, kurulu Windows fontlarından gelir

Composite glyph'ler font subsetting sırasında neden bozuldu?

Composite glyph'ler subsetting sırasında bozuldu; çünkü subsetleyici, bir bileşen ölçek ya da 2x2 dönüşüm taşıdığında bileşen kayıtlarını yanlış adımla ilerliyor, sonra bir sonraki bileşenin glif indeksini yanlış okuyordu. HotPDF bunu v2.770.83'te düzeltti. Subsetting, rendering hatasının yazma tarafındaki ikizidir: HotPDF kurulu bir TrueType fontun bir subset'ini gömerken bir composite'in referans verdiği her glifi tutmak ve o referansları yeni, sıkışık glif ID'lerine yeniden numaralamak zorundadır

Eski gezgin yalnızca argüman boyutu bayrağına ve more-components bayrağına bakıyordu; kayıt başına 6 ya da 8 bayt ilerliyordu. Ölçek bayraklarının eklediği 2, 4 ya da 8 baytlık dönüşüm verisini yok sayıyordu. Ölçekli bir bileşenden sonra okuduğu bir sonraki "glif indeksi" gerçekte dönüşüm baytlarıydı; böylece gerçek bileşen subset'in dışında kalabiliyor ve yeniden numaralama adımı yeni bir glif ID'sini doğrudan dönüşümün içine yazabiliyordu. Sonuç, bazı görüntüleyicilerde yanlış aksanla, bozuk bir aksanla ya da hiç aksanla render olan aksanlı bir harfti ve yalnızca bileşenlerini ölçekleyen fontlarda görülüyordu; çoğu testin onu hiç yakalamamasının nedeni de buydu

Adım hatasının yanında iki sorun daha duruyordu. Bir glif indeksini yeniden numaralamak idempotent değildir ve iç içe composite'ler iki kez yeniden numaralanıyordu: bir kez işaretlenmiş her composite üzerinden geçen turca, bir kez de içine özyinelemeli inen turca. HotPDF artık her composite'i tam olarak bir kez yeniden numaralar ve "bu composite hangi gliflere ihtiyaç duyar" adımını bir iş kuyruğundan sürer; böylece birbirine referans veren composite döngüsü sonlanır, fontun maxp glif sayısını aşan glif indeksleri reddedilir ve boş glifler asla composite olarak çözümlenmez

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'accents-subset.pdf';
    Pdf.FontEmbedding := True;          // kullanılan glifleri göm
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 14);
    // é, Å, ü ve kıvrımlı tırnaklar Arial'da hep composite glyph'tir
    Pdf.CurrentPage.UnicodeTextOut(50, 720, 0,
      'Café, Ångström, München, “quoted”');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

O dosyayı yukarıdaki probe'tan ve ikinci bir görüntüleyiciden geçirin. v2.770.83'ten beri her composite glyph'in her bileşeni, ölçek ya da 2x2 dönüşümün arkasındakiler dâhil, subsetting'ten sağ çıkar; böylece aksanlar HotPDF'de, Acrobat'ta ve tarayıcı görüntüleyicilerinde aynı render olur

Composite glyph işleyişinin kapsamadıkları

Composite glyph düzeltmesi, neredeyse her gerçek fontun kullandığı kayma tabanlı yerleştirmeyi kapsar ve gerisini tutucu biçimde yaklaştırır. İki argümanın kayma vermek yerine hizalanacak noktaları adlandırdığı TrueType nokta eşleştirme compositeleri sıfır kaymayla çizilir. CID anahtarlı CFF fontlarındaki seac, şartname orada yasakladığı için tasarım gereği yok sayılır. Temel ya da aksan kodunun kırpılmış bir subset'te glifi olmayan bir seac, bulabildiği bileşeni çizer. Bu durumların hiçbiri render motorunu çökertmez; ama arşivsel ya da PDF/A kullanımı için PDF üretiyorsanız daha güvenli hamle, kayma composite'leri kullanan ve seac'siz Type 2 charstring'li fontları gömmektir; böylece tek bir render motoru değil, her tüketici aynı harfleri görür

FAQ

Aksanlı harfler PDF render motorumda neden aksansız render olur?

Aksanlı harfler genellikle composite glyph ya da CFF seac glyph'leridir ve aksan, kayması olan tek bileşendir. Bileşen kaymasını cihaz matrisinden sonra uygulayan ya da endchar içindeki seac'i yok sayan bir render motoru, temel harf normal render olurken aksanı düşürür ya da yanlış yere koyar

HotPDF, endchar'ın CFF seac biçimini destekliyor mu?

Evet. v2.768.11'den beri HotPDF, dört argümanlı ya da genişlik dâhil beş argümanlı endchar'ı, (adx, ady)'ye kaydırılmış bir aksan artı bir temel glif olarak çizer; her ikisini de StandardEncoding adlarından ve font charset'inden çözer; hem bitmap rendering hem de gömülü fontlu SVG dışa aktarımı için geçerlidir

HotPDF TrueType fontları subset'lerken composite glyph'ler korunur mu?

Evet. v2.770.83'ten beri HotPDF 2, 4 ya da 8 baytlık bileşen dönüşümlerinin üzerinden doğru adımla geçer, referans verilen her bileşeni subset'te tutar, iç içe composite'leri bir kez yeniden numaralar ve döngüsel ya da aralık dışı glif referanslarında güvenle durur

Composite glyph rendering, CFF seac desteği ve güvenli TrueType subsetting, harici DLL olmadan PDF belgeleri oluşturan, düzenleyen, çıkaran ve render eden yerel bir VCL kütüphanesi olan HotPDF Delphi PDF Component'in parçası olarak gelir