A PDFium Component táblakinyerése a 3.117.0 verziótól kezdve táblavonalként kezel egy vékony kitöltött téglalapot. Bekapcsolt DetectFilledRulings mellett, ami az alapérték, a MaxRulingThickness (3 pont) vastagságot el nem érő, kitöltött, tengelypárhuzamos doboz egyetlen vonalakká válik a hosszú tengelye mentén, egy nagyobb kitöltött doboz a négy élét adja, és minden vonalkoordináta a RulingSnapTolerance (4 pont) értéken belül csúszik össze, mielőtt a rács felépül. A Wordből, a Google Docsból és böngészőkből exportált táblák így kész rácsokként érkeznek a vonalazott detektorhoz ahelyett, hogy töredékként átcsúsznának a whitespace-detektálásba
A korábbi, táblázatdetektálásról és -kinyerésről szóló cikk azt állította, hogy a vonalazott detektálás a megrajzolt vonalakat használja, és hogy minden körvonalazott útvonalszegmens oldalkoordinátákra transzformálódik. Az a mondat igaz volt és hiányos. Útvonalobjektumok megszámlálása tizenhárom valós mintadokumentumból álló halmazon megmutatta, hogy közülük kilenc egyáltalán nem tartalmaz körvonalazott útvonalat, mindazonáltal mindegyik oldaluk több száz, 0,5–1 pont vastagságú kitöltött téglalapot hordoz. A csak körvonalra építő detektor semmit nem látott, minden oldal a whitespace-detektálásba esett, a kimenet pedig apró töredékek szórványa lett táblák helyett. A 3.116.4-ben hozzáadott kompakt oszlopok előbeállítás ezt a töredék szintjén enyhítette; a kiváltó ok az volt, hogy a detektor a rossz festési operátort olvasta
Miért nincsenek körvonalazott vonalak egy Wordből exportált táblán?
A szövegszerkesztő nem vonalként gondol a szegélyre; szélességgel rendelkező dobozként gondol rá, és azt a dobozt kitöltéssel festi meg. Az ISO 32000-1 §8.5.2.1 az re operátort négyszög-alútvonal hozzáfűzéseként definiálja, a §8.5.3 pedig szétválasztja a festési operátorokat: az S az aktuális vonalvastagsággal körvonalazza az útvonalat, az f kitölti a belsejét. Egy 0,5 pontos cellaszegély x y w 0.5 re f alakban jön ki, és a körvonalazó gépezet — vonalvastagság, illesztések és szaggatásminta — soha nem fut le. A cellaárnyékolás ugyanez a konstrukció, csak nagyobb dobozzal. Az m, l és S operátorokkal rajzolt körvonalazott rács az, amire az eredeti detektort írták, és amit szinte semmi nem állít elő, amit irodai alkalmazásból exportálnak:
% egy cellaszegély szövegszerkesztő-exportból: 0,5 pt magas kitöltött doboz
72 700 468 0.5 re f
% cellaárnyékolás: a cella méretű kitöltött doboz
72 676 117 24 re f
% a körvonalazott rácsvonal, amelyre az eredeti detektort írták
72 700 m 540 700 l S
Egy olyan detektor számára, amely a FPDFPath_GetDrawMode hívástól csak azt kérdezi, hogy be van-e állítva a körvonalazás jelző, mindkét kitöltött doboz láthatatlan. A cellákban lévő szavak így eljutnak a whitespace-detektálásig, ahol a 6 pontos közökkel elválasztott oszlopok a 12 pontos MinColumnGap alapérték alatt ülnek, és az jön vissza, hogy a soroknak épp az a részhalmaza, amely elég jól igazodik ahhoz, hogy átmenjen a MinRows szűrőn. Ez a töredékviselkedés, és semmilyen paraméterhangolás nem fordítja át abba a rácsba, amelyet a szerző megrajzolt
Hogyan alakítja a PDFium Component a kitöltött dobozt vonalakká?
A TableCollectObjectRulings minden útvonalobjektumot alútvonalanként vizsgál meg. A festési mód a FPDFPath_GetDrawMode hívásból származik; egy útvonal akkor számít kitöltöttnek, ha a DetectFilledRulings be van kapcsolva, és a kitöltési mód nem none. Minden pont átmegy az objektummátrixon és összegyűlik, alútvonalanként legfeljebb MaxSubpathPoints (8) darab, és bármely görbeszegmens görbültnek jelöli meg az alútvonalat. Amikor az alútvonal bezárul, vagy új MoveTo kezdődik, a FlushSubpath eldönti, mi volt: a görbült alútvonal eldobódik, és ugyanígy minden olyan zárt sokszög, amelynek a pontjai nem ülnek mind a befoglaló doboz éleitől számított PointTolerance (0,05 pont) értéken belül legalább az egyik tengelyen. Háromszögből, csúcsos nyílból vagy lekerekített fülből soha nem lesz vonal, és ez az, ami távol tartja a díszítő grafikát a rácstól
Ami túlél, az egy tengelypárhuzamos téglalap, amelyet a befoglaló doboza osztályoz. A MaxRulingThickness értékénél nem nagyobb szélesség és az annál nagyobb magasság egyetlen függőleges vonalat ad a vízszintes középpontban, a dobozt alulról felfelé átfogva; a tükör eset egyetlen vízszintes vonalat ad. Ha mindkét méret a küszöb felett van, az árnyékolt cellát jelent, a doboz pedig négy vonallal járul hozzá, élenként eggyel. Ha mindkét méret a küszöb alatt vagy azzal egyenlő, semmivel nem járul hozzá, így egy 2 pontos négyzetes felsorolásjel nem tévesztődik vonallal. A körvonalazott útvonal a régebbi utat járja az AddLine híváson keresztül, tengelypárhuzamos szegmensenként egy vonallal, így az S operátorral rajzolt rács pontosan úgy kezelődik, mint korábban, az egyszerre kitöltéssel és körvonalazással festett útvonal pedig átfedő darabokat ad, amelyeket az összefésülő menet összevon:
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-alapú
Options := TPdfTableExtractionOptions.Default;
// ezek a 3.117.0 alapértékei, a szemléletesség kedvéért kiírva
Options.DetectFilledRulings := True; // a vékony kitöltött dobozokból vonalak lesznek
Options.MaxRulingThickness := 3.0; // pont; a vastagabb dobozok árnyékolásnak számítanak
Options.RulingSnapTolerance := 4.0; // pont; a 0 kikapcsolja az összecsúsztatást
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;
Mit tesz a RulingSnapTolerance az árnyékolt cellás táblákkal?
A RulingSnapTolerance az, amitől a pusztán árnyékolásból épült tábla egyetlen ráccsá kapcsolódik össze. Egyes exportok egyáltalán nem rajzolnak szegélyt: minden cella egy saját színű kitöltött doboz, a szomszédos dobozokat pedig 1–3 pontos fehér köz választja el. Minden doboz négy élvonalat ad, egy cella jobb éle és a következő bal éle viszont 2 pontra van egymástól, az összekapcsoltsági vizsgálat pedig a RulingTolerance értéket használja, amelynek alapértéke 1 pont. Összecsúsztatás nélkül minden cella a saját négy vonalából álló összefüggő komponenst alkot, egyetlen komponens sem éri el a MinRows értéket, az oldal pedig semmit nem jelent. A TableSnapRulings összegyűjti az összes játékban lévő X koordinátát (az egyes függőleges vonalak pozícióját, valamint az egyes vízszintesek kezdetét és végét), és ugyanígy az összes Y koordinátát, rendezi mindkét listát, fürtökre bontja azokat olyan értékek láncolásával, amelyeknek a szomszédja legfeljebb a toleranciával tér el, minden fürtöt a közepével helyettesít, majd minden pozíciót, kezdetet és véget a legközelebbi fürtközépre mozdít. A köz két oldala ugyanazzá a vonallá válik, és az összekapcsoltság fennáll
Az összecsúsztatás a TableMergeRulings előtt fut, amely rendezi a vonalakat, és összekapcsolja azokat az egy egyenesbe eső darabokat, amelyek a RulingTolerance értéken belül érintkeznek vagy átfedik egymást, mindkettő pedig a TableDetectRuled elé fut be, mielőtt az egyáltalán látná az adatokat, így a páronkénti összekapcsoltsági vizsgálat a rácsvonalak számával arányos, nem a cellánkénti töredékek számával. Körvonalazott rács esetén a menetek ártalmatlanok, mert a már azonos koordináták önmagukra csúsznak. Egy dolgot érdemes észben tartani: a láncoló fürtözésnek magának nincs szélességkorlátja, egy 3 pontos távolságokból álló koordinátasor egyetlen középpontba omlik össze. A 4 pontos alapértéknél ez csak az egy karakternél keskenyebb oszlopokat érinti, de ha egy dokumentumban valódi, 3 pontos közök vannak, amelyeknek külön kell maradniuk, csökkentse a toleranciát, vagy állítsa 0-ra az összecsúsztatás kikapcsolásához:
// A vonalazott stratégia elkülönítése és annak összevetése, mit lát az egyes beállításokból egy oldalon
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;
// Egy Word-export jellemzően 0-t, majd N-t, végül N-nél kevesebbet jelez:
// a csak körvonal semmit nem lát, az összecsúsztatás összekapcsolja az árnyékolt cellákat,
// az összecsúsztatás kikapcsolása pedig minden árnyékolt cellát a saját szigeteként hagy
Writeln(CountRuledTables(Pdf, False, 4.0));
Writeln(CountRuledTables(Pdf, True, 4.0));
Writeln(CountRuledTables(Pdf, True, 0.0));
Vonalak form XObjecteken belül
Az oldaltervező eszközök gyakran form XObjectbe csomagolnak egy táblát, vagy az egész oldaltörzset, és azt a Do operátorral festik meg. Az ISO 32000-1 §8.10.1 úgy rendelkezik, hogy a formmátrix a festéskor összefűződik az aktuális transzformációs mátrixszal, így a formon belüli téglalap a form terében él, és csak két vagy több transzformáció után kerül az oldalra. A TableCollectObjectRulings rekurzívan belép a formobjektumokba, ha az IncludeFormXObjects be van állítva: beolvassa az objektummátrixot, a TableMultiplyMatrix hívással összevonja a szülőmátrixszal, amelynek argumentumsorrendje azt jelenti, hogy „az első mátrixon keresztül képezzük le, majd a másodikon", a gyerekeket pedig a FPDFFormObj_CountObjects és a FPDFFormObj_GetObject hívásokkal sorolja fel, továbbadva az összevont mátrixot. A MaxFormDepth (8) mélységnél mélyebb beágyazás csendben kimarad, ami patologikus fájlok elleni védelem, nem pedig olyan korlát, amelyhez valódi export közelítene. Azért számít a szorzás sorrendje, ugyanazért, amit a mátrix elé fűzése és utána fűzése tárgyal: az operandusok felcserélése elmozdítja a transzlációs tagot, és az a vonal, amelynek az oldal tetejére kellene esnie, az origóra esik helyette
Miért négyszereződött meg a vonalak kerete?
A MaxRulingSegments alapértéke 4096-ról 16384-re nőtt a 3.117.0-ban, mert a cellánkénti szegélyek jóval nagyobb számban érkeznek, mint a körvonalazott rácsvonalak. Egy körvonalazott, 30 soros, 6 oszlopos tábla 38 vonalszegmens. Ugyanez a tábla kitöltött dobozokként exportálva cellánként legfeljebb négy szegély, azaz 720 darab az összefésülés előtt, egy árnyékolt cellás űrlap pedig megduplázza ezt. Két ilyen tábla egy oldalon már kimerítette volna a régi keretet. A keretet a TableAppendRuling kényszeríti ki a Check híváson keresztül, amely EPdfError kivételt dob a "Table ruling-segment budget exceeded" üzenettel; nincs csökkentett eredmény, nincs részleges rács, és a whitespace-menet sem fut le. Ha saját, szigorúbb keretet állít be nem megbízható bemenethez, kapja el a kivételt és döntsön, ahelyett hogy egy üres eredményt „nincs tábla" jelentésként olvasna:
Options := TPdfTableExtractionOptions.Default;
Options.MaxRulingSegments := 2048; // szándékosan szűk, nem megbízható bemenethez
try
Tables := Pdf.ExtractTables(Options);
except
on E: EPdfError do
begin
Log(E.Message); // 'Table ruling-segment budget exceeded'
Options.MaxRulingSegments := 16384; // a 3.117.0 alapértéke
Tables := Pdf.ExtractTables(Options);
end;
end;
Mért eredmények, és hol áll meg a megközelítés
Ugyanazon a tizenhárom mintadokumentumon a kinyerés 43 tábláról — közülük 9 vonalazott és 34 whitespace-töredék vagy téves találat — 41 vonalazott táblára és nulla whitespace-téves találatra jutott. A tisztulás egy része a 3.117.0 két kísérő változásáé: a vonalazott rács által már igénybe vett szavak eltávolítódnak, mielőtt a whitespace-detektálás lefut, így egy tábla soha nem jelentődik kétszer, egy whitespace-oszlophatárnak pedig mostantól szövegmentes folyosónak kell lennie minden általa elválasztott soron keresztül, és ez az, ami megállította, hogy sorkizárt bekezdések 5x4-es táblaként pontozódjanak. A kitöltött téglalapok olvasója az, ami magukat a táblákat átmozgatta a töredékoszlopból a vonalazott oszlopba
A határokat érdemes egyenesen kimondani. Egy szövegréteg nélküli oldal továbbra is kiadja a rácscsontvázat, minden cella üresen, mert a vonalak a geometriából, a szöveg pedig a szövegoldalról származik; a beolvasott oldalakhoz előbb OCR kell. A görbékkel, lekerekített sarkokkal vagy nem négyszögletes körvonalakkal rendelkező kitöltött alakzatok teljesen eldobódnak, így annak a táblának, amelynek a szegélyei lekerekített téglalap körvonalaként vannak megrajzolva, továbbra is a whitespace-detektálásra van szüksége. Az a tábla, amelynek sem szegélye, sem árnyékolása nincs, ezektől egyáltalán nem változik, és továbbra is a táblakinyerési cikkben leírt whitespace-stratégia területe marad; amikor még az sem elég, a strukturált szöveg és olvasási sorrend szódobozai és blokkjai adják a nyersanyagot egy területspecifikus olvasóhoz. A komponenshez érkező TableExtractionLab demó az opciópanelén kiadja a DetectFilledRulings beállítást, és ez a leggyorsabb módja annak, hogy lássa, milyen egy adott export vele és nélküle; a teljes API a PDFium Component for Delphi oldalán van leírva