Bir PDF'ten emoji veya bir Japon aile sicili adını metin olarak çekin, çıktı karakterin olması gereken yerde bir kutu, bir soru işareti veya hiçbir şey göstermez. PDFium Bileşeni'nin Character[] özelliği genellikle bunun nedenidir: her karakteri, işaretsiz 32 bitlik bir değer olarak tam bir Unicode kod noktası döndüren FPDFText_GetUnicode aracılığıyla okur, ardından bunu Delphi'ye tek bir 16 bitlik WideChar olarak sunar. U+FFFF'in ötesindeki herhangi bir kod noktası bu yolculuğu tek parça halinde yapamaz ve bozulma, render edilmiş sayfaya bakarken hiçbir zaman ortaya çıkmaz, çünkü render etme ve metin çıkarma PDFium içinde ayrı kod yollarından geçer -bir belge emojisini mükemmel şekilde gösterebilir ve yine de bir döngüde Character[]'ı okuyup ondan bir dize kurduğunuz an size çöp verebilir
Temel Çok Dilli Düzlem ve WideChar neden U+FFFF'te duruyor
Delphi'nin WideChar'ı yalnızca bir UTF-16 kod birimi tutabilen 16 bitlik bir türdür. Unicode'un Temel Çok Dilli Düzlemi, U+0000'dan U+FFFF'e kadar olan aralık, tam olarak bunun içine sığar; bu yüzden Latin, Kiril, Yunan ve yaygın CJK Unified Ideographs bloğunun tümü herhangi bir olay olmadan tek bir WideChar üzerinden gidip gelir. İki karakter ailesi gerçek belgelerde rutin olarak bunun dışına düşer: birçoğu U+1F600'de başlayan Emoticons bloğunda olan emoji ve CJK Unified Ideographs Extension B'den, birçok kişisel ve yer adı dahil daha az yaygın Çince, Japonca ve Korece karakterler için ayrılmış U+20000 ile U+2A6DF arasındaki aralıktan nadir CJK ideogramları. UTF-16, U+FFFF'in üzerindeki her şeyi bir vekil çift (surrogate pair) ile ele alır -$D800 ile $DBFF arasında bir yüksek vekil ve ardından $DC00 ile $DFFF arasında bir düşük vekil olmak üzere birlikte bir kod noktasını kodlayan iki 16 bitlik kod birimi- ve bu eşleştirmenin arkasındaki matematik, Pascal'da doğrudan gösterilecek kadar sabittir
function ToSurrogatePair(CodePoint: LongWord; out Hi, Lo: WideChar): Boolean;
var
V: LongWord;
begin
Result := CodePoint > $FFFF;
if Result then
begin
V := CodePoint - $10000;
Hi := WideChar($D800 + (V shr 10));
Lo := WideChar($DC00 + (V and $3FF));
end;
end;
Sırıtan yüz emojisi olan U+1F600'ü o fonksiyondan geçirin ve sonuç, tek değil iki 16 bitlik değer olan $D83D yüksek vekili ve $DE00 düşük vekilidir. İkisinin yarısı da tek başına hiçbir anlam ifade etmez; arkasında $DE00 olmadan bir dizede oturan yalnız bir $D83D, sarkan bir vekildir ve bir tanesiyle karşılaşan çoğu metin işleme kodu ya onu düşürür, bir yer tutucu karakterle değiştirir ya da bir hata fırlatır
FPDFText_GetUnicode Character[]'ın tutamayacağı bir değeri neden döndürüyor?
FPDFText_GetUnicode, tam 32 bitlik bir değer olan bir LongWord döndürür, çünkü bir PDF'in metin kodlaması zaten her karakter için tam Unicode skaler değerini taşır. Bir PDF'in ToUnicode CMap'i, karakter kodlarını Unicode metnine eşler ve bir karakter, gayri resmi olarak astral-düzlem karakteri denen şeyi -Temel Çok Dilli Düzlemin ötesindeki her şeyi- temsil ettiğinde, bu eşleme 16 bitlik bir parça değil tam bir kod noktasıdır. PDFium onu dahili olarak skaler bir değere geri çözer ve DLL sınırının ötesine FPDFText_GetUnicode aracılığıyla döndürür ve o sınır, tam olarak 32 bitlik bir değerin bir Delphi özelliğinin kodunuza geri verebileceği bir şeye dönüşmesi gereken yerdir
Bariz uygulama WideChar(FPDFText_GetUnicode(TextPage, Index))'dir ve bu aynı zamanda yanlış olanıdır. 32 bitlik bir değerden 16 bitlik bir türe sert bir dönüştürme, yalnızca alt 16 biti tutar ve gerisini hiçbir istisna ve hiçbir aralık kontrolü olmadan sessizce atar. U+1F600 için bu, $F600'ü tutmak ve gerçek değerin bir zamanlar U+FFFF'in üzerinde olduğu gerçeğini kaybetmek anlamına gelir; bu da geçerli bir sarkan vekil bile olmayan, yalnızca o alt bitleri paylaşan ilgisiz bir Temel Çok Dilli Düzlem karakteri üretir. Bunlardan birkaç binini bir dizede birleştirin ve aşağı akış kodunun bozuk bir karakteri meşru bir karakterden ayırt etmesinin artık hiçbir yolu kalmaz
Character[] ve Charcode[] şimdi astral-düzlem kod noktaları için ne döndürüyor
PDFium Bileşeni'nin Character[] ve Charcode[] özellikleri, altta yatan kod noktası U+FFFF'i aştığında, onu sessizce kırpmak yerine Unicode değiştirme karakteri olan U+FFFD'yi döndürür. Bu koruma, Character[]'ın arkasındaki özellik getter'ının doğrudan içinde oturur
function TPdf.GetCharacter(Index: Integer): WideChar;
var
Code: LongWord;
begin
LoadTextPage;
Code := FPDFText_GetUnicode(FTextPage, Index);
if Code > $FFFF then
Result := #$FFFD // astral-plane code point: cannot fit in one WideChar
else
Result := WideChar(Code);
end;
Kırpılmış bir parça yerine U+FFFD döndürmek, bir yeniden tasarım değil kasıtlı, dar bir düzeltmedir. Character[] ve Charcode[], hem TPdf hem de TPdfView üzerinde WideChar olarak tiplenmiştir ve o dönüş türünü tam bir kod noktası taşıyacak şekilde genişletmek, indeks başına bir karakterin 16 bitlik bir değer anlamına geldiğini bekleyen her mevcut çağıranı bozardı. U+FFFD, Unicode Standardı'nın tam olarak bu durum için belirlediği kendi yer tutucusudur; bu yüzden onu kontrol eden bir çağıran, sessizce yanlış veri yerine tanımlı, belgelenmiş bir sinyal alır. Bilinmeye değer bir sınır durumu: U+FFFD aynı zamanda kendi başına meşru bir karakterdir de; bu yüzden zaten gerçek bir değiştirme karakteri karakteri içeren nadir bir belgede, o indeks yalnızca değere bakarak kırpılmış bir astral karakterden ayırt edilemez
Delphi'de emoji ve CJK Extension B metnini nasıl doğru çıkarırsınız?
Gerçek metinsel içerik önemli olduğunda Character[]'ı dolaşmak yerine Text'i çağırın, çünkü Text, FPDFText_GetText aracılığıyla okur ve indeks başına sabit genişlikte bir değer yerine aralıktaki her astral-düzlem karakteri için düzgün vekil çiftleriyle tam bir WString döndürür. Pdf.Text(0, MaxInt), veya kısaltması Pdf.Text, tek bir çağrıda tüm bir sayfayı doğru şekilde çıkarır ve Pdf.Text(StartIndex, Count) aynı şekilde daha küçük bir aralığı çeker. Character[], yalnızca bir indekste konum, yazı tipi veya bayrak verisine ihtiyaç duyduğunuzda ve kod noktasının kendisine hiç dokunmadığınızda yerini hâlâ kazanır -CharacterOrigin[], FontSize[] ve CharacterMapError[], altta yatan karakterin astral olup olmadığını umursamaz
function ExtractLineSafely(Pdf: TPdf): WString;
var
I: Integer;
begin
Result := '';
for I := 0 to Pdf.CharacterCount - 1 do
if not (Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I]) then
Result := Result + Pdf.Text(I, 1); // full code point, never a truncated WideChar
end;
O döngüdeki üretilmiş-ve-eşlenmemiş-atla kontrolü, PDFium Bileşeni ile PDF belgelerinden metin çıkarma makalesinde düz metin çıkarma için kullanılan aynı örüntüdür; tek değişiklik, doğrudan bir Character[I] eklemesini Text'e tek indeksli bir çağrıyla takas eden son satırdır; böylece astral karakterler yer tutucu yerine tam vekil çiftleri olarak gelir
Bunun gerçekte ısırdığı yer: sohbet dışa aktarımları, kişisel adlar ve gömülü CJK yazı tipleri
Emoji, bir PDF'in gayri resmi iletişimi yakaladığı her yerde ortaya çıkar: dışa aktarılmış sohbet günlükleri, uygulama mağazası inceleme dökümleri, bir uyumluluk arşivi için PDF'e kaydedilmiş bilet sistemi transkriptleri. CJK Extension B, daha dar ama daha yüksek riskli bir yerde, kişisel ve yer adlarında ortaya çıkar, çünkü Japon aile sicilleri, Çin hane kayıt belgeleri ve Tayvan kimlik belgeleri, ortak CJK bloğuna hiç girmemiş karakterlerin klasik kaynaklarıdır. Taranmış devlet evraklarından ad çıkaran bir bordro veya kimlik doğrulama boru hattı, sessizce bozulmuş bir karakterin kozmetik bir aksaklık yerine başarısız bir eşleşmeye dönüştüğü tam olarak bu tür bir iş yüküdür
Nadir CJK ideogramları, yalnızca kodlama sorunlarıyla değil, yazı tipi sorunlarıyla da seyahat etme eğilimindedir, çünkü bir yazı tipi, herhangi bir şey render edilmeden önce bir U+20000 aralığı kod noktası için bir glif taşımak zorundadır ve kurulu az sayıda sistem yazı tipi bunu yapar. PDFium Bileşeni ile PDF yazı tipi özelliklerini okuma makalesinin tarif ettiği şekilde zaten karakter başına FontIsEmbedded[]'ı dolaşan herkes, aynı indeksi her iki sorun için birlikte kontrol etmelidir: Character[]'dan U+FFFD döndüren ve gömülü olmayan bir yazı tipi bildiren bir indeks, o karakteri ne doğru çıkaracak ne de doğru yazdıracak bir belgedir ve düzeltme sizin çıkarma kodunuzda değil, PDF'in nasıl üretildiğinde, akış yukarısında bulunur
Burada açıklanan Character[], Charcode[] ve Text özellikleri, Delphi ve C++Builder için standart PDFium Bileşeni'nin bir parçasıdır