Tabelluttrekket i PDFium Component behandler fra versjon 3.117.0 et tynt fylt rektangel som en tabellinje. Med DetectFilledRulings slått på, som er standard, blir en fylt aksejustert boks som ikke er tykkere enn MaxRulingThickness (3 punkter), til én linje langs den lange aksen sin, en større fylt boks bidrar med de fire kantene sine, og hver linjekoordinat snappes innenfor RulingSnapTolerance (4 punkter) før rutenettet settes sammen. Tabeller eksportert fra Word, Google Docs og nettlesere når derfor den linjebaserte detektoren som komplette rutenett i stedet for å falle gjennom til mellomromsdeteksjon som fragmenter
Den tidligere artikkelen om tabellgjenkjenning og -uttrekk sa at linjebasert gjenkjenning bruker de tegnede linjene, og at hvert strøkne banesegment transformeres til sidekoordinater. Den setningen var sann og ufullstendig. Å telle baneobjekter på tvers av et sett med 13 virkelige eksempeldokumenter viste at 9 av dem ikke inneholder noen strøken bane i det hele tatt, men hver av sidene deres bærer hundrevis av fylte rektangler som er 0.5 til 1 punkt tykke. Detektoren som bare så på strøk, fant ingenting, hver side falt til mellomromsdeteksjon, og utdataene var et strø av små fragmenter i stedet for tabeller. Forhåndsinnstillingen for kompakte kolonner som ble lagt til i 3.116.4, myknet dette på fragmentnivå; rotårsaken var at detektoren leste feil malingsoperator
Hvorfor har en Word-eksportert tabell ingen strøkne linjer?
Et tekstbehandlingsprogram tenker ikke på en ramme som en linje; det tenker på den som en boks med en bredde, og maler den boksen med en fyll. ISO 32000-1 §8.5.2.1 definerer re-operatoren som å legge til en rektangelunderbane, og §8.5.3 skiller malingsoperatorene: S strøker banen med gjeldende linjebredde, f fyller det indre. En celleramme på 0.5 punkt kommer ut som x y w 0.5 re f, og strøkmaskineriet, med linjebredde, skjøter og stiplet mønster, kjører aldri. Cellefarge er samme konstruksjon med en større boks. Et strøket rutenett tegnet med m, l og S er det den opprinnelige detektoren forventet, og det er det nesten ingenting eksportert fra et kontorprogram lager:
% én celleramme fra en tekstbehandler-eksport: en fylt boks som er 0.5 pt høy
72 700 468 0.5 re f
% cellefarge: en fylt boks på størrelse med cellen
72 676 117 24 re f
% den strøkne rutenettlinjen den opprinnelige detektoren var skrevet for
72 700 m 540 700 l S
For en detektor som bare spør FPDFPath_GetDrawMode om strøkflagget er satt, er begge de fylte boksene usynlige. Ordene inne i cellene når da mellomromsdeteksjonen, der kolonner skilt av et mellomrom på 6 punkter ligger under standardverdien MinColumnGap på 12 punkter, og det som kommer tilbake, er den delmengden av rader som tilfeldigvis er justert godt nok til å passere MinRows. Det er fragmentoppførselen, og ingen mengde parameterjustering gjør den om til rutenettet forfatteren tegnet
Hvordan gjør PDFium Component en fylt boks om til en tabellinje?
TableCollectObjectRulings inspiserer hvert baneobjekt én underbane om gangen. Malingsmodusen kommer fra FPDFPath_GetDrawMode; en bane teller som fylt når DetectFilledRulings er på og fyllmodusen ikke er none. Hvert punkt transformeres gjennom objektmatrisen og samles inn, opptil MaxSubpathPoints (8) per underbane, og ethvert kurvesegment merker underbanen som krum. Når underbanen lukkes eller en ny MoveTo begynner, avgjør FlushSubpath hva den var: en krum underbane forkastes, og det samme gjør enhver lukket polygon der punktene ikke alle ligger innenfor PointTolerance (0.05 punkter) fra avgrensningsboksens kanter på minst én akse. En trekant, en spiss eller en avrundet fane blir aldri en tabellinje, og det er dette som holder dekorativ grafikk ute av rutenettet
Det som overlever, er et aksejustert rektangel, klassifisert etter avgrensningsboksen sin. Bredde på eller under MaxRulingThickness med høyde over gir én loddrett linje ved den vannrette midten, som spenner over boksen fra bunn til topp; speiltilfellet gir én vannrett linje. Begge dimensjoner over terskelen betyr en farget celle, og boksen bidrar med fire linjer, én per kant. Begge dimensjoner på eller under terskelen bidrar med ingenting, så en firkantet kule på 2 punkter blir ikke forvekslet med en linje. En strøken bane tar den eldre veien gjennom AddLine, én linje per aksejusterte segment, så et rutenett tegnet med S håndteres nøyaktig som før, og en bane malt med både fyll og strøk gir overlappende biter som sammenslåingspasset kollapser:
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; // 1-basert
Options := TPdfTableExtractionOptions.Default;
// dette er standardverdiene i 3.117.0, skrevet ut for tydelighetens skyld
Options.DetectFilledRulings := True; // tynne fylte bokser blir til linjer
Options.MaxRulingThickness := 3.0; // punkter; tykkere bokser regnes som farge
Options.RulingSnapTolerance := 4.0; // punkter; 0 slår av snapping
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;
Hva gjør RulingSnapTolerance for tabeller av fargede celler?
RulingSnapTolerance er det som får en tabell bygget bare av farge til å henge sammen til ett rutenett. Noen eksporter tegner ingen ramme i det hele tatt: hver celle er en fylt boks i sin egen farge, og nabobokser skilles av et mellomrom på 1 til 3 punkter med hvitt. Hver boks gir fire kantlinjer, men høyre kant av én celle og venstre kant av den neste ligger 2 punkter fra hverandre, og koblingstesten bruker RulingTolerance, som har standardverdi 1 punkt. Uten snapping danner hver celle sin egen sammenhengende komponent av fire linjer, ingen komponent når MinRows, og siden rapporterer ingenting. TableSnapRulings samler hver X-koordinat som er i spill (posisjonen til hver loddrette linje pluss start og slutt for hver vannrette) og hver Y-koordinat på samme måte, sorterer hver liste, klynger den ved å kjede sammen verdier der naboen ikke avviker mer enn toleransen, erstatter hver klynge med middelverdien sin, og flytter deretter hver posisjon, start og slutt til nærmeste klyngesenter. De to sidene av et mellomrom blir den samme linjen, og sammenhengen holder
Snapping kjører før TableMergeRulings, som sorterer linjene og slår sammen kollineære biter som berører eller overlapper hverandre innenfor RulingTolerance, og begge kjører før TableDetectRuled i det hele tatt ser dataene, så den parvise koblingssjekken er proporsjonal med antallet rutenettlinjer og ikke med antallet fragmenter per celle. På et strøket rutenett er passene harmløse, fordi koordinater som allerede var identiske, snapper til seg selv. Det ene du bør huske, er at klynging ved kjeding ikke har noen egen breddegrense: en serie koordinater som ligger 3 punkter fra hverandre, kollapser til ett enkelt senter. Ved standardverdien på 4 punkter rammer det bare kolonner som er smalere enn et tegn, men hvis et dokument har ekte mellomrom på 3 punkter som må forbli atskilt, senker du toleransen eller setter den til 0 for å slå av snapping:
// Isoler den linjebaserte strategien og sammenlign hva hver innstilling ser på én side
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;
// En Word-eksport rapporterer typisk 0, N og deretter færre enn N:
// bare-strøk ser ingenting, snapping kobler de fargede cellene,
// og å slå av snappingen lar hver farget celle stå som sin egen øy
Writeln(CountRuledTables(Pdf, False, 4.0));
Writeln(CountRuledTables(Pdf, True, 4.0));
Writeln(CountRuledTables(Pdf, True, 0.0));
Tabellinjer inne i form XObjects
Verktøy for sidelayout pakker ofte en tabell, eller hele sidekroppen, inn i et form XObject og maler det med Do. ISO 32000-1 §8.10.1 sier at formmatrisen konkateneres med gjeldende transformasjonsmatrise når formen males, så et rektangel inne i formen lever i formrommet og lander først på siden etter to eller flere transformasjoner. TableCollectObjectRulings går rekursivt inn i formobjekter når IncludeFormXObjects er satt: den leser objektmatrisen, kombinerer den med foreldrematrisen gjennom TableMultiplyMatrix, der argumentrekkefølgen betyr «avbild gjennom den første matrisen, deretter den andre», og ramser opp barna med FPDFFormObj_CountObjects og FPDFFormObj_GetObject, og sender den kombinerte matrisen videre ned. Nesting dypere enn MaxFormDepth (8) hoppes stille over, noe som er en vakt mot patologiske filer og ikke en grense noen ekte eksport nærmer seg. Grunnen til at multiplikasjonsrekkefølgen betyr noe, er den samme som diskuteres i prepend kontra append av matriser: bytter du operandene, flytter translasjonsleddet seg, og en linje som skulle landet øverst på siden, lander i origo i stedet
Hvorfor ble linjebudsjettet firedoblet?
Standardverdien MaxRulingSegments økte fra 4096 til 16384 i 3.117.0 fordi rammer per celle kommer inn i langt større antall enn strøkne rutenettlinjer. En strøket tabell med 30 rader og 6 kolonner er 38 linjesegmenter. Den samme tabellen eksportert som fylte bokser er opptil fire rammer per celle, 720 biter før sammenslåing, og et skjema med fargede celler dobler det. To slike tabeller på en side ville ha sprengt det gamle budsjettet. Budsjettet håndheves i TableAppendRuling gjennom Check, som kaster EPdfError med meldingen «Table ruling-segment budget exceeded»; det finnes ikke noe degradert resultat, ikke noe delvis rutenett, og mellomromspasset kjører heller ikke. Setter du et strammere budsjett selv for ubetrodd input, fanger du unntaket og tar en beslutning, i stedet for å lese et tomt resultat som «ingen tabeller»:
Options := TPdfTableExtractionOptions.Default;
Options.MaxRulingSegments := 2048; // bevisst stramt for ubetrodd input
try
Tables := Pdf.ExtractTables(Options);
except
on E: EPdfError do
begin
Log(E.Message); // 'Table ruling-segment budget exceeded'
Options.MaxRulingSegments := 16384; // standardverdien i 3.117.0
Tables := Pdf.ExtractTables(Options);
end;
end;
Målte resultater og hvor tilnærmingen stopper
På de samme 13 eksempeldokumentene gikk uttrekket fra 43 tabeller, 9 av dem linjebaserte og 34 mellomromsfragmenter eller falske treff, til 41 linjebaserte tabeller og ingen falske mellomromstreff. En del av den opprydningen tilhører to følgeendringer i 3.117.0: ord som allerede er kravd av et linjebasert rutenett, fjernes før mellomromsdeteksjonen kjører, så en tabell aldri rapporteres to ganger, og en mellomromskolonnegrense må nå være en tekstfri korridor gjennom hver rad den skiller, og det er dette som stoppet høyrestilte avsnitt fra å skåre som 5x4-tabeller. Leseren for fylte rektangler er det som flyttet tabellene selv fra fragmentkolonnen til den linjebaserte kolonnen
Grensene er verdt å si rett ut. En side uten tekstlag gir fortsatt rutenettskjelettet, med hver celle tom, fordi linjer kommer fra geometri og tekst kommer fra tekstsiden; skannede sider trenger OCR først. Fylte former med kurver, avrundede hjørner eller ikke-rektangulære omriss droppes helt, så en tabell der rammene er tegnet som omriss av avrundede rektangler, trenger mellomromsdeteksjon som før. En tabell med verken rammer eller farge er uendret av alt dette og forblir mellomromsstrategiens område, beskrevet i artikkelen om tabelluttrekk; når ikke engang det er nok, er ordboksene og -blokkene fra strukturert tekst og leserekkefølge råmaterialet for en domenespesifikk leser. Demoen TableExtractionLab som leveres med komponenten, eksponerer DetectFilledRulings i optionspanelet sitt, og det er den raskeste måten å se hvordan en gitt eksport ser ut med og uten den; hele API-et er beskrevet på siden for PDFium Component for Delphi