PDF-inkrementaaliset päivitykset antavat Delphi-sovelluksen muokata asiakirjaa lisäämällä vain muuttuneet objektit, jättäen jokaisen alkuperäisen tavun koskemattomaksi. losLab PDF Library toteuttaa tämän AppendToStream-metodilla, joka kirjoittaa vain ISO 32000-1 §7.5.6:ssa määritellyn inkrementaalisen osion, joten yhden kirjanmerkin muokkaus 2 GB:n tiedostoon maksaa vain kilotavuja tulostetta täyden uudelleenkirjoituksen sijaan. Sama mekanismi selittää, miksi allekirjoitettuja asiakirjoja voidaan päivittää mitätöimättä niiden allekirjoituksia
Ongelma, jonka tämä ratkaisee, on konkreettinen. Täysi tallennus kirjoittaa koko tiedoston uudelleen: jokainen objekti serialisoidaan uudelleen, jokainen ristiviittausoffset lasketaan uudelleen, eikä tuloste ole tavutasolla missään yhteydessä lähtötiedostoon. 40 KB:n laskulle tämä ei ole ongelma. Mutta 2 GB:n skannatulle arkistolle, jossa korjasit vain kirjoitusvirheen asiakirjan otsikossa, kahden gigatavun uudelleenkirjoittaminen kahdenkymmenen tavun muuttamiseksi on järjetöntä — ja jos tiedostossa oli digitaalinen allekirjoitus, uudelleenkirjoitus tuhosi sen juuri
Miksi PDF-tiedoston tallentaminen rikkoo sen digitaalisen allekirjoituksen?
PDF-digitaalinen allekirjoitus ei allekirjoita asiakirjan loogista sisältöä, vaan fyysisen tiedoston tavualueita. Allekirjoitussanakirjan /ByteRange-kenttä tallentaa täsmälleen, mitkä tiedoston osat kryptografinen tiiviste kattaa. Mikä tahansa tallennustoimenpide, joka serialisoi nuo tavut uudelleen — jopa sellainen, joka tuottaa semanttisesti identtisen asiakirjan — muuttaa tiivisteen, ja jokainen validaattori ilmoittaa allekirjoituksen rikkoutuneeksi. Tämä on suunniteltua: allekirjoitus todistaa ne tavut, jotka allekirjoittaja näki, ei jotain abstraktia asiakirjamallia
Inkrementaaliset päivitykset ovat PDF-määrittelyn tarjoama pelastusreitti. Koska inkrementaalinen tallennus lisää uutta dataa alkuperäisen %%EOF-merkinnän jälkeen eikä koskaan kosketa allekirjoitettuja tavualueita, olemassa oleva allekirjoitus pysyy validina niitä tavuja vastaan, jotka se kattaa. Validaattorit luokittelevat sitten lisätyt muutokset erikseen — toinen allekirjoitus, lomakkeen täyttö, huomautus — ja päättävät, ovatko ne sallittuja muutoksia. Jokainen moniallekirjoitusprosessi perustuu tähän: kukin allekirjoittaja lisää inkrementaalisen osion edellisen päälle. Jos rakennat allekirjoitusputkia, rinnakkaisartikkeli PAdES-allekirjoitus ja validointi Delphissä käsittelee yksityiskohtaisesti, miten allekirjoituksen tavualueet ja inkrementaaliset osiot ovat vuorovaikutuksessa
Miten inkrementaaliset päivitykset toimivat ISO 32000-1 §7.5.6:n mukaan
ISO 32000-1 §7.5.6 määrittelee mallin kolmella säännöllä. Ensinnäkin alkuperäinen tiedostosisältö jätetään täysin koskemattomaksi — yksikään tavu ei siirry. Toiseksi muuttuneet ja äskettäin luodut objektit lisätään viimeisen %%EOF-merkinnän jälkeen, kukin samalla objektinumerolla kuin sillä oli aiemmin (muuttuneet objektit saavat yksinkertaisesti uudemman määrittelyn, joka varjostaa vanhan). Kolmanneksi lisätään uusi ristiviittausosio ja trailer; trailerin /Prev-kenttä osoittaa takaisin edellisen ristiviittausosion tavuoffsetiin, muodostaen ketjun, jota lukija kulkee uusimmasta vanhimpaan ratkaistakseen kunkin objektin viimeisimpään määrittelyynsä
Tästä rakenteesta seuraa kaksi hyödyllistä ominaisuutta. Päivitykset ovat edullisia suhteessa siihen, mitä muuttui, ei asiakirjan kokoon — lisäyksen kustannus on muutettujen objektien koko plus pieni xref/trailer-ylikuormitus. Ja tiedostosta tulee oma versiohistoriansa: jokainen aiempi versio on edelleen fyysisesti läsnä, joten tarkastaja voi katkaista tiedoston miltä tahansa aiemmalta %%EOF-kohdalta ja palauttaa täsmälleen sen asiakirjan, joka oli olemassa tuolla hetkellä. Vaatimustenmukaisuusprosesseille, joiden on todistettava, miltä asiakirja näytti ennen kutakin muutosta, tämä sisäänrakennettu tarkastuspolku on usein ratkaiseva argumentti inkrementaalisten tallennusten puolesta
Inkrementaalisen päivityksen kirjoittaminen AppendToStream-metodilla
losLab PDF Library tarjoaa inkrementaalisen tulostuksen AppendToStream(AppendMode: Integer; OutStream: TStream): Integer -metodin kautta, joka palauttaa arvon 1 onnistuessaan ja 0 epäonnistuessaan. AppendMode-parametri valitsee, mitä kohdevirtaan päätyy. Tila 0 kirjoittaa täydellisen tiedoston: alkuperäiset lähdetavut kopioidaan ensin virtaan, minkä jälkeen inkrementaalinen osio lisätään. Tila 1 kirjoittaa vain itse inkrementaalisen osion — deltan — ja ohittaa lähdetavut kokonaan. Tila 2 kirjoittaa ensin kutsujan toimittaman etuliitteen, joka on rekisteröity SetAppendInputFromString-metodilla, ja lisää sitten päivitysosion sen päälle
var
Doc: TPDFlib;
Delta: TMemoryStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('contract.pdf', '') <= 0 then
Exit;
// Pieni muokkaus: tämänkaltainen muutos ei saisi
// aiheuttaa koko tiedoston uudelleenkirjoitusta
Doc.SetInformation(3, 'Amended 2026-07-04'); // avain 3 = /Subject
Delta := TMemoryStream.Create;
try
// AppendMode = 1: kirjoita vain inkrementaalinen osio.
// Alkuperäiset tavut + Delta = täydellinen, validi PDF.
if Doc.AppendToStream(1, Delta) = 1 then
Delta.SaveToFile('contract.delta.bin');
finally
Delta.Free;
end;
finally
Doc.Free;
end;
end;
Tila 1 on kiinnostava järjestelmäsuunnittelun kannalta. Koska delta on itsenäinen, voit toimittaa sen erillään alkuperäisestä: tallenna versiot erillisinä blobeina objektivarastoon, replikoi vain deltat etäsijaintiin tai rekonstruoi mikä tahansa versio yhdistämällä perustiedosto sen lisäysketjuun. Rekonstruointisääntö on yksinkertainen tavujen yhdistäminen — ensin alkuperäinen tiedosto, sitten kukin delta järjestyksessä — koska juuri tämä on se rakenne, jonka §7.5.6 määrää inkrementaalisesti päivitetylle tiedostolle
Miten kirjasto laskee xref-offsetit kopioimatta alkuperäistä tiedostoa?
Inkrementaalisen osion sisällä olevien ristiviittausmerkintöjen on sisällettävä absoluuttisia tavuoffseteja — positioita, jotka on mitattu koko tiedoston alusta, ei deltan alusta. Tämä luo pulman tilalle 1: kirjoittaja ei koskaan tuota alkuperäisiä tavuja, mutta jokaisen sen tallentaman offsetin on silti teeskenneltävä, että ne ovat olemassa. losLab PDF Library ratkaisee tämän sisäisellä virta-adapterilla, TPDFAppendSectionStream, joka esittää serialisoijalle virtuaalisen koordinaattiavaruuden. Adapteri luodaan alkuperäisen tiedoston tavupituus perusoffsettinaan, se ilmoittaa positionsa ja kokonsa tuona perusarvona plus sen, mitä on tähän mennessä lisätty, ja välittää kutsujan kohdevirtaan vain vasta kirjoitetut tavut
Seurauksena on, että tila 1 ei koskaan materialisoi kopiota lähdeasiakirjasta — ei levylle, ei muistiin. Naiivi toteutus (kirjoita koko tiedosto väliaikaiseen puskuriin ja leikkaa sitten häntä irti) kantaisi mukanaan väliaikaisen kopion koko alkuperäisestä PDF-tiedostosta, mikä gigatavun kokoluokan syötteille on juuri se kustannus, jonka välttämiseksi inkrementaaliset päivitykset ovat olemassa. Tämä offsetin virtualisointitekniikka on läheinen sukulainen kirjaston muualla käytetylle tavuviittausten siirtotekniikalle; artikkeli nopeasta PDF-yhdistämisestä tavuviittausten siirrolla näyttää saman idean sovellettuna asiakirjojen yhdistämiseen, ja opas suurten PDF-tiedostojen yhdistämiseen ja jakamiseen suoralla tiedostokäytöllä käsittelee ympäröivää I/O-arkkitehtuuria tiedostoille, jotka eivät mahdu mukavasti RAM-muistiin
Täysien tallennusten striimaus SaveToStream-metodilla
Inkrementaalinen tulostus on puolet striimaustarinasta; toinen puoli on se, mitä tapahtuu täydessä tallennuksessa. losLab PDF Libraryn SaveToStream ohjaa asiakirjan serialisoijan suoraan kohdevirtaa vasten, sen sijaan että se ensin renderöisi koko asiakirjan väliaikaiseen AnsiString-muuttujaan ja kirjoittaisi sitten sen puskurin ulos yhdellä kutsulla. Vanhempi lähestymistapa toimi, mutta se tarkoitti, että jokainen täysi tallennus piti tilapäisesti muistissa toisen täydellisen kopion tulosteesta — harmiton 10 MB:n kohdalla, tuskallinen 500 MB:n kohdalla ja ehdoton este monigigatavuisille tulosteille 32-bittisissä prosesseissa. Suora serialisointi saa muistin huippukäytön seuraamaan asiakirjan objektirakenteita sen serialisoidun pituuden sijaan
var
Doc: TPDFlib;
Output: TFileStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('archive.pdf', '') <= 0 then
Exit;
// ... muokkaukset, jotka oikeuttavat täyden uudelleenkirjoituksen ...
Output := TFileStream.Create('archive-rewritten.pdf', fmCreate);
try
if Doc.SaveToStream(Output) = 0 then
Writeln('Save failed, error ', Doc.LastErrorCode);
finally
Output.Free;
end;
finally
Doc.Free;
end;
end;
Jaettu tila -oppitunti: kun AppendToFile palautti arvon 0
Yksi tämän alueen regressio kannattaa kertoa uudelleen, koska virhekuvio yleistyy. AppendToFile(FileName) lisää inkrementaalisen päivityksen suoraan olemassa olevaan PDF-tiedostoon levyllä — luonnollinen kutsu paikalliselle tarkastuspolkuprosessille: lataa tiedosto, tee muutos, lisää samaan polkuun. Versiossa v3.71.2 juuri tämä sekvenssi alkoi palauttaa arvon 0. Perimmäinen syy oli latausosassa, ei kirjoitusosassa: tukeakseen suurten asiakirjojen tarpeenmukaista lukemista LoadFromFile pitää lähdetiedoston kahvan auki koko asiakirjaolion elinajan, ja tuo kahva avattiin fmShareDenyWrite-tilassa. Kun AppendToFile sitten yritti avata saman tiedoston uudelleen kirjoitusta varten, latausosan oma jakotila esti sen, ja API epäonnistui ennen kuin yhtäkään tavua oli kirjoitettu
Korjaus löysensi latausosan jakotilan arvoon fmShareDenyNone, mikä on turvallista juuri siksi, mitä inkrementaalinen lisäys on: se lisää tavuja tiukasti tiedoston lopun jälkeen eikä koskaan kirjoita uudelleen aluetta, jota lukijan pitkäikäinen kahva palvelee. Yleinen opetus kaikille, jotka kapseloivat tätä kirjastoa — tai rakentavat samankaltaisia striimaavia latausosia — on, että laiskat, kahvaa pitävät lukijat ja saman tiedoston kirjoittajat ovat jännitteessä keskenään, ja avaushetkellä valittu jakotila on API-sopimus, ei toteutusyksityiskohta. Jos AppendToFile koskaan palauttaa arvon 0 koodissasi, tarkista ensin, pitääkö jokin muu prosessissasi kohdetiedostoa yhä hallussaan rajoittavalla jakotilalla
Rehelliset kustannukset: milloin inkrementaaliset päivitykset ovat väärä työkalu
Inkrementaaliset päivitykset vaihtavat tiedostokoon kirjoitustehokkuuteen, eikä vaihto ole aina edullinen. Jokainen versio lisää muuttuneet objektinsa, kun taas korvatut määrittelyt jäävät tiedostoon, joten satoja kertoja muokattu asiakirja kerää kuolleita objekteja ja pitkän /Prev-ketjun, jonka jokaisen lukijan on kuljettava läpi. Vielä pahempaa, ”poistettu” sisältö ei ole todella poissa: versiossa viisi poistettu teksti on edelleen fyysisesti läsnä version neljä tavuissa, ja kuka tahansa tiedoston katkaiseva voi palauttaa sen. Redaktointi, puhdistus tai minkä tahansa arkaluonteisen sisällön poistaminen vaatii siksi täyden uudelleenkirjoituksen — redaktoinnin inkrementaalinen tallennus on tietovuoto ylimääräisillä vaiheilla
Täysi tallennus on myös oikea valinta, kun tavoitteena on tiivistäminen (kertyneiden lisäysten ja käyttämättömien objektien poistaminen), kun muutetaan asiakirjan laajuisia ominaisuuksia kuten salausta — uudelleensalaus koskettaa jokaista merkkijonoa ja virtaa, joten muutoksessa ei ole enää mitään ”inkrementaalista” — tai kun tuotetaan puhdas toimitettava tiedosto, jonka mukana muokkaushistorian ei tulisi kulkea. Kohtuullinen sääntö: käytä AppendToStream- tai AppendToFile-metodia, kun asiakirja on elossa ja muuttuu, erityisesti kun siinä on allekirjoituksia; käytä täyttä SaveToStream-uudelleenkirjoitusta elinkaaren rajapyykeillä, kun asiakirja poistuu järjestelmästäsi tai sen historia on litistettävä
Inkrementaaliset päivitykset, virtuaalioffset-pohjainen deltatulostus ja suora striimauskirjoitus ovat kaikki osa standardia losLab PDF Library -kirjastoa Delphille, C#:lle ja VB.NET:lle; tuotesivu listaa täyden tallennuksen ja lisäys-API:n pinnan yhdessä yllä käsiteltyjen allekirjoitus- ja suurtiedosto-ominaisuuksien kanssa