Teknik Makale

Delphi'de Takılabilir Şekillendirme: Uniscribe ve HarfBuzz

PDFium bileşeninde metin şekillendirme kurulabilir tek bir nesneden geçer. ConfigureTextShaper, her şekillendirme giriş noktasının üzerinden geçtiği şekillendiriciyi kurar ve orada olanı değiştirip serbest bırakır; ActiveTextShaper kurulu olanı döndürür ve ilk kullanımda platform varsayılanını oluşturur; ActiveTextShaperName hangi arka ucun canlı olduğunu bildirir; ClearTextShaper kurulumu düşürür ve varsayılanın yeniden oluşturulmasına izin verir. Windows'ta varsayılan TPdfUniscribeTextShaper'dır. Free Pascal altında TPdfHarfBuzzTextShaper vardır; libharfbuzz'ı çalışma zamanında bağlar, böylece eksik bir kitaplık bir yükleme başarısızlığı değil bildirilen bir durumdur

PDFium Delphi bileşeninde takılabilir metin şekillendirme mimarisi: ConfigureTextShaper, ActiveTextShaper ve ClearTextShaper kurulu tek arka ucu yönetir; Windows'ta Uniscribe, Free Pascal altında çalışma zamanında bağlanan HarfBuzz
Her şekillendirme çağrısı kurulu tek şekillendirici nesnesinden geçer; her hedefte bir platform varsayılanı vardır

Tek arayüz, işi tamamen farklı bölen iki arka uç. O asimetrinin anlaşılması, taşınabilir yolun doğru şekillendirilmiş ama yanlış konumlanmış metin üretmesini engelleyen şeydir

Windows arka ucu neden tek sınıf, taşınabilir olan neden üç parça?

Çünkü Uniscribe, tekmiş gibi görünen dört API'dir. ScriptItemize bir diziyi yazı sistemine göre parçalar ve çift yönlü düzeyleri çözer; ScriptShape karakterleri gliflere eşler; ScriptPlace ilerlemeleri ve ofsetleri hesaplar; ScriptLayout sonuçlanan çalıştırmaları görsel sıraya koyar. Üzerine kurulan bir arka ucun ekleyecek hiçbir şeyi kalmaz; Windows şekillendiricisinin tek yöntemli tek bir sınıf olmasının nedeni budur

HarfBuzz ortadaki ikisini kapsar. Çağıranın yönünü ve yazı sistemini önceden belirlediği bir çalıştırmayı şekillendirir ve yerleştirir; bir paragrafın çalıştırmalara nasıl bölüneceği ya da o çalıştırmaların hangi sırada görüneceği hakkında fikri yoktur. Dolayısıyla taşınabilir arka uç gerisini sağlar: çift yönlü algoritma gömme düzeylerini çözer, HarfBuzz Unicode işlevleri metni yazı sistemine göre parçalar ve çalıştırmalar, UAX #9 L2 kuralının ürettiği görsel sıraya dizilir. Çift yönlü yarısı, kendi birimi olmaya değer kadar büyüktür; UAX #9 gömme düzeyleri makalesinde betimlenir

PDF metni için şekillendirme hattı karşılaştırması: Uniscribe ScriptItemize, ScriptShape, ScriptPlace ve ScriptLayout işlevlerini tek sınıfın içinde sağlar; HarfBuzz ise bileşenin kendi UAX #9 aşamaları çevresinde yalnızca şekillendirme ve yerleştirmeyi kapsar
Uniscribe dört aşamayı da kapsar; taşınabilir yol parçalamayı ve görsel sırayı kendisi sağlamalıdır

Şekillendirici fontları çözmez ve bu bilinçlidir

Uniscribe, font ikilisini bir GDI aygıt bağlamından okur. Bunun taşınabilir bir eşdeğeri yoktur ve bir şekillendirme biriminin içinde bir tane icat etmek, her uygulama adına fontların fontconfig'den mi, CoreText'ten mi, bir uygulama font klasöründen mi yoksa bir veritabanından mı geleceğine karar vermek demek olurdu. Bu yüzden HarfBuzz arka ucu bir çözümleyici alır: bir font adını TrueType ya da OpenType baytlarına eşleyen bir geri çağrı. False döndürmek, şekillendirme isteğini Windows'ta okunamayan bir GDI fontunun başarısız kıldığı şekilde başarısız kılar

uses
  FPdfTextShaping
{$IFDEF FPC}
  , FPdfTextShapingHb
{$ENDIF}
  ;

function TFontCatalogue.Resolve(const FontName: WideString;
  out FontData: TBytes): Boolean;
var
  Path: string;
begin
  // Politikanız: fontconfig, CoreText, bir uygulama font klasörü,
  // bir veritabanı
  Result := FLookup.TryGetValue(LowerCase(FontName), Path);
  if Result then
    FontData := TFile.ReadAllBytes(Path);
end;

procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
  // Sahiplik birime geçer; başlangıçta, herhangi bir şey metni
  // şekillendirmeden önce bir kez çağırın
  ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
  // Delphi'de platform varsayılanı (Uniscribe) talep üzerine
  // oluşturulur; dolayısıyla hiçbir kuruluma gerek yoktur
  LogInfo('shaping backend: ' + ActiveTextShaperName);
end;

Font keşfini şekillendiricinin dışında tutmanın sunucularda ortaya çıkan ikinci bir yararı vardır: aynı işlem, makinede kurulu olanla hiçbir ilgisi olmayan gömülü bir font kümesiyle şekillendirebilir; çıktının makineler arasında bayt bakımından yeniden üretilebilir olması gerektiğinde isteyeceğiniz budur. Bileşen ayrıca, kurulu fontları gerçekten istediğiniz durumlar için bir ana sistem fontu sağlayıcısı açar; sistem fontu sağlayıcısı makalesinde kapsanır

Sonuç kaydı arka uçtan bağımsızdır ve bunun nedeni kümelerdir

İki arka uç da aynı TPdfShapedText kaydını doldurur: kaynak metin, font adı, boyut, font baytları, bir çalıştırma dizisi, toplam genişlik, glif sayısı ve mantıksal karakter sayısı. Her TPdfShapedRun, kaynak metindeki aralığını, görsel X konumunu, genişliğini, çift yönlü düzeyini ve sağdan sola bayrağını, artı gliflerini taşır. Her TPdfShapedGlyph, bir glif tanımlayıcısı, bir ilerleme, X ve Y ofsetleri ve ait olduğu kümeyi kaynak metinde bir başlangıç ile bir uzunluk olarak taşır

O küme alanları, kaydı yalnızca bilgilendirici olmaktan çıkarıp kullanılabilir kılan şeydir. Şekillendirme bire bir eşleme değildir: bir Devanagari hecesi dört karakterden tek bir glife dönüşür, bir Arapça ligatürü ikisini birleştirir ve tek bir karakter birkaç işaret üretebilir. Küme aralıkları olmadan bir şapka işareti koyamazsınız, bir tıklamaya çarpma testi yapamazsınız ya da bir seçimi vurgulayamazsınız; çünkü bir glifin hangi karakterlere ait olduğunu söyleyemezsiniz. Onlarla birlikte aritmetik yereldir ve aynı kod her iki arka uçta da çalışır

TPdfShapedText içinde glif küme aralıkları: dört karakterden bir Devanagari hece glifi, ikiden bir Arapça ligatürü ve bir karakterden taban artı işaret; her biri ClusterStart ve ClusterLength üzerinden geriye eşlenir
Küme aralıkları her glifi kaynak karakterlerine geri eşler; böylece şapka işaretleri, çarpma testleri ve seçimler çalışır
var
  Shaped: TPdfShapedText;
  R, G: Integer;
begin
  if ShapePdfText(Line, 'Noto Sans Arabic', 14, ptdAuto, Shaped) then
    for R := 0 to High(Shaped.Runs) do
    begin
      // Çalıştırmalar, VisualX doldurulmuş olarak zaten görsel sırada gelir
      X := Shaped.Runs[R].VisualX;
      for G := 0 to High(Shaped.Runs[R].Glyphs) do
      begin
        EmitGlyph(Shaped.Runs[R].Glyphs[G].GlyphID,
          X + Shaped.Runs[R].Glyphs[G].OffsetX,
          Shaped.Runs[R].Glyphs[G].OffsetY);
        X := X + Shaped.Runs[R].Glyphs[G].Advance;
      end;
    end;
end;

Bütçeler seçenek kaydına aittir

TPdfTextShapingOptions, bir yön artı üç sınır taşır: en fazla karakter, en fazla glif ve en fazla çalıştırma; makul değerleri dolduran bir Default sınıf işleviyle. Sınırlar bozuk girdi paranoyası değildir; aritmetiktirler. Şekillendirme büyütür: agresif bağlamsal ikamesi olan bir font, girdi karakterlerinden daha çok glif yayabilir ve her birkaç karakterde yazı sistemi değiştiren bir paragraf her değişimde bir çalıştırma üretir. İkisini en üst düzeye çıkarmak için kurgulanmış bir belge, mütevazı bir diziyi büyük bir ayırmaya çevirir ve güvenilmeyen PDF'lerden metin şekillendiren bir hizmet, makinenin dayattığı bir sınır değil kendi seçtiği bir sınıra ihtiyaç duyar

Yönü otomatikte bırakmak yerine açıkça ayarlamak, zaten biliyorsanız yapmaya değer. Otomatik, paragraf yönü kurallarını ilk güçlü karakterden tahmin etmek için uygular; bu, serbest metin için doğrudur ve yönü, içine yazılan değerin değil alanın özelliği olan bir form alanı için yanlıştır

Derleme bağımlılığı değil, çalışma zamanı bağlaması

HarfBuzz arka ucu kitaplığı dinamik yükler. Bu, gerçek sonuçları olan bir dağıtım kararıdır: tek bir ikili, HarfBuzz'lı bir makinede de ondan yoksun makinede de çalışır ve ikinci durumda başlamayı başarısız kılmak yerine azaltılmış yetenek bildirir. Diğer geliştiricilere gönderilen bir kitaplık için çalışabilir tek düzen budur; çünkü bir PDF bileşeninin her tüketicisinden, ihtiyaç duymayabilecekleri bir şekillendirme kitaplığını edinmesini ve sürüm eşlemesini isteyemezsiniz

Çağıranlar için karşılık gelen kural denetlemektir. ActiveTextShaper, platformun varsayılanı olmadığında ve hiçbiri yapılandırılmamışken nil döndürür ve şekillendirme giriş noktası bunu bir şekillendirme başarısızlığı değil kullanılamaz şekillendirici olarak bildirir. Bunlar farklı sorunlardır ve farklı mesajları hak eder: biri bir dağıtım boşluğu, diğeri bir font ya da metin sorunudur

Bir kez kurun, herhangi bir şey şekillendirmeden önce

Kurulum önceki şekillendiriciyi değiştirir ve serbest bırakır; dolayısıyla onu tekrar tekrar çağırmak güvenlidir ama anlamsızdır ve başka bir iş parçacığı şekillendirirken çağırmak hiç güvenli değildir. Başlangıç sırasında yapın. Daha sonra platform varsayılanına geri düşmeniz gerekirse nil geçirin; bir testin sonunda test ikamesini geri almanın yolu da budur

Bir arka uç kurulduktan sonra ölçüm ile sarma her iki platformda da aynı davranır; çünkü platformu doğrudan çağırmak yerine çalıştırma ve glif metriklerini tüketirler. Sarma modeli metin ölçümü ve sözcük sarma makalesinde betimlenir. Bileşen için desteklenen platformlar ve araç zincirleri PDFium Delphi component ürün sayfasında listelenir