PDFium Component -versio 3.117.0 ketjuttaa sivunrajan yli katkeavan taulukon, kun joko molemmat fragmentit koskettavat sivun reunoja tai ensimmäisen fragmentin alapuolella ja toisen yläpuolella ei ole leipätekstiä, juoksevat ylä- ja alatunnisteet huomiotta jättäen. ExtractDocumentTables soveltaa tuota sisältötietoista testiä vanhemman sivumarginaalitestin vaihtoehtona, hylkää seuraavan sivun fragmentin, jonka ensimmäinen rivi on yksi koko levyinen kuvatekstisolu, ja pitää seuraavalle sivulle valuvan yksittäisen rivin osana omaa jatkoketjuaan
Taulukoiden tunnistusta ja poimintaa käsittelevä artikkeli esitteli jatkumisen neljänä tiukkana porttina ja käsitteli sivun reunan koskettamista yhtenä niistä. Kuvaus oli tarkka sen julkaisun osalta, jota se koski, ja se oli myös väärä useimmille taulukoille, joita ihmiset todella syöttävät komponentille. Tämä artikkeli on korjaus: mitkä asiakirjat marginaalitesti ei kykene käsittelemään, mikä sen korvasi ja ne kaksi sivuasiaa, jotka korjaus veti mukanaan
Miksi sivumarginaalitesti epäonnistuu Word-vienneissä?
Sivumarginaalitesti epäonnistuu, koska tekstinkäsittelyohjelma lopettaa rivien asettelun alamarginaaliin eikä paperin reunaan. Oletusarvoisella ContinuationMargin-arvolla 36 pistettä alkuperäinen sääntö vaati, että aiemman fragmentin alareuna on 36 pisteen sisällä sivun alareunasta ja myöhemmän fragmentin yläreuna 36 pisteen sisällä sivun yläreunasta. Wordista oletusarvoisilla yhden tuuman marginaaleilla viety asiakirja sijoittaa viimeisen rivin vähintään 72 pistettä sivun alareunan yläpuolelle, alatunnisteen läsnä ollessa vielä kauemmas, joten ehto ei koskaan täyttynyt. Jokainen pitkä taulukko sellaisessa asiakirjassa palasi itsenäisinä fragmentteina ContinuationGroup-arvolla nolla, ja kutsuja oli taas ompelemassa käsin. Testi on yhä mielekäs sen suhteen, mihin se suunniteltiin: asettelumoottorien tuottamat raportit, jotka täyttävät sivun kiinteään sisältölaatikkoon ja aloittavat seuraavan sivun aivan ylhäältä. Se ei ole huono sääntö, se on puutteellinen, minkä takia versio 3.117.0 piti sen ja lisäsi toisen polun sen korvaamisen sijaan
Mitä sisältötietoinen testi tarkistaa sen sijaan?
Sisältötietoinen testi tarkistaa, valtaako mikä tahansa muu kuin taulukko kahden fragmentin väliin jäävän tilan, käyttäen kunkin sivun sanalaatikoita sivugeometrian sijaan. Kun ExtractDocumentTables käy asiakirjaa läpi, se tallentaa sivukohtaisesti sen sanan alimman alareunan, jonka yläreuna on alatunnistevyöhykkeen yläpuolella, ja sen sanan ylimmän yläreunan, jonka alareuna on ylätunnistevyöhykkeen alapuolella. Molemmat vyöhykkeet ovat ContinuationMargin pistettä syviä, joten sama asetus palvelee nyt kahta asiaa: sivun reunan liikkumavaraa ja juoksevien ylä- ja alatunnisteiden vyöhykkeiden korkeutta. Fragmenttipari menee läpi, kun aiemman alareuna on samalla tasolla tai sen alapuolella kuin sivun alin leipäteksti ja myöhemmän yläreuna samalla tasolla tai sen yläpuolella kuin seuraavan sivun ylin leipäteksti, kumpikin AlignmentTolerance-arvon sisällä. Selkokielellä: taulukko oli viimeinen asia sivulla N ja ensimmäinen asia sivulla N+1, eikä sivunumero tai asiakirjan otsikko marginaalivyöhykkeellä lasketa. Tuo poissulkeminen ei ole mielivaltaista. ISO 32000-1 §14.8.2.2 luokittelee juoksevat ylä- ja alatunnisteet sivunvaihdon artefakteiksi, sisällöksi joka on olemassa sivunvaihdon takia eikä siitä huolimatta, ja sama ajatus, joka antaa tagatun lukijan ohittaa ne, antaa taulukon jatkua niiden ohi. Merkittyä sisältöä käsittelevä artikkeli kertoo, miten tagatut tiedostot julistavat nuo artefaktit nimenomaisesti; tässä luokittelu päätellään sijainnista, koska useimmat viedyt taulukot eivät kanna lainkaan tageja
Kaksi testiä yhdistyvät OR-operaatiolla. Asettelumoottorin raportti, jonka taulukot yltävät paperin reunaan, läpäisee ensimmäisen; Word-vienti, jonka taulukot pysähtyvät marginaaliin, läpäisee toisen; asiakirja joka tekee molemmat, läpäisee kahdesti. Vasta kun toinen niistä onnistuu, loput portit ajetaan, ja ne ajetaan kiinteässä järjestyksessä: sivunumeroiden on oltava vierekkäiset, myöhempi fragmentti ei saa alkaa kuvatekstirivillä ja sarakerajojen on täsmättävä kaksinkertaisen AlignmentTolerance-arvon sisällä, mikä oletuksilla on 6 pistettä. Luettelotyyppi on TPdfTableContinuation arvoillaan ptcNone, ptcStart, ptcMiddle ja ptcEnd. Fragmentti, joka merkittiin arvolla ptcEnd ja ketjuttuu sitten vielä eteenpäin seuraavalle sivulle, korotetaan arvoon ptcMiddle, joten kolmisivuinen taulukko luetaan sivujärjestyksessä alku, keskikohta, loppu. Ryhmänumerot alkavat ykkösestä ja 0 tarkoittaa ketjuttamatonta, ja ToJson tuottaa saman tiedon continuation- ja continuationGroup-jäseninä, mikä on se muoto, jota kannattaa suosia jos ketjutuksen tekee jokin loppupään palvelu
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfTableExtractionOptions;
Tables: TPdfTables;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'itinerary-from-word.pdf';
Pdf.LoadDocument;
Options := TPdfTableExtractionOptions.Default;
Options.DetectContinuations := True; // oletus; näkyvissä selvyyden vuoksi
Options.ContinuationMargin := 54; // kaksirivinen alatunniste, ~50 pt syvä
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
case Tables[I].Continuation of
ptcStart:
Writeln(Format('group %d starts on page %d (%d rows)',
[Tables[I].ContinuationGroup, Tables[I].PageNumber,
Tables[I].RowCount]));
ptcMiddle, ptcEnd:
Writeln(Format('group %d continues on page %d (%d rows)',
[Tables[I].ContinuationGroup, Tables[I].PageNumber,
Tables[I].RowCount]));
else
Writeln(Format('standalone table on page %d (%d rows)',
[Tables[I].PageNumber, Tables[I].RowCount]));
end;
finally
Pdf.Free;
end;
end;
Miten kuvatekstirivi estää kahden taulukon sulautumisen?
Seuraavan sivun fragmenttia, jonka ensimmäinen rivi on yksi solu joka kattaa kaikki sarakkeet, kohdellaan uutena taulukkona, ei koskaan edellisen jatkona. Tämä sääntö on olemassa siksi, että sisältötietoinen testi yksinään ketjuttaa liian innokkaasti. Tapaus, joka sen paljasti, oli litterointityylinen lomake: taulukko päättyy lähelle sivun 1 alaosaa, toinen taulukko jolla on identtiset sarakeleveydet alkaa läheltä sivun 2 yläosaa, niiden välissä ei ole muuta kuin alatunniste, ja sarakkeet täsmäävät pisteen tarkkuudella. Marginaalitestin alla ne kaksi eivät koskaan kohdanneet, koska kumpikaan ei koskettanut reunaa; sisältötestin alla ne ketjuttuivat välittömästi, ja osioista koostunut lomake muuttui yhdeksi epäjohdonmukaiseksi ruudukoksi. Ne erottaa toisistaan se, mikä näkyy solurakenteessa. Toinen taulukko alkaa osio-otsikolla kuten RECIPIENT INFORMATION, joka on aseteltu yhdeksi yhdistetyksi soluksi koko leveydelle, eikä aito jatko koskaan tee niin, koska kuvateksti kuuluu taulukolle, joka on jo alkanut edellisellä sivulla. TableStartsWithCaptionRow koodaa täsmälleen tuon: fragmentissa on vähintään kaksi saraketta ja siinä on solu, jonka RowIndex = 0, ColumnIndex = 0 ja ColumnSpan = ColumnCount. Tarkistus ajetaan vain myöhemmälle fragmentille, joten taulukko, jonka oma kuvatekstirivi on sen ensimmäisellä sivulla, ei muutu mihinkään; kuvateksti on sivulla N ja vain sivun N+1 fragmentti tarkastetaan
Seuraava sarakevertailu, TablesHaveMatchingColumns, on tiukempi kuin sama sarakemäärä. Se rakentaa kummankin fragmentin rajasijainnit solusuorakaiteista, interpoloi rajat, jotka yhdistetyt solut kätkevät, ja hylkää parin kun yksikin raja ajautuu toleranssia enemmän. Kaksi nelisarakkeista taulukkoa, joilla on eri suhteet, pysyvät siksi erillään vaikka kaikki muu osuisi kohdakkain
Mitä tapahtuu yksittäiselle riville, joka valuu seuraavalle sivulle?
Viivaruudukko, joka vie yhden rivin seuraavalle sivulle, havaitaan nyt ja ketjutetaan, edellyttäen että se päätyy jatkoketjuun; yksinään se hylätään. Oletusarvoinen MinRows 2 on olemassa pitämässä yksittäistä viivaparia raportoitumasta taulukoksi, mutta sivunvaihdon yli työnnetty viimeinen rivi on todellinen rivi, jonka kova kahden rivin alaraja pudotti hiljaisesti, ja taulukon loppu näytti täydelliseltä vaikka ei ollut. Asiakirjatason läpikäynti hoitaa sen kolmessa vaiheessa. Kun sekä DetectContinuations että DetectRuledTables on asetettu, sivukohtainen kierros ajaa viivailmaisimen rivialarajan ollessa väliaikaisesti laskettu yhteen, minkä takia ExtractTables hyväksyy nyt MinRows-arvon 1 viivaruudukoille samalla kun tyhjän tilan tunnistus pitää sisäisen alarajan 2:ssa. Jatkumiset merkitään koko tuloksen yli. Sitten jokainen taulukko, joka on kutsujan MinRows-arvoa lyhyempi eikä kuulu mihinkään ketjuun, poistetaan. Yhden rivin fragmentti selviää vain siksi, että se ketjutettiin, ja yhden rivin ruudukko muuten tavallisen sivun keskellä suodatetaan pois täsmälleen kuten ennenkin
// Rakenna jokainen ketju yhdeksi CSV:ksi pudottaen toistuvat otsikkorivit
// jatkofragmenteista
procedure ExportChains(const Tables: TPdfTables; const Folder: string);
var
I, R: Integer;
Lines: TStringList;
Csv: TStringList;
begin
Csv := TStringList.Create;
Lines := TStringList.Create;
try
for I := 0 to High(Tables) do
begin
if Tables[I].Continuation in [ptcNone, ptcStart] then
Csv.Clear;
Lines.Text := string(Tables[I].ToCsv);
if (Tables[I].Continuation in [ptcMiddle, ptcEnd]) and
(Lines.Count > 1) and (Tables[I].RowCount > 1) then
Lines.Delete(0); // tekstinkäsittelyohjelman toistama otsikko
for R := 0 to Lines.Count - 1 do
Csv.Add(Lines[R]);
if Tables[I].Continuation in [ptcNone, ptcEnd] then
Csv.SaveToFile(Format('%s\page%d-group%d.csv',
[Folder, Tables[I].PageNumber, Tables[I].ContinuationGroup]));
end;
finally
Lines.Free;
Csv.Free;
end;
end;
Kaksi yksityiskohtaa tuossa rutiinissa on tarkoituksellista. Yhden rivin valumaa ei koskaan riisuta, koska RowCount-vartija pitää sen, ja tekstinkäsittelyohjelma, joka toistaa otsikkorivin jokaisella sivulla, tuottaa fragmentin jonka ensimmäinen rivi on taas otsikko, joten rivin nolla pudottaminen keski- ja loppufragmenteilta on oikein tuossa tapauksessa ja väärin generaattorille, joka ei toista otsikoita. Tarkista yksi asiakirja ennen kuin päästät rutiinin irti kansioon
Mihin säännöt yhä pysähtyvät
Sisältötietoinen testi on vain niin hyvä kuin lukemansa tekstitaso. Skannatulla sivulla, jolla ei ole tekstiä lainkaan, tallennetut leipätekstin ääriarvot palautuvat sivun rajoihin, mikään ei ole välissä -ehto täyttyy tyhjästi ja jäljelle jäävät vain kuvatekstirivi- ja sarakeporit; viivaruudukko sellaisella sivulla löytyy yhä tyhjänä kehyksenä, joten ketju voi linkittyä oikein, mutta ympäröivästä tekstistä ei todella varmistettu mitään. Lisää tekstitaso ensin, jos sillä on väliä. Alatunnisteet, jotka on renderöity kuvina eivätkä tekstinä, ovat näkymättömiä vyöhykelogiikalle ja vaarattomia samasta syystä
Vyöhykkeet ovat yksi luku. ContinuationMargin-arvoa syvempi alatunniste jättää alemmat rivinsä leipätekstivyöhykkeen sisään, mikä saa aiemman fragmentin näyttämään tekstin seuraamalta ja estää linkityksen; nosta asetus todelliseen vyöhykekorkeuteen, kuten ensimmäinen esimerkki tekee. Nosta sitä liikaa, niin sivun alaosassa oleva lyhyt päättävä kappale luiskahtaa vyöhykkeeseen ja jää huomiotta, mikä ketjuttaa taulukon siihen mitä seuraa. Kuvatekstisäännöllä on peilikuvavikansa: generaattori, joka kirjoittaa yhdistetyn jatkuu-bannerin jokaisen jatkofragmentin ensimmäiseksi riviksi, saa nuo fragmentit hylättyinä uusina taulukoina, eikä ainoa parannuskeino tänään ole muu kuin ommella itse ContinuationGroup-arvon mukaan löysäämättä mitään, koska säännössä ei ole kytkintä
Tyhjän tilan perusteella havaitut taulukot eivät saa lainkaan yhden rivin helpotusta. Tyhjän tilan strategia tarvitsee kaksi kohdistettua riviä nähdäkseen taulukon lainkaan, joten viivaton taulukko, joka valuu yhden rivin, raportoidaan yhä tuon rivin verran lyhyempänä. Kun törmäät siihen, jäsennellyt tekstilohkot ja lukujärjestys -taustalla olevat sanalaatikot antavat raakasijainnit sen palauttamiseen. Tämän työn liikkeelle panneessa näytejoukossa, kolmessatoista tekstinkäsittelijä- ja selainviennissä, kaikki viisi asiakirjaa joissa oli aitoja monisivuisia taulukoita ketjuttuivat yhdeksi ketjuksi ja aiemmin sulautunut litterointilomake pysyi erillään, mikä on se rima, jota vasten julkaisu mitattiin, ei lupaus jokaisesta asettelusta
Jatkumisten merkintä, kuvatekstisääntö ja yhden rivin kierros elävät kaikki asiakirjatason polussa, joka on yhteinen Delphi-, C++Builder- ja Lazarus-buildien kanssa; täysi taulukonpoiminta-API kuvataan PDFium Component for Delphi -sivulla