Lewati ke konten
getnextpdf.com

Mengapa teks dalam sebuah PDF bukan benar-benar teks

Ketika Anda membaca sebuah PDF, Anda melihat kata-kata. Berkas itu tidak memuat kata-kata. Ia memuat instruksi untuk menggambar bentuk pada koordinat, dan bentuk-bentuk itu kebetulan tampak seperti huruf. Celah antara apa yang digambar sebuah PDF dan apa yang dimaksudkannya adalah alasan sebuah halaman bisa tampak sempurna namun tetap tersalin sebagai omong kosong. Halaman ini membahas celah itu, dan tentang peta kecil dan terpisah yang menutupnya.

Segala yang Anda lakukan pada sebuah PDF sebagai teks — menyeleksinya, menyalinnya, mencarinya, mengindeksnya untuk pengambilan, membacakannya dengan screen reader — bergantung pada pemulihan karakter yang tidak pernah disimpan secara langsung oleh berkas. Jika pemulihan itu gagal, kegagalannya tak terlihat. Halaman tetap dirender. Tidak ada yang menyadarinya sampai seseorang menyalin sebuah paragraf ke dalam email dan mendapatkan □□□□, atau mencari sebuah klausa yang jelas-jelas ada dalam kontrak 400 halaman dan tidak menemukan apa-apa.

Itu adalah kelas bug yang mahal justru karena ia lolos dari setiap peninjauan visual. Tidak ada yang bisa dilihat. Dokumen itu tampak otoritatif dan, untuk keperluan mesin, bisu.

  • Sebuah PDF menggambar glyph — bentuk visual yang dipilih oleh kode numerik ke dalam sebuah font — bukan karakter Unicode.
  • Bentuk yang sama dapat berarti karakter yang berbeda, dan karakter yang sama dapat digambar oleh glyph yang berbeda. Tampilan dan makna sengaja dipisahkan.
  • Untuk mengeluarkan teks kembali, sebuah reader menjalankan jalur penggambaran secara terbalik: kode ke karakter. CMap /ToUnicode font adalah tabel pencarian untuk langkah balik itu (Spec: ISO 32000-2, §9.10).
  • Tanpa /ToUnicode, atau dengan yang salah, salin-tempel menjadi kacau dan pencarian gagal — sementara halaman tetap sempurna piksel demi piksel.
  • Ini adalah persoalan makna. Apakah glyph tampak benar adalah persoalan tampilan yang terpisah, yang dibahas di Font: bagian yang sulit. NextPDF menghasilkan /ToUnicode yang benar sehingga perjalanan bolak-baliknya setia.

Mulailah dari bagaimana teks sampai ke halaman sama sekali. Sebuah content stream tidak mengatakan “tulis kata file.” Ia memilih sebuah font, lalu menyerahkan sederet kode ke sebuah text-showing operator (Spec: ISO 32000-2, §9). Setiap kode adalah sebuah indeks — sebuah posisi dalam program font — dan font mengubah indeks itu menjadi outline glyph dan menggambarnya. Kode 70 bukan karakter F. Ia adalah “slot 70 dalam font ini,” dan slot 70 kebetulan berisi sebuah kurva berbentuk F. Pilih font yang berbeda dan slot 70 bisa jadi adalah kepingan salju.

Jadi sebuah kode hanya bermakna relatif terhadap encoding fontnya. Untuk font sederhana, encoding itu kira-kira satu byte per glyph. Untuk aksara yang membutuhkan ribuan glyph — CJK, atau dokumen full-Unicode mana pun — itu tidak cukup ruang, dan PDF beralih ke composite (Type 0) font (Spec: ISO 32000-2, §9.7). Sebuah composite font membaca kode multi-byte melalui sebuah CMap, mengubahnya menjadi CID (character identifier), dan memetakan CID ke glyph. Itu adalah indireksi elegan yang memungkinkan satu font mengalamati himpunan glyph yang sangat besar. Itu juga satu tempat lagi di mana jejak dari “apa yang ditampilkan” ke “apa yang dimaksudkan” bisa menjadi buntu.

Sekarang jalankan secara terbalik. Untuk mengekstrak teks, sebuah reader mengambil kode yang ditemukannya dalam content stream dan harus memulihkan karakter (Spec: ISO 32000-2, §9.10). Encoding yang menggambar glyph berjalan karakter ke glyph; ekstraksi membutuhkan kode glyph ke karakter, dan arah itu tidak dijamin dapat dibalik. Sebuah subset font mungkin telah menomori ulang glyph-nya. CID sebuah composite font mungkin bersifat privat. Bentuk di halaman tidak membawa makna Unicode yang melekat.

Solusinya adalah mengirim peta balik bersama font. Peta itu adalah CMap /ToUnicode: untuk setiap kode yang digunakan dokumen, ia mencatat nilai Unicode (atau nilai-nilai) yang diwakili kode tersebut. Dengannya, ekstraksi menjadi pencarian yang bersih. Tanpanya, sebuah reader terpaksa menebak dari encoding font dan heuristik — dan menebak adalah persis tempat di mana fi menjadi tanda tanya.

  1. Your charactersThe Unicode text you set, for example the word 'file'.
  2. Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
  3. Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
  4. Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
  5. Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
Dua arah teks PDF. Rendering berjalan karakter → glyph dan itulah yang Anda lihat. Ekstraksi berjalan kode → karakter dan itulah yang dapat Anda salin, cari, dan bacakan dengan lantang — dan ia hanya bekerja ketika peta /ToUnicode menutup loop-nya.

NextPDF menuliskan peta balik itu sebagai hal yang lumrah. Ketika ia menyematkan sebuah composite font, ia menghasilkan turunan CIDFontType2, induk Type0, dan sebuah CMap /ToUnicode sehingga kode dipetakan kembali ke Unicode — pekerjaan encoding yang dijelaskan di Font: bagian yang sulit. Hal yang patut dibedakan di sini adalah mengapa: stream /ToUnicode bukan tentang membuat halaman tampak benar. Glyph-nya sudah tampak benar tanpanya. Ia adalah satu-satunya artefak yang menjaga teks tetap dapat diekstrak.

Tempat paling terkenal di mana ini muncul adalah ligatur fi. Banyak font menggambar f dan i sebagai satu glyph gabungan, karena titik pada i bertabrakan dengan kait pada f. Di halaman, ia adalah satu bentuk, digambar oleh satu kode. Pertanyaannya adalah apa yang terjadi ketika Anda menyalinnya.

% A /ToUnicode entry that maps the single ligature code to TWO characters,
% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.
1 beginbfchar
<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'
endbfchar

Satu entri itu adalah perbedaan antara pencarian “file” yang cocok dan pencarian “file” yang diam-diam meleset dari setiap kemunculan yang dirender dengan ligatur. Petakan kode ligatur ke urutan dua karakter f + i, dan kata itu dapat dicari dan disalin. Biarkan ia tak terpetakan, dan halaman tetap menampilkan file dengan sempurna sementara teksnya diam-diam tidak menyatakan apa pun yang dapat dibaca mesin.

Inilah mengapa “fi” bisa terekstrak sebagai satu karakter atau dua, dan mengapa perbedaannya bukan acak — ia adalah apa pun yang diperintahkan untuk dinyatakan oleh peta /ToUnicode. Sebuah peta yang benar menguraikan ligatur kembali menjadi karakter-karakternya. NextPDF menghasilkan persis entri jenis itu, sehingga sebuah ligatur di halaman menjadi dua huruf biasa di clipboard.

Jebakannya, dinamai: “teksnya dirender, jadi teksnya baik-baik saja.” Rendering dan ekstraksi adalah mekanisme berbeda yang membaca data berbeda. Rendering mengikuti jalur kode-ke-glyph dan itulah yang diperiksa mata Anda. Ekstraksi mengikuti jalur kode-ke-karakter melalui /ToUnicode dan itulah yang diperiksa setiap konsumen mesin. Sebuah dokumen bisa unggul pada yang pertama dan gagal total pada yang kedua, karena secara konstruksi tidak ada apa pun secara visual yang mengungkapnya.

Jebakan kedua yang lebih halus: mengasumsikan sebuah glyph “jelas-jelas” tahu ia karakter yang mana. Ia tidak tahu. Sebuah glyph adalah bentuk dengan sebuah indeks. Pemetaan kembali ke sebuah karakter adalah informasi tambahan yang harus disediakan penghasilnya. Jika penghasil tidak pernah menuliskannya, tidak ada reader yang dapat memulihkannya dengan setia — ia hanya bisa menebak.

Faithful text extraction from documents NextPDF writes — edition availability
EditionAvailability
CoreNextPDF emits a correct /ToUnicode CMap for the fonts it embeds, including the ligature and composite-font cases, so the documents it produces are searchable and copyable.
ProNot in this edition
EnterpriseNot in this edition

Sebuah peta /ToUnicode hanya dapat membawa informasi yang memang diekspos oleh font sumber. Di mana sebuah glyph tidak memiliki karakter yang dapat ditentukan — sebuah tanda murni dekoratif, sebuah simbol private-use tanpa penetapan Unicode — tidak ada peta yang dapat menciptakan makna yang sejak awal tidak ada. NextPDF menghasilkan pemetaan yang jujur yang dapat diturunkannya; ia tidak mengarang karakter untuk mengisi sebuah celah.

Halaman ini membahas dokumen yang ditulis NextPDF. Ia bukan alat perbaikan untuk PDF masukan sembarang yang /ToUnicode-nya sudah hilang atau salah; memulihkan teks dari yang seperti itu adalah persoalan heuristik yang terpisah. Dan ekstraksi yang setia memang perlu untuk aksesibilitas namun bukan keseluruhannya — urutan baca, tag, dan teks alternatif dibahas di Apa yang membuat sebuah PDF dapat diakses.

Mengapa PDF yang sama tersalin baik di satu reader dan buruk di reader lain? Reader yang berbeda melakukan fallback secara berbeda ketika /ToUnicode hilang. Sebagian menebak dari encoding bawaan font dan beruntung pada teks Latin umum; sebagian lain tidak. Sebuah /ToUnicode yang benar menghilangkan undian itu — setiap reader yang konforman mendapatkan karakter yang sama.

Apakah ini hal yang sama dengan font yang tidak disematkan? Tidak. Penyematan adalah tentang tampilan — apakah glyph tergambar tanpa font terpasang. /ToUnicode adalah tentang makna — apakah kode dipetakan kembali ke sebuah karakter. Sebuah font bisa tersemat dengan sempurna namun tetap tidak memiliki /ToUnicode yang dapat dipakai, yang merupakan kegagalan tak-dapat-dicari-tetapi-cantik.

Apakah ekstraksi pernah membutuhkan lebih dari satu karakter per kode? Ya — kasus ligatur adalah persis itu: satu kode dipetakan ke dua karakter. Sebuah entri /ToUnicode dapat memetakan satu kode ke sebuah urutan pendek, dan itulah cara ligatur, serta sebagian bentuk komposit, kembali sebagai huruf-huruf penyusunnya.

  • Glyph — sebuah bentuk visual dalam sebuah font (sebuah outline). Apa yang sebenarnya digambar sebuah PDF. Sebuah glyph memiliki indeks tetapi tidak ada makna karakter yang melekat.
  • Karakter — sebuah unit bahasa tertulis, diidentifikasi oleh sebuah code point Unicode. Apa yang Anda maksudkan, dan apa yang coba dipulihkan ekstraksi.
  • Kode — nilai numerik dalam sebuah content stream yang memilih sebuah glyph dari font saat ini. Bukan karakter Unicode; bermakna hanya relatif terhadap fontnya.
  • CID — sebuah character identifier yang digunakan oleh composite font untuk mengalamati sebuah glyph dalam himpunan besar; sebuah langkah antara antara kode dan glyph.
  • Composite (Type 0) font — sebuah font yang membaca kode multi-byte melalui sebuah CMap untuk mencapai CID dan glyph, digunakan ketika satu font harus mengalamati ribuan glyph.
  • /ToUnicode — stream CMap yang memetakan kode yang digunakan sebuah dokumen kembali ke nilai Unicode; yang membuat teks PDF dapat dicari, disalin, dan diakses.
  • Ligatur — sebuah glyph tunggal yang menggambar dua huruf atau lebih sebagai satu bentuk (misalnya fi); entri /ToUnicode-nya menguraikannya kembali menjadi karakter-karakternya.