Извличането на таблици в PDFium Component, от версия 3.117.0, третира тънък запълнен правоъгълник като table ruling. С включено DetectFilledRulings — което е по подразбиране — запълнен, осно-подравнен бокс не по-дебел от MaxRulingThickness (3 пункта) става едно ruling по дългата си ос, по-голям запълнен бокс внася четирите си ръбове, а всяка ruling координата се snap-ва в рамките на RulingSnapTolerance (4 пункта), преди мрежата да е сглобена. Таблици, експортирани от Word, Google Docs и браузъри, следователно достигат ruled детектора като пълни мрежи, вместо да пропаднат към whitespace откриване като фрагменти
По-ранната статия за откриване и извличане на таблици заяви, че ruled откриването ползва чертаните линии и че всеки stroked path сегмент се трансформира в координати на страницата. Това изречение беше вярно и непълно. Броянето на path обекти през набор от 13 реални примерни документа показа, че 9 от тях не съдържат изобщо stroked path, а всяка от страниците им носи стотици запълнени правоъгълници с дебелина 0.5 до 1 пункт. Stroke-само детекторът не видя нищо, всяка страница падна на whitespace откриване, а изходът беше разсейване от малки фрагменти, не таблици. Compact-columns preset-ът, добавен в 3.116.4, смекчи това на ниво фрагмент; основната причина беше, че детекторът четеше грешния рисуващ оператор
Защо таблица, експортирана от Word, няма stroked линии?
Текстов процесор не мисли за рамка като за линия; той мисли за нея като за бокс с ширина и боядисва този бокс с запълване. ISO 32000-1 §8.5.2.1 дефинира оператора re като добавящ правоъгълников под-път, а §8.5.3 разделя рисуващите оператори: S очертава пътя с текущата дебелина на линия, f запълва интериора му. Клетъчна рамка от 0.5 пункта излиза като x y w 0.5 re f, а machinery-то за очертаване — дебелина на линията, съединения и dash шаблон — изобщо не тръгва. Сянката на клетка е същата конструкция с по-голям бокс. Очертана мрежа, чертана с m, l и S, е това, което оригиналният детектор очакваше, и е това, което почти нищо, експортирано от офис приложение, произвежда:
% една клетъчна рамка от export на текстов процесор: запълнен бокс с височина 0.5 pt
72 700 468 0.5 re f
% сянка на клетка: запълнен бокс с размера на клетката
72 676 117 24 re f
% линираната мрежова линия, за която беше писан оригиналният детектор
72 700 m 540 700 l S
За детектор, който пита FPDFPath_GetDrawMode само дали флагът за очертаване е вдигнат, и двата запълнени бокса са невидими. Думите вътре в клетките после достигат whitespace откриването, където колони, разделени с 6-пунктова пролука, седят под подразбиращия се MinColumnGap от 12 пункта, а това, което се връща, е какъвто и да е подмножество редове, които случайно са се подравнили достатъчно добре, за да минат MinRows. Това е фрагментното поведение, и никакво количество настройка на параметри не го превръща в мрежата, която авторът е начертал
Как PDFium Component превръща запълнен бокс в ruling?
TableCollectObjectRulings инспектира всеки path обект по един под-път наведнъж. Draw режимът идва от FPDFPath_GetDrawMode; път брои за запълнен, когато DetectFilledRulings е включен и fill режимът не е none. Всяка точка се трансформира през обектната матрица и се събира, до MaxSubpathPoints (8) на под-път, а всеки криволинеен сегмент маркира под-пътя като крив. Когато под-пътят се затвори или нов MoveTo започне, FlushSubpath решава какво е бил: крив под-път се изхвърля, а така също и всеки затворен полигон, чиито точки не седят всички в рамките на PointTolerance (0.05 пункта) от ръбовете на bounding box-а на поне една ос. Триъгълник, шеврон или заоблен таб никога не става ruling — това държи декоративната графика извън мрежата
Това, което оцелява, е осно-подравнен правоъгълник, класифициран по неговия bounding box. Ширина на или под MaxRulingThickness с височина над него дава едно вертикално ruling на хоризонталния център, обхващащо бокса от дъно до връх; огледалният случай дава едно хоризонтално ruling. И двете размерности над прага означава оцветена клетка, а боксът внася четири rulings, по едно на ръб. И двете размерности на или под прага не внасят нищо, така че квадратна точка от 2 пункта не се побърква за линия. Stroked път върви по стария маршрут през AddLine, едно ruling на осно-подравнен сегмент, така че мрежа, чертана с S, се обработва точно както преди, а път, боядисан и с запълване, и с очертаване, произвежда застъпващи се парчета, които merge проходът свива:
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
Options := TPdfTableExtractionOptions.Default;
// това са подразбиращите се от 3.117.0, изписани за яснота
Options.DetectFilledRulings := True; // тънки запълнени боксове стават rulings
Options.MaxRulingThickness := 3.0; // пункта; по-дебели боксове броят за сянка
Options.RulingSnapTolerance := 4.0; // пункта; 0 изключва 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;
Какво прави RulingSnapTolerance за таблици от оцветени клетки?
RulingSnapTolerance е това, което кара таблица, построена само от сянка, да се свърже в една мрежа. Някои експорти не чертаят изобщо рамка: всяка клетка е запълнен бокс в собствен цвят, а съседните боксове са разделени с 1 до 3 пункта бяла пролука. Всеки бокс дава четири ръбови rulings, но десният ръб на една клетка и левият на следващата седят на 2 пункта един от друг, а тестът за свързаност ползва RulingTolerance, която по подразбиране е 1 пункт. Без snapping всяка клетка образува свой собствен свързан компонент от четири rulings, никой компонент не стига MinRows, а страницата не докладва нищо. TableSnapRulings събира всяка X координата в игра (позицията на всяко вертикално ruling плюс началото и краят на всяко хоризонтално) и всяка Y координата съответно, сортира всеки списък, клъстеризира го чрез верижени стойности, чиито съсед се различава с не повече от толеранса, заменя всеки клъстер със средното му, и после мести всяка позиция, начало и край към най-близкия клъстерен център. Двете страни на пролука стават една линия, а свързаността се държи
Snapping тръгва преди TableMergeRulings, който сортира rulings-ите и съединява колинеарни парчета, докосващи се или застъпващи се в рамките на RulingTolerance, а и двете тръгват преди TableDetectRuled изобщо да види данните, така че двойчната проверка за свързаност е пропорционална на броя мрежови линии, а не на броя клетъчни фрагменти. Върху очертана мрежа проходите са безвредни, защото координати, вече идентични, snap-ват към самите себе си. Единственото нещо, което да пазите в глава, е, че верижното клъстеризиране няма собствен лимит на ширина: серия от координати, всяка на 3 пункта разстояние, се свива в един-единствен център. При подразбиращите се 4 пункта това засяга само колони, по-тесни от знак, но ако документ има истински 3-пунктови пролуки, които трябва да останат отделни, смалете толеранса или го задайте на 0, за да изключите snapping:
// Изолируй ruled стратегията и сравни какво вижда всяка настройка на една страница
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 export обикновено докладва 0, N и после по-малко от N:
// stroke-само не вижда нищо, snapping свързва оцветените клетки,
// а изключването на snap оставя всяка оцветена клетка свой собствен остров
Writeln(CountRuledTables(Pdf, False, 4.0));
Writeln(CountRuledTables(Pdf, True, 4.0));
Writeln(CountRuledTables(Pdf, True, 0.0));
Rulings вътре в form XObjects
Инструменти за странично оформление често увиват таблица, или цялото тяло на страницата, в form XObject и я боядисват с Do. ISO 32000-1 §8.10.1 специфицира, че формната матрица се конкатенира с текущата матрица на трансформация, когато формата се боядисва, така че правоъгълник вътре във формата живее в form пространство и каца на страницата само след две или повече трансформации. TableCollectObjectRulings рекурсира в form обекти, когато IncludeFormXObjects е зададен: чете обектната матрица, съчетава я с родителската чрез TableMultiplyMatrix, чийто ред на аргументите означава „картирай през първата матрица, после през втората“, и изброява децата с FPDFFormObj_CountObjects и FPDFFormObj_GetObject, предавайки съчетаната матрица надолу. Влагане, по-дълбоко от MaxFormDepth (8), се прескача тихо — това е guard срещу патологични файлове, а не лимит, който който и да е истински export доближава. Причината редът на умножението да има значение е същата, обсъдена в matrix prepend срещу append: разменянето на операндите мести члена за транслация, и ruling, който трябва да кацне на върха на страницата, каца в origin-а вместо това
Защо ruling бюджетът се учетвори?
Подразбиращият се MaxRulingSegments скочи от 4096 на 16384 в 3.117.0, защото per-cell рамките пристигат в много по-големи бройки от очертаните мрежови линии. Очертана таблица от 30 реда и 6 колони е 38 линейни сегмента. Същата таблица, експортирана като запълнени боксове, е до четири рамки на клетка — 720 парчета преди merging — а форма с оцветени клетки удвоява това. Две такива таблици на страница щяха да изчерпят стария бюджет. Бюджетът се налага в TableAppendRuling чрез Check, който вдига EPdfError със съобщение "Table ruling-segment budget exceeded"; няма влошен резултат, няма частична мрежа, а whitespace проходът също не тръгва. Ако зададете собствен по-стегнат бюджет за ненадежден вход, хванете exception-а и решете, вместо да четете празен резултат като „няма таблици“:
Options := TPdfTableExtractionOptions.Default;
Options.MaxRulingSegments := 2048; // нарочно стегнат за ненадежден вход
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
Tables := Pdf.ExtractTables(Options);
end;
end;
Измерени резултати и къде подходът спира
Върху същите 13 примерни документа извличането мина от 43 таблици, 9 от тях ruled и 34 whitespace фрагменти или фалшиви положителни, на 41 ruled таблици и никакви whitespace фалшиви положителни. Част от това почистване принадлежи на две придружаващи промени в 3.117.0: думи, вече претендирани от ruled мрежа, се премахват, преди whitespace откриването да тръгне, така че таблица никога не се докладва два пъти, а whitespace колоночна граница вече трябва да бъде текстово-свободен коридор през всеки ред, който разделя — това спря обоснованите параграфи да се точкуват като таблици 5x4. Запълнено-правоъгълниковият четец е това, което премести самите таблици от колоната фрагменти в колоната ruled
Границите си заслужават да се кажат ясно. Страница без текстов слой все още дава скелета на мрежата, всяка клетка празна, защото rulings-ите идват от геометрията, а текстът идва от текстовата страница; сканираните страници първо се нуждаят от OCR. Запълнени форми с криви, заоблени ъгли или не-правоъгълникови очертания се изхвърлят изцяло, така че таблица, чиито рамки са начертани като очертания на заоблени правоъгълници, се нуждае от whitespace откриване както преди. Таблица без рамки и без сянка не се променя от нищо от това и остава провинция на whitespace стратегията, описана в статията за извличане на таблици; когато дори това не стига, word боксовете и блоковете от структуриран текст и ред на четене са суровината за специфичен за домейна четец. Демото TableExtractionLab, което се доставя с компонента, излага DetectFilledRulings в своя панел с опции — най-бързият начин да видите как изглежда даден export с него и без него; пълното API е описано на страницата на PDFium Component за Delphi