Word'den ya da Google Docs'tan dışa aktarılan bir tablo, tek bir sayfada yüzlerce kenar parçası taşıyabilir ve tek bir çizgili çizgi taşımaz. Sayfayı qpdf --qdf ile açtığınızda içerik akışı, her biri yarım punto yüksekliğinde bir kutuyu anlatan bir re f çiftleri sütunu gibi okunur. Yalnızca çizgili segmentlere bakan bir tablo dedektörü hiçbir şey bulamaz, sözcükleri aralarındaki beyaz boşluğa göre dizmeye geri düşer ve yazarın tek bir temiz ızgara çizdiği yerde iki-üç satırlık parçalar savurur. Kenarlar hep oradadır. Dolguyla boyanırlar; bu yüzden dedektörün dolu dikdörtgenleri çizgi olarak okuması ve sonra birbirine yaklaşan kenarları paylaşılan ızgara çizgilerine çekmesi gerekir
Bir Word tablosunun neden çizgili çizgisi yoktur?
Bir kelime işlemci, hücre kenarını genişlikli bir kutu sayar ve o kutuyu fill operator'üyle boyar. ISO 32000-1 §8.5.2.1, re'yi kapalı bir dikdörtgen alt yolu eklemek olarak tanımlar ve §8.5.3 boyama operator'lerini ayırır: S yolu geçerli çizgi genişliğiyle çizer, f içini doldurur. 0.5 puntoluk bir kenar, x y w 0.5 re f olarak çıkar ve stroke mekanizması (çizgi genişliği, birleşimler, kesik çizgi deseni) hiç çalışmaz. Hücre gölgelendirmesi, daha büyük kutulu aynı yapıdır. Çizgi-odaklı bir dedektörün beklediği çizgili biçim — m, l, S — office çıktılarının nadiren kullandığı biçimdir:
% bir kelime işlemci çıktısından bir hücre kenarı: 0.5 pt yüksekliğinde dolu kutu
72 700 468 0.5 re f
% hücre gölgelendirmesi: hücre boyutunda dolu bir kutu
72 676 117 24 re f
% yalnızca-stroke arayan bir dedektörün beklediği çizgili ızgara çizgisi
72 700 m 540 700 l S
Ham PDFium API'si üzerinde kendi dedektörünüzü yazıyorsanız ilk taslak genelde FPDFPath_GetDrawMode çağırır, stroke bayrağı setken yolu tutar ve yoluna devam eder. Yukarıdaki iki dolu kutu da o testte kalır. Hücrelerin içindeki sözcükler boşluk algılamasına ulaşır ve sıkı bir çıktıda sütunlar belki 6 punto arayla durur. PDFium Component'ta varsayılan MinColumnGap 12 puntodur; o sütunlar kayda geçmez. Kayda geçene kadar MinColumnGap'i düşürmek, iki yana yaslanmış paragrafların gerilmiş sözcük aralığını da kayda geçirir ve yazarın çizdiği ızgara asla geri gelmez; çünkü onu tanımlayan bilgi dolu kutularda yaşar
PDFium Component dolu bir kutuyu nasıl çizgi olarak okur?
PDFium Component tablo geometrisini, sayfadaki her path nesnesinden bir alt yol okur ve TPdfTableExtractionOptions.DetectFilledRulings varsayılan olarak True olduğundan, fill modu none olmayan her yol, çizgili yolların yanında çizgiler için incelenir. Arkadan gelen filtre bilerek katıdır. Dolu bir alt yol, yalnızca dört ile sekiz arası noktası olduğunda, curve segmenti içermediğinde ve her noktası, alt yolunun sınırlayıcı kutusunun bir köşesinde 0.05 punto içinde oturduğunda hak kazanır. re ile çizilen bir dikdörtgen geçer; tekrarlanan bir köşeyle beş açık nokta olarak çizilen de geçer. Bir üçgen, bir chevron, L biçimli bir kontur ya da yuvarlatılmış bir sekme testi geçemez ve hiçbir şey katmaz
Hayatta kalan dikdörtgen, sınırlayıcı kutusuyla MaxRulingThickness'e karşı sınıflandırılır; o, varsayılan olarak 3 puntodur. Genişlik eşiğin altında ya da eşitken yükseklik üzerindeyse, kutunun bütün yüksekliğince akan, yatay merkezde tek bir dikey çizgi verilir; ayna durum tek bir yatay çizgi verir. Her iki boyut da eşiği aştığında kutu gölgeli bir hücre sayılır ve kenar başına bir olmak üzere dört çizgi katlar. Her iki boyut da eşiğin altındayken ya da eşitken kutu hiçbir şey katmaz; böylece bir listedeki 2 puntoluk kare madde imi, asla masa ızgarasının ucu olmaz. Dolgu renginin bunda hiçbir payı yoktur. Beyaz sayfadaki beyaz kutu, siyah olanın verdiği çizgilerin aynısını verir; bu yüzden tablo boyutunda bir arka plan paneli dış çerçeve olarak görünebilir
Çizgili yollar kendi rotalarını korur. Uçları tek bir eksende RulingTolerance'ten (varsayılan 1 punto) fazlasıyla farklılaşmayan her düz segment, öteki eksen boyunca bir çizgiye dönüşür; curve segmentleri atlanır ve kapalı bir alt yolunun kapanış segmenti de sayılır. Hem fill hem stroke ile boyanan bir yol iki rotadan da katkı verir ve üst üste binen parçaları daha sonra birleştirme adımı çöker. Çizgili yuvarlatılmış bir dikdörtgen, düz kenarları için yine çizgi verir; yalnızca köşelerdeki yaylar düşer. Çıkarma çağrısının kendisinin özel bir kurulumu gerekmez:
uses
System.SysUtils, PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
Mode: string;
I, J, WithText: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'itinerary-from-word.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 1; // 1 tabanlı
Options := TPdfTableExtractionOptions.Default;
// dolu-çizgi alanları, varsayılan değerleriyle açıkça yazılmış
Options.DetectFilledRulings := True;
Options.MaxRulingThickness := 3.0; // punto; 0'dan büyük olmalı
Options.RulingSnapTolerance := 4.0; // punto; 0 yaslamayı kapatır
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';
WithText := 0;
for J := 0 to High(Tables[I].Cells) do
if Tables[I].Cells[J].Text <> '' then
Inc(WithText);
Writeln(Format('%dx%d %s, confidence %.2f, %d of %d cells hold text',
[Tables[I].RowCount, Tables[I].ColumnCount, Mode,
Tables[I].Confidence, WithText, Length(Tables[I].Cells)]));
end;
finally
Pdf.Free;
end;
end;
MaxRulingThickness'i 0 yapmak özelliği kapatmaz. ExtractTables geometri option'larını baştan doğrular ve sıfır ya da altındaki bir eşik için "Tablo çıkarma geometri option'ları geçersiz" iletisiyle EPdfError yükseltir. Yalnızca-stroke davranışını almak için DetectFilledRulings'i False yapın. Bir office paketinden ya da bir tarayıcı yazdırma iletişiminden çıkan her şey için açık bırakın; maliyeti düşük tutan, katı dikdörtgen filtresidir, çünkü dekoratif dolgular neredeyse hiç ondan geçmez
RulingSnapTolerance gölgeli-hücreli tablolar için ne yapar?
RulingSnapTolerance, yalnızca gölgelendirmeden kurulmuş bir tabloyu tek bir ızgaraya bağlayan şeydir. Bazı çıktılar hiç kenar çizmez: her hücre, komşularından 1-3 puntoluk beyaz bir aralıkla ayrılmış dolu bir kutudur. Her kutu dört kenar çizgisini verir ama bir hücrenin sağ kenarı ile bir sonrakinin sol kenarı, diyelim ki, 2 punto arayla durur ve iki çizginin aynı ızgaraya ait olup olmadığına karar veren test yalnızca RulingTolerance'e izin verir — 1 punto. Yaslama olmadan her hücre, 1x1 bir ızgarayı anlatan dört çizgili bir adadır; hiçbir şey MinColumns'a ya da MinRows'a (ikisi de varsayılan 2) ulaşmaz ve çizgili geçiş, açıkça tablo taşıyan bir sayfada hiçbir tablo raporlamaz
Yaslama adımı, oyundaki her X koordinatını toplar — her dikey çizginin konumu artı her yatay çizginin başlangıcı ve sonu — ve her Y koordinatını da aynı şekilde. Her liste sıralanır ve iki komşu toleransten fazlasıyla farklılaştığı her yerde kümeler halinde bölünür; her küme ortalamaıyla değiştirilir ve her konum, başlangıç ile uç en yakın merkeze taşınır. 230.0 ve 232.0'daki kenarlar ikisi de 231.0 olur, iki hücre bir çizgiyi paylaşır ve yatay çizgiler onlarla birlikte gerilir, köşeler buluşsun diye
RulingTolerance içinde dokunan ya da üst üste binen aynı-doğrultudaki parçalar tek çizgilerde birleştirilir. Izgara dedektörü ancak ondan sonra çalışır ve çizgileri ikili karşılaştırır; 720 kenar parçası olarak gelen 30 satırlık bir tablo o karşılaştırmaya bir düzine çizgi olarak ulaşır
Çizgili bir ızgarada adım hiçbir şeyi değiştirmez, çünkü zaten eşleşen koordinatlar kendilerine yaslanır. Peki zincirlemenin kendi genişlik sınırı yoktur. 100, 103, 106 ve 109'daki, her biri 3 punto arayla giden bir değer dizisi, dış değerler 9 punto arayla dururken 104.5'te tek bir merkeze çöker. 4 puntoluk varsayılanla bu, yalnızca bir karakterden dar sütunlarda sokar; office çıktısı 4 puntoluk varsayılanı tutabilir. Bir form kasıtlı olarak dar sütunlar taşıdığında (onay kutuları, hücre-başına-rakam hesap numarası alanları) 1 ya da 2'ye doğru indirin ve yaslamayı kapatmak için 0 yapın. Çizgili stratejiyi üç ayar altında tek başına çalıştırmak, bir sayfanın hangi mekâna bel bağladığını gösterir:
// Yalnızca çizgili strateji, böylece boşluk algısı sonucu maskeler
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;
// Yalnızca gölgeli hücrelerle 2 pt aralıklı çizilmiş bir tablo için beklenen:
// yalnızca stroke -> 0 (hiç çizgili geometri yok)
// dolu kutular, snap 4 -> 1 (aralık kenarları bir çizgiyi paylaşır)
// dolu kutular, snap 0 -> 0 (her hücre 1x1 bir ada olarak kalır)
Writeln('stroke only: ', CountRuledTables(Pdf, False, 4.0));
Writeln('filled boxes, snap 4: ', CountRuledTables(Pdf, True, 4.0));
Writeln('filled boxes, snap 0: ', CountRuledTables(Pdf, True, 0.0));
Üçüncü sayım ikincinin altına düştüğünde sayfa yaslamaya bel bağlar ve toleransı indirmeden gözle kontrol edilecek sayfa da odur
Form XObject'lerinin içindeki çizgiler
Sayfa-düzeni araçları sık sık bir tabloyu ya da bütün sayfa gövdesini bir form XObject'ine sarar ve onu Do ile boyar. ISO 32000-1 §8.10.1, form boyandığında form matrisinin geçerli dönüşüm matrisiyle birleştirildiğini söyler; bu yüzden formun içindeki bir dikdörtgen form uzayında yazılır ve sayfa uzayına ancak iki ya da daha fazla dönüşümden sonra ulaşır. Varsayılan olan IncludeFormXObjects setken PDFium Component her form nesnesinin içine yürür, nesne matrisini ebeveyninin matrisiyle birleştirir ve birleşik matrisi her çocuğa devreder; böylece ötelenmiş bir formun içinde 72 700 468 0.5 re f olarak çizilen bir kenar, görüntüleyicinin gösterdiği yere düşer. Yürüyüş, sekiz düzey yuvalanmış formda durur
O birleşmenin sırası önem taşır. Formun içindeki bir nokta önce nesne matrisinden, sonra ebeveyn matrisinden geçer; operandları değiştirin, öteleme terimi yanlış uzayda uygulanır ve sayfanın tepesine ait bir çizgi orijinin ya da sayfanın dışında biter. Aynı tuzak, matrisleri kendiniz kurduğunuz her seferde karşınıza çıkar ve matris prepend ile append karşılaştırması yazısı onu bir döndürme ekseniyle gezer
Varsayılan çizgi bütçesi neden 16384?
16384'lük varsayılan MaxRulingSegments, hücre-başına bir kenar çıktısının kaç parça ürettiğini yansıtır. Çizgili 30 satırlı, 6 sütunlu bir tablo 38 çizgi segmenti ister: 31 yatay, 7 dikey. Dolu kutular olarak dışa aktarılan aynı tablo, hücre başına dört kenar parçası taşıyabilir — birleştirmeden önce 720 — ve her hücrenin gölgelendirilmesi hücre başına dört kenar çizgisi daha ekler; toplam 1440. Böyle iki tablo tek bir sayfada 3000'e yakın ister
Bütçeyi aşmak bir hatadır ve öyle yüzeye çıkar. ExtractTables, "Tablo çizgi-segment bütçesi aşıldı" iletisiyle EPdfError yükseltir, kısmi bir ızgara döndürmez ve o sayfanın boşluk geçişi de çalışmaz. Sıkı bir bütçe, güvenilmeyen girdi için makuldür — işlenmiş bir sayfa milyonlarca minicik kutu taşıyabilir — ama o zaman çağıranın "bu bütçe için fazla karmaşık" ile "bu sayfada tablo yok"u ayırt etmesi gerekir:
function TryExtractTables(Pdf: TPdf; Budget: Integer;
out Tables: TPdfTables): Boolean;
var
Options: TPdfTableExtractionOptions;
begin
Options := TPdfTableExtractionOptions.Default;
Options.MaxRulingSegments := Budget; // varsayılan 16384
try
Tables := Pdf.ExtractTables(Options);
Result := True;
except
on E: EPdfError do
begin
// LogWarning sizin loglama rutininizdir, kütüphanenin parçası değildir
LogWarning(Format('Page %d: %s', [Pdf.PageNumber, E.Message]));
Tables := nil;
Result := False; // bütçe aşımı ya da geçersiz option'lar
end;
end;
end;
// False bir sonuç "karara bağlanmadı" demektir; bu, boş bir diziden farklıdır
if not TryExtractTables(Pdf, 4096, Tables) then
QueueForReview(Pdf.FileName, Pdf.PageNumber); // bu da sizin rutininiz
Aynı istisna türü kelime bütçesini ve geçersiz option'ları da kapsar; gözden geçirme kuyruğunda onları ayırt eden şey günlüğe kaydedilen iletidir
Çizgili yolun devrettiği yer
Çizgili algı önce, boşluk algısı sonra çalışır ve iki kural ikisinin birbirinin üstüne basmasını önler. Çizgili geçişin zaten raporladığı bir ızgaranın içine merkezleri düşen sözcükler, boşluk algısı başlamadan kaldırılır; böylece bir tablo asla iki kez raporlanmaz. Bir boşluk sütun sınırının ayrıca, ayırdığı her satır boyunca metinsiz bir dikey koridor olması gerekir; bu, iki yana yaslanmış paragrafların tablo sayılmasını önler — boşluk tablosu yanlış pozitifleri yazısı o kuralı kapsar. Dolu dikdörtgenleri okumak, office çıktısı tabloları boşluk yolundan çizgili yola taşır
Çizgiler path nesnelerinden, metin metin sayfasından geldiği için metin katmanı olmayan bir sayfa, her hücresi boş ızgara iskeletini yine üretir. Çizgili bir ızgaranın güveni, 0.88 artı metin tutan hücrelerin oranının 0.12 katıdır; boş bir iskelet tam olarak 0.88 rapor eder ve ilk listedeki hücre sayımı metinli 0 hücre gösterir. O bileşim, önce OCR çalıştırma sinyalidir — OCR ile aranabilir bir metin katmanı eklemede anlatıldığı gibi. Eğrili ya da dikdörtgen-olmayan konturlu dolu şekiller hiçbir şey katmaz; hücreleri dolu yuvarlatılmış dikdörtgenler olan bir tablo boşluk algısına düşer. Ne kenarı ne gölgelendirmesi olan bir tablo, tablo algılama ve çıkarma yazısında anlatılan boşluk stratejisine aittir; o bile yetmediğinde yapılandırılmış metin ve okuma sırasındaki kelime kutuları ve bloklar, tek bir belge ailesi için yazılmış bir okuyucunun ham maddesidir
Bileşenle gelen TableExtractionLab demosunda DetectFilledRulings için bir onay kutusu vardır; bir çıktı yükleyip ızgaranın belirmesini ve kayboluşunu hiç kod yazmadan görebilirsiniz. Option'lar, kayıt türleri ve çıkarma API'sinin geri kalanı Delphi için PDFium Component ürün sayfasında listelenir