Uniscribe, çoğu çağıranın fark ettiğinden daha çok iş yapar. ScriptItemize, tek geçişte çift yönlü analiz ile yazı sistemi parçalamasını yerine getirir ve ScriptLayout, sonuçlanan çalıştırmaların görsel sırasını üretir. İnsanların başvurduğu taşınabilir yedek HarfBuzz ise ikisini de yapmaz: yönü ve yazı sistemi başkasınca belirlenmiş tek bir çalıştırmayı şekillendirir. Dolayısıyla bir Windows PDF metin hattını Linux'a ya da macOS'a taşımanın zor kısmı bir şekillendirme motoru bağlamak değildir. Uniscribe'ın sessizce sağladığı çift yönlü algoritmayı sağlamaktır ve PDFium bileşeninde FPdfBidi birimi tam olarak bunun içindir
Birim, UAX #9'u doğrudan uygular: paragraf yönü için P2 ve P3, açık gömmeler ve izolatlar için X1'den X10'a, zayıf türler için W1'den W7'ye, nötrler ve parantezler için N0'dan N2'ye, örtük düzeyler için I1 ve I2 ve son yeniden sıralama için L1 ile L2. İki işlev onu taşır: PdfResolveBidiLevels, UTF-16 kod birimi başına bir gömme düzeyi döndürür ve PdfBidiVisualOrder, o düzeyleri kod birimlerini soldan sağa yerleştiren permütasyona çevirir
Algoritmanın verdikleri ve vermedikleri
Size sayılar verir. Çift düzeyler soldan sağadır, tek düzeyler sağdan soladır ve her karakterin düzeyi, o karakterin içinde oturduğu yönlü çalıştırmaların iç içeliğini kodlar. L2, o sayılardan bir permütasyon türetir. Algoritmanın bilinçli olarak yapmadığı şey hangi fontun kullanılacağına karar vermek, ligatür kurmak ya da bir küme içindeki glifleri yeniden sıralamaktır; onlar şekillendirme meseleleridir ve bu aşamadan sonraki aşamaya aittir
uses
FPdfBidi;
var
Levels: TPdfBidiLevels;
Order: TPdfBidiOrder;
ParagraphLevel: Byte;
Text, Visual: WideString;
I: Integer;
begin
Text := SourceLine;
// pbdAuto P2-P3 kurallarını uygular: ilk güçlü karakter karar verir
if PdfResolveBidiLevels(Text, pbdAuto, Levels, ParagraphLevel) then
begin
Order := PdfBidiVisualOrder(Text, Levels);
SetLength(Visual, Length(Order));
for I := 0 to High(Order) do
Visual[I + 1] := Text[Order[I] + 1];
// Visual artık soldan sağa okunur; Levels dizisi hangi
// çalıştırmaların RTL olduğunu hâlâ söyler, böylece bir
// şekillendiriciye doğru yönler verilebilir
end;
end;
Karakter sınıfı tablosu üretilir, elle yazılmaz
Her kod noktasının bir Bidi_Class özelliği vardır ve algoritma ona sürekli başvurur; dolayısıyla tablo, her şeyin üzerine kurulduğu temeldir. Unicode Karakter Veritabanından üretilir, elle bakım görmez: UnicodeData.txt dosyasının beşinci alanı atanmış sınıfları verir ve DerivedBidiClass.txt içindeki @missing bildirimleri, veritabanının atamadığı kod noktaları için varsayılanları verir; atanmamış blokların bu yüzden L'ye değil doğru biçimde R, AL, ET ya da BN'ye varsaydığı budur
Sıkıştırma hilesi, sınıfı L olmayan aralıkları yaymaktır. Hiçbir aralığın dışında kalan her şey L'dir; o hem Unicode varsayılanı hem de kod noktalarının ezici çoğunluğunun sınıfıdır. Bu, yoksa binlerce girdiye ulaşacak bir tabloyu 745 aralığa ve yaklaşık 6,7 KB'a indirir. İşlemsel sonucu söylemeye değer: yeni bir Unicode sürümüne geçtiğinizde üreteci yeniden çalıştırın. Include dosyasını elle düzenlemek çalışır ve bir sonraki yükseltmede veritabanından sessizce sapmaya da başlar
L2 kod birimlerini değil kod noktalarını yeniden sıralamalıdır
Gerçekten bozuk çıktı üreten hata budur ve ilk uygulama onu yaptı. L2, her düzeyde en yüksekten en düşük tek düzeye dek bitişik çalıştırmaları tersine çevirmeyi söyler. Bir UTF-16 dizisi üzerinde yazıldığında "bir çalıştırmayı tersine çevir" doğal olarak içindeki kod birimlerini tersine çevirmek demektir. Temel Çok Dilli Düzlemdeki karakterler için bu sorun değildir. Astral düzlemde bir RTL karakteri için, U+10800 yakınındaki Kıbrıs ya da Eski Güney Arabistan bloklarındakiler gibi, değildir: karakter bir yedek çiftidir, çalıştırmayı tersine çevirmek düşük yedeği yükseğin önüne koyar ve dize artık bir karakter yerine iki eşleşmemiş yedek içerir. Aşağı akışta hiçbir şey onu kurtaramaz
Düzeltme, L2'yi kod noktası birimleri üzerinde yapmaktır. Uygulama, kod birimlerini kod noktası birimlerine birleştirir, ters çevirmeleri o birimler üzerinde yapar ve sonunda sonucu kod birimi indekslerine geri genişletir. PdfBidiVisualOrder işlevinin yalnızca düzeyler dizisini değil metni almasının nedeni budur: yedek sınırlarının nerede olduğunu düzeylerden anlayamaz. Aynı yedek çifti disiplini, metin API'lerinde genel olarak da sürer; emoji, CJK ve yedek çifti makalesinde anlatılır
Düzeylerdeki iniş, hiç gerçekleşmeyen düzeyleri de kapsamalıdır
İkinci hata daha incedir ve çökme üretmez; yalnızca yeniden sıralanmamış metin üretir. L2, bulunan en yüksek düzeyden başlayıp en düşük tek düzeye dek inilmeyi söyler. Doğal iyileştirme, gerçekte gerçekleşen düzeyler kümesini toplamak ve o küme üzerinde yinelemektir. Yanlıştır
Sağdan sola bir gömme içindeki bir Latince metin satırını düşünün. Paragraf düzeyi 0'dır, gömme Latince karakterleri 2. düzeye iter ve hiçbir karakter 1. düzeyde oturmaz. Gerçekleşen düzeyler üzerinde yinelemek yalnızca 0 ile 2'yi bulur ve hiç tek düzey yoktur; dolayısıyla döngü hiçbir ters çevirme yapmaz. O cevap doğrudur, ama iyileştirmenin bilmediği bir nedenden: 2. düzeyde bir ters çevirmenin ardından 1. düzeyde bir ters çevirme tam olarak birbirini sileceğinden ikisinin de yapılmaması doğru sonuçtur. Girdiyi biraz değiştirin, 1 ve 3. düzey karakterler bulunsun ama 2. düzey bulunmasın; küme tabanlı döngü, algoritmanın gerektirdiği 2. düzey ters çevirmesini atlar
// Doğru: en yüksekten en düşük tek düzeye dek her düzeyi gezin;
// hiçbir karakterin gerçekten sahip olmadığı düzeyler dahil
Level := MaxLevel;
while Level >= LowestOddLevel do
begin
ReverseRunsAtOrAbove(Level); // nitelikli çalıştırma yoksa no-op
Dec(Level);
end;
Düz bir azalan döngü olarak yazıldığında davranış bedavaya düşer ve no-op yinelemeleri ölçülebilir bir şey maliyet etmez. Burada bariz iyileştirme hafifçe yanlış değildir; küçük bir test derlemesinin asla açığa çıkaramayacağı, girdiye bağlı bir şekilde yanlıştır
Parantezler: pragmatik bir tabloyla BD16
N0 kuralı ve BD16 parantez çifti algoritması, karışık yönlü metindeki bir parantezin, yanı tesadüfen bitişik olana değil sardığı şeyin yönüne çözülmesi için vardır. Bu, bir parantez çifti tablosu gerektirir. Uygulama, Unicode parantez dosyasının tüm içeriği yerine yaygın kullanımdaki çiftleri taşır: ASCII, CJK, tam genişlikli, matematiksel ve süsleme parantezleri
Listede olmayan bir parantez bir hata değildir. N1 ile N2 üzerinden sıradan bir nötr olarak çözülür; Unicode 6.3'ün N0'ı getirmesinden önce her uygulamanın davranışı tam olarak buydu. Dolayısıyla sınır "nadir parantezler için daha az rafine", "yanlış" değildir. Bir ayrıntının açık ele alınması gerekir: U+2329 ile U+232A'daki açılı parantezlerle U+3008 ile U+3009'dakiler arasındaki kanonik eşdeğerlik, çiftler eşleştirilirken katlanmalıdır; yoksa bir biçimde yazılmış açma parantezi öbür biçimde yazılmış bir kapatma paranteziyle eşleşemez
Otuz etkileşen kuralı nasıl test edersiniz
Büyük bir derlemle değil, en azından önce değil. Verimli yaklaşım, her biri belirli bir kuralı çalıştırmak için seçilmiş ve her biri UAX #9'un üretmesi gerektiğini söylediği düzeylerle denetlenmiş on altı elle doğrulanmış durumdur: P2 ve P3 altında paragraf yönü saptaması, W2, W3 ve W7 zayıf tür kuralları, I1 ve I2 örtük düzey kuralları, X2 ile X7 üzerinden açık gömme, X5a ile X6a üzerinden izolatlar, sondaki boşluk ve ayraçların L1 sıfırlaması, bir N0 parantez durumu ve yedek ele alışını sabitlemek için astral karakterli bir durum
Bilinen-doğru beklenen düzeyli on altı durum, inandırıcı görünen çıktılı bin altı yüz durumdan fazlasını yakalar; çünkü bir çift yönlü uygulamanın hata modu, neredeyse doğru okunan metindir. Onlar geçtikten sonra derlem, tablo boşluklarını ve performans sorunlarını bulmak için faydalıdır; onlar farklı hata sınıflarıdır
PDFium bileşeninin içinde düzeyler iki tüketiciyi besler. Yazma tarafında şekillendirme arka ucuna her çalıştırmanın yönünü söyler; HarfBuzz'ın gerektirdiği girdi odur. Okuma tarafında seçim geometrisini ve okuma sırasını bilgilendirir; çünkü RTL metindeki bir tıklama görsel değil mantıksal bir konuma eşlenmek zorundadır. O eşleme görsel satır seçimi makalesinde, okuma sırası modeli ise yapılandırılmış metin blokları ve okuma sırasında kapsanır. Bileşenin platform desteği ayrıntıları PDFium Delphi component ürün sayfasındadır