PDFium Component izvlačenje tabela, od verzije 3.117.0, tretira tanak ispunjen pravougaonik kao liniju tabele. Sa uključenim DetectFilledRulings, što je podrazumevano, ispunjen box poravnat sa osama koji nije deblji od MaxRulingThickness (3 poena) postaje jedna linija duž svoje duge ose, veći ispunjen box doprinosi svoje četiri ivice, a svaka koordinata linije snapuje se unutar RulingSnapTolerance (4 poena) pre nego što se mreža sastavi. Tabele izvezene iz Word-a, Google Docs-a i browser-a zato stižu do detektora mreža kao potpuni gridovi umesto da padnu na detekciju praznina kao fragmenti
Raniji članak o detekciji i izvlačenju tabela naveo je da detekcija mreža koristi nacrtane linije i da se svaki segment stroke putanje transformiše u koordinate stranice. Ta rečenica bila je tačna i nepotpuna. Prebrojavanje path objekata kroz skup od 13 uzoraka realnih dokumenata pokazalo je da 9 od njih ne sadrži nijednu stroke putanju, a ipak svaka njihova stranica nosi stotine ispunjenih pravougaonika debljine 0,5 do 1 poena. Detektor samo za stroke nije video ništa, svaka stranica je pala na detekciju praznina, a izlaz je bio rasipanje malih fragmenata umesto tabela. Preset za kompaktne kolone dodat u 3.116.4 ublažio je to na nivou fragmenta; korenski uzrok bio je da detektor čita pogrešan operator bojenja
Zašto tabela izvezena iz Word-a nema stroke linije?
Word processor ne misli o ivici kao o liniji; misli o njoj kao o boxu sa širinom, i taj box boji fill-om. ISO 32000-1 §8.5.2.1 definiše operator re kao dodavanje subputanje pravougaonika, a §8.5.3 razdvaja operatore bojenja: S crta putanju sa trenutnom širinom linije, f ispunjava njenu unutrašnjost. Ivica ćelije od 0,5 poena izlazi kao x y w 0.5 re f, a stroke mašinerija, uključujući širinu linije, spojeve i dash pattern, nikad se ne pokreće. Senčenje ćelije je ista konstrukcija sa većim boxom. Stroke grid nacrtan sa m, l i S je ono što je originalni detektor očekivao, i to je ono što skoro ništa izvezeno iz kancelarijske aplikacije ne proizvodi:
% jedna ivica ćelije iz izvoza word processora: ispunjen box visok 0.5 pt
72 700 468 0.5 re f
% senčenje ćelije: ispunjen box veličine ćelije
72 676 117 24 re f
% stroke linija mreže za koju je originalni detektor napisan
72 700 m 540 700 l S
Za detektor koji preko FPDFPath_GetDrawMode pita samo da li je stroke zastavica postavljena, oba ispunjena boxa su nevidljiva. Reči unutar ćelija zatim stižu do detekcije praznina, gde kolone razdvojene razmakom od 6 poena stoje ispod podrazumevanog MinColumnGap od 12 poena, a vraća se onaj podskup redova koji se slučajno poravna dovoljno dobro da prođe MinRows. To je ponašanje fragmenta, i nikakvo doterivanje parametara ne pretvara ga u grid koji je autor nacrtao
Kako PDFium Component pretvara ispunjen box u liniju?
TableCollectObjectRulings pregleda svaki path objekat subputanju po subputanju. Režim crtanja dolazi iz FPDFPath_GetDrawMode; putanja se računa kao ispunjena kada je DetectFilledRulings uključen i režim popune nije none. Svaka tačka se transformiše kroz matricu objekta i sakuplja, do MaxSubpathPoints (8) po subputanji, a svaki segment krive označava subputanju kao zakrivljenu. Kada se subputanja zatvori ili počne novi MoveTo, FlushSubpath odlučuje šta je bila: zakrivljena subputanja se odbacuje, a isto i svaki zatvoreni poligon čije tačke ne leže sve unutar PointTolerance (0,05 poena) od ivica bounding box-a na najmanje jednoj osi. Trougao, ševron ili zaobljen tab nikad ne postaje linija, i to je ono što drži dekorativnu grafiku izvan mreže
Ono što preživi je poravnat sa osama pravougaonik, klasifikovan po svom bounding box-u. Širina na ili ispod MaxRulingThickness sa visinom iznad nje daje jednu vertikalnu liniju u horizontalnom centru, koja pokriva box od dna do vrha; obrnut slučaj daje jednu horizontalnu liniju. Obe dimenzije iznad praga znače senčenu ćeliju, i box doprinosi četiri linije, po jednu za svaku ivicu. Obe dimenzije na ili ispod praga ne doprinose ništa, pa se kvadratni bullet od 2 poena ne pomeša sa linijom. Stroke putanja ide starijom putanjom kroz AddLine, jednu liniju po segmentu poravnatom sa osama, pa se grid nacrtan sa S obrađuje tačno kao pre, a putanja obojena i fill-om i stroke-om proizvodi delove koji se preklapaju i koje prolaz spajanja sažima:
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
Mode: string;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'itinerary-from-word.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 1; // indeks od 1
Options := TPdfTableExtractionOptions.Default;
// ovo su podrazumevane vrednosti iz 3.117.0, navedene radi jasnoće
Options.DetectFilledRulings := True; // tanki ispunjeni boxovi postaju linije
Options.MaxRulingThickness := 3.0; // poeni; deblji boxovi se računaju kao senčenje
Options.RulingSnapTolerance := 4.0; // poeni; 0 isključuje snapovanje
Options.IncludeFormXObjects := True;
Tables := Pdf.ExtractTables(Options);
for I := 0 to High(Tables) do
begin
if Tables[I].DetectionMode = ptdmRuled then
Mode := 'ruled'
else
Mode := 'whitespace';
Writeln(Format('%dx%d %s, confidence %.2f',
[Tables[I].RowCount, Tables[I].ColumnCount, Mode,
Tables[I].Confidence]));
end;
finally
Pdf.Free;
end;
end;
Šta RulingSnapTolerance radi za tabele od senčenih ćelija?
RulingSnapTolerance je ono što čini da se tabela izgrađena samo od senčenja poveže u jedan grid. Neki izvozi ne crtaju nikakvu ivicu: svaka ćelija je ispunjen box u svojoj boji, a susedni boxovi razdvojeni su razmakom od 1 do 3 poena belog. Svaki box daje četiri ivice kao linije, ali desna ivica jedne ćelije i leva ivica sledeće stoje 2 poena jedna od druge, a test povezanosti koristi RulingTolerance, koji podrazumevano iznosi 1 poen. Bez snapovanja, svaka ćelija formira sopstvenu povezanu komponentu od četiri linije, nijedna komponenta ne stiže do MinRows, i stranica ne prijavljuje ništa. TableSnapRulings sakuplja svaku X koordinatu u igri (poziciju svake vertikalne linije plus početak i kraj svake horizontalne) i isto tako svaku Y koordinatu, sortira svaku listu, klasteruje je ulančavanjem vrednosti čiji se sused razlikuje najviše za toleranciju, zamenjuje svaki klaster njegovom sredinom, a zatim pomera svaku poziciju, početak i kraj na najbliži centar klastera. Dve strane razmaka postaju ista linija, i povezanost važi
Snapovanje se izvršava pre TableMergeRulings, koji sortira linije i spaja kolinearne delove koji se dodiruju ili preklapaju unutar RulingTolerance, i oba se izvršavaju pre nego što TableDetectRuled uopšte vidi podatke, pa je provera povezanosti parova proporcionalna broju linija mreže a ne broju fragmenata po ćeliji. Na stroke gridu ti prolazi su bezopasni, jer koordinate koje su već bile identične snapuju se same na sebe. Jedno što treba imati na umu je da klasterovanje ulančavanjem nema sopstveno ograničenje širine: niz koordinata na po 3 poena jedna od druge sažima se u jedan centar. Pri podrazumevanih 4 poena to pogađa samo kolone uže od jednog znaka, ali ako dokument ima prave razmake od 3 poena koji moraju ostati razdvojeni, snizite toleranciju ili je postavite na 0 da isključite snapovanje:
// Izoluj strategiju mreže i uporedi šta svaka postavka vidi na jednoj stranici
function CountRuledTables(Pdf: TPdf; FilledRulings: Boolean;
SnapTolerance: Double): Integer;
var
Options: TPdfTableExtractionOptions;
begin
Options := TPdfTableExtractionOptions.Default;
Options.DetectWhitespaceTables := False;
Options.DetectFilledRulings := FilledRulings;
Options.RulingSnapTolerance := SnapTolerance;
Result := Length(Pdf.ExtractTables(Options));
end;
// Word izvoz obično prijavljuje 0, N i zatim manje od N:
// samo stroke ne vidi ništa, snapovanje povezuje senčene ćelije,
// a isključivanje snap-a ostavlja svaku senčenu ćeliju kao sopstveno ostrvo
Writeln(CountRuledTables(Pdf, False, 4.0));
Writeln(CountRuledTables(Pdf, True, 4.0));
Writeln(CountRuledTables(Pdf, True, 0.0));
Linije unutar form XObject-a
Alati za raspored stranice često umotaju tabelu, ili celo telo stranice, u form XObject i oboje ga sa Do. ISO 32000-1 §8.10.1 nalaže da se matrica forme konkatenira sa trenutnom matricom transformacije kada se forma oboji, pa pravougaonik unutar forme živi u prostoru forme i sleće na stranicu samo posle dva ili više transforma. TableCollectObjectRulings rekurzivno ulazi u form objekte kada je IncludeFormXObjects postavljen: čita matricu objekta, kombinuje je sa roditeljskom matricom preko TableMultiplyMatrix, čiji redosled argumenata znači „preslikaj kroz prvu matricu, zatim kroz drugu", i nabraja decu preko FPDFFormObj_CountObjects i FPDFFormObj_GetObject, prosleđujući kombinovanu matricu na niže. Ugnježđavanje dublje od MaxFormDepth (8) tiho se preskače, što je zaštita od patoloških fajlova a ne ograničenje kojem se ma koji pravi izvoz približava. Razlog zašto je redosled množenja važan isti je onaj obrađen u tekstu prepend naspram append kod matrica: zamena operanada pomera član translacije, i linija koja je trebalo da sleti na vrh stranice sleće u koordinatni početak
Zašto se budžet za linije učetvorostručio?
Podrazumevani MaxRulingSegments porastao je sa 4096 na 16384 u 3.117.0 jer ivice po ćelijama stižu u mnogo većem broju nego stroke linije mreže. Stroke tabela sa 30 redova i 6 kolona je 38 segmenata linija. Ista tabela izvezena kao ispunjeni boxovi je do četiri ivice po ćeliji, 720 delova pre spajanja, a formular sa senčenim ćelijama to udvostručuje. Dve takve tabele na stranici iscrpele bi stari budžet. Budžet se sprovodi u TableAppendRuling preko Check, koji baca EPdfError sa porukom „Table ruling-segment budget exceeded"; nema degradiranog rezultata, nema delimičnog grida, a ni prolaz kroz praznine se ne izvršava. Ako postavite sopstveni tesniji budžet za nepouzdan ulaz, uhvatite izuzetak i odlučite, umesto da prazan rezultat čitate kao „nema tabela":
Options := TPdfTableExtractionOptions.Default;
Options.MaxRulingSegments := 2048; // namerno tesno za nepouzdan ulaz
try
Tables := Pdf.ExtractTables(Options);
except
on E: EPdfError do
begin
Log(E.Message); // 'Table ruling-segment budget exceeded'
Options.MaxRulingSegments := 16384; // podrazumevano u 3.117.0
Tables := Pdf.ExtractTables(Options);
end;
end;
Izmereni rezultati i gde se pristup zaustavlja
Na istih 13 uzoraka dokumenata, izvlačenje je otišlo sa 43 tabele, od kojih 9 mreža i 34 fragmenta praznina ili lažnih pozitiva, na 41 tabelu mreža i nijednu lažnu pozitivu iz praznina. Deo tog čišćenja pripada dvema pratećim izmenama u 3.117.0: reči koje je već prisvojio grid sa linijama uklanjaju se pre nego što se pokrene detekcija praznina, pa se tabela nikad ne prijavljuje dvaput, a granica kolone u prazninama sada mora biti koridor bez teksta kroz svaki red koji razdvaja, što je zaustavilo da se poravnati pasusi boduju kao tabele 5x4. Čitač ispunjenih pravougaonika je ono što je same tabele prebacilo iz kolone fragmenata u kolonu mreža
Granice vredi reći jasno. Stranica bez tekstualnog sloja i dalje daje skelet mreže, sa svakom ćelijom praznom, jer linije dolaze iz geometrije a tekst iz tekstualne stranice; skenirane stranice prvo traže OCR. Ispunjeni oblici sa krivama, zaobljenim uglovima ili ne-pravougaonim obrisima odbacuju se u potpunosti, pa tabela čije su ivice nacrtane kao obrisi zaobljenih pravougaonika traži detekciju praznina kao i pre. Tabela bez ivica i bez senčenja nepromenjena je svim ovim i ostaje domen strategije praznina opisane u članku o izvlačenju tabela; kada ni to nije dovoljno, box-ovi reči i blokovi iz strukturiranog teksta i redosleda čitanja sirovina su za čitač specifičan za domen. TableExtractionLab demo koji se isporučuje uz komponentu izlaže DetectFilledRulings u svom panelu opcija, što je najbrži način da vidite kako dati izvoz izgleda sa njim i bez njega; pun API opisan je na stranici PDFium Component za Delphi