Teknik Makale

Delphi ve HotPDF ile PDF'de CJK Dikey Metin

Sayfaya Japonca bir roman yerleştirdiğinizde fark ettiğiniz ilk şey, metnin satır boyunca değil, sütun boyunca aşağı doğru inmesi ve sütunların sayfanın sağ kenarından sola doğru ilerlemesidir. Bu şekilde yetişmiş bir okuyucu, yatay metni biraz soğuk (duygusuz) bulur. Buradaki mühendislik sorunu, PDF'in de neredeyse her dijital metin sistemi gibi, soldan sağa doğru büyüyen yatay bir taban çizgisi (baseline) etrafında inşa edilmiş olmasıdır ve bir içerik akışının "bu paragrafı onun yerine aşağı doğru yaz" diye bir kavramı yoktur. Dolayısıyla, bir Delphi uygulamasının Tayvanlı, Japon veya Koreli bir okuyucu için bir sertifika, bir şiir, bir tabela veya geleneksel formatta yasal bir belge üretmesi gerektiğinde, düzenin (layout) elle oluşturulması gerekir: bir karakter diğerinin altına, bir sütun öncekinin soluna

HotPDF size her karakter için yapılması gereken hesaplamaları sizin yerinize yapan bir anahtar (switch) sunar. Ayarladığınız yazı tipi bir IsVertical bayrağı (flag) taşır ve bu açık olduğunda, tek bir TextOut çağrısı, tüm bir dizeyi (string) bir taban çizgisi boyunca yürütmek yerine dikey bir sütunda yığar. Sütun yerleşimi, sağdan sola sıralama ve sessizce önem taşıyan bir glif değişimi, bu sayfanın geri kalanında işlenecek konulardır

A4 PDF page produced by HotPDF showing Chinese, Japanese, and Korean text stacked into vertical columns that read from right to left
Tek bir Delphi prosedüründen oluşturulmuş, dikey sütunlarda Çince, Japonca ve Korece metin içeren tek bir A4 sayfası.

Anahtar SetFont üzerinde bulunur

Dikey düzen sayfanın veya belgenin bir özelliği değildir. Çizim yaptığınız yazı tipinin bir özelliğidir ve SetFont'un beşinci argümanı ile açarsınız:

// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// 5. argüman geçerli yazı tipini dikey moda geçirir.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // yatay
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True);  // dikey

Bayrak (flag) yazı tipine bağlı olduğu için, yalnızca farklı bir son argümanla SetFont öğesini tekrar çağırarak yatay ve dikey yazma arasında geçiş yapabilirsiniz. Bir sayfa, üstte yatay bir başlık ve altında dikey gövde metni barındırabilir ve HotPDF bu iki modu sayfa başına değil, yazı tipi nesnesi başına ayrı tutar. Kendi tarafınızda herhangi bir özel mod işlemi yapmanıza gerek kalmadan karma düzenleri mümkün kılan da budur: dikey bir SetFont'tan sonraki her TextOut yığılır, yatay olandan sonraki her TextOut taban çizgisi boyunca uzanır ve en son SetFont kazanır

Dördüncü argüman, yatay bir çağrının aldığıyla aynı olan Windows karakter setidir. DEFAULT_CHARSET geçilmesi, sistemin her bir dize için glifleri çözümlemesini sağlar ki bu önemlidir, çünkü dikey bir belge genellikle farklı alfabeleri karıştırır. Yazı tipiyle ilgili diğer her şey hâlâ geçerlidir: derleme (build) makinesinde yüklü olması gerekir ve dosyanın, Arial Unicode MS'ye hiç sahip olmamış bir okuyucuda aynı CJK gliflerini işlemesi (render) için neredeyse her zaman FontEmbedding := True yapmak istersiniz

Bir TextOut çağrısı bir sütundur

Dikey bir yazı tipi etkinken, bir TextOut çağrısı artık dizesini verilen noktadan yanlara doğru yaymaz. İlk karakteri en üste yerleştirir ve geri kalanını her glif için yazı tipinin satır yüksekliği kadar ilerleyerek dümdüz aşağıya indirir. Geçtiğiniz X sütunu sabitler; geçtiğiniz Y, sütunun tepesinin nerede başlayacağını sabitler. Bu nedenle gerçek bir pasaj oluşturmak için sütun başına bir TextOut yayınlarsınız ve çağrılar arasında X'i sola doğru kaydırırsınız, çünkü CJK sütunları sağdan sola okunur

var
  Pdf: THotPDF;
const
  ColTop = 760;   // her sütundaki ilk glifin y konumu (nokta cinsinden)
  ColGap = 28;    // sütunlar arasındaki yatay mesafe
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'VerticalText.pdf';
    Pdf.FontEmbedding := True;       // taşınabilir oluşturma için CJK yazı tipini göm
    Pdf.BeginDoc;
    Pdf.CurrentPage.Size := psA4;

    // Önce standart yazma modunda yatay bir başlık.
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
    Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');

    // Yazı tipini dikey moda geçirin; artık aşağıdaki her TextOut yığılır (stack).
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);

    // Sütunlar sağdan sola doğru ilerler, bu nedenle her çağrıda X azalır.
    Pdf.CurrentPage.TextOut(520, ColTop, 0, '床前明月光');
    Pdf.CurrentPage.TextOut(520 - ColGap,     ColTop, 0, '疑是地上霜');
    Pdf.CurrentPage.TextOut(520 - ColGap * 2, ColTop, 0, '舉頭望明月');
    Pdf.CurrentPage.TextOut(520 - ColGap * 3, ColTop, 0, '低頭思故鄉');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Şiir olması gerektiği gibi okunur: önce en sağdaki sütun, yukarıdan aşağıya, ardından göz sola, bir sonraki sütuna atlar. Dizenin (string) kendisinde bu sırayı kodlayan hiçbir şey yoktur. Bunu, her çağrıda X koordinatlarını azaltarak siz kodlarsınız. Yönü tersten yaparsanız mısra ters çıkar, ki bu yatay bir düzeni dikeye taşırken en sık yapılan hatadır

Koordinatlar: sayfada aşağıya, sağdan sola

İki eksen (axis) devrededir ve bunlar zıt yönlerde çeker, bu yüzden kesin olmakta fayda vardır. HotPDF, Y yukarı doğru artacak şekilde, noktasal (point) olarak sayfanın sol alt köşesinden ölçüm yapar. Bu nedenle dikey bir sütun yüksek bir Y'den (sayfanın üst kısmına yakın) başlar ve HotPDF her biri için bir satır yüksekliği çıkardıkça karakterler oradan aşağı doğru iner. Bu başlangıç Y'sini sütun başına bir kez ayarlarsınız ve bileşen iniş kısmını halleder

Yatay eksen manuel olarak yürüttüğünüz eksendir. Her sütun kendi X konumunda yer alır ve birbirini izleyen sütunlar daha küçük X değerlerine adım atar, çünkü okuma sırası sağdan soladır. Mantıklı bir ritim, örneğin ColGap ile yaptığı gibi, en sağdaki sütunun X'ini seçip ardından her ardışık çağrı için sabit bir sütun boşluğu (gap) çıkarmaktır. Boşluğu seçmek size kalmıştır; çok sıkı olursa bitişik sütunlar birbirine değer, çok gevşek olursa blok seyrek görünür. 12 ile 18 punto arasındaki gövde metinleri için, yazı tipi boyutundan biraz daha büyük bir boşluk rahatça okunur

Şekil değiştirmesi gereken glifler

Bazı karakterler yalnızca yatay hallerinin döndürülmüş kopyaları değildir; metin dikeye dönüştüğünde bunların değiştirilmesi (substitution) gerekir. HotPDF'in sizin için hallettiği şey, コーヒー gibi katakana sözcüklerinde görünen uzun ünlü (vowel) çubuğu olan Japonca uzatılmış ses işareti (U+30FC)'dir. Yatay olarak çizildiğinde, taban çizgisinde kısa bir çizgidir. Aynı glifi bir sütuna yığarsanız sütun boyunca yatay duracaktır ki bu yanlıştır: dikey Japoncada bu işaret, aralarında oturduğu iki karakteri birbirine bağlayan dikey bir çizgiye (stroke) dönüşür. HotPDF, dikey yoldaki U+30FC'yi algılar ve uzun ünlü işaretinin sizden hiçbir çaba gerektirmeden doğru yönü göstermesi için onu dikey bir çubuk (U+007C) olarak çizer

Bu tek değişim, çoğu basit (naive) uygulamanın bozulduğu durumu kapsar, ancak genel sorunun nerede daha da derinleştiğini bilmekte fayda vardır. Tam dikey tipografi ayrıca Latin harflerini ve Batı noktalama işaretlerini doksan derece döndürür, küçük kanaları kaydırır, parantez ve virgülleri dikey formlarına göre yeniden konumlandırır ve bunun eksiksiz bir uygulaması, sabit kurallardan ziyade bir yazı tipinin OpenType dikey özelliklerinde bulunur. Yazı tipi bu özellikleri taşıdığında HotPDF bunlardan faydalanabilir: dikey alternatifler (vert ve vrt2 GSUB özellikleri) ve dikey aralıklandırma/kerning (vkrn ve vpal GPOS aramaları) isteğe bağlıdır ve yalnızca etkin yazı tipi bunları gerçekten tanımladığında dikey yol boyunca uygulanır. Arial Unicode MS gibi tek bir geniş kapsamlı yazı tipindeki (face) karma CJK metinleri için, yerleşik U+30FC işleme ve buna ek olarak eşit bir satır yüksekliği adımı doğru, okunabilir sütunlar üretmek için yeterlidir; OpenType özellikleri, ince dikey dizgi için tasarlanmış bir yazı tipine geçtiğinizde ve onun yerel kana konumlandırmasını ve glifler arası boşluklandırmasını istediğinizde önem kazanır

Bir sayfada alfabeleri ve yönelimleri (orientation) karıştırma

Gerçek belgeler nadiren saftır. Dikey bir Japonca sayfası yatay bir İngilizce altyazı, altta bir sayfa numarası veya Japonca'nın yanına dikey olarak yerleştirilmiş bir Korece bloğu taşıyabilir. Dikey bayrağı (flag) yazı tipi düzeyinde bir anahtar (switch) olduğu için, sayfa çapında herhangi bir durumu (state) yönetmek yerine SetFont çağrılarını değiştirerek bunları oluşturursunuz. Yatay bir yazı tipi ayarlayın, çalışan üstbilgiyi (header) ve sayfa numarasını (folio) yazın, dikey bir yazı tipi ayarlayın, sütunları yerleştirin, altbilgi (footer) için tekrar yatay bir yazı tipi ayarlayın. Her bölge en son SetFont modunu devralır, bu nedenle gereken tek kural, yön değiştirdiğinizde bunu çağırmaktır

Alfabeler karıştığında planlanması gereken bir ayrıntı: Çince, Japonca ve Korece ideograflar kareye yakındır ve eşit bir adımda yığılırlar, ancak dikey bir sütuna gömülü çalışan Latin karakterlerinin böyle tek tip bir ilerlemesi yoktur. Aksi takdirde dikey olan bir metnin içinde birkaç Latince kelimeye ihtiyacınız varsa, bunların sütun boyunca aşağı inecek şekilde döndürülüp döndürülmeyeceğine veya kısa yatay bir iç metin (inset) olarak dik durup durmayacağına bilerek karar verin ve bu parçayı, ideograflara yönelik dikey adımı izlemesine izin vermek yerine kendi TextOut işleviyle konumlandırın. Karma diziyi (run) kendi yerleşim problemi olarak ele almak, sütun ritmini sağlam tutar

Örnekten (sample) üretime

Parçalar küçüktür ve öngörülebilir şekilde birleşirler. SetFont işlevinde dikey bayrağı (flag) açın, sabit bir üst Y'den sütun başına bir TextOut yayınlayın ve sütunların sağdan sola okunması için çağrılar boyunca X'i azaltın. CJK gliflerinin bir okuyucunun makinesine olan yolculuktan sağ çıkabilmesi için yazı tipini gömün (embed) ve bileşenin U+30FC uzun ünlü (vowel) işaretini ve yazı tipinin desteklediği yerlerde OpenType dikey özelliklerini halletmesine izin verin. Oradan itibaren düzeni (layout) üretim aşamasına taşımak çoğunlukla aritmetiktir: ölçülen bir blok genişliğinden sütun X konumlarını elde etmek, uzun pasajları sayfa yüksekliğine uyan sütunlara bölmek ve yatay üstbilgiler (header) ve sayfa numaraları (folio) için alan ayırmak

Yatay TextOut kuralları ve Unicode yazı tipi kaydı da dahil olmak üzere bunun üzerine inşa edildiği daha geniş metin ve yazı tipi yüzeyi (surface) için çok dilli Hello World örneğine ve TextOut örneğine bakın. Burada gösterilen SetFont dikey anahtarı ve TextOut çağrıları, Delphi ve C++Builder için HotPDF Bileşeni'nin (Component) bir parçasıdır