Pudota seitsemän sivua 200-sivuisesta käsikirjasta, ja jokainen kirjanmerkki osuu jonnekin väärin. Korjaus ei ole jäsennyksen rakentaminen uudelleen tasaisesta otsikkoluettelosta. PDFiumPas tarjoaa työkalun TPdfOutlineEditor, joka lataa oikean jäsennyspuun, antaa sinun siirtää ja kohdistaa kohteita uudelleen ja suorittaa sitten funktion ApplyPageMap siirtääkseen jokaisen eksplisiittisen kohteen sivusuunnitelmasi mukaisille paikoille
Miksi sivujen poistaminen rikkoo jokaisen kirjanmerkin?
Koska jäsennyskohde ei tallenna sivunumeroa. Se tallentaa viittauksen sivuobjektiin, ja kun sivuobjektit muuttuvat, viittaus osoittaa joko sivulle, joka siirtyi, tai ei mihinkään. ISO 32000-1 §12.3.2.2 määrittelee eksplisiittisen kohteen taulukoksi, jonka ensimmäinen elementti on epäsuora viittaus sivusanakirjaan ja jota seuraa fit-nimi kuten /Fit tai /XYZ. Poista sivu, niin jäljelle jää viittaus, joka ei osoita mihinkään; järjestä sivut uudelleen, niin viittaus on yhä kelvollinen mutta kuvaa nyt eri lukua. PDFiumPas selvittää kyseisen taulukon takaisin sivunumeroksi ladatessaan, joten TPdfOutlineItem.PageNumber antaa yksiin perustuvan sivuindeksin, joka vastaa julkista TPdf-API:a objektinumeron sijaan. Siinä on koko abstraktion pointti: uudelleenkohdistuslogiikkasi toimii samassa koordinaatistossa kuin sivusuunnitelma, jonka rakensit jo, kun jaosit, järjestit tai asetit asiakirjan. Jos rakennat kyseistä suunnitelmaa, sama yksiin perustuva konventio kulkee läpi artikkelien PDF-asiakirjojen jakaminen useisiin tiedostoihin ja n-up asettelu ja sivujen uudelleenjärjestäminen
Jäsennys on kaksisuuntaisesti linkitetty puu, ei luettelo
Syy siihen, ettei otsikoiden tasaista taulukkoa voi yksinkertaisesti sarjallistaa, on, että ISO 32000-1 §12.3.3 kytkee jokaisen jäsennyskohteen viiteen erilliseen linkkiin: /Parent, /Prev, /Next, /First ja /Last. Yksittäisen alipuun siirtäminen kirjoittaa siksi uudelleen vanhan vanhemman, uuden vanhemman, kummankin puolen viereiset sisarukset leikkaus- ja lisäyskohdassa sekä siirretyn solmun oma vanhempiosoittimen. Jos yksi niistä menee väärin, standardinmukaiset katselimet näyttävät katkenneen puun tai silmukoituvat. PDFiumPas säilyttää muokkaustilan syvyyshaun mukaisena TPdfOutlineItem-tietueiden taulukkona vakaalla kokonaislukuarvoisella Id-tunnisteella, joten alipuu on yhtenäinen viipale ja sisarketju johdetaan, ei koskaan ylläpidetä käsin. TPdfOutlineEditor.Move nostaa kyseisen viipaleen, asettaa sen uudelleen uuden vanhemman alle pyydettyyn sisarindekksiin ja osoittaa uudelleen vain lohkon juuren. Se kieltäytyy myös kahdesta siirrosta, jotka korruptoisivat graafin: kohteen siirtäminen omaan alipuuun ja vanhemman nimeäminen, jota ei ole olemassa
Miksi /Count on etumerkitty?
Koska etumerkki kantaa avattu-tilaa, ei kokoa. Positiivinen /Count tarkoittaa, että kohde on avoin ja luku kertoo, kuinka monta jälkeläistä on parhaillaan näkyvissä; negatiivinen /Count tarkoittaa, että kohde on supistettu. PDFiumPas kirjoittaa jälkeläislukumäärän jokaiselle kohteelle, jolla on lapsia, ja tekee siitä negatiivisen, kun IsOpen on False, ja ladatessaan se lukee tilan takaisin muodossa IsOpen := HasCount and (CountValue > 0). Tämä on yleisin itse koodattu virhe jäsennysten kirjoittajissa: etumerkittömän luvun emittoiminen ja koko puun hiljainen avaaminen
var
Source, Dest: TMemoryStream;
Editor: TPdfOutlineEditor;
Options: TPdfOutlineEditOptions;
Report: TPdfOutlineValidationReport;
RootId, ChapterId: Integer;
begin
Source := TMemoryStream.Create;
Dest := TMemoryStream.Create;
Editor := nil;
try
Source.LoadFromFile('handbook.pdf');
Options := TPdfOutlineEditOptions.Default; // MaxItems 100000, MaxDepth 64
if not TPdfOutlineEditor.TryLoad(Source, Options, Editor, Report) then
raise Exception.Create(Report.ErrorMessage);
RootId := Editor[0].Id;
ChapterId := Editor[2].Id;
Editor.Move(ChapterId, RootId, 1); // tulee juuren toiseksi lapseksi
Editor.SetTitle(ChapterId, 'Appendix B');
Editor.SetStyle(ChapterId, [posBold, posItalic]);
Editor.SetColor(ChapterId, 0.25, 0.5, 0.75);
Editor.SetExpanded(RootId, False); // kirjoittaa negatiivisen /Count-arvon
Editor.Retarget(ChapterId, 12, '/XYZ 10 20 1');
if not Editor.SaveIncremental(Source, Dest, Report) then
raise Exception.Create(Report.ErrorMessage);
Dest.SaveToFile('handbook-edited.pdf');
finally
Editor.Free;
Dest.Free;
Source.Free;
end;
end;
Retarget käsittelee molemmat muodot, jotka määrittely sallii. Anna DestinationInAction arvolla False ja PDFiumPas kirjoittaa suoran /Dest-taulukon; anna arvo True ja se kirjoittaa Go-To-toiminnon, /A << /S /GoTo /D [ page ref suffix ] >>, standardin ISO 32000-1 §12.6.4.2 mukaisesti. Molemmissa tapauksissa se poistaa ensin kaikki olemassa olevat /Dest- ja /A-merkinnät kohteesta, jotta ne kaksi eivät voi olla rinnakkain ja olla eri mieltä. Jälkiliite on oletuksena /Fit ja sen on alettava PDF-nimellä, minkä vuoksi tyhjä tai muuten virheellinen jälkiliite nostaa poikkeuksen välittömästi sen sijaan että tuottaisi kohdetaulukon, jota mikään katselin ei osaa jäsentää
Miten ApplyPageMap ottaa sivusuunnitelman vastaan?
ApplyPageMap ottaa täsmälleen sen taulukon, jonka sivusuunnitelmasi jo vahvisti: NewPageNumbers, indeksoituna vanha sivu miinus yksi, joka sisältää uuden yksiin perustuvan sivunumeron tai nollan, kun kyseinen sivu ei jäänyt henkiin. Se kulkee kohdetaulukon läpi taaksepäin, jotta alipuun poistaminen ei koskaan mitätöi indeksiä, jota se ei ole vielä käynyt, ja se raportoi tekemänsä arvojen RemappedDestinationCount ja RemovedDanglingItemCount kautta
var
NewPageNumbers: array of Integer;
Report: TPdfOutlineValidationReport;
I: Integer;
begin
// Yksi merkintä ALKUPERÄISEN asiakirjan sivua kohden
SetLength(NewPageNumbers, OriginalPageCount);
for I := 0 to OriginalPageCount - 1 do
NewPageNumbers[I] := 0; // 0 == tämä sivu pudotettiin
NewPageNumbers[0] := 1; // vanha sivu 1 -> uusi sivu 1
NewPageNumbers[1] := 2;
NewPageNumbers[9] := 3; // vanha sivu 10 -> uusi sivu 3
// True: poista koko roikkuva alipuu. False: säilytä kohde, irroita sen kohde
if not Editor.ApplyPageMap(NewPageNumbers, True, Report) then
raise Exception.Create(Report.ErrorMessage);
WriteLn(Format('%d remapped, %d dangling items removed',
[Report.RemappedDestinationCount, Report.RemovedDanglingItemCount]));
end;
Lippu DeleteDangling päättää politiikan kohteelle, joka mapautui nollaan, ja molemmat haarat ovat harkittuja. Arvolla True PDFiumPas poistaa kohteen ja koko sen alipuun, koska jäsennyssolmu, jonka kohde katosi, johtaa yleensä lukua, joka katosi mukana. Arvolla False kohde säilyy otsikko ja hierarkia ennallaan mutta ilman /Dest- ja /A-merkintöjään, mikä on se, mitä haluat, kun ihminen aikoo kohdistaa sen uudelleen katselmuksessa. Aidosti virheellinen syöte epäonnistuu yhä äänekkäästi sen sijaan että paikattaisiin: negatiivinen merkintä tai kohde, joka osoittaa toimitetun kartan lopun ohi, palauttaa arvon False ja asettaa IssueKind-kentän arvoon poviInvalidPageMap
Läpinäkymättömät kohteet ja rehellinen kompromissi
Kaikilla jäsennyskohteilla ei ole sivunumeroa, josta PDFiumPas voisi päätellä mitään. Kolmenlaista kuljetetaan läpi koskemattomina: nimettyjä kohteita, toimintoja, jotka eivät ole /S /GoTo, ja tuntemattomia sanakirja-avaimia, joita tiedoston tuottaja lisäsi. Nämä latautuvat PageNumberin ollessa nolla, säilyttävät alkuperäiset tavunsa kohteessa ja kirjoitetaan takaisin sanatarkasti, ellet kutsu niihin eksplisiittisesti funktiota Retarget
- Nimetty kohde on avain asiakirjan nimipuuhun, joten sen oikea uudelleenkohdistus merkitsee puun selvittämistä ja kohdemerkinnän kirjoittamista uudelleen, ei arvaamista jäsennyksen tasolla
/URI-,/Launch- tai JavaScript-toiminnolla ei ole lainkaan sivusemantiikkaa, eikä sitä saa hiljaisesti muuntaa Go-To-toiminnoksi- Toimittajakohtaiset avaimet ja rakennekohteet säilytetään, koska ymmärtämättömän pudottaminen on se, miten kierrot menettävät tietoa
Hinta on todellinen ja sen sanominen suoraan kannattaa: ApplyPageMap ohittaa kyseiset kohteet kokonaan, joten asiakirja, jonka kirjanmerkit käyttävät kaikki nimettyjä kohteita, tulee sivun poiston läpi jäsennykseltään rakenteellisesti kelvollisena mutta semanttisesti vanhentuneena. Se on harkittu valinta — vanhentunut linkki, jonka katselmoija huomaa, voittaa varmasti väärän linkin, jota kukaan ei huomaa. Jos teet triagea saapuville tiedostoille ennen muokkausta, inventointikierros työkalussa PDF-intake-katselmointityöpöytä kertoo, mitkä asiakirjat kuuluvat kyseiseen kategoriaan
Tallennus: inkrementaalinen revisio ja sitten riippumaton uudelleenlataus
TPdfOutlineEditor.SaveIncremental liittää harvan inkrementaalisen revision tiedoston uudelleenkirjoittamisen sijaan. Ladatut kohteet säilyttävät alkuperäisen epäsuoran objektiviittauksensa tarkan sukupolven kera, joten olemassa olevat ristiviittaukset pysyvät kelvollisina; vain lisäämäsi kohteet saavat tuoreen numeron, joka varataan yhdestä revision maksimiobjektinumeron jälkeen. Katalogi päivitetään samassa revisiossa, ja puuttuva /Outlines-merkintä lisätään siihen, kun lähteessä ei ollut jäsennystä lainkaan
Se, mitä kirjoittamisen jälkeen tapahtuu, on osa, joka kannattaa kopioida. PDFiumPas avaa kohdevirran uudelleen täysin riippumattomalla editorilla ja vertaa uudelleenladattua puuta muistissa olevaan — kohdemäärä, otsikot, sivunumerot, kohteen jälkiliitteet, toiminto-versus-suora kohde -muoto, tyylit, avattu-tila ja vanhempi-suhteet. Mikä tahansa ero tai mikä tahansa latausvirhe tyhjentää kohdevirran ja palauttaa arvon poviVerificationFailure sen sijaan että antaisi sinulle uskottavan näköisen tiedoston. Salatut lähteet torjutaan heti alussa arvolla poviEncryptedInput, koska uudet otsikot ja kohteet luovat merkkijonosisältöä, jota ei voi tuottaa kopioimalla /Encrypt-traileria eteenpäin
if not Editor.SaveIncremental(Source, Dest, Report) then
case Report.IssueKind of
poviEncryptedInput:
Log('Source is encrypted; outline editing needs an unprotected copy');
poviInvalidDestination:
Log(Format('Item %d %d targets a missing page',
[Report.ObjectNumber, Report.Generation]));
poviVerificationFailure:
Log('Reload check rejected the written revision: ' + Report.ErrorMessage);
else
Log(Report.ErrorMessage);
end;
Käsittele jäsennystä sen mukaisesti, mikä se on — linkitettynä objektigraafina, jolla on omat invarianttinsa — ja sivujen poistaminen lopettaa olemasta kirjanmerkkikatastrofi ja muuttuu sivukartaksi, jonka annat yhdelle metodikutsulle. TPdfOutlineEditor, ApplyPageMap ja varmistettu inkrementaalinen kirjoittaja toimitetaan PDFiumPasissa versiosta v3.98.0 alkaen Delphille, C++Builderille ja Lazarukselle; koko API:n ja kokeiluversion saat PDFium Delphi Component -tuotesivulta