Technical Article

Objektų srautai ir laipsniški atnaujinimai „Delphi“ aplinkoje su HotPDF

PDF 1.5 pristatė dvi saugojimo struktūras, kurių ankstesnis failo formatas negalėjo išreikšti: objektų srautą ir kryžminių nuorodų srautą. Objektų srautas yra vienas „Flate“ algoritmu suspaustas konteineris su žyma /Type /ObjStm, kuriame saugoma daug mažų netiesioginių objektų, supakuotų vienas po kito, užuot juos išbarsčius po visą failo turinį. Kryžminių nuorodų srautas yra failo paieškos lentelė, perrašyta kaip suspaustas dvejetainis failas su kintamo pločio laukais, pakeičianti fiksuoto pločio ASCII lentelę, kuria baigdavosi visi PDF failai iki 1.4 versijos. Jie veikia kartu. Kai objektai yra sudedami į srautą, senoji tekstinė lentelė nebegali jų pasiekti, todėl kartu turi būti naudojama dvejetainė kryžminių nuorodų lentelė.

Palyginus tai su klasika tapusiu išdėstymu, lengva pamatyti, kokias sąnaudas tai panaikina. PDF 1.4 faile kiekvienas netiesioginis objektas yra nesuspaustas ir eina po savo obj antraštės, o lentelė failo gale naudoja tiksliai 20 baitų ASCII simbolių kiekvienam įrašui, be galimybės ją suspausti. Dokumentas su 200 000 objektų turi apie 4 MB kryžminių nuorodų duomenų dar prieš nupiešiant pirmąjį simbolį, o virš to dar pridedami visi nesuspausti žodynų turiniai. PDF 1.5 sprendžia abi šias problemas vienu metu: žodynai sudedami į „Flate“ konteinerius, o 4 MB lentelė sumažėja iki kelių šimtų kilobaitų dvejetainio kodo. ISO 32000-1 šias dvi struktūras apibrėžia §7.5.7 ir §7.5.8.

Kur iš tikrųjų pasiekiamas sutaupymas

Objektų srautai veikia tik ne srautinius objektus, zodžiu, jie suspaudžia struktūrą, o ne vaizdo taškus. Puslapio turinys jau iki 1.5 versijos buvo suspaustas „Flate“ algoritmu, o vaizdo duomenys naudoja savo kodekus, todėl brošiūros su daug vaizdų dydis beveik nepasikeičia. Labiausiai sumažėja failai, turintys sudėtingą struktūrą: AcroForms formos su tūkstančiais laukų žodynų, giliomis struktūrinėmis schemomis, pažymėto PDF struktūros elementais. Šie objektai yra maži, gausūs ir beveik identiški vienas kitam, o šį pasikartojimą „Flate“ puikiai išnaudoja, kai jie yra viename buferyje, o ne išbarstyti faile su antraštėmis tarp jų.

Lengva neįvertinti, kokią didelę senojo failo dalį sudaro pertekliniai duomenys. Formų archyvas, kuris buvo redaguojamas daugelį metų, gali sunaudoti gerokai daugiau nei pusę savo baitų žodynų antraštėms, xref užpildams ir versijoms, kurių joks skaitytojas niekada nežiūrės. Šios dvi funkcijos leidžia sutaupyti vietą pirmųjų dviejų sąskaita. Trečioji – sukauptos versijos – pasiduoda tik suglaudinimui, kai failui nebereikia atsiminti savo paties istorijos.

„HotPDF“ aplinkoje abu nustatymus įjungiate naudodami savybių porą, o jų tarpusavio priklausomybė yra svarbesnė nei jų nustatymo eilė:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalog-2026.pdf';
    Pdf.UseXRefStream := True;      // binary xref, prerequisite for ObjStm
    Pdf.UseObjectStreams := True;   // pack objects into /Type /ObjStm
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
    Pdf.EndDoc;                     // emits XRefStm + ObjStm containers
  finally
    Pdf.Free;
  end;
end;

Savybei UseObjectStreams reikalinga savybė UseXRefStream nustatyta į True. Suspaustas objektas pasiekiamas per 2-ojo tipo xref įrašą, kuris nurodo objekto srauto numerį ir indeksą, o klasikinė 20 baitų tekstinė eilutė neturi vietos šiai porai išsaugoti. Todėl viena savybė UseObjectStreams neduoda jokio matomo rezultato; abi vėliavėlės, nustatytos prieš BeginDoc, yra veikianti konfigūracija. Nustatę jas po BeginDoc, HotPDF jau bus pasirinkusi senesnį išdėstymą.

Kodėl abi savybės pagal numatytuosius nustatymus yra išjungtos

HotPDF abi šias savybes pateikia kaip nustatytas į False, o to priežastis išryškėja integruojantis su senu kodu. Peržiūros programa, suprantanti tik PDF 1.4 formatą, nepraneša, kad negali apdoroti suspaustų objektų. Ji aptinka xref srautą, neranda jokių laukiamų pagrindinių raktinių žodžių ir praneša apie sugadintą kryžminių nuorodų lentelę arba tiesiog atsisako atidaryti failą. Jei jūsų sukurti failai keliauja į pasenusius faksų siuntimo serverius, spausdintuvus su integruotais senais interpretatoriais arba apdorojimo programas, sukurtas pagal 1.4 specifikaciją prieš dešimtmetį, palikite abi vėliavėles išjungtas ir susitaikykite su didesniu failo dydžiu. Archyvavimui ir platinimui internetu, kur kiekviena populiari peržiūros programa jau dvidešimt metų palaiko PDF 1.5, šių savybių įjungimas suteikia suspaudimą praktiškai nemokamai.

Yra ir kitas šalutinis poveikis, apie kurį verta įspėti palaikymo komandą. Kai žodynai supakuojami į objektų srautus, dviejų sugeneruotų failų palyginimas baitas po baito praranda prasmę, nes vieno lauko pakeitimas gali iš naujo suspausti visą konteinerį ir perrikiuoti viską po jo. Tokių failų skirtumus tikrinkite pagal objektų turinį, o ne dvejetainiu palyginimu.

Laipsniški atnaujinimai ir jų saugomi baitų poslinkiai

Skaitmeninis parašas apima aiškų rėžį /ByteRange: dvi fizinio failo sritis, nurodytas kaip absoliutūs baitų poslinkiai, kurioms buvo apskaičiuota CMS maiša. Perrašius failą, net jei ekrane jis atrodys visiškai taip pat, visi šie poslinkiai pasikeis. Maiša nebesutaps ir parašas bus rodomas kaip sugadintas. Tai yra ta pati problema, kurią ISO 32000-1 §7.5.6 išsprendžia naudodamas laipsniškus atnaujinimus. Nauji ir pakeisti objektai pridedami po esamo %%EOF žymeklio, o tada įrašoma nauja kryžminių nuorodų sekcija, kurios įrašas /Prev nurodo ankstesnę lentelę. Pradiniai baitai niekada nekeičiami, todėl pasirašyta versija išlieka patikrinama, o „Acrobat“ parašų skydelyje gali parodyti kiekvieną pasirašytą versiją atskirai.

HotPDF tai leidžia atlikti per savo funkciją:

Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf');  // appends the delta only

Čia dažniausiai klystama dėl dviejų dalykų. Funkcijai BeginIncrementalUpdate turi būti perduotas originalus failo vardas, nes pridėta xref sekcija įrašo poslinkius, kurie turi prasmę tik tuose pradiniuose baituose; jei nurodysite pervardytą ar iš naujo išsaugotą kopiją, poslinkiai aprašys failo, kurio nebėra. Be to, išsaugojimas pagal savo struktūrą leidžia tik pridėti duomenis, zodžiu, rezultatas visada yra didesnis už pradinį failą. Šis padidėjimas nėra perteklius, kurį reikėtų pašalinti. Tai yra savybė, leidžianti išsaugoti ankstesnes pasirašytas versijas nepaliestas.

Įkelto failo keitimas naudojant LoadFromFile

Kūrėjai, kurie susipažino su HotPDF per kūrimo API, dažnai susiduria su tam tikra kliūtimi. BeginDoc sukuria visiškai naują dokumentą, o tai netinka, jei norite pakeisti jau esamą. Esamo failo redagavimui reikia naudoti įkelto dokumento funkcijas:

PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5);  // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');

Jei sumaišysite šiuos du būdus, gausite rezultatą, kuriame bus tik jūsų naujas turinys ir nieko iš originalo, nes BeginDoc sukuria naują dokumento versiją šalia to, kurį manėte redaguojantys. Supraskite LoadFromFile kartu su SaveLoadedDocument kaip vieną rinkinį, o BeginDoc su EndDoc – kaip kitą. Kodas, kuris naudoja abu būdus tam pačiam failui, beveik visada yra klaidingas.

Kada suglaudinti failą su pridėtais pakeitimais

Duomenų tik pridėjimo būdu išsaugojimas turi savo kainą. Kasnakt vykdoma užduotis, įrašanti vieną būsenos eilutę į tą patį PDF, per metus sukuria 365 versijas, ir kiekviena versija prideda naują xref sekciją. Kai ši istorija praranda prasmę ir jokie faile esantys parašai nebėra reikalingi, galite viską suglaudinti perrašydami failą per įkelto dokumento kelią:

Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');

Šis išsaugojimas iš naujo yra pilnas failo perrašymas. Jis tikslingai pašalina ankstesnes versijas ir sugadina faile esantys parašus, todėl taikykite jam tokius pačius saugumo ribojimus kaip ir bet kuriam kitam destruktyviam veiksmui. Viena iš gamybinių taisyklių: suglaudinkite, kai versijų skaičius viršija nustatytą ribą arba kai pridėtų duomenų dydis viršija tam tikrą pradinio failo dalį, ir niekada nesuglaudinkite dokumento, kuriame yra pasirašytų parašų.

Rezultato patikrinimas prieš platinimą

Šių funkcijų veikimo patikrinimas yra pakankamai konkretus. Atidarykite rezultatą „Adobe Acrobat“ programoje ir įsitikinkite trimis dalykais: dokumento savybės rodo PDF 1.5 ar vėlesnę versiją, kai įjungti objektų srautai; parašų skydelis vis dar patvirtina kiekvieną anksčiau pasirašytą versiją po laipsniško atnaujinimo; o puslapių skaičius ir žymės sėkmingai perėjo įkėlimo, keitimo ir išsaugojimo ciklą. Archyvavimo dokumentams taip pat verta patikrinti failą per „veraPDF“, nes suspaustas kryžminių nuorodų srautas yra kaip tik ta struktūra, kurią griežti tikrinimo įrankiai analizuoja atidžiau nei įprastos peržiūros programos. Jei jūsų darbas susijęs su labai dideliais failais, patikros metodai iš mūsų vadovo apie Direct File API dideliems PDF failams puikiai dera su laipsnišku išsaugojimu, o parašų mechanika, susijusi su baitų rėžiais, yra išsamiai aprašyta straipsnyje apie skaitmeninius parašus ir PAdES.

Abi funkcijos pateikiamos kaip HotPDF komponento dalis, skirta „Delphi“ ir „C++Builder“, kartu su kūrimo, formų, šifravimo ir pasirašymo API, aprašytais kitose šio tinklaraščio dalyse. Produkto puslapyje pateikiama nuoroda į pilną API aprašymą, jei norite pritaikyti šiuos iškvietimus savo dokumentų apdorojimo procese.