Teknik Makale

Delphi'de HotPDF ile Arapça ve İbranice Comb Alanları

HotPDF, Arapça, İbranice, birleştirici işaretli ve karışık yönlü metni, THPDFHeadlessDocument.SetTextFieldValueWithFamilies üzerinden AcroForm comb alanlarına doldurur. Comb ızgarası Unicode skalerlerinde sayılır; bir ligatür ya da işaretleriyle bir harf, skalerlerinin sahiplendiği k hücresine tek küme olarak ortalanır ve bütün satır, herhangi bir hücre atanmadan önce Unicode Bidirectional Algorithm'dan geçirilir

O üç kuralın her biri, Latin bir demoda kusursuz görünüp ilk gerçek formda dağılan bir kestirmenin yerine geçer. Adı Arapça yazılmış, on kutulu comb alanı olan bir devlet başvurusu düşünün. Karakter başına mantıkla doldurursanız harfler izole biçimlerinde soldan sağa dizilir, bir lam-alef ligatürü tek kutuya sıkışırken komşusu boş kalır ve bir fatha yanlış hücrenin üstünde uçar. O kusurların her biri, yanlış birimi saymaktan ya da metni yanlış anda düzenlemekten çıkar

Delphi'de HotPDF ile bir Arapça comb alanını doldurmanın iki yolu: karakter başına kısayol lam-alef ligatürünü ezer, şekillendirilmiş doldurma her kümeyi ortalar
Unicode skalerlerini saymak ve satırı, hücreler atanmadan önce yeniden düzenlemek, ligatürü iki kutusunun ortasında tutar ve hiçbir hücreyi boş bırakmaz

Comb bayrağı bir PDF yazarından gerçekte ne ister?

Comb bayrağı, yazardan alanı tam olarak MaxLen adet eşit aralıklı konuma bölmesini ve her birine bir karakter koymasını ister. ISO 32000-1 §12.7.4.3, Comb'ı Tablo 228'deki bit 25 olarak tanımlar; yani metin alanlarına özgü alan bayrakları olarak ve yalnızca Tablo 229'dan gelen MaxLen girişi mevcutken ve Multiline, Password ile FileSelect bayrakları temizken anlamlıdır. Bitin kendisi, 32 bitlik /Ff değeri içinde $1000000'dır; comb alanlarını bir kayan nokta değişkeni üzerinden geçirdikten sonra ters davranırken gördüyseniz 32 bitlik AcroForm alan bayrakları yazısı, o bite neden ilk giden bit olduğunu anlatıyor

Belirtim "character" der ve orada durur. Sabit adımlı Latin kutuları düşünülerek yazılmıştır; bir Arapça harf çifti tek glifte birleştiğinde ya da bir sesli işaretin kendi genişliği olmadığında bir karakterin ne olduğunu söylemez. HotPDF bir birim seçmek zorundadır ve yapısal kuralları seçerken katı uygular: aynı zamanda multiline olan ya da pozitif bir MaxLen taşımayan bir comb alanı bir teşhisle reddedilir ve parola alanları hiçbir biçimde comb alanı olamaz

UTF-16 birimleri, glyphler ya da grafemler yerine neden Unicode skalerleri sayılıyor?

HotPDF comb hücrelerini Unicode skalerlerinde sayar, çünkü MaxLen'i zorunlu kılmak için kullandığı birim aynıdır; ızgara ile uzunluk kontrolünün uyuşması gerekir, yoksa doğrulamayı geçen bir değer kutularına sığmaz. Cazip üç alternatifin her biri o uyumu başka bir şekilde bozar

  • UTF-16 kod birimleri, Delphi'nin Length işlevinin döndürdüğü şeydir; U+20BB7 gibi bir tamamlayıcı karakter iki kutu ve alan bütçesinin iki yerini yer
  • Glyph sayısı fonte bağlıdır: çoğu Arapça fontu lam ile alef'i tek glifte çevirir; dolayısıyla hücre sayısı, yazı tipini değiştirdiğiniz an değişirdi
  • Grafem sayısı, taban harfi işaretleriyle birleştirir; bu da ızgaranın, kullanıcının yazdığı her skalere sayan bir MaxLen kontrolüyle anlaşmazlığa düşmesi demektir

THPDFHeadlessDocument içindeki MaxLen doğrulaması, dizgeyi yalnızca UTF-16 uzunluğu sınırı aştığında gezdirir ve o zaman geçerli bir surrogate çiftini bir kez sayar, eşleşmemiş bir surrogate'i ise açıkça reddeder. Arayüzünüz çağrıdan sonra istisna yakalamak yerine önceden uyarmak istiyorsa aynı şekilde sayın

Delphi'de HotPDF comb hücreleri için UTF-16 birimlerinin, glyph ve grafemlerin Unicode skalerleriyle karşılaştırıldığı matris; lam-alef, beh artı fatha, U+20BB7 ve aksanlı e örnekleriyle
yalnızca Unicode skaler sütunu MaxLen kontrolüyle uyuşur: bir surrogate çifti bir kez sayılır ve hücre sayısı hiçbir zaman fonte bağlı kalmaz
// Comb ızgarasının yaptığı gibi Unicode skalerlerini sayar:
// geçerli bir surrogate çifti bir skalerdir
function CountScalars(const S: UnicodeString): Integer;
var
  I: Integer;
begin
  Result := 0;
  I := 1;
  while I <= Length(S) do
  begin
    if (Ord(S[I]) >= $D800) and (Ord(S[I]) <= $DBFF) and
       (I < Length(S)) and
       (Ord(S[I + 1]) >= $DC00) and (Ord(S[I + 1]) <= $DFFF) then
      Inc(I, 2)
    else
      Inc(I);
    Inc(Result);
  end;
end;

Birkaç değer, birimlerin ne kadar açıldığını ve neden yalnızca skaler sütununun comb alanının bütçelediğiyle eşleştiğini gösteriyor

DeğerUTF-16 birimiSkaler (hücre)Tipik glyph
Lam + alef (لا)221 ligatür
Beh + fatha (بَ)222, işaret harfe ekli
U+20BB7 (𠮷)211
Ayrıştırılmış e + U+0301221 ya da 2, fonta bağlı

Ligatürler ve birleştirici işaretler hücrelerini nasıl paylaşıyor?

k skalere yayılan şekillendirilmiş bir küme, k bitişik hücre kaplar ve tek bir birim olarak bunların ortasına yerleşir. Şekillendirme sonrası her küme, geldiği kaynak metnin tam aralığına geri eşlenir; böylece HotPDF hangi skalerleri ve dolayısıyla hangi hücreleri sahiplendiğini bilir. Yukarıdaki lam-alef ligatürü iki kutu alır ve ikisinin de ortasında durur. Shadda ve fatha taşıyan bir beh üç skalerdir; üç kutu alır, işaretler harfe ekli kalır

Ortalama, bariz uygulamanın yanlış gittiği yerdir. Her glifi kendi kutusunda ortalamak, işareti tabanından koparır; çünkü işaretin konumu, yalnızca süslediği harfe göreliyken anlam taşır. HotPDF her glifi, küme içinde şekillendirilmiş ilerlemesi ile X ve Y ofsetlerinde tutar ve kümeyi, doğal ilerlemenin, konumlanmış glif kökenlerinin ve gerçek glif mürekkebinin birleşik yatay kapsamıyla ortalar. Küme blok olarak taşınır; içindeki hiçbir şey yeniden dizilmez

Bir sonucu gözden kaçması kolaydır. U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK ve embedding ile isolate kontrol karakterleri gibi yönsel kontroller, glifsiz Unicode skalerleridir. MaxLen'e sayılır ve boş kalan bir hücre tutarlar. Kaynak veriniz bu işaretleri isimlere serpiştiren bir sistemden geliyorsa doldurmadan önce temizleyin; yoksa alan boşluk gösterir ve değerleri, kullanıcının beklediğinden bir karakter önce reddeder

Bidi yeniden düzenlemesi neden hücreler atanmadan önce yapılmalı?

HotPDF bütün satırı önce Unicode Bidirectional Algorithm'dan (UAX #9) geçirir ve ancak ondan sonra her skalere, görsel konumundaki hücreyi verir. Hücreleri mantıksal düzende atayıp sonucu sonra çevirmek, saf Arapça bir dizge için çalışır; satır rakam ya da bir Latin parçası içerir misiniz bozulur, çünkü o parçalar sağdan sola akan satırın içinde soldan sağa okunmayı sürdürmek zorundadır. Arapça önekli ve numaralı bir referans kodu gibi bir değer, rakamlarını yalnızca yeniden düzenleme bütün satırı birden görürse doğru sırada tutar. Karmaşık yazı sistemi metin şekillendirme genel bakışı, mantıksal ve görsel düzen sorununu daha derinden ele alıyor

Delphi için HotPDF'te rakamlı bir Arapça değer için comb hücre ataması: mantıksal düzen sayıyı ters çevirirken UAX 9 önce rakamları bozulmadan tutuyor
önce çift yönlü algoritmayı bütün satırda çalıştırın, sonra her skalere görsel konumundaki hücreyi verin; gömülü rakamlar düzenlerini korur

Hizalama, sonra kullanılan hücrelerin bütün ızgarasını alanın içinde konumlandırır. Alanın kalıtsal /Q girişi, mevcutken kazanır; Options.Alignment yalnızca formda hiçbiri yokken uygulanır. Sağdan sola formlar için bu önemlidir: sola hizalı bir comb alanında kısa bir Arapça değer, en soldaki kutuları doldurur; bu belirtim uyarınca doğrudur ve form tasarımcısının kastettiği şey neredeyse hiçbir zaman değildir. Formun /Q değerini düzeltin ya da alanın kendi quadding'i yokken htaRight kurun. Widget dönüşü /MK içindeki /R girişinden gelir ve 90 ya da 270 derecede mantıksal metin görünüm alanı genişlik ile yüksekliği değiştirirken hücre mantığı aynı kalır

Delphi'den karışık fontlu bir comb alanı doldurmak

Karışık fontlu comb metni, verilen her font için tek hücre ızgarası, tek font boyutu ve tek taban çizgisi kullanır. FallbackFontFamilies değeri True iken HotPDF her tam grafem için, verilen ailelerden yapılandırılmış sıralarında bir font seçer; böylece Arapça bir yüz harfleri taşıyabilirken Latin bir yüz gömülü ASCII'yi taşıyabilir. Her font parçası bütün satır bağlamıyla şekillendirilir, bir işaret fontlar arasında asla tabanından ayrılmaz ve bir font sınırının ötesinde ligatür icat edilmez. Her parçayı kendi yerel ızgarasıyla dizmek ya da kendi sınırlama kutusuna ölçeklemek daha basit olurdu; ama font düşüşü satır ortasında font değiştirdiği an hem hücre konumlarını hem döndürülmüş alanları bozardı

uses
  System.SysUtils, System.IOUtils,
  HPDFHeadlessDocument, HPDFHeadlessAppearance;

procedure FillArabicComb(const SourceFile, TargetFile, Value: string);
var
  Doc: THPDFHeadlessDocument;
  Field: THPDFHeadlessFormField;
  Families: THPDFHeadlessRichTextFontFamilies;
  Options: THPDFHeadlessTextAppearanceOptions;
  FieldIndex: Integer;
begin
  Doc := THPDFHeadlessDocument.Create;
  try
    Doc.LoadFromFile(SourceFile);
    FieldIndex := Doc.FindFormField('applicant.name_ar');
    if FieldIndex < 0 then
      raise Exception.Create('Field not found');
    Field := Doc.GetFormField(FieldIndex);
    if (Field.Flags and $1000000) = 0 then   // bit 25: Comb
      raise Exception.Create('Not a comb field');

    SetLength(Families, 2);                  // birden on altıya aile
    Families[0].FamilyName := 'Noto Naskh Arabic';
    Families[0].Fonts[hrfsRegular] :=
      TFile.ReadAllBytes('NotoNaskhArabic-Regular.ttf');
    Families[1].FamilyName := 'Noto Sans';
    Families[1].Fonts[hrfsRegular] :=
      TFile.ReadAllBytes('NotoSans-Regular.ttf');

    Options := THPDFHeadlessTextAppearanceOptions.Default;
    Options.ShapeComplexText := True;        // zaten varsayılan
    Options.FallbackFontFamilies := True;    // varsayılan False
    Options.FontSize := 0;                   // otomatik sığdırma, 12 ile sınırlı
    Options.Alignment := htaRight;           // yalnızca /Q yokken kullanılır

    Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
    Doc.SaveToFile(TargetFile);              // SourceFile'dan farklı olmalı
  finally
    Doc.Free;
  end;
end;

Örneğin ne kurmadığına dikkat edin. Belge doldurma sırasında Width, Height, Rotation ve CombCells, her widget dikdörtgeninden, /MK girişinden ve alanın MaxLen değerinden türetilir; o üyelere koyduğunuz değerlerin üzerine yazılır. Sıfır bir FontSize otomatik boyutlama ister; otomatik boyutlama her kümenin kapsamını, k hücresinin genişliğiyle katılan tüm fontların paylaşılan metrik ve mürekkep yüksekliğiyle karşılaştırır; sabit bir boyut basitçe widget içine kırpar. CharacterSpacing ve WordSpacing comb konumlarını hiçbir zaman kaydırmaz; bir kümenin nereye düşeceğine yalnızca ızgara karar verir. HotPDF HarfBuzz ve FriBidi çalışma zamanlarını yalnızca bir değer gerçekten şekillendirme istediğinde yükler ve şekillendirme gerektirmeyen değerler, mevcut doğrudan comb dizilimini değişmeden korur. Genel yüklenmiş PDF doldurma iş akışı, izinler ve kaydetme dahil, bkz. yüklenmiş bir PDF'te form alan değerlerini ayarlama

Şekillendirilmiş bir comb görünümü neyi garanti etmez?

Şekillendirilmiş bir comb görünümü, doğru hücre atamasını, bozulmamış kümeleri ve korunmuş mantıksal değeri garanti eder; her araçta kusursuz bitişik el yazısı bağlantılarını ya da kusursuz metin çıkarımını garanti etmez. İki sınır da comb alanının ne olduğundan çıkar ve bir müşteriye bir şey vaat etmeden önce bilmenizde fayda var

İlki, hücreler arası bitişik el yazısı bağlantılarıdır. Arapça harfler yine de bütün satır bağlamıyla şekillendirilir; her biri doğru başlangıç, orta ya da son biçimini alır, ama sabit hücre adımı ayrı kümeler arasında boşluk bırakır ve bağlayan darbeler, akan metinde birleştikleri gibi birleşmez. Birleştiren bir yazı sistemini kutulara çizmenin doğal sonucudur; ne kadar ortalarsa ortalasın gizlenmez

İkincisi metin çıkarımıdır. Alanın /V değeri sade mantıksal değeri tutar ve görünümün ana Form XObject'i girdinin tamamını taşıyan tek bir /ActualText parçası barındırır (ISO 32000-1 §14.9.4); çocuk Formlar hiçbirini taşımaz. ActualText'e saygı duyan çıkarıcılar orijinal dizgeyi geri kazanır. Yok sayan çıkarıcılar metni glif konumlarından yeniden kurar ve farklı bir düzen ya da kopyalanmış küme metni döndürebilir; çünkü çok glifli bir kümenin glif başına tek bariz Unicode değeri yoktur. Aşağı akış bir sistemin değere ihtiyacı varsa alanı okuyun, sayfa metnini değil

Kalan sınırlar mekaniktir. Comb metni sekmeler içermeyen tek satır olmak zorundadır ve hücrelerden daha çok skalere sahip bir değer kırpılmak yerine başarısız olur. Başarısızlıklar işlemseldir: eksik bir glif, geçersiz bir font, bütçe aşımı ya da bir iptal, önceki değeri ve görünümleri olduğu gibi bırakır; reddedilen bir doldurma asla yarısı çizilmiş bir alan arkasında bırakmaz

try
  Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
except
  on E: EHPDFHeadlessDocumentError do
    if E.Code = hdLimitExceeded then
      ShowFieldError('The value has more characters than the field allows')
    else
      raise;
  on E: EHPDFHeadlessAppearanceError do
    if E.Failure = haMissingGlyph then
      ShowFieldError('None of the supplied fonts covers this text')
    else
      raise;
end;
// İki hatada da alan önceki değerini ve görünümünü korur

Şekillendirilmiş comb desteği bir form hattında nereye oturuyor?

Şekillendirilmiş comb desteği en çok, çoktan var olan formlar için önem taşır: kutulu alanlarla tasarlanmış ve şimdi Arapça, İbranice, Farsça ya da Urduca doldurulan vergi beyannameleri, vize başvuruları, banka açılış paketleri ve kimlik kayıtları. Skalerde sayın, orijinal değeri /V içinde tutun, her fontu tek ızgaraya tabi tutun ve sağdan sola harfleri, rakamları ve bir birleştirici işareti karıştıran bir değerle test edin; o tek dizge bu yazıdaki her kuralı çalıştırır. Burada anlatılan headless belge API'si, comb şekillendirme ve font düşüşü, HotPDF Delphi PDF bileşeninin bir parçasıdır