Tarik emoji atau sebuah nama register-keluarga Jepang dari sebuah PDF sebagai teks, dan outputnya menampilkan sebuah kotak, sebuah tanda tanya, atau tidak ada apa-apa di tempat karakter tersebut seharusnya berada. Properti Character[] milik PDFium Component biasanya menjadi penyebabnya: ia membaca setiap glyph lewat FPDFText_GetUnicode, yang mengembalikan sebuah code point Unicode penuh sebagai sebuah nilai unsigned 32-bit, lalu mengeksposnya ke Delphi sebagai satu WideChar 16-bit tunggal. Code point mana pun di atas U+FFFF tidak bisa melakukan perjalanan itu dalam satu potongan, dan kerusakannya tidak pernah terlihat saat Anda melihat halaman yang dirender, karena rendering dan ekstraksi teks berjalan lewat jalur kode terpisah di PDFium — sebuah dokumen bisa menampilkan emoji-nya dengan sempurna dan tetap menyerahkan sampah kepada Anda begitu Anda membaca Character[] dalam sebuah loop dan membangun sebuah string darinya
Basic Multilingual Plane dan mengapa WideChar berhenti di U+FFFF
WideChar milik Delphi adalah sebuah tipe 16-bit yang hanya bisa memegang satu unit kode UTF-16. Basic Multilingual Plane milik Unicode, rentang U+0000 hingga U+FFFF, pas persis di dalamnya, itulah sebabnya Latin, Cyrillic, Yunani, dan blok CJK Unified Ideographs umum semuanya melakukan round-trip lewat satu WideChar tunggal tanpa insiden. Dua keluarga karakter secara rutin jatuh di luar itu dalam dokumen sungguhan: emoji, banyak di antaranya dalam blok Emoticons mulai dari U+1F600, dan ideograf CJK langka dari CJK Unified Ideographs Extension B, rentang U+20000 hingga U+2A6DF yang dicadangkan untuk karakter Cina, Jepang, dan Korea yang kurang umum termasuk banyak nama pribadi dan nama tempat. UTF-16 menangani apa pun di atas U+FFFF dengan sebuah surrogate pair — dua unit kode 16-bit, sebuah high surrogate dalam rentang $D800 hingga $DBFF diikuti sebuah low surrogate dalam $DC00 hingga $DFFF, yang bersama-sama meng-encode satu code point — dan matematika di balik pemasangan itu cukup tetap untuk didemonstrasikan langsung dalam Pascal
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;
Masukkan U+1F600, emoji grinning-face, lewat fungsi itu dan hasilnya adalah sebuah high surrogate $D83D dan sebuah low surrogate $DE00, dua nilai 16-bit, bukan satu. Tak satu pun dari kedua separuh itu berarti apa-apa dengan sendirinya; sebuah $D83D tunggal yang duduk dalam sebuah string tanpa $DE00 di belakangnya adalah sebuah dangling surrogate, dan kebanyakan kode penanganan-teks yang menemuinya baik membuangnya, mensubstitusi sebuah glyph pengganti, atau memunculkan sebuah error
Mengapa FPDFText_GetUnicode mengembalikan sebuah nilai yang tidak bisa dipegang Character[]?
FPDFText_GetUnicode mengembalikan sebuah LongWord, sebuah nilai 32-bit penuh, karena encoding teks PDF sudah membawa nilai scalar Unicode lengkap untuk setiap glyph. Sebuah CMap ToUnicode milik PDF memetakan kode karakter ke teks Unicode, dan ketika sebuah glyph merepresentasikan apa yang secara informal disebut karakter astral-plane — apa pun melewati Basic Multilingual Plane — pemetaan itu adalah sebuah code point penuh, bukan sebuah fragmen 16-bit. PDFium mendekodenya kembali ke sebuah nilai scalar secara internal dan mengembalikannya melintasi batas DLL lewat FPDFText_GetUnicode, dan batas itu persis tempat sebuah nilai 32-bit harus menjadi sesuatu yang bisa diserahkan kembali sebuah properti Delphi ke kode Anda
Implementasi yang jelas adalah WideChar(FPDFText_GetUnicode(TextPage, Index)), dan itu juga yang salah. Sebuah cast keras dari sebuah nilai 32-bit ke dalam sebuah tipe 16-bit hanya mempertahankan 16 bit rendah dan membuang sisanya secara diam-diam, tanpa exception dan tanpa pemeriksaan rentang. Untuk U+1F600 itu berarti mempertahankan $F600 dan kehilangan fakta bahwa nilai sebenarnya pernah di atas U+FFFF, yang menghasilkan sebuah unit kode yang bahkan bukan sebuah dangling surrogate yang valid, hanya sebuah karakter Basic Multilingual Plane tak-berkaitan yang kebetulan berbagi bit rendah itu. Rangkaikan beberapa ribu dari itu ke dalam sebuah string dan kode hilir tidak memiliki cara lagi untuk membedakan sebuah karakter yang rusak dari yang sah
Apa yang dikembalikan Character[] dan Charcode[] untuk code point astral-plane sekarang
Properti Character[] dan Charcode[] milik PDFium Component mengembalikan U+FFFD, karakter pengganti Unicode, kapan pun code point yang mendasari melebihi U+FFFF, alih-alih diam-diam memotongnya. Penjaga itu duduk langsung di dalam property getter di balik Character[]
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;
Mengembalikan U+FFFD alih-alih sebuah fragmen terpotong adalah sebuah perbaikan yang disengaja dan sempit alih-alih sebuah desain ulang. Character[] dan Charcode[] bertipe WideChar baik pada TPdf maupun TPdfView, dan melebarkan tipe kembalian itu untuk membawa sebuah code point penuh akan merusak setiap pemanggil yang ada yang mengharapkan satu glyph per indeks berarti satu nilai 16-bit. U+FFFD adalah placeholder resmi yang ditetapkan Unicode Standard sendiri persis untuk situasi ini, sehingga seorang pemanggil yang memeriksanya mendapatkan sebuah sinyal yang terdefinisi dan terdokumentasi alih-alih data yang diam-diam salah. Satu kasus batas layak diketahui: U+FFFD juga sebuah karakter sah dalam haknya sendiri, sehingga pada dokumen langka yang memang sudah mengandung sebuah glyph karakter-pengganti sungguhan, indeks itu tidak bisa dibedakan dari sebuah karakter astral yang terpotong hanya berdasarkan nilainya
Bagaimana Anda mengekstrak teks emoji dan CJK Extension B dengan benar di Delphi?
Panggil Text alih-alih menelusuri Character[] kapan pun konten tekstual sesungguhnya penting, karena Text membaca lewat FPDFText_GetText dan mengembalikan sebuah WString penuh dengan surrogate pair yang tepat untuk setiap karakter astral-plane dalam rentang, alih-alih satu nilai lebar-tetap per indeks. Pdf.Text(0, MaxInt), atau singkatannya Pdf.Text, mengekstrak seluruh halaman dengan benar dalam satu pemanggilan, dan Pdf.Text(StartIndex, Count) menarik sebuah rentang lebih kecil dengan cara yang sama. Character[] masih layak digunakan ketika Anda hanya membutuhkan data posisi, font, atau flag pada sebuah indeks dan tidak pernah menyentuh code point itu sendiri — CharacterOrigin[], FontSize[], dan CharacterMapError[] tidak peduli apakah glyph yang mendasarinya astral
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;
Pemeriksaan skip-generated-and-unmapped dalam loop itu adalah pola yang sama yang digunakan untuk ekstraksi teks-polos di mengekstrak teks dari dokumen PDF dengan PDFium Component; satu-satunya perubahan adalah baris terakhir, yang menukar sebuah append Character[I] langsung dengan sebuah pemanggilan satu-indeks ke Text sehingga karakter astral tiba sebagai surrogate pair lengkap alih-alih placeholder pengganti
Di mana ini sebenarnya menggigit: ekspor chat, nama pribadi, dan font CJK yang disematkan
Emoji muncul di mana pun sebuah PDF menangkap komunikasi informal: log chat yang diekspor, dump review app-store, transkrip sistem-tiket yang disimpan ke PDF untuk sebuah arsip kepatuhan. CJK Extension B muncul di tempat yang lebih sempit tetapi berisiko lebih tinggi, nama pribadi dan tempat, karena register keluarga Jepang, catatan registrasi rumah tangga Cina, dan dokumen identitas Taiwan adalah sumber klasik karakter yang tidak pernah masuk ke dalam blok CJK umum. Sebuah pipeline penggajian atau verifikasi-identitas yang mengekstrak nama dari dokumen pemerintah hasil scan persis merupakan jenis beban kerja di mana sebuah karakter yang diam-diam rusak berubah menjadi sebuah kegagalan pencocokan alih-alih sekadar gangguan kosmetik
Ideograf CJK langka juga cenderung membawa masalah font, bukan hanya encoding, karena sebuah font harus membawa sebuah glyph untuk sebuah code point rentang-U+20000 sebelum apa pun bisa dirender sama sekali, dan sedikit font sistem yang terinstal melakukannya. Siapa pun yang sudah menelusuri FontIsEmbedded[] per karakter dengan cara yang dijelaskan membaca properti font PDF dengan PDFium Component seharusnya memeriksa indeks yang sama untuk kedua masalah sekaligus: sebuah indeks yang mengembalikan U+FFFD dari Character[] dan melaporkan sebuah font non-embedded adalah sebuah dokumen yang tidak akan mengekstrak atau mencetak karakter itu dengan benar, dan perbaikannya berada di hulu dalam bagaimana PDF itu diproduksi, bukan dalam kode ekstraksi Anda
Properti Character[], Charcode[], dan Text yang dijelaskan di sini adalah bagian dari PDFium Component standar untuk Delphi dan C++Builder