A PDF Library for Delphi az AddPageLabels-szal írja az oldalcímke tartományokat, és a v3.539.10 óta ez a hívás olyan betöltött fájlokon is működik, amiknek a /PageLabels számfa /Kids csomópontokra van bontva: a gyökér egyetlen /Nums lappá lapul ki, mielőtt az új tartomány bekerülne, így a címke tényleg megjelenik a megjelenítőben ahelyett, hogy csendben figyelmen kívül hagynák. A tipikus áldozat egy tördelőeszközből származó könyv-stílusú PDF, római számokkal az eleji részeken, arab számozással a szövegtörzsben és egy A-1, A-2 jelzésű függelékkel, ahol csak a függeléket akartad újracímkézni, és semmi sem változott
Mik azok a PDF oldalcímkék, és hogyan tárolódnak?
Az oldalcímkék azok a stringek, amiket a megjelenítő az oldalablakában mutat a fizikális oldalindex helyett, az ISO 32000-1 §12.4.2-e pedig számfaként tárolja őket a katalógus /PageLabels kulcsa alatt. Minden kulcs egy nullától induló oldalindex, ami egy címkézési tartományt nyit, minden érték pedig egy oldalcímke szótár legfeljebb három bejegyzéssel: /S a számozási stílushoz (D, R, r, A vagy a), /P egy prefix stringhez, /St pedig a tartomány első oldalának numerikus értékéhez, ami alapértelmezetten 1. Egy tartomány a következő kulcsig tart, a specifikáció pedig megköveteli, hogy a fa tartalmazzon értéket a 0. oldalindexhez, így minden oldalt lefed valamelyik tartomány
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('handbook.pdf', '') <> 1 then
Exit;
// 1-4. oldalak: i, ii, iii, iv (kisbetűs római)
Lib.AddPageLabels(1, 3, 1, '');
// 5-120. oldalak: 1, 2, 3 ... (decimális)
Lib.AddPageLabels(5, 1, 1, '');
// 121-től: A-1, A-2 ... (decimális prefixszel)
Lib.AddPageLabels(121, 1, 1, 'A-');
WriteLn(Lib.GetPageLabel(5)); // 1
WriteLn(Lib.GetPageLabel(122)); // A-2
Lib.SaveToFile('handbook-labeled.pdf');
finally
Lib.Free;
end;
end;
A TPDFlib.AddPageLabels(Start, Style, Offset, Prefix) akkor képezi le az argumentumait arra a szótárra meglepetések nélkül, ha ismered a három szabályt. A Start 1-től indul, akárcsak a library minden más oldalargumentuma, és Start - 1-ként íródik a fába. A Style 0-tól 5-ig megy, ahol a 0 csak prefixet jelent, az 1-től 5-ig pedig /S értékként D, R, r, A és a lesz; bármi, ami ezen a tartományon kívül esik, 0-t ad vissza és semmit nem érint. Az Offset csak akkor válik /St-vé, ha nagyobb nullánál, így a 0 átadása egyszerűen elhagyja a kulcsot, és a megjelenítő az 1-es alapértékre esik vissza. Mivel az oldalcímkék a PDF 1.3-ban érkeztek, a hívás lefuttatja az EnsureMinVersion('1.3', '/PageLabels')-t is, ami egy régebbi fájl kimeneti verzióját megemeli, hacsak nem zároltad expliciten a mentési verziót
Miért tűnnek el az új oldalcímkék, amikor a fa /Kids-t hordoz?
Az új címkék azért tűnnek el, mert az ISO 32000-1 §7.9.7-e (37. táblázat) úgy szabja meg a számfa gyökerét, hogy az vagy /Kids-t vagy /Nums-t hordozzon, soha mindkettőt, a korábbi NumTreeSet helper pedig csak azt tudta, hogyan keressen /Nums-t. A hosszú dokumentumokat gyártó producertek gyakran köztes csomópontokra bontják a fát, mindegyiket egy /Limits párral, és egy csak /Kids-t hordozó gyökérre akasztják. A régi kód nem talált /Nums-t azon a gyökéren, frisset gyártott a meglévő /Kids mellé, és oda szúrta be az új tartományt. Az eredmény egy gyökér lett két egymást kizáró belépési ponttal. A megjelenítők a /Kids-en keresztül ereszkednek, és soha nem nézik meg a kóbor tömböt, a library saját EnumNumTree-e is először a /Kids-t ellenőrzi, a NumTreeLookup pedig elutasít egy csomópontot, ahol a HasKids xor HasNums hamis. A AddPageLabels továbbra is 1-et adott vissza, és a mentett fájl továbbra is tisztán nyílt meg, ami a legrosszabb fajta hiba: semmi nem panaszkodik, a címkék egyszerűen ugyanazok maradnak
A NumTreeSet-beli javítás a gyökeret lappá alakítja, mielőtt bármit beszúrna. Amikor a gyökér /Kids-t hordoz, az EnumNumTree sorban bejárja minden levelét, és összegyűjti minden kulcs-érték párt, ebből a listából új, lapos /Nums tömb épül, a /Kids, a /Limits és minden elavult /Nums pedig kitisztul a gyökérből, mielőtt a lapos tömb rákerül. A /Limits elhagyása nem kozmetika, mert a 37. táblázat szerint ez a bejegyzés csak köztes és levél csomópontokon megengedett, a gyökéren soha. Attól a ponttól a beszúrás közönséges rendezett beszúrás egyetlen tömbbe, és a meglévő tartományok túlélik az eredeti címkeszótáraikkal. A kompromisszum szándékos: a fát nem építik újra kiegyensúlyozott /Kids csomópontokká utána. Oldalcímkékre ez semmit sem kerül, mert még egy nagy referenciakézikönyvnek is ritkán van több mint néhány tucat tartománya, és a legtöbb producer amúgy is egyetlen levelet ír
// A függelék újracímkézése olyan fájlban, aminek a /PageLabels gyökere /Kids-t használ
if Lib.LoadFromFile('vendor-manual.pdf', '') = 1 then
begin
WriteLn('Before: ', Lib.GetPageLabel(121)); // e.g. A-1
// A 121. oldalon kezdődő tartomány cseréje: App-a, App-b ...
if Lib.AddPageLabels(121, 5, 1, 'App-') = 1 then
Lib.SaveToFile('vendor-manual-relabeled.pdf');
// A meglévő római és decimális tartományok még a kilapított levélben vannak
WriteLn('After: ', Lib.GetPageLabel(121)); // App-a
WriteLn('Front: ', Lib.GetPageLabel(2)); // ii, változatlan
end;
Hogyan olvasható félre kulcsokként egy /Nums tömb?
Egy /Nums tömb akkor olvasódik félre, amikor a kód elemenként járja be, mert a tömb váltakozó párok lapos futása, [key0 value0 key1 value1 ...], és csak a páros pozíciók kulcsok. A régi NumTreeSet ciklus minden elemet numerikus típusra tesztelt, így egy véletlenül számot adó érték kulcsként hasonlítódott; egy kisebb-egyelő találat a beszúrási pontot páratlan indexre tehette, és az új párt egy meglévő közepére ejthette, eltolva minden későbbi párt fázison kívülre. Az EnumNumTree ugyanazt az egylépéses bejárást hordozta. Mindkettő most kettes lépésközzel iterálja a párokat, a kulcsot X * 2-nél, az értéket X * 2 + 1-nél olvasva, és egy pontos kulcsegyezés lecseréli az értéket, majd Break-kel lép ki. Az igazsághoz hozzátartozik, hogy az oldalcímkék értékei szótárak, így ez a második hiba ritkán aktiválódott magán a /PageLabels-en, de egy számfa-helper, ami rossz lépésközzel olvas, abban a pillanatban romlásba megy, amint bármelyik érték szám, és ugyanabban a menetben javították
Címkék visszolvasása és oda-vissza vitelük
A TPDFlib.GetPageLabel(Page) egy 1-től induló oldal címkéjét adja vissza, és két tartalék viselkedése van, amit érdemes ismerni. Ha egyáltalán nincs /PageLabels bejegyzés, a decimális oldalszámot adja vissza, így a hívó feltétel nélkül használhatja. Ha van fa, de nincs az oldalt lefedő tartomány, üres stringet ad vissza, ami pontosan az, ami akkor történik, amikor egy fájl kihagyja a kötelező 0. indexű bejegyzést; a referenciadokumentáció szerint az 1. oldalon kezdődő tartománynak léteznie kell ahhoz, hogy a címkék helyesen jelenjenek meg, és a kód láthatóvá teszi ezt a követelményt. A betűstílusok a specifikációt követik, nem a táblázatoszlopokat: a Z után AA jön, aztán BB, a betű ismétlődik, nem átvitel történik
var
P: Integer;
Data: WideString;
begin
// Gyors átvizsgálás, mit fog mutatni a megjelenítő az oldalablakában
for P := 1 to Lib.PageCount do
WriteLn(P, ' -> ', Lib.GetPageLabel(P));
// A 4-es opcióérték csak a címketartományokat exportálja PageLabelBegin rekordokként
Data := Lib.ExportDocumentData(4);
// Az import a ClearPageLabels + AddPageLabels párosán játssza vissza őket
Lib.ImportDocumentData(Data, 0);
end;
Tömeges szerkesztéshez az ExportDocumentData 4-es opcióértékkel minden tartományt PageLabelBegin blokként ír, PageLabelNewIndex, PageLabelStart, PageLabelPrefix és PageLabelNumStyle sorokkal, az ImportDocumentData pedig az első látott címkerekordot teljes cserének tekinti: egyszer meghívja a ClearPageLabels-t, majd minden rekordot a AddPageLabels-nek ad. Ez a szöveges oda-vissza utat determinisztikussá teszi akkor is, amikor az eredeti fájl /Kids fát használt, mert a törlés az egész katalógusbejegyzést leszedi, és az újraépített fa kezdettől fogva egyetlen levél
Mit sem garantál még mindig a javítás?
A kilapítás egyirányú, és bízik a megtalált sorrendben. Az EnumNumTree fáilsorrendben gyűjti a párokat, a GetPageLabel pedig azt az utolsó tartományt alkalmazza, aminek a kulcsa kisebb vagy egyenlő az oldalindexnél, így egy idegen fájl, aminek a levelei nincsenek sorrendben — amit a §7.9.7 tilt, de ami mégis kering — továbbra is adhat rossz címkéket, amíg újra nem építed a tartományokat ClearPageLabels-szal és friss AddPageLabels hívásokkal. A címkék oldalindexekhez kötődnek, nem oldalobjektumokhoz, így minden művelet, ami az oldalszámot vagy a sorrendet változtatja, a tartományokat a helyükön hagyja. Egy helyben végzett csere, mint a oldalak cseréje objektumszámok megőrzésével, megtartja a számot, ezért a címkék összhangban maradnak, míg egy összefésülés, mint a váltakoztatott duplex szkennelik összefésülése, új oldalsorrendet állít elő, ami frissen írt tartománysort érdemel
Az itt leírt oldalcímke hívások, a számfa-kezelés és a dokumentumadat export és import mind a PDF Library for Delphi részét képezik Delphi, C++Builder és Lazarus alatt, a AddPageLabels referencia-bejegyzése pedig dokumentálja a stílusértékeket és a visszatérési kódokat