Tekninen artikkeli

Sisältötietoinen taulukon jatkuminen PDF-sivujen yli

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

Miksi PDFium Componentin taulukon jatkuminen tarvitsee kaksi testiä: Wordin yhden tuuman marginaaleilla sivumarginaalitesti vaatii fragmenttien reunoja 36 pt ikkunoiden sisään joihin asettelu ei koskaan yllä, kun taas sisältötietoinen testi vertaa sanalaatikoita ja linkittää kun taulukko on viimeinen leipätekstisisältö sivulla N ja ensimmäinen sivulla N+1, jättäen juoksevat ylä- ja alatunnistevyöhykkeet huomiotta
Kumpi tahansa testi avaa portin, ja vasta sitten loput tarkistukset ajetaan: vierekkäiset sivut, ei koko levyistä kuvatekstiriviä myöhemmässä fragmentissa ja sarakerajojen täsmääminen kaksinkertaisen AlignmentTolerancen sisällä

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

Kuvatekstiriviportti PDFium Componentissa: aito jatko alkaa datariveillä ja liittyy samaan ContinuationGroup-ryhmään, kun taas myöhempi fragmentti, jonka rivi nolla sisältää yhden yhdistetyn solun arvoilla RowIndex 0, ColumnIndex 0 ja ColumnSpan yhtä suuri kuin ColumnCount, hylätään jatkona ja raportoidaan uutena taulukkona
Tarkastus koskee vain myöhempää fragmenttia, joten taulukko, jonka oma kuvatekstirivi on sen ensimmäisellä sivulla, ei muutu mihinkään, ja portti ajetaan vasta kun jompikumpi kahdesta reunatestistä on jo ketjuttanut parin

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

Miten PDFium Component pitää sivunvaihdon yli valuvan viivarivin: sivukohtainen viivakierros ajetaan rivialarajan ollessa yksi kun DetectContinuations ja DetectRuledTables on asetettu, jatkumiset merkitään koko tuloksen yli, ja vain kutsujan MinRows-arvoa lyhyemmät fragmentit jotka jäävät kaikkien ketjujen ulkopuolelle poistetaan
Valunut rivi säilyy, koska sen ketju linkittää sen, kun taas itsenäinen yhden rivin ruudukko tavallisella sivulla suodatetaan täsmälleen kuten ennenkin, eivätkä tyhjällä tilalla havaitut taulukot saa vastaavaa helpotusta kaksiriviseen alarajaansa
// 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