PDFium Component lentelių ištraukimas nuo 3.117.0 versijos ploną užpildytą stačiakampį laiko lentelės tinklelio linija. Įjungus DetectFilledRulings, kas yra numatytoji reikšmė, ašims lygiuotas užpildytas stačiakampis, ne storesnis už MaxRulingThickness (3 taškai), virsta viena linija išilgai savo ilgosios ašies, didesnis užpildytas stačiakampis duoda savo keturis kraštus, o kiekviena tinklelio linijos koordinatė prieš surenkant tinklelį suvedama į RulingSnapTolerance (4 taškai) ribą. Todėl iš Word, Google Docs ir naršyklių eksportuotos lentelės pasiekia brūkšnių detektorių kaip ištisi tinkleliai, o ne nukrenta į tarpų aptikimą kaip fragmentai
Ankstesnis straipsnis apie lentelių aptikimą ir ištraukimą teigė, kad brūkšnių aptikimas naudoja nubrėžtas linijas ir kad kiekvienas brėžiamas kelio segmentas paverčiamas puslapio koordinatėmis. Tas sakinys buvo teisingas ir neišsamus. Suskaičiavus kelio objektus 13 realių pavyzdinių dokumentų rinkinyje paaiškėjo, kad 9 iš jų neturi nė vieno brėžiamo kelio, o vis dėlto kiekviename jų puslapyje yra šimtai užpildytų stačiakampių, 0,5–1 taško storio. Tik brūkšnius matantis detektorius nematė nieko, kiekvienas puslapis nukrisdavo į tarpų aptikimą, o išvestis buvo smulkių fragmentų išberta krūva, o ne lentelės. 3.116.4 pridėtas kompaktiškų stulpelių šablonas tai sušvelnino fragmentų lygmeniu; šakninė priežastis buvo ta, kad detektorius skaitė netinkamą piešimo operatorių
Kodėl iš Word eksportuota lentelė neturi brėžiamų linijų?
Teksto procesorius rėmelio nemąsto kaip linijos; jis mąsto jį kaip stačiakampį su pločiu ir tą stačiakampį nupiešia užpildu. ISO 32000-1 §8.5.2.1 apibrėžia re operatorių kaip pridedantį stačiakampio poprotį, o §8.5.3 atskiria piešimo operatorius: S brėžia kelią esamu linijos pločiu, f užpildo jo vidų. 0,5 taško ląstelės rėmelis išeina kaip x y w 0.5 re f, ir brėžimo mechanizmas — su linijos pločiu, sujungimais ir brūkšnių raštu — niekada neįsijungia. Ląstelių fono užpildas yra ta pati konstrukcija su didesniu stačiakampiu. Brėžiamas tinklelis, piešiamas su m, l ir S, yra tai, ko tikėjosi pradinis detektorius, ir tai, ko beveik nieko, eksportuoto iš biuro programos, nesukuria:
% vienas ląstelės rėmelis iš teksto procesoriaus eksporto: 0,5 taško aukščio užpildytas stačiakampis
72 700 468 0.5 re f
% ląstelės fonas: užpildytas stačiakampis, ląstelės dydžio
72 676 117 24 re f
% brėžiama tinklelio linija, kuriai buvo rašytas pradinis detektorius
72 700 m 540 700 l S
Detektoriui, kuris FPDFPath_GetDrawMode teiraujasi tik ar nustatytas brėžimo požymis, abu užpildyti stačiakampiai yra nematomi. Tada žodžiai ląstelių viduje pasiekia tarpų aptikimą, kur stulpeliai, atskirti 6 taškų tarpu, nusileidžia žemiau numatytojo MinColumnGap 12 taškų, ir grįžta tik ta eilučių poaibis, kuris atsitiktinai sulygiuotas pakankamai, kad praeitų MinRows. Tai ir yra fragmentų elgesys, ir joks parametrų derinimas nepaverčia jo tinkleliu, kurį autorius nubrėžė
Kaip PDFium Component užpildytą stačiakampį paverčia tinklelio linija?
TableCollectObjectRulings apžiūri kiekvieną kelio objektą po vieną poprotį. Piešimo režimas ateina iš FPDFPath_GetDrawMode; kelias laikomas užpildytu, kai DetectFilledRulings įjungtas, o užpildo režimas nėra none. Kiekvienas taškas transformuojamas per objekto matricą ir surenkamas, iki MaxSubpathPoints (8) vienam popročiui, o bet kuris kreivės segmentas pažymi poprotį kaip kreivą. Kai poprotis užsidaro arba prasideda naujas MoveTo, FlushSubpath nusprendžia, kas tai buvo: kreivas poprotis atmetamas, o taip pat atmetamas bet kuris uždaras daugiakampis, kurio taškai ne visi telpa PointTolerance (0,05 taško) ribose nuo ribinio stačiakampio kraštų bent vienoje ašyje. Trikampis, strėlės formos figūra ar užapvalintas skirtukas niekada netampa tinklelio linija, ir būtent tai neleidžia dekoratyvinei grafikai patekti į tinklelį
Kas išlieka, yra ašims lygiuotas stačiakampis, klasifikuojamas pagal savo ribinį stačiakampį. Plotis, lygus MaxRulingThickness arba mažesnis, kai aukštis didesnis, duoda vieną vertikalią liniją horizontaliame centre, apimančią stačiakampį iš apačios į viršų; veidrodinis atvejis duoda vieną horizontalią liniją. Abu matmenys virš ribos reiškia užpildytą ląstelę, ir stačiakampis duoda keturias linijas, po vieną kiekvienam kraštui. Abu matmenys ties riba arba žemiau jos neduoda nieko, tad 2 taškų kvadratinis ženklelis nėra palaikomas linija. Brėžiamas kelias eina senuoju keliu per AddLine, po vieną liniją kiekvienam ašims lygiuotam segmentui, tad su S nubrėžtas tinklelis apdorojamas lygiai kaip anksčiau, o kelias, nupieštas ir užpildu, ir brūkšniu, sukuria persidengiančias dalis, kurias suliejimo ėjimas sujungia:
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; // nuo 1
Options := TPdfTableExtractionOptions.Default;
// tai 3.117.0 numatytosios reikšmės, išrašytos aiškumui
Options.DetectFilledRulings := True; // ploni užpildyti stačiakampiai tampa linijomis
Options.MaxRulingThickness := 3.0; // taškai; storesni laikomi fonu
Options.RulingSnapTolerance := 4.0; // taškai; 0 išjungia suvedimą
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;
Ką RulingSnapTolerance duoda lentelėms su fonu užpildytomis ląstelėmis?
RulingSnapTolerance yra tai, kas vien iš fono sukonstruotą lentelę sujungia į vieną tinklelį. Kai kurie eksportai apskritai nepiešia rėmelio: kiekviena ląstelė yra užpildytas stačiakampis savo spalva, o gretimi stačiakampiai atskirti 1–3 taškų baltu tarpu. Kiekvienas stačiakampis duoda keturias kraštų linijas, bet vienos ląstelės dešinysis kraštas ir kitos kairysis yra 2 taškų atstumu, o junglumo patikra naudoja RulingTolerance, kuri pagal nutylėjimą yra 1 taškas. Be suvedimo kiekviena ląstelė sudaro savo atskirą sujungtąjį komponentą iš keturių linijų, nė vienas komponentas nepasiekia MinRows, ir puslapis nepraneša nieko. TableSnapRulings surenka visas dalyvaujančias X koordinates (kiekvienos vertikalios linijos padėtį plius kiekvienos horizontalios pradžią ir pabaigą) ir taip pat visas Y koordinates, surikiuoja kiekvieną sąrašą, sugrupuoja jį grandinėmis jungdamas reikšmes, kurių kaimynės skiriasi ne daugiau nei tolerancija, kiekvieną grupę pakeičia jos vidurkiu, o paskui kiekvieną padėtį, pradžią ir pabaigą perkelia į artimiausią grupės centrą. Abi tarpelio pusės tampa ta pačia linija, ir junglumas išsilaiko
Suvedimas vyksta prieš TableMergeRulings, kuris surikiuoja linijas ir sujungia kolinerines dalis, kurios liečiasi arba persidengia RulingTolerance ribose, o abu vyksta prieš tai, kai TableDetectRuled apskritai pamato duomenis, tad porinė junglumo patikra yra proporcinga tinklelio linijų skaičiui, o ne atskirų ląstelių fragmentų skaičiui. Brėžiamame tinklelyje šie ėjimai nekenkia, nes koordinatės, kurios jau buvo identiškos, suvedamos į save pačias. Vienas dalykas, kurį verta turėti omenyje: grandininis grupavimas neturi savo pločio ribos — koordinačių seka, kurioje kiekviena kita už 3 taškų, susitraukia į vieną centrą. Su numatytuoju 4 taškų nustatymu tai paliečia tik siauresnius už simbolį stulpelius, bet jei dokumente yra tikrų 3 taškų tarpelių, kurie turi likti atskiri, sumažinkite toleranciją arba nustatykite 0, kad suvedimas būtų išjungtas:
// Atskirti brūkšnių strategiją ir palyginti, ką viename puslapyje mato kiekvienas nustatymas
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 eksportas paprastai praneša 0, N, o paskui mažiau nei N:
// tik brūkšniai nemato nieko, suvedimas sujungia fono ląsteles,
// o išjungus suvedimą kiekviena fono ląstelė lieka savo sala
Writeln(CountRuledTables(Pdf, False, 4.0));
Writeln(CountRuledTables(Pdf, True, 4.0));
Writeln(CountRuledTables(Pdf, True, 0.0));
Linijos form XObject viduje
Puslapių dėstymo įrankiai dažnai įvynioja lentelę arba visą puslapio turinį į form XObject ir nupiešia jį su Do. ISO 32000-1 §8.10.1 nustato, kad piešiant formą formos matrica sujungiama su esama transformacijos matrica, tad stačiakampis formos viduje gyvena formos erdvėje ir puslapį pasiekia tik po dviejų ar daugiau transformacijų. TableCollectObjectRulings rekursyviai įeina į formos objektus, kai IncludeFormXObjects nustatytas: jis perskaito objekto matricą, sujungia ją su tėvo matrica per TableMultiplyMatrix, kurio argumentų tvarka reiškia „atvaizduoti per pirmą matricą, paskui per antrą", ir išvardija vaikus su FPDFFormObj_CountObjects bei FPDFFormObj_GetObject, perduodamas jungtinę matricą žemyn. Įdėjimas gilesnis už MaxFormDepth (8) praleidžiamas tyliai — tai apsauga nuo patologinių failų, o ne riba, kurią pasiektų koks nors tikras eksportas. Kodėl daugybos tvarka svarbi, aptarta tame pačiame straipsnyje apie matricos pridėjimą prieš ir po: sukeitus operandus pasislenka poslinkio narys, ir linija, kuri turėjo atsidurti puslapio viršuje, atsiduria ištakose
Kodėl tinklelio linijų biudžetas išaugo keturis kartus?
Numatytasis MaxRulingSegments 3.117.0 versijoje pakilo nuo 4096 iki 16384, nes ląstelių rėmeliai ateina kur kas didesniais kiekiais nei brėžiamos tinklelio linijos. Nubrėžta 30 eilučių ir 6 stulpelių lentelė yra 38 linijų segmentai. Ta pati lentelė, eksportuota kaip užpildyti stačiakampiai, yra iki keturių rėmelių vienai ląstelei — 720 dalių prieš suliejimą — o forma su fono ląstelėmis tą padvigubina. Dvi tokios lentelės puslapyje būtų išeikvoję senąjį biudžetą. Biudžetas tikrinamas TableAppendRuling per Check, kuris išmeta EPdfError su pranešimu „Table ruling-segment budget exceeded"; jokio prastesnio rezultato nėra, jokio dalinio tinklelio nėra, ir tarpų ėjimas taip pat nepaleidžiamas. Jei nepatikimai įvesčiai nustatote griežtesnį savo biudžetą, gaudykite išimtį ir nuspręskite, o ne skaitykite tuščią rezultatą kaip „lentelių nėra":
Options := TPdfTableExtractionOptions.Default;
Options.MaxRulingSegments := 2048; // tyčia griežtas nepatikimai įvesčiai
try
Tables := Pdf.ExtractTables(Options);
except
on E: EPdfError do
begin
Log(E.Message); // 'Table ruling-segment budget exceeded'
Options.MaxRulingSegments := 16384; // 3.117.0 numatytoji reikšmė
Tables := Pdf.ExtractTables(Options);
end;
end;
Išmatuoti rezultatai ir kur šis metodas sustoja
Tuose pačiuose 13 pavyzdinių dokumentų ištraukimas nuo 43 lentelių — 9 brūkšniuotų ir 34 tarpų fragmentų ar klaidingų teigiamų — pakilo iki 41 brūkšniuotos lentelės ir nė vieno klaidingo teigiamo tarpų atvejo. Dalis to sutvarkymo priklauso dviem lydintiems 3.117.0 pakeitimams: žodžiai, kuriuos brūkšniuotas tinklelis jau pasiėmė, pašalinami prieš paleidžiant tarpų aptikimą, tad lentelė niekada nepranešama du kartus, o tarpų stulpelio riba dabar turi būti be teksto einantis koridorius per kiekvieną eilutę, kurią ji skiria, ir būtent tai sustabdė lygiuotų pastraipų vertinimą kaip 5x4 lentelių. Užpildytų stačiakampių skaitytuvas ir yra tai, kas pačias lenteles perkėlė iš fragmentų stulpelio į brūkšniuotų stulpelį
Ribas verta išdėstyti tiesiai. Puslapis be tekstinio sluoksnio vis tiek duoda tinklelio skeletą su tuščiomis ląstelėmis, nes linijos ateina iš geometrijos, o tekstas — iš tekstinio puslapio; nuskaitytiems puslapiams pirmiausia reikia OCR. Užpildytos figūros su kreivėmis, užapvalintais kampais ar ne stačiakampiais kontūrais atmetamos visiškai, tad lentelė, kurios rėmeliai nubrėžti kaip užapvalintų stačiakampių kontūrai, kaip ir anksčiau reikalauja tarpų aptikimo. Lentelė be rėmelių ir be fono visa to nepaveikiama ir lieka tarpų strategijos sritimi, aprašyta straipsnyje apie lentelių ištraukimą; kai ir to nepakanka, žodžių stačiakampiai ir blokai iš struktūrizuoto teksto ir skaitymo tvarkos yra žaliava sričiai specifiniam skaitytuvui. Su komponentu platinamas TableExtractionLab demonstracinis pavyzdys atidengia DetectFilledRulings savo nustatymų skydelyje, ir tai greičiausias būdas pamatyti, kaip tam tikras eksportas atrodo su juo ir be jo; visa API aprašyta PDFium Component for Delphi puslapyje