PDFium Component versi 3.117.0 menyambungkan tabel yang terpotong batas halaman ketika kedua fragmennya menyentuh tepi halaman, atau ketika tidak ada teks body di bawah fragmen pertama dan di atas fragmen kedua, dengan running header dan footer diabaikan. ExtractDocumentTables menerapkan uji content-aware itu sebagai alternatif uji page-margin yang lebih lama, menolak fragmen halaman berikutnya yang baris pertamanya satu sel caption selebar penuh, dan mempertahankan satu baris yang meluber ke halaman berikutnya sebagai bagian dari rantai sambungannya
Artikel deteksi dan ekstraksi tabel menyajikan continuation sebagai empat gate ketat dan memperlakukan "menyentuh tepi halaman" sebagai salah satunya. Deskripsi itu akurat untuk rilis yang dibahasnya, dan juga keliru untuk sebagian besar tabel yang benar-benar disuapkan orang ke komponen ini. Artikel ini adalah koreksinya: dokumen seperti apa yang tidak bisa ditangani uji margin, apa yang menggantikannya, dan dua kasus sampingan yang ikut terbawa oleh perbaikannya
Kenapa uji page-margin gagal pada ekspor Word?
Uji page-margin gagal karena word processor berhenti menata barisnya di batas bawah margin, bukan di tepi kertas. Dengan ContinuationMargin default 36 poin, aturan aslinya menuntut tepi bawah fragmen sebelumnya berada dalam 36 poin dari dasar halaman dan tepi atas fragmen berikutnya dalam 36 poin dari puncak halaman. Dokumen yang diekspor dari Word dengan margin satu inci default-nya menaruh baris terakhir setidaknya 72 poin di atas dasar halaman, lebih jauh lagi kalau ada footer, jadi syarat itu tidak pernah terpenuhi. Setiap tabel panjang di dokumen seperti itu kembali sebagai fragmen-fragmen mandiri dengan ContinuationGroup nol, dan pemanggilnya kembali menjahit dengan tangan. Uji itu tetap masuk akal untuk apa yang jadi rancangan awalnya: laporan yang dihasilkan layout engine yang mengisi halaman sampai kotak konten tetap lalu memulai halaman berikutnya rapat di puncak. Ia bukan aturan yang buruk, ia aturan yang tidak lengkap, itulah sebabnya versi 3.117.0 mempertahankannya dan menambahkan jalur kedua alih-alih menggantinya
Apa yang diperiksa uji content-aware sebagai gantinya?
Uji content-aware memeriksa apakah ada sesuatu selain tabel yang menempati ruang di antara kedua fragmen, memakai word box setiap halaman alih-alih geometri halamannya. Selama ExtractDocumentTables menelusuri dokumennya ia mencatat, per halaman, tepi bawah terendah dari kata mana pun yang puncaknya berada di atas band footer dan tepi atas tertinggi dari kata mana pun yang dasarnya berada di bawah band header. Kedua band-nya sedalam ContinuationMargin poin, sehingga opsi yang sama kini merangkap dua tugas: sebagai kelonggaran tepi halaman dan sebagai tinggi zona running header serta footer. Sepasang fragmen lolos ketika tepi bawah fragmen sebelumnya berada di atau di bawah teks body terendah di halamannya dan tepi atas fragmen berikutnya berada di atau di atas teks body tertinggi di halaman berikutnya, masing-masing dalam toleransi AlignmentTolerance. Sederhananya: tabel itu adalah hal terakhir di halaman N dan hal pertama di halaman N+1, dan nomor halaman atau judul dokumen di band margin tidak dihitung. Pengecualian itu bukan sembarangan. ISO 32000-1 §14.8.2.2 mengklasifikasikan running header dan footer sebagai artefak paginasi, konten yang ada karena page break, bukan meskipun ada page break, dan gagasan yang sama yang membuat tagged reader bisa melewatinya juga yang membuat tabel bisa berlanjut melewatinya. Artikel marked content membahas bagaimana file bertag mendeklarasikan artefak itu secara eksplisit; di sini klasifikasinya disimpulkan dari posisi, karena sebagian besar tabel hasil ekspor tidak membawa tag sama sekali
Kedua uji itu bergabung dengan OR. Laporan layout engine yang tabelnya mencapai tepi kertas lolos uji pertama; ekspor Word yang tabelnya berhenti di margin lolos uji kedua; dokumen yang memenuhi keduanya lolos dua kali. Baru setelah salah satunya berhasil, gate sisanya berjalan, dan urutannya tetap: nomor halamannya harus berdampingan, fragmen berikutnya tidak boleh dibuka baris caption, dan batas kolomnya harus cocok dalam dua kali AlignmentTolerance, yaitu 6 poin pada nilai default-nya. Enumerasinya adalah TPdfTableContinuation dengan nilai ptcNone, ptcStart, ptcMiddle, dan ptcEnd. Fragmen yang ditandai ptcEnd lalu menyambung lagi ke halaman berikutnya dipromosikan menjadi ptcMiddle, sehingga tabel tiga halaman terbaca start, middle, end dalam urutan halaman. Nomor grup mulai dari 1 dan 0 berarti tidak tersambung, dan ToJson mengeluarkan informasi yang sama sebagai anggota continuation dan continuationGroup, dan bentuk itulah yang sebaiknya dipilih kalau layanan hilir yang menjahitnya
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'itinerary-from-word.pdf';
Pdf.LoadDocument;
Options := TPdfTableExtractionOptions.Default;
Options.DetectContinuations := True; // default; ditampilkan agar jelas
Options.ContinuationMargin := 54; // footer dua baris, kedalamannya ~50 pt
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
case Tables[I].Continuation of
ptcStart:
Writeln(Format('group %d starts on page %d (%d rows)',
[Tables[I].ContinuationGroup, Tables[I].PageNumber,
Tables[I].RowCount]));
ptcMiddle, ptcEnd:
Writeln(Format('group %d continues on page %d (%d rows)',
[Tables[I].ContinuationGroup, Tables[I].PageNumber,
Tables[I].RowCount]));
else
Writeln(Format('standalone table on page %d (%d rows)',
[Tables[I].PageNumber, Tables[I].RowCount]));
end;
finally
Pdf.Free;
end;
end;
Bagaimana baris caption mencegah dua tabel menyatu?
Fragmen halaman berikutnya yang baris pertamanya satu sel yang membentang seluruh kolom diperlakukan sebagai tabel baru, tidak pernah sebagai kelanjutan tabel sebelumnya. Aturan ini ada karena uji content-aware, kalau berdiri sendiri, menyambung terlalu bersemangat. Kasus yang membeberkannya adalah formulir bergaya transkrip: sebuah tabel berakhir di dekat dasar halaman 1, tabel kedua dengan lebar kolom identik mulai di dekat puncak halaman 2, tidak ada apa pun selain footer di antara keduanya, dan kolomnya cocok sampai titik terakhir. Di bawah uji margin keduanya tidak pernah bertemu karena tidak ada yang menyentuh tepi; di bawah uji konten keduanya langsung tersambung, dan formulir bersection menjadi satu grid yang tidak koheren. Yang memisahkan keduanya terlihat di struktur selnya. Tabel kedua dibuka dengan caption section seperti "RECIPIENT INFORMATION" yang ditata sebagai satu sel gabungan selebar penuh, dan continuation yang sungguhan tidak pernah begitu, karena caption-nya milik tabel yang sudah dimulai di halaman sebelumnya. TableStartsWithCaptionRow mengodekan persis itu: fragmennya punya minimal dua kolom dan memuat sel dengan RowIndex = 0, ColumnIndex = 0, dan ColumnSpan = ColumnCount. Pemeriksaannya hanya berjalan pada fragmen berikutnya, jadi tabel yang baris caption-nya sendiri berada di halaman pertamanya tidak terpengaruh; caption-nya ada di halaman N, dan hanya fragmen halaman N+1 yang diperiksa
Perbandingan kolom yang menyusul, TablesHaveMatchingColumns, lebih ketat dari "jumlah kolom yang sama". Ia membangun ulang posisi batas setiap fragmen dari persegi selnya, menginterpolasi batas yang disembunyikan sel gabungan, dan menolak pasangannya ketika ada batas yang bergeser lebih dari toleransinya. Dua tabel empat kolom dengan proporsi berbeda karena itu tetap terpisah walaupun semua hal lain sejajar
Apa yang terjadi pada satu baris yang meluber ke halaman berikutnya?
Grid bergaris yang membawa satu barisnya ke halaman berikutnya kini terdeteksi dan tersambung, asalkan ia berakhir di sebuah rantai continuation; kalau berdiri sendiri ia dibuang. MinRows default 2 ada untuk menjaga sepasang garis nyasar agar tidak dilaporkan sebagai tabel, tapi baris terakhir yang terdorong melewati page break adalah baris sungguhan yang diam-diam dijatuhkan oleh batas minimum 2, dan bagian tabel lainnya tampak lengkap padahal tidak. Pemindaian level dokumen menanganinya dalam tiga langkah. Ketika DetectContinuations dan DetectRuledTables keduanya aktif, pass per halaman menjalankan detektor ruled dengan batas barisnya diturunkan sementara ke 1, itulah sebabnya ExtractTables kini menerima MinRows bernilai 1 untuk grid bergaris sementara deteksi whitespace tetap memakai batas internal 2. Continuation-nya ditandai atas hasil yang lengkap. Lalu setiap tabel yang lebih pendek dari MinRows milik pemanggil dan tidak menjadi bagian rantai mana pun dihapus. Fragmen satu barisnya bertahan hanya karena tersambung, dan grid satu baris di tengah halaman yang biasa-biasa saja disaring persis seperti sebelumnya
// Bangun ulang setiap rantai sebagai satu CSV, buang baris header yang berulang
// pada fragmen continuation-nya
procedure ExportChains(const Tables: TPdfTables; const Folder: string);
var
I, R: Integer;
Lines: TStringList;
Csv: TStringList;
begin
Csv := TStringList.Create;
Lines := TStringList.Create;
try
for I := 0 to High(Tables) do
begin
if Tables[I].Continuation in [ptcNone, ptcStart] then
Csv.Clear;
Lines.Text := string(Tables[I].ToCsv);
if (Tables[I].Continuation in [ptcMiddle, ptcEnd]) and
(Lines.Count > 1) and (Tables[I].RowCount > 1) then
Lines.Delete(0); // header yang diulang oleh word processor
for R := 0 to Lines.Count - 1 do
Csv.Add(Lines[R]);
if Tables[I].Continuation in [ptcNone, ptcEnd] then
Csv.SaveToFile(Format('%s\page%d-group%d.csv',
[Folder, Tables[I].PageNumber, Tables[I].ContinuationGroup]));
end;
finally
Lines.Free;
Csv.Free;
end;
end;
Dua detail di rutin itu disengaja. Baris tunggal yang meluber tidak pernah dipotong, karena guard pada RowCount menjaganya, dan word processor yang mengulang baris header di setiap halaman menghasilkan fragmen yang baris pertamanya adalah header lagi, jadi membuang baris nol pada fragmen middle dan end tepat untuk kasus itu dan keliru untuk generator yang tidak mengulang header. Periksa satu dokumen dulu sebelum melepas rutin itu ke seluruh folder
Di mana aturannya masih berhenti
Uji content-aware hanya sebaik text layer yang dibacanya. Di halaman hasil scan yang tidak punya teks sama sekali, ekstrem teks body yang tercatat jatuh kembali ke batas halaman, syarat "tidak ada apa pun di antaranya" terpenuhi secara hampa, dan yang tersisa hanya gate caption dan kolom; grid bergaris di halaman seperti itu tetap ditemukan sebagai kerangka kosong, jadi rantainya bisa saja tersambung dengan benar, tapi tidak ada apa pun soal teks di sekitarnya yang benar-benar terverifikasi. Tambahkan text layer dulu kalau itu penting. Footer yang digambar sebagai gambar alih-alih teks tidak terlihat oleh logika band-nya dan tidak berbahaya karena alasan yang sama
Band-nya hanya satu angka. Footer yang lebih dalam dari ContinuationMargin meninggalkan baris bawahnya di dalam zona body, yang membuat fragmen sebelumnya tampak diikuti teks lalu memblokir sambungannya; naikkan opsi itu ke kedalaman band yang sebenarnya, seperti yang dilakukan contoh pertama. Naikkan terlalu jauh dan paragraf penutup pendek di dekat dasar halaman menyelinap ke band-nya dan diabaikan, yang menyambungkan tabel ke apa pun yang menyusulnya. Aturan caption punya kegagalan yang bercermin: generator yang menulis banner gabungan "continued" sebagai baris pertama setiap fragmen continuation akan membuat fragmen-fragmen itu ditolak sebagai tabel baru, dan satu-satunya obat hari ini adalah menjahit sendiri lewat ContinuationGroup setelah tidak melonggarkan apa pun, karena aturannya tidak punya switch
Tabel hasil deteksi whitespace tidak mendapat keringanan satu baris itu. Strategi whitespace butuh dua baris yang sejajar untuk bisa melihat tabel sama sekali, jadi tabel tanpa garis yang meluber satu baris tetap dilaporkan kurang sebanyak baris itu. Kalau Anda menemui itu, word box di balik structured text block dan reading order memberi Anda posisi mentahnya untuk memulihkannya. Pada set sampel yang menggerakkan pekerjaan ini, tiga belas ekspor word processor dan browser, lima dokumen dengan tabel multi-halaman sungguhan semuanya tersambung menjadi rantai tunggal dan formulir transkrip yang sebelumnya menyatu tetap terpisah, dan itulah patokan yang dipakai mengukur rilisnya, bukan janji tentang setiap tata letak
Penandaan continuation, aturan caption, dan pass satu barisnya semuanya ada di jalur level dokumen yang dipakai bersama build Delphi, C++Builder, dan Lazarus; API ekstraksi tabel lengkapnya dijelaskan di halaman PDFium Component for Delphi