Technical Article

Hybridiviitteisten PDF-tiedostojen lataaminen Wordistä ja Excelistä Delphissä

Avaa Microsoft Wordin tai Excelin tuottama PDF, selaa sitä, eikä mikään näytä epätavalliselta. Lataa se Delphi-ohjelmaan, lue sivumäärä, ja luku on oikein. Tallenna se sitten uudelleen salauksen ollessa käytössä, ja ajo epäonnistuu EListError-virheeseen, tai tuloste avautuu varoituksella vaurioituneesta ristiviitteestä. Tiedosto ei ollut koskaan viallinen. Kyseessä on hybridiviitteinen tiedosto, ja juuri se rakenne, joka sallii viisitoista vuotta vanhan katseluohjelman avata sen, on se rakenne, joka kaataa liian aikaisin lukemisen lopettavan lataajan.

Tämä on yksi yleisimmistä tavoista, joilla kaikki sisäiset testit läpäissyt PDF-putki kohtaa tiedoston, jota se ei pysty round-trippaamaan. Kaikki syötteet luotiin talon sisällä, joten ne eivät koskaan olleet hybridimuotoisia. Ensimmäinen hybriditiedosto saapuu sinä päivänä, kun asiakas välittää taulukkolaskentaohjelmasta viedyn laskun.

Mitä Word ja Excel todellisuudessa kirjoittavat

ISO 32000-1 kuvailee hybridiviiteleiskaan kohdassa §7.5.8.4. Sovellus, joka haluaa PDF 1.5 -ominaisuuksia, kuten oliovirtoja, mutta sallii silti PDF 1.4 -lukijan avata tiedoston, kirjoittaa ristiviitetiedot kahdesti. Siinä on perinteinen ristiviitetaulukko, eli ne kiinteälevyiset ASCII-rivit, jotka päättivät jokaisen PDF:n versioon 1.4 asti, ja ristiviitevirta, joka indeksoi loput. Perinteisen osion traileri sisältää /XRefStm-merkinnän, jonka arvo on kyseisen virran tavusiirtymä.

Työnjako on tahallinen. Oliot, joihin vanhan lukijan on päästävä käsiksi - mukaan lukien katalogi ja sivupuu - ovat osoitettavissa perinteisestä taulukosta. Oliot, jotka taitettiin pakattuihin oliovirtoihin, merkitään vapaiksi perinteisessä taulukossa tyypin f merkinnällä, joten 1.4-lukija ohittaa ne suoraan eikä koskaan törmää rakenteeseen, jota se ei osaa jäsentää. Niiden todelliset sijainnit elävät vain ristiviitevirrassa. Tällaisen tiedoston allekirjoitus on sen loppuosa: lyhyt perinteinen osio, usein vain xref, jota seuraa 0 0 -alaosion otsikko, ja jonka traileri osoittaa /XRefStm-kohtaan, jossa varsinaiset palautustiedot sijaitsevat.

Miksi oikea sivumäärä ei todista mitään

Koska katalogi ja sivupuu ovat tarkoituksella saavutettavissa perinteisestä taulukosta, vain kyseistä taulukkoa lukeva lataaja löytää /Root-olion, käy läpi sivupuun ja raportoi oikean sivumäärän. Kaikki mitä vanha lukija tarvitsee on paikalla, joten tiedosto vaikuttaa terveeltä. Puuttumaan jääneet oliot ovat niitä, jotka on pakattu oliovirtoihin: AcroForm-kenttien sanakirjat, merkityn PDF:n rakenne-elementit ja pienien sanakirjojen pitkä häntä, joiden ei koskaan tarvinnut olla näkyvissä perinteiselle katseluohjelmalle.

Et huomaa aukkoa ennen kuin jokin koskee näihin olioihin, ja täydellinen uudelleentallennus koskee niihin kaikkiin. Dokumentin läpikäynti sen salaamiseksi uudelleen tai uudelleenkirjoittamiseksi on juuri se operaatio, joka pyytää jokaisen olionumeron vuorotellen, minkä vuoksi oire nousee pintaan tallennusvaiheessa eikä latausvaiheessa, kaukana sen syystä.

Ansa on tunnistin, joka näkee xref-sanan ja pysähtyy

Halpa tapa päättää, miten tiedosto on indeksoitu, on seurata startxref-kohtaa ja tarkastaa ensimmäiset tavut, joihin se osoittaa. Avainsana xref tarkoittaa perinteistä taulukkoa; virtaolio tarkoittaa ristiviitevittaa. Tämä testi on oikea mille tahansa tiedostolle, joka sitoutuu yhteen järjestelmään. Se on väärä hybriditiedostolle, jonka startxref tähtää perinteiseen osioon vain vanhojen lukijoiden tyydyttämiseksi, kun taas kyseisen osion trailerissa oleva /XRefStm on paikka, johon suurin osa dokumentista on todellisuudessa indeksoitu. Tunnistin, joka palauttaa arvon "classic" ensimmäisen kohtaamansa xref-sanan kohdalla, ei koskaan lue /XRefStm-merkintää, ja jokaisesta vain virrassa elävästä oliosta tulee näkymätön.

var
  Pdf: THotPDF;
  PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('Invoice_XLS.pdf');  // count is correct
    // inspect or edit the loaded document here
    Pdf.SaveLoadedDocument('Invoice_secured.pdf');     // walks every object
  finally
    Pdf.Free;
  end;
end;

Kun ennenaikaisen poistumisen tunnistin on käytössä, lataus näyttää hyvältä ja uudelleentallennus on vaihe, jossa puuttuvat oliot ilmoittavat itsestään. Korjaus ei ole lukea enemmän tavuja alussa; kyse on hybriditrailerin tunnistamisesta ja /XRefStm-merkinnän seuraamisesta ennen kuin päätetään tiedoston olevan valmis.

Yhdistämisjärjestys ei ole neuvoteltavissa

Kun molemmat indeksit on luettu, ne voidaan yhdistää vain yhteen suuntaan. Ristiviitevirta on yhdistettävä ensin, ja perinteiset merkinnät täytetään sen ympärille. Syynä on formaatin ytimessä oleva pieni petos. Hybriditiedosto merkitsee pakatut olionsa vapaiksi perinteisessä taulukossa, jotta vanhat lukijat ohittavat ne. Lataaja, joka noudattaa "ensimmäisenä nähty voittaa" -käytäntöä ja lukee perinteisen taulukon ensin, kirjaa kyseiset olionumerot vapaiksi ja hylkää sitten virran merkinnät, jotka todellisuudessa paikallistavat ne, koska paikat on jo varattu. Kumoa järjestys, ja virrasta tulevat tyypin 2 merkinnät (joista jokainen on oliovirran numero plus indeksi) voittavat paikat, jotka niiden kuuluu omistaa, ja perinteiset merkinnät asettuvat niiden ympärille.

Sama kuri suojaa siltä, että vanhempi versio herättäisi henkiin poistetun olion. Inkrementaaliset päivitykset ketjuuntuvat taaksepäin /Prev-merkinnän kautta, ja tyypin 0 vapaa merkintä on vartija (sentinel), joka kertoo uudemman osion poistaneen olionumeron käytöstä. Ketjussa myöhemmin olevaa vanhempaa osiota ei saa sallia korvaavan tätä vartijaa vanhentuneella sijainnilla. Kohtele ensimmäisenä nähtyä määräävänä vapaamerkintöjen kohdalla, ja poistettu olio pysyy poistettuna; käsittele sitä huolimattomasti, ja tiedoston oma historia herättää henkiin sisällön, jonka viimeisin versio poisti.

Mitä tämä tarkoittaa HotPDF-komponentissa

Moottori ratkaisee hybridiviitteiset tiedostot puolestasi, ja se tekee niin jokaisella polulla, jonka täytyy jäsentää ristiviitetietoja. Lataa dokumentti LoadFromFile- tai LoadFromStream-metodilla, tee muutokset ja kutsu SaveLoadedDocument-metodia; tai aja kertaluonteinen operaatio, kuten EncryptFile, joka lukee syötteen ja kirjoittaa tulosteen. Kummassakin tapauksessa palautus lukee /XRefStm-merkinnän, yhdistää virtaosion ennen perinteisiä merkintöjä ja ratkaisee virroissa elävät oliot ennen kuin kirjoitus luettelee ne. AES-256-salauspolku on paikka, jossa ongelma ilmeni ensimmäisen kerran, koska dokumentin salaaminen kirjoittaa uudelleen jokaisen olion ja vaatii siten, että jokainen olio on jo paikallistettu.

// One-shot: read the hybrid input, write an AES-256 encrypted copy
Pdf.EncryptFile('Letter_DOC.pdf', 'Letter_secured.pdf',
  'owner-secret', '', aes256, [prPrint, prFillAnnotations]);

Mieleen painamisen arvoinen yksityiskohta sijaitsee rajapinnan ylävirrassa. Tiedostot, jotka saapuvat Wordistä, Excelistä, PowerPointista ja pitkästä listasta "Tallenna PDF-muodossa" -putkia, ovat rutiininomaisesti hybridejä, joten lataaja, jota käytät vain oman generaattorisi tulosteita vastaan, ei ehkä koskaan kohtaa sellaista testauksessa. Lisää testitapauksiisi aidoista Office-sovelluksista vietyjä dokumentteja, älä vain oman koodisi tuottamia tiedostoja.

Epäilemäsi tiedoston tarkistaminen

Kaksi tarkastusta ratkaisee kysymyksen nopeasti. Avaa tiedosto heksanäkymässä ja lue viimeisen startxref-sanan jälkeiset tavut; hybriditiedosto näyttää lyhyen perinteisen osion, jonka traileri-sanakirja sisältää /XRefStm-merkinnän. Tai vertaa täyden jäsennyksen ilmoittamaa oliomäärää korkeimpaan olionumeroon, jonka trailerin /Size ilmoittaa. Suuri ero tarkoittaa, että oliot piileskelevät virroissa, joita lataaja ei ole avannut, mikä on sama puute, joka muuttuu tallennusaikaiseksi virheeksi myöhemmin.

Tämän tarinan kirjoittajapuoli eli se, miten oliovirtoja ja pakattuja ristiviitteitä alun perin tuotetaan, käsitellään artikkelissamme oliovirrat ja inkrementaaliset päivitykset. Kun kyseinen hybriditiedosto on myös erittäin suuri, lataustekniikat Direct File API -läpikäynnissä suurille PDF-työnkuluille antavat sinun tarkastaa sen lataamatta koko tiedostoa muistiin. Molemmat pariutuvat luonnollisesti tässä kuvatun palautuksen kanssa, joka toimitetaan osana Delphi- ja C++Builder-ohjelmille tarkoitettua HotPDF Component -komponenttia yhdessä muiden tässä blogissa käsiteltyjen lataus-, muokkaus-, salaus- ja allekirjoitusrajapintojen kanssa.