Teknisk artikkel

Inkrementelle PDF-oppdateringer i Delphi: AppendToStream-veiledning

Inkrementelle PDF-oppdateringer lar en Delphi-applikasjon endre et dokument ved bare å legge til de endrede objektene, slik at hver opprinnelige byte forblir urørt. losLab PDF Library implementerer dette gjennom AppendToStream, som kun skriver den inkrementelle delen definert av ISO 32000-1 §7.5.6, slik at en redigering av et enkelt bokmerke i en fil på 2 GB koster kilobyte med utdata i stedet for en full omskriving. Den samme mekanismen er grunnen til at signerte dokumenter kan oppdateres uten at signaturene deres blir ugyldige

Smerten dette løser er konkret. En lagring av hele filen skriver om alt: hvert objekt blir reserialisert, hver kryssreferanseforskyvning blir beregnet på nytt, og utdataene har ingen relasjon på bytenivå til inndataene. For en faktura på 40 KB er det greit. For et skannet arkiv på 2 GB der du bare rettet en skrivefeil i dokumenttittelen, er det absurd å skrive om to gigabyte for å endre tjue byte — og hvis filen hadde en digital signatur, ødela omskrivingen den akkurat

Hvorfor ødelegger lagring av en PDF dens digitale signatur?

En digital PDF-signatur signerer ikke dokumentets logiske innhold; den signerer byte-områder i den fysiske filen. /ByteRange-oppføringen i signaturordboken registrerer nøyaktig hvilke deler av filen det kryptografiske sammendraget dekker. Enhver lagringsoperasjon som reserialiserer disse bytene — selv en som produserer et semantisk identisk dokument — endrer sammendraget, og alle validatorer vil rapportere signaturen som ødelagt. Dette er tilsiktet: signaturen bevitner bytene signatøren så, ikke en abstrakt dokumentmodell

Inkrementelle oppdateringer er rømningsveien PDF-spesifikasjonen tilbyr. Fordi en inkrementell lagring legger til nye data etter den opprinnelige %%EOF og never touches the signed byte ranges, the existing signature keeps validating against the bytes it covers. Validators then classify the appended changes separately — a second signature, a form-fill, an annotation — and decide whether they are permitted modifications. Every multi-signature workflow depends on this: each signer adds an incremental section on top of the last. If you are building signing pipelines, the companion article on PAdES-signering og -validering i Delphi covers how signature byte ranges and incremental sections interact in detail

Hvordan inkrementelle oppdateringer fungerer under ISO 32000-1 §7.5.6

ISO 32000-1 §7.5.6 definerer modellen i tre regler. For det første forblir det opprinnelige filinnholdet helt urørt — ikke én byte flyttes. For det andre blir endrede og nyopprettede objekter lagt til etter den siste %%EOF, hver med samme objektnummer som det hadde før (endrede objekter får ganske enkelt en nyere definisjon som skyggelegger den gamle). For det tredje blir det lagt til en ny kryssreferansedel og trailer; trailerens /Prev-oppføring peker tilbake til byteforskyvningen til den forrige kryssreferansedelen, og danner en kjede som en leser går gjennom fra nyeste til eldste for å løse hvert objekt til dets nyeste definisjon

To nyttige egenskaper følger av denne strukturen. Oppdateringer er billige i forhold til hva som ble endret, ikke til dokumentstørrelsen — tilføyingskostnaden er størrelsen på de endrede objektene pluss litt xref/trailer-overhead. Og filen blir sin egen versjonshistorikk: alle tidligere revisjoner er still physically present, so an auditor can truncate the file at any earlier %%EOF and recover exactly the document that existed at that point. For compliance workflows that must prove what a document looked like before each amendment, this built-in audit trail is often the deciding argument for incremental saves

Skrive en inkrementell oppdatering med AppendToStream

losLab PDF Library eksponerer inkrementelle utdata gjennom AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, som returnerer 1 ved suksess og 0 ved feil. Parameteren AppendMode velger hva som havner i målstrømmen. Modus 0 skriver en komplett fil: de opprinnelige kildebytene kopieres til strømmen først, deretter legges den inkrementelle delen til. Modus 1 skriver bare selve den inkrementelle delen — deltaet — og hopper over kildebytene helt. Modus 2 skriver først et brukerlevert prefiks registrert via SetAppendInputFromString, og legger deretter til oppdateringsdelen på toppen av det

var
  Doc: TPDFlib;
  Delta: TMemoryStream;
begin
  Doc := TPDFlib.Create;
  try
    if Doc.LoadFromFile('contract.pdf', '') <= 0 then
      Exit;

    // Small edit: the kind of change that should not
    // trigger a rewrite of the whole file
    Doc.SetInformation(3, 'Amended 2026-07-04');  // key 3 = /Subject

    Delta := TMemoryStream.Create;
    try
      // AppendMode = 1: write only the incremental section.
      // Original bytes + Delta = a complete, valid PDF.
      if Doc.AppendToStream(1, Delta) = 1 then
        Delta.SaveToFile('contract.delta.bin');
    finally
      Delta.Free;
    end;
  finally
    Doc.Free;
  end;
end;

Modus 1 er den interessante for systemdesign. Fordi deltaet er selvstendig, kan du sende det uavhengig av originalen: lagre revisjoner som separate blober i objektlagring, replikere bare deltaer til et eksternt nettsted, eller rekonstruere enhver revisjon ved å slå sammen basefilen med dens kjede av inkrementer. Rekonstruksjonsregelen er ren bytesammenslåing — originalfilen først, deretter hvert delta i rekkefølge — fordi det er akkurat den layouten §7.5.6 foreskriver for en inkrementelt oppdatert fil

Hvordan beregner biblioteket xref-forskyvninger uten å kopiere originalfilen?

Kryssreferanseoppføringene i en inkrementell del må inneholde absolutte byteforskyvninger — posisjoner målt fra starten av den komplette filen, ikke fra starten av deltaet. Det skaper et puslespill for modus 1: skriveren sender aldri ut de opprinnelige bytene, men likevel må hver forskyvning den registrerer late som om de er der. losLab PDF Library løser dette med en intern strømadapter, TPDFAppendSectionStream, som presenterer et virtuelt koordinatrom for serialiseringen. Adapteren opprettes med originalfilens bytelengde som baseforskyvning, rapporterer sin posisjon og størrelse som denne basen pluss det som er lagt til så langt, og videresender bare de nyskrevne bytene til brukerens målstrøm

Konsekvensen er at modus 1 aldri materialiserer en kopi av kildedokumentet — verken på disk eller i minnet. Den naive implementeringen (skrive hele filen til en midlertidig buffer, og deretter kutte av halen) ville medføre en midlertidig kopi av hele den opprinnelige PDF-en, noe som for inndata på gigabyte-skala er akkurat den kostnaden inkrementelle oppdateringer eksisterer for å unngå. Denne forskyvningsvirtualiseringsteknikken er en nær slektning av byte-referanseskiftet som brukes andre steder i biblioteket; artikkelen om rask PDF-sammenslåing med byte-referanseskifte viser samme idé brukt på å kombinere dokumenter, og veiledningen for sammenslåing og oppsplitting av store PDF-er med direkte filtilgang dekker den tilhørende I/O-arkitekturen for filer som ikke får plass i RAM

Strømme fulle lagringer med SaveToStream

Inkrementelle utdata er halve strømmehistorien; den andre halvdelen er hva som skjer ved en full lagring. SaveToStream i losLab PDF Library driver dokumentserialiseringen direkte mot målstrømmen, i stedet for først å rendre hele dokumentet til en midlertidig AnsiString og deretter skrive den bufferen ut i ett kall. Den eldre tilnærmingen fungerte, men den betydde at hver fulle lagring midlertidig holdt en ny komplett kopi av utdataene i minnet — ufarlig ved 10 MB, smertefullt ved 500 MB, og en hard vegg for utdata på flere gigabyte på 32-biters prosesser. Direkte serialisering gjør at minnetoppen sporer dokumentets objektstrukturer i stedet for dets serialiserte lengde

En lærdom om delingsmodus: da AppendToFile returnerte 0

En regresjon på dette området er verdt å fortelle på nytt fordi feilmønsteret generaliserer. AppendToFile(FileName) legger til en inkrementell oppdatering direkte i en eksisterende PDF på disk — det naturlige kallet for en arbeidsflyt med revisjonsspor på stedet: laste en fil, gjøre en endring, legge til på samme bane. I v3.71.2 begynte akkurat den sekvensen å returnere 0. Rotårsaken lå i innleseren, ikke skriveren: for å støtte lesing av store dokumenter ved behov, holder LoadFromFile kildefilhåndtaket åpent i hele levetiden til dokumentobjektet, og det håndtaket ble åpnet med fmShareDenyWrite. Da AppendToFile deretter prøvde å gjenåpne den samme filen for skriving, nektet innleserens egen delingsmodus det, og API-et feilet før det skrev en eneste byte

Fiksen løsnet innleserens delingsmodus til fmShareDenyNone, noe som er trygt nettopp på grunn av hva en inkrementell tilføyelse er: den legger til byte strengt etter slutten av filen og skriver aldri om området som leserens langlivede håndtak betjener. Den generelle lærdommen for alle som pakker inn dette biblioteket — eller bygger lignende strømmende innlesere — er at late, håndtaksholdende lesere og skrivere til samme fil er i konflikt, og delingsmodusen du velger ved åpningstidspunktet er en API-kontrakt, ikke en implementeringsdetalj. Hvis AppendToFile noen gang returnerer 0 i koden din, sjekk først om noe annet i prosessen din fortsatt holder målfilen med en restriktiv delingsmodus

De ærlige kostnadene: når inkrementelle oppdateringer er feil verktøy

Inkrementelle oppdateringer bytter filstørrelse mot skriveeffektivitet, og byttet er ikke alltid gunstig. Hver revisjon legger til sine endrede objekter mens de erstattede definisjonene forblir i filen, så et dokument som er redigert hundrevis av ganger akkumulerer døde objekter og en lang /Prev-kjede som alle lesere må gå gjennom. Verre er det at "slettet" innhold ikke er borte: tekst fjernet i revisjon fem er fortsatt fysisk til stede i bytene til revisjon fire, og kan gjenopprettes av alle som avkorter filen. Sladding, sanering eller fjerning av sensitivt innhold krever derfor en full omskriving — en inkrementell lagring av en sladding er en datalekkasje med ekstra trinn

En full lagring er også det riktige valget når målet er komprimering (skvise ut akkumulerte inkrementer og ubrukte objekter), ved endring av dokumentomspennende egenskaper som kryptering — rekryptering berører hver streng og strøm, så det er ingenting "inkrementelt" igjen ved endringen — eller når du produserer en ren leveranse der redigeringshistorikken ikke bør følge med filen. En rimelig regel: bruk AppendToStream eller AppendToFile og SaveToStream i Delphi

Inkrementelle oppdateringer, delta-utdata med virtuell forskyvning og direkte-til-strøm-serialisering er alle en del av standard losLab PDF Library for Delphi, C# og VB.NET; produktsiden viser hele lagrings- og tilføyings-API-overflaten sammen med signerings- og storfilfunksjonene diskutert ovenfor