HotPDF gali įrašyti įkeltą dokumentą diagnostine forma, skirta žmogui skaityti ir redaguoti, o vėliau, jums atlikus pakeitimus, failą pataisyti. SaveLoadedDocumentDiagnostic įrašo dokumentą su determinuota žodynų raktų tvarka, kiekvienu objektu kaip įprastu netiesioginiu objektu, be objektų srautų, be kryžminių nuorodų srauto ir be linearizavimo. RepairDiagnosticDocument tada atkuria baitų poslinkius, srautų ilgius, kryžminių nuorodų lentelę ir trailer, kuriuos jūsų atliktas redagavimas sugadino
Ši pora egzistuoja dėl vieno darbo eigos tikslo: suprasti arba pataisyti tai, kas iš tikrųjų yra PDF viduje. Kiekvienas programuotojas, taisęs atvaizdavimo problemą, kada nors norėjo atverti failą, pakeisti vieną skaičių viename žodyne ir pažiūrėti į rezultatą. Įprastame PDF faile tai neįmanoma, nes turinys suspaustas į objektų srautus, o kiekvienas įterptas baitas pastumia poslinkius, kuriuos kryžminių nuorodų lentelė fiksuoja tiksliai
Kuo skiriasi diagnostinis įrašymas?
Kartu keičiasi šeši nustatymai. Žodynų raktai įrašomi determinuota tvarka, todėl dviejų to paties dokumento įrašymų failai yra palyginami. Diagnostinis išvesties režimas anotuoja struktūrą. Priverstinai atliekamas pilnas perrašymas, todėl niekas nepaliekama kaip nepaliesta prieauginė (incremental) redakcija. Linearizavimas išjungtas, nes linearizuotas failas skirtas žiniatinklio žiūryklei, o ne skaitytojui. Kryžminių nuorodų srautai išjungti, todėl lentelė yra paprastas tekstas. Ir objektų srautai išjungti, todėl joks objektas nėra paslėptas suspaustame konteineryje
Rezultatas yra failas, kurio antraštėje šalia įprasto %PDF- parašo yra diagnostinis žymeklis, o jo turinį galima peržiūrėti bet kuriame redaktoriuje. Objektai išdėstyti vienas po kito, žodynai yra įskaitomi, o turinio srautai yra ten, kur juos galima rasti. Pataisymo įrankis tikrina šį žymeklį ir atmeta failus, kuriuose jo nėra, nes įprasto PDF kompaktiškos struktūros baitų diapazonų pataisymo įrankis interpretuoti neturėtų bandyti
uses
HPDFDoc;
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoice.pdf') <= 0 then
Exit;
// Įskaitoma, po vieną objektą bloke, niekas nepaslėpta objektų srautuose
Pdf.SaveLoadedDocumentDiagnostic('invoice-diagnostic.pdf');
finally
Pdf.Free;
end;
end;
Kodėl redagavimas sugadina failą ir ką reikia atkurti?
PDF faile trys dalykai koduoja baitų pozicijas arba baitų kiekius. Kryžminių nuorodų lentelė kiekvieną objekto numerį susieja su baitų poslinkiu. startxref reikšmė failo pabaigoje nurodo į šią lentelę. Ir kiekvienas srauto žodynas turi /Length įrašą, nurodantį, kiek baitų užima srauto turinys
Pakeitus vieną simbolį bet kur prieš failo pabaigą, po jo einantys poslinkiai tampa klaidingi. Pridėjus eilutę į turinio srautą, jo /Length irgi tampa klaidingas. Dauguma žiūryklių tokį failą atmes, o tos, kurios jį atkuria, tai daro skenuodamos, o tai klaidą tik paslepia, o ne pataiso. Pataisymas visus tris dalykus atkuria iš to, ką baitai iš tikrųjų dabar rodo
var
Options: THPDFDiagnosticRepairOptions;
Report: THPDFDiagnosticRepairReport;
begin
Options := THPDFDiagnosticRepairOptions.Default;
Options.VerifyOutput := True; // prieš pakeičiant įkelti rezultatą ir jį patikrinti
if THotPDF.RepairDiagnosticDocument('invoice-diagnostic.pdf',
'invoice-fixed.pdf', Options, Report) then
Writeln(Format('%d objects, %d streams, %d lengths corrected, ' +
'%d xref rows rebuilt, %d pages verified',
[Report.ObjectCount, Report.StreamCount,
Report.RepairedStreamLengthCount, Report.RebuiltXRefEntryCount,
Report.VerifiedPageCount]))
else
Writeln(Format('repair failed (%d): %s',
[Ord(Report.Status), string(Report.Diagnostic)]));
end;
Kopijuoti baitus, o ne perserializuoti
Projektinis sprendimas, dėl kurio šiuo įrankiu galima pasitikėti, yra tas, kad jis niekada neįkelia dokumento ir neišveda jo iš naujo. Jis kopijuoja patvirtintų objektų baitų diapazonus, keičia tik tas /Length reikšmes, kurios yra klaidingos, ir aplink jas atkuria lentelę, trailer ir startxref
Įkėlimą ir pakartotinį serializavimą būtų kur kas lengviau įgyvendinti, tačiau tai tyliai keistų dalykus. Nežinoma objekto sintaksė būtų normalizuota, eilučių escape simboliai perrašyti pageidaujama rašytojo forma, o jau užkoduoti srautų turiniai galėtų būti perkoduoti. Derinimo įrankiui tai nepriimtina: jūs pakeitėte vieną skaičių, o diff turėtų rodyti vieną skaičių, o ne tūkstantį atsitiktinių formatavimo pakeitimų. Jei norite tiksliai žinoti, kas pasikeitė tarp dviejų failų, tai palyginimo uždavinys, aprašytas straipsnyje dviejų PDF failų palyginimas pagal struktūrą ir pikselius
Dvi analizės taisyklės, saugančios nuo klaidingų objektų
Baitų diapazonų skeneris susiduria su akivaizdžia spąstų problema: tekstas, panašus į objekto antraštę, gali pasitaikyti srauto turinyje arba eilutėje. Naivus skenavimas sukurtų kryžminių nuorodų eilutę, rodančią į turinio srauto vidurį, ir gautas failas būtų sugadintas nauju ir dar labiau klaidinančiu būdu
HotPDF to išvengia praleisdama visą kiekvieno patvirtinto objekto baitų diapazoną, naudodama jo užfiksuotą pabaigos poslinkį, todėl niekas objekto viduje niekada nelaikoma kandidatu. Tarp objektų leidžiami tik PDF tarpo simboliai ir komentarai; bet kas kita reiškia, kad sritis nesuprasta, ir jos baitai neturi patekti į išvestį
Srautų ilgiams reikia lygiai tiek pat dėmesio. Tikrasis turinio ilgis neapima eilutės pabaigos skirtuko, kurį rašytojas įterpia prieš endstream. Kai deklaruotas ilgis tiksliai pataiko į žymeklį, ta deklaracija priimama. Kai nepataiko, pataisymo įrankis atlieka ribotą endstream ir endobj poros paiešką ir atima lygiai vieną skirtuką — CR, LF arba CRLF. Suklydus čia vienu baitu, gaunamas failas, kuris atsiveria ir atvaizduoja puslapį su vienu paklydusiu operatoriumi turinio srauto pabaigoje
Ką trailer leidžiama išsaugoti
Atkurtas trailer nėra seno kopija. /Size, /Prev ir /XRefStm visi aprašo ankstesnį failo išdėstymą ir yra perskaičiuojami arba pašalinami. Išlieka tik tie įrašai, kurių taikiniai buvo patvirtinti: /Info, /ID ir /Encrypt. O /Root turi rodyti lygiai į vieną Catalog objektą — ne nulį ir ne kelis, o būtent tai dažnai gaunasi faile, sukurtame kopijuojant ir įterpiant objektų blokus
Išvesties saugumas tvarkomas taip, kaip turėtų būti įrankyje, dirbančiame su failais, kurie žmonėms svarbūs. Rezultatas įrašomas į laikiną failą tikslinio katalogo viduje, tada patikrinamas jį įkeliant įprastu keliu su išjungtu kryžminių nuorodų atkūrimu, todėl failas, kuris atsiveria tik atkūrimo dėka, laikomas nesėkme. Tik po to, kai puslapių medis patvirtinamas, laikinas failas atomiškai pakeičia tikslinį. Nepavykęs pataisymas niekada neperrašo esamos išvesties
Biudžetai ir sąžiningi nesėkmės kodai
Trys ribos apibrėžia darbą: įvesties baitai, kandidatų objektų skaičius ir didžiausias objekto numeris, kurių numatytosios reikšmės yra 8 GiB, du milijonai ir du milijonai. Objekto numerio riba svarbesnė, nei atrodo, nes reta lentelė su keliais objektais, sunumeruotais milijardų diapazone, kitaip milžiniškai išpūstų atkurtą kryžminių nuorodų lentelę. Klasikinės kryžminių nuorodų lentelės taip pat atmeta poslinkius, kuriems reikėtų daugiau nei dešimties dešimtainių skaitmenų — tai formato riba, o ne įgyvendinimo pasirinkimas
Nesėkmės praneša konkrečią būseną, o ne tiesiog loginę reikšmę: neteisingi argumentai, per didelis šaltinis, ne diagnostinis failas, viršyta objektų riba, neteisingas objektas, dubliuotas objektas, trūkstamas katalogas, neteisingas trailer, išvesties klaida arba nepavykęs patvirtinimas. Kiekviena atitinka skirtingą taisymą. Dubliuotas objektas reiškia, kad objekto bloką įklijavote du kartus. Trūkstamas katalogas dažniausiai reiškia, kad objektas, laikantis /Type /Catalog, buvo pažeistas. Nepavykęs patvirtinimas reiškia, kad struktūra dabar nuosekli, bet puslapių medis neįsikelia, o tai paprastai yra /Kids masyvas, redaguotas nesutampant su savo /Count
Dvi praktinės pastabos užbaigia šį ciklą. Saugokite ne tik pataisytą, bet ir diagnostinį failą, kad kitas redagavimas prasidėtų nuo įskaitomo pagrindo. Ir netiekite diagnostinės išvesties galutiniams vartotojams: ji didesnė už įprastą įrašymą, nes niekas nesuspausta į objektų srautus, o kompaktiško failo gamybinis kelias yra įprastas įrašymo kelias, aprašytas straipsnyje objektų srautai ir prieaugio atnaujinimai. Kai tikslas yra rasti, kas negerai, o ne tai pakeisti, automatizuotas kelias, aprašytas straipsnyje preflight ataskaitos automatizavimas, atsako greičiau nei baitų skaitymas
Diagnostinis įrašymas, pataisymas, preflight ir palyginimas veikia su tuo pačiu įkelto dokumento modeliu Delphi ir C++Builder aplinkose; pilnas funkcijų sąrašas pateiktas HotPDF Delphi PDF komponento puslapyje