Sutarties, dėl kurios jau susitarta, 3 puslapio pakeitimas neturėtų perkelti turinio lentelės. Ištrink seną puslapį, įterpk naują, ir kiekviena žymė, kuri anksčiau rodė ten, dabar atsiduria kažkur kitur. PDFlibPas Delphi PDF biblioteka to išvengia, palikdama patį tikslinį puslapio objektą ir perkeldama tik įrašus, nešančius vaizdinį turinį
Kodėl žymės sugenda pakeitus PDF puslapį?
Žymės sugenda todėl, kad PDF paskirties taškas įvardija puslapį netiesiogine objekto nuoroda, ne puslapio numeriu. ISO 32000-1 §12.3.2.2 apibrėžia aiškų paskirties tašką kaip masyvą, kurio pirmasis elementas yra netiesioginė nuoroda į puslapio objektą. Ištrink tą objektą ir pridėk pakaitalą, ir nuoroda tampa kabanti: dauguma peržiūros programų reaguoja nusodindamos skaitytoją į 1 puslapį, kas yra būtent tas simptomas, apie kurį žmonės praneša po ištrynimo-įterpimo pakeitimo. Puslapių medis atrodo puikiai, puslapių skaičius teisingas, atvaizdavimas teisingas, o visas navigacijos sluoksnis tyliai neteisingas
Pavadinti paskirties taškai jūsų taip pat neišgelbsti. §12.3.2.3 nukreipia pavadinimą per /Dests pavadinimų medį dokumento kataloge, tačiau lapas, į kurį tas pavadinimas išsprendžiamas, vis tiek yra aiškus paskirties taško masyvas, laikantis tą pačią puslapio nuorodą. Pavadinimas prideda netiesiogumo sluoksnį virš puslapio nuorodos, ne aplink ją. Ta pati logika apima likusią interaktyviojo sluoksnio dalį, aprašytą §12.5: nuorodos anotacija neša /Dest arba /A GoTo veiksmą, kurio /D yra tas masyvas, kiekviena anotacija gali nešti /P įrašą, kuris yra netiesioginė nuoroda į jos puslapį, o formos lauko valdiklis yra anotacija lygiai tokiu pačiu pagrindu. Vienas naivus puslapio keitimas atsieja keturias posistemes iš karto, ir jei norite jas pamatyti išvardytas realiame faile, tas pats objektų grafas yra tai, per ką eina outline ir anotacijų introspekcija
Kurie puslapio įrašai neša tapatybę, o kurie – išvaizdą
Puslapio žodynas maišo dviejų rūšių įrašus, o pakeitimas vietoje pavyksta būtent tada, kai juos atskiriate. Išvaizdos pusė yra baigtinė ir išvardijama: /Contents, /Resources, penki puslapio langeliai /MediaBox, /CropBox, /BleedBox, /TrimBox ir /ArtBox, plius /Rotate, /Group, /UserUnit ir /BoxColorInfo. Šie vienuolika įrašų nulemia viską, ką raster'inis atvaizduotojas sukuria puslapiui, ir niekas kitas faile į juos nerodo pagal vardą
Tapatybės pusė yra tai, prie ko susietas likęs dokumentas: puslapio objekto numeris ir karta, /Parent atgalinė nuoroda į puslapių medį ir /Annots. PDFlibPas kiekvieną iš jų palieka nepaliestą. ReplacePageRanges pašalina vienuolika vaizdinių įrašų iš tikslinio puslapio žodyno ir juos vėl prideda iš importuoto šaltinio puslapio, todėl tikslinis puslapio objektas mutuojamas vietoje, o ne pakeičiamas. §7.7.3 reikalaujama puslapių medžio struktūra taip pat lieka baitas į baitą identiška formos atžvilgiu: /Kids tvarka, /Count ir kiekvienas išlikęs /Parent tokie patys prieš ir po, nes joks mazgas niekada nebuvo atjungtas
Kaip PDFlibPas pakeičia puslapį nepernumeruodama objektų?
Iškvietimas ima šaltinio dokumentą, 1-pagrįstą tikslinį pradinį puslapį, šaltinio intervalo išraišką ir parinkčių vėliavėlę. Abu dokumentai turi būti atidaryti tame pačiame egzemplioriuje, o tikslinis dokumentas yra pasirinktas. Kadangi tikslinio dokumento puslapių skaičius niekada nesikeičia, jūsų prašomas intervalas turi tilpti dokumente, pradedant nuo TargetStartPage, ir tai patikrinama prieš sukuriant bet ką
var
Lib: TPDFlib;
TargetDoc, SourceDoc: Integer;
begin
Lib := TPDFlib.Create;
try
// The document whose bookmarks and links must survive
if Lib.LoadFromFile('contract-final.pdf', '') <> 1 then
Exit;
TargetDoc := Lib.SelectedDocument;
// The revised clause page, rendered by whatever produced it
if Lib.LoadFromFile('clause-7-revised.pdf', '') <> 1 then
Exit;
SourceDoc := Lib.SelectedDocument;
Lib.SelectDocument(TargetDoc);
// Source page 1 overwrites the visuals of target page 3.
// Page count, page 3 object number, bookmarks and annotations are kept.
if Lib.ReplacePageRanges(SourceDoc, 3, '1', 0) = 1 then
Lib.SaveToFile('contract-final.pdf');
finally
Lib.Free;
end;
end;
Viduje šaltinio puslapių tiesiog negalima nuskaityti per dokumentų ribas, nes kiekviena netiesioginė nuoroda juose priklauso šaltinio objektų numeracijai. Todėl šaltinio intervalas pirmiausia importuojamas įprastu būdu, kaip laikini puslapiai, pridedami po paskutinio realaus puslapio, o tai paleidžia pilną objektų grafo pernumeravimą: turinio srautai, šriftai, XObject'ai, šešėliai ir spalvų erdvės visi pernumeruojami į tikslinį dokumentą. Tik tada vienuolika vaizdinių įrašų nukopijuojami iš kiekvieno laikino puslapio į jo tikslinį puslapį, ir tik tada laikini puslapiai atjungiami nuo puslapių medžio. Pernumeravimo darbas vyksta ten, kur jis pigus ir saugus, o destruktyvus redagavimas sumažinamas iki žodyno lygio keitimo jau egzistuojančiuose puslapiuose
Ištrynimo kelias, kuris sugriautų tai, ką ką tik perkėlėte
Tų laikinų puslapių pašalinimas yra žingsnis, kuris atrodo trivialus, bet tokiu nėra. Įprastas puslapio ištrynimo kelias bibliotekoje daro daugiau nei atjungia mazgą: jis sujungia kiekvieno ištrinamo puslapio sluoksnius, ištuština pirmąjį turinio srautą ir susigrąžina resursus, kuriais nesidalija joks kitas puslapis. Tai teisingas elgesys tikram ištrynimui, ir katastrofiškas čia, nes iki laikinų puslapių pašalinimo momento tiksliniai puslapiai jau nurodo būtent tuos turinio srautus ir resursų objektus. Jų ištuštinimas nutrintų puslapį, kurį ką tik pakeitėte, o resursų valymas surinktų šriftus ir vaizdus, kurie dabar turi gyvą savininką
Sprendimas yra išsaugoti-nurodomus-objektus režimas vidiniame ištrynimo kelyje. Kai jis nustatytas, ištrynimas praleidžia tiek nebendrai naudojamų resursų valymą, tiek turinio srauto ištuštinimą, ir nieko nedaro, tik atjungia puslapius nuo puslapių medžio bei sutvarko medžio buhalteriją. Perkelti objektai išlieka su nauju savininku, o objektų nuosavybė po operacijos yra tai, ką nubrėžtumėte ant lentos: vienas turinio srautas, vienas savininkas puslapis, vienas objekto numeris, kuris niekada nepasikeitė. Susijusios puslapių kūrimo, ištrynimo ir pertvarkymo gyvavimo ciklo taisyklės aprašytos atskirai pastabose apie dokumento ir puslapio gyvavimo ciklo operacijas
Tvarka, dublikatai ir viskas-arba-nieko nesėkmė
Parinkčių vėliavėlė nustato, kaip interpretuojamas šaltinio intervalas. 0 surūšiuoja nagrinėtus puslapių numerius ir pašalina dublikatus, kas yra sveikas numatytasis nustatymas, kai iškvietėjas perduoda kažką panašaus į '4-6,2' ir tiesiog reiškia tuos keturis puslapius. 1 išlaiko jūsų parašytą tvarką ir leidžia puslapiui kartotis, todėl '2,1,2' iš tikrųjų reiškia tris pakeitimus, paimtus iš dviejų šaltinio puslapių. Patvirtinimas vyksta pirmiausia ir vyksta pilnai: intervalo sintaksė, kiekvienas puslapio numeris pagal šaltinio puslapių skaičių, pati parinkties reikšmė ir tikslinis pajėgumas visi patikrinami prieš sukuriant vieną objektą. Atmestas iškvietimas nustato LastErrorCode į 412, atkuria anksčiau pasirinktą puslapį ir palieka dokumentą tiksliai tokį, koks jis buvo
var
Replaced: Integer;
begin
Lib.SelectDocument(TargetDoc);
// Options = 1: source order is preserved and repeats are allowed, so
// target pages 5, 6 and 7 receive source pages 2, 1 and 2 respectively
Replaced := Lib.ReplacePageRanges(SourceDoc, 5, '2,1,2', 1);
if Replaced = 0 then
raise Exception.CreateFmt('Replacement rejected, LastErrorCode = %d',
[Lib.LastErrorCode]);
// On success the selection is the first replaced page
Assert(Lib.SelectedPage = 5);
end;
Atomiškumas tęsiasi ir po patvirtinimo, pačiame perkėlime. Prieš importuojant pirmąjį šaltinio puslapį, vienuolika vaizdinių įrašų kiekvieno intervale esančio tikslinio puslapio yra užfiksuojami kaip užkoduotos reikšmės. Jei importas nepavyksta, arba importuotų puslapių skaičius nesutampa su tuo, kas buvo prašyta, nuotraukos iškoduojamos atgal ant tikslinių puslapių, o laikini puslapiai pašalinami, todėl vidury vykstantis gedimas vis tiek palieka pradinius vaizdus vietoje ant jų pradinių objektų. Tai svarbiau, nei skamba: iš dalies pakeistas puslapių intervalas sutartyje yra blogiau nei nepavykęs iškvietimas, nes niekas faile nepažymi jo kaip pusiau atlikto
// Post-conditions worth asserting in a regression test
Lib.SelectPage(3);
// Geometry now comes from the source page
WriteLn(Format('%.2f x %.2f', [Lib.PageWidth, Lib.PageHeight]));
// Annotations that were already on target page 3 are still attached
WriteLn(Lib.AnnotationCount);
// The bookmark created before the replacement still resolves to page 3
WriteLn(Lib.GetOutlinePage(OutlineID));
// And the document is still the same length
WriteLn(Lib.PageCount);
Ko pakeitimas vietoje jums vis dar nepadaro?
Šaltinio anotacijos, šaltinio formos laukai ir šaltinio outline'ai sąmoningai neimportuojami. Valdiklio perkėlimas be jo /AcroForm lauko įrašo, arba pažymėto turinio anotacijos be jos struktūros medžio nuosavybės, sukuria pusiau importuotą interaktyvų objektą, apie kurį jokia peržiūros programa negali spręsti, todėl operacija perkelia tik išvaizdą. Praktinė pasekmė ta, kad jei pakeitimo puslapis turėtų nešti naujus formos laukus ar naujas nuorodas, jūs juos pridedate prie tikslinio puslapio vėliau, prie tikslinio puslapio objekto, kuris vis dar ten sėdi jų laukdamas
Dar dvi ribas verta patikrinti savo failuose. Pirma, /Annots išsaugomas, bet puslapio geometrija ne, todėl 220 mm puslapio pakeitimas 320 mm puslapiu palieka anotacijų stačiakampius jų senose koordinatėse skirtingo dydžio /MediaBox viduje; jei geometrija keičiasi, perkelkite anotacijas, kurias išlaikėte. Antra, įrašai už vienuolikos vaizdinių raktų ribų lieka su tiksliniu puslapiu pagal dizainą, kas teisinga /Trans ar /AA atveju ir pasenusi /Thumb atveju, todėl po pakeitimo iš naujo sukurkite miniatiūras. Žymėti dokumentai reikalauja vieno papildomo apmąstymo: struktūros elementai vis tiek rodo į teisingą puslapio objektą per /Pg, tačiau jų pažymėto turinio identifikatoriai apibūdina turinį, kurio ten nebėra, todėl puslapio keitimas PDF/UA eigoje yra ir struktūros medžio redagavimas, ir turinio redagavimas. Jei jūsų darbas iš tikrųjų yra komponavimas, o ne keitimas, sluoksniuojant grafiką ant puslapių, kuriuos išlaikote, puslapių siuvimo ir šablonų metodas yra pigesnis įrankis
Viskas, kas aprašyta čia, įskaitant intervalo išraiškos sintaksę, parinkčių reikšmes ir aplinkinę puslapių manipuliavimo API, pristatoma standartiniame PDFlibPas Delphi PDF Library, skirtame Delphi ir C++Builder, kurio nuorodų dokumentacijoje pateikiamas pilnas puslapio keitimo iškvietimo įrašas ir jo klaidų kodai