HotPDF Delphi Component membangun ulang spasi kata dan line break di THotPDF.ExtractLoadedPageText dari geometri glyph, bukan dari karakter spasi. Spasi masuk ketika celah setelah lebar glyph sendiri melebihi 0,15 dari tinggi teks, dan baris baru hanya mulai ketika origin teks bergerak melintasi arah tulisan lebih dari setengah tinggi teks. Sejak v2.768.3 teks halaman juga menyertakan teks yang digambar lewat Form XObject dan membuang glyph di luar area crop yang terlihat. Sisa tulisan ini menjelaskan kenapa tiap aturan berbentuk seperti sekarang, karena semuanya menggantikan aturan yang lebih sederhana yang menghasilkan output terlihat masuk akal tapi salah pada dokumen nyata
Gejalanya familier bagi siapa pun yang pernah memasukkan teks PDF ke search index. Halaman sampul terekstrak sebagai PDFReferenceManualNovember4,1998, formulir pajak terpecah menjadi 156 baris, watermark diagonal datang satu karakter per baris, dan proof yang dipangkas dipimpin slug line printer yang tak pernah ditampilkan viewer mana pun. Tak satu pun dari file-file itu rusak. Masing-masing memakai cara penempatan teks yang sepenuhnya legal yang salah dibaca oleh extractor naif
Kenapa teks PDF hasil ekstraksi kehilangan spasi katanya?
Teks hasil ekstraksi kehilangan spasi kata karena PDF tak pernah diwajibkan memuatnya. Produser bisa memisahkan kata dengan menampilkan karakter spasi, tapi sama mudahnya menggerakkan pena dengan angka di dalam array TJ (ISO 32000-1 §9.4.3) atau dengan Td segar (§9.4.2), dan output TeX, banyak file Distiller, serta sebagian besar layout justified memang melakukan persis itu. Sebelum v2.766.76, HPDFAssemblePageText hanya melirik pergerakan vertikal, jadi pemisah kata yang dibuat oleh positioning begitu saja lenyap. Assembler kini mengukur, sepanjang arah tulisan glyph sebelumnya, jarak dari ujung lebar glyph itu sendiri ke origin glyph saat ini, dan menyisipkan satu spasi ketika jaraknya melebihi 0,15 dari tinggi kotak glyph saat ini, diukur dari ascent ke descent di user space. Tak ada spasi ditambahkan ketika salah satu sisi sudah kosong, dan tidak pula di antara dua karakter CJK, karena justification merenggangkan ideograf tanpa renggangan itu berarti batas kata. Record glyph memaparkan geometri yang sama, jadi Anda bisa mereproduksi keputusannya ketika file tertentu membuat Anda bingung
uses
SysUtils, HPDFDoc, HPDFContentStream;
procedure DumpWordGaps(Pdf: THotPDF; PageIndex: Integer);
var
Glyphs: THPDFGlyphArray;
I: Integer;
Height, Gap: Double;
begin
if not Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
Exit;
for I := 1 to High(Glyphs) do
begin
// tinggi kotak glyph dari ascent ke descent, di user space
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// teks horizontal: celah dari ujung lebar glyph sebelumnya sendiri
Gap := Glyphs[I].BaselineStartX - Glyphs[I - 1].GlyphEndX;
if (Height > 0) and (Gap > 0.15 * Height) then
Writeln(Format('U+%.4x gap %.2f height %.2f: space',
[Glyphs[I].Unicode, Gap, Height]));
end;
end;
Kenapa mengukur dari lebar glyph sendiri alih-alih posisi pena?
HotPDF mengukur celah kata dari GlyphEndX / GlyphEndY karena posisi pena setelah sebuah glyph sudah memuat spacing yang bukan celah. ISO 32000-1 §9.4.4 mendefinisikan perpindahan horizontal sebagai lebar glyph kali ukuran font, plus character spacing Tc, plus word spacing Tw, semuanya diskalakan oleh Tz. BaselineEndX / BaselineEndY menyimpan perpindahan penuh itu, sedangkan GlyphEndX / GlyphEndY hanya menyimpan advance font dan Tz. Pembedaannya penting untuk produser yang mengencangkan tracking dengan Tc negatif lalu mengembalikan ruangnya lewat penyesuaian TJ setelah setiap glyph: diukur dari posisi pena, pengembaliannya tampak seperti celah, dan istilah Mandarin “95后” terekstrak sebagai “9 5 后”. Threshold-nya dikaitkan ke tinggi kotak glyph alih-alih ke ukuran Tf dengan alasan serupa. Export Word sering menulis 1 Tf dan membawa ukuran sebenarnya di Tm yang diskalakan, jadi Tfs mengatakan 1 sementara teksnya setinggi 10 poin, dan aturan yang dikunci ke Tfs akan memperlakukan kedua ejaan halaman yang sama secara berbeda
Aturan ini punya tepi yang jujur. Judul yang disetel dengan tracking sangat renggang, yang Tc-nya saja membuka lebih dari 0,15 tinggi teks di antara huruf, terekstrak dengan spasi di antara setiap huruf — itulah yang tampak di halaman, tapi mungkin bukan yang ingin Anda indeks. Potongan yang digambar tak berurutan pada satu baseline menghasilkan celah negatif dan menyatu tanpa spasi. Kedua kasus tak umum di teks badan, dan di corpus test perubahan ini menaikkan kecocokan kata terhadap extractor referensi di 28 halaman tanpa menurunkan satu pun
Kapan HotPDF memulai baris baru di teks hasil ekstraksi?
Sejak v2.766.79, baris baru dimulai ketika pergerakan dari origin glyph sebelumnya ke yang sekarang, diproyeksikan ke normal arah tulisan sebelumnya, melebihi setengah dari tinggi kotak yang lebih besar di antara kedua glyph. Aturan lama membandingkan pergerakan Y mentah dengan setengah Tfs, yang gagal ke dua arah. Dengan 1 Tf dan Tm yang diskalakan, threshold menyusut menjadi setengah unit, sehingga superscript yang dinaikkan text rise 0,4 atau jitter baseline yang biasa saja memecah baris. Aturan itu juga mengabaikan X sepenuhnya, sehingga teks di bawah Tm terotasi turun melintasi halaman dengan setiap glyph dan keluar satu glyph per baris. Proyeksi ke normal arah membuat run terotasi berperilaku seperti yang horizontal, dan mengambil tinggi yang lebih besar dari keduanya menjaga kata sampel besar beserta keterangannya yang kecil di satu baris ketika mereka berbagi baseline. Di formulir pajak yang disebut di atas, jumlah baris turun dari 156 menjadi 97. Teks vertikal dalam writing mode 1 (§9.7.4.3) mengikuti jalur terpisah: glyph-glyph itu dikelompokkan menjadi kolom, dibaca kanan ke kiri dan atas ke bawah, dengan line break di setiap pergantian kolom
Teks mana yang disertakan dan ditinggalkan ExtractLoadedPageText?
ExtractLoadedPageText mengembalikan teks yang ditampilkan viewer. Sejak v2.766.80 ia bekerja hanya dari glyph yang terlihat, membuang setiap glyph yang pusat kotaknya jatuh di luar GetLoadedPageVisibleBox, yaitu CropBox yang di-clip ke MediaBox (§14.11.2). Itu menghilangkan slug line dan tanda printer lain yang disetel sebagai teks di luar area trim. ExtractLoadedPageGlyphs sengaja tetap mengembalikan setiap glyph dari content stream halaman, jadi Anda masih bisa menemukan materi itu ketika membutuhkannya. Filternya tes kotak, bukan tes keterlihatan: teks yang disembunyikan clipping path, digambar putih, atau tertutup gambar tetap terekstrak
var
Pdf: THotPDF;
Glyphs: THPDFGlyphArray;
PageText: UnicodeString;
L, B, R, T: Single;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('trimmed-proof.pdf');
if Pdf.GetLoadedPageVisibleBox(0, L, B, R, T) then
Writeln(Format('Visible box: %.1f %.1f %.1f %.1f', [L, B, R, T]));
// setiap glyph dari content stream halaman, slug line termasuk
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// hanya yang ditampilkan halaman, dengan teks Form XObject disisipkan
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
Teks yang digambar lewat Form XObject menjadi bagian teks halaman sejak v2.768.3. Header, stamp, dan watermark sangat sering tinggal di form, dan beberapa dokumen standar kehilangan 30 sampai 35 persen karakternya sebelum perubahan ini. THotPDF.InterpretContentWithForms mencatat setiap Do bersama CTM yang berlaku, menginterpretasi form pada /Matrix-nya kali CTM itu (§8.10.1), dan menyisipkan glyph milik form di posisi Do tersebut, berekursi ke form bersarang. Form tanpa /Resources sendiri meminjam milik stream yang menggambarnya, seperti diizinkan §7.8.3. Glyph form membawa TokenIndex = -1, dan ExtractLoadedPageGlyphs tetap hanya mengembalikan glyph page-stream, karena search, replace, dan redaksi menulis perubahan kembali lewat TokenIndex dan akan menyunting byte yang salah kalau glyph form menyelinap masuk. Dua penyederhanaan layak diketahui: teks form tak di-clip ke /BBox form, dan rekursi berhenti di 12 level alih-alih lewat deteksi siklus, jadi form malformed yang menggambar dirinya sendiri mengulang teksnya sampai mencapai batas itu
Kenapa teks setelah operator Q terdekode sebagai sampah?
Teks setelah Q bisa terdekode salah sebelum v2.766.73 karena extractor hanya menyimpan CTM saat q. Parameter text state — font, ukuran, Tc, Tw, Tz, TL, rendering mode, dan rise — adalah bagian graphics state (§9.3.1), jadi Q harus memulihkannya bersama semua yang lain di stack (§8.4.2). Satu laporan industri memilih font Identity-H dua byte di dalam q … Q lalu menampilkan teks WinAnsi satu byte tanpa Tf miliknya sendiri. Extractor menyimpan font bagian dalam itu, membaca leader dan kata “Adobe” di daftar isi sebagai kode dua byte, dan membuang 15% karakter halamannya. Stack q/Q milik interpreter kini menyimpan text state penuh. Aturan ekstraksi yang dijelaskan di sini berlaku untuk setiap halaman, jadi satu dokumen utuh bisa masuk ke file dalam satu panggilan
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// rentang kosong = semua halaman; form feed antar halaman; BOM UTF-8
Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
Writeln(Pages, ' pages extracted');
finally
Output.Free;
end;
end;
API teks HotPDF mana yang sebaiknya Anda pakai?
ExtractLoadedPageText tetap dalam urutan content stream, yang merupakan default yang tepat untuk search dan indexing; rantai decoding di bawahnya dibahas di mengekstrak teks dari PDF termuat dengan HotPDF. Untuk dokumen bertag yang urutan pembuatannya penting, ekstraksi teks urutan struktur menelusuri structure tree alih-alih menebak dari geometri, dan untuk data yang terkunci di tabel, ekstraksi tabel bertipe lintas pergantian halaman mengembalikan sel alih-alih baris. Referensi API lengkap dan unduhan trial ada di halaman produk HotPDF Delphi PDF Component