Teknik Makale

Delphi'de PDF sayfa etiketleri: /Kids number tree onarımı

PDF Library for Delphi, sayfa etiketi aralıklarını AddPageLabels ile yazar ve v3.539.10'dan bu yana o çağrı, /PageLabels number tree'si /Kids düğümlerine bölünmüş yüklü dosyalarda da çalışır: yeni aralık girmeden önce kök tek bir /Nums yaprağına düzleştirilir, böylece etiket sessizce yok sayılmak yerine görüntüleyicide gerçekten görünür. Klasik kurban, bir dizgi aracından çıkan kitap tarzı bir PDF'tir: ön bölümde roma rakamları, gövdede arap rakamları ve A-1, A-2 olarak etiketlenmiş bir ek; yalnızca eki yeniden etiketlemek istediniz ve hiçbir şey değişmedi

PDF sayfa etiketleri nedir ve nasıl saklanır?

Sayfa etiketleri, görüntüleyicinin sayfa kutusunda fiziksel sayfa indeksi yerine gösterdiği dizelerdir ve ISO 32000-1 §12.4.2 bunları katalog anahtarı /PageLabels altında bir number tree olarak saklar. Her anahtar, bir etiketleme aralığı başlatan 0 tabanlı bir sayfa indeksidir ve her değer, en çok üç girdili bir sayfa etiketi sözlüğüdür: numaralandırma stili için /S (D, R, r, A ya da a), önek dizesi için /P ve aralıktaki ilk sayfanın sayısal değeri için /St; varsayılanı 1'dir. Bir aralık sıradaki anahtara kadar sürer ve spesifikasyon, ağacın 0 sayfa indeksi için bir değer içermesini ister; böylece her sayfayı bir aralık kapsar

PDFlibPas terimleriyle sayfa etiketi saklaması: /PageLabels number tree her aralığı sıfır tabanlı başlangıç sayfasıyla anahtarlar, her değer /S stili, /P öneki ve /St ilk sayısı taşıyan bir etiket sözlüğüdür ve kitap örneği roma ön bölümü, arap gövde sayfalarını ve A- ekini üç aralığa eşler
Bir aralık sıradaki anahtara kadar sürer, spesifikasyon 0 sayfa indeksi için bir değer ister ve GetPageLabel anahtarı sayfaya eşit ya da ondan küçük olan son aralığı uygular; böylece her sayfa bir şeye çözülür
var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('handbook.pdf', '') <> 1 then
      Exit;
    // Sayfalar 1-4: i, ii, iii, iv (küçük harf roma)
    Lib.AddPageLabels(1, 3, 1, '');
    // Sayfalar 5-120: 1, 2, 3 ... (ondalık)
    Lib.AddPageLabels(5, 1, 1, '');
    // Sayfalar 121 ve sonrası: A-1, A-2 ... (önekli ondalık)
    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;

TPDFlib.AddPageLabels(Start, Style, Offset, Prefix), argümanlarını üç kuralı bildikten sonra sürprizsiz biçimde o sözlüğe eşler. Start, kütüphanedeki diğer her sayfa argümanı gibi 1 tabanlıdır ve ağaca Start - 1 olarak yazılır. Style 0'dan 5'e gider: 0 yalnızca önek demektir ve 1 ile 5, /S değerleri D, R, r, A ve a olur; bu aralığın dışındaki her şey 0 döndürür ve hiçbir şeye dokunmaz. Offset, sıfırdan büyük olduğunda /St olur; 0 geçmek anahtarı atlar ve görüntüleyici varsayılan 1'e döner. Sayfa etiketleri PDF 1.3'te geldiği için çağrı ayrıca EnsureMinVersion('1.3', '/PageLabels') çalıştırır; bu, kayıt sürümünü açıkça kilitlemediyseniz eski bir dosyanın çıktı sürümünü yükseltir

Ağaç /Kids taşıdığında yeni sayfa etiketleri neden kaybolur?

Yeni etiketler kaybolur, çünkü ISO 32000-1 §7.9.7 (Tablo 37) bir number tree'nin kökünün ya /Kids ya /Nums taşımasını, ikisini birden asla taşımamasını ister ve önceki NumTreeSet yardımcısı yalnızca /Nums aramayı biliyordu. Uzun belgeler üreten yazılımlar ağacı sık sık ara düğümlere böler; her biri bir /Limits çifti taşır ve yalnızca /Kids içeren bir köke asılır. Eski kod o kökte /Nums bulamayınca mevcut /Kidsin yanında taze bir tane oluşturuyor ve yeni aralığı oraya ekliyordu. Sonuç, iki karşılıklı dışlayıcı giriş noktalı bir köktü. Görüntüleyiciler /Kids üzerinden iner ve başıboş diziyi hiç bakmaz; kütüphanenin kendi EnumNumTree'si de önce /Kids'e bakar ve NumTreeLookup, HasKids xor HasNums false olan bir düğümü reddeder. AddPageLabels yine de 1 döndürüyordu ve kaydedilen dosya yine de sorunsuz açılıyordu; en kötü türden başarısızlık: hiçbir şey şikâyet etmez, etiketler sadece aynı kalır

NumTreeSet'teki düzeltme, bir şey eklemeden önce kökü yaprağa dönüştürür. Kök /Kids taşıyorduğunda EnumNumTree her yaprağı sırayla gezer ve her anahtar-değer çiftini toplar; o listeden yeni bir düz /Nums dizisi kurulur ve düz dizi eklenmeden önce /Kids, /Limits ve bayat bir /Nums varsa kökten temizlenir. /Limits'i düşürmek kozmetik değil, çünkü Tablo 37 o girdiye yalnızca ara ve yaprak düğümlerde izin verir, kökte asla. O noktadan sonra ekleme, tek bir diziye sıradan bir sıralı eklemedir ve mevcut aralıklar özgün etiket sözlükleriyle hayatta kalır. Takas bilinçlidir: ağaç sonradan dengeli /Kids düğümlerine yeniden kurulmaz. Sayfa etiketleri için bunun maliyeti sıfırdır, çünkü büyük bir başvuru kılavuzu bile nadiren birkaç düzineden fazla aralık taşır ve çoğu üretici zaten tek yaprak yazar

PDFlibPas'ta number tree onarımı: /Kids ve başıboş bir /Nums dizisi taşıyan kök, ISO 32000-1 ikisinden yalnızca birine izin verdiği için görüntüleyicilere görünmezdir; NumTreeSet her yaprağı tek bir /Nums dizisine düzleştirir ve Tablo 37'nin kökte asla izin vermediği /Kids ile /Limits'i temizler
Her kontrol geçtiği için hiçbir şey şikâyet etmedi: AddPageLabels 1 döndürdü, kaydedilen dosya sorunsuz açıldı ve /Kids'i önce indiren — görüntüleyicilerin ve kütüphanenin kendisinin yaptığı gibi — okuyucu yeni aralığı asla bulamıyor
// /PageLabels kökü /Kids kullanan bir dosyada eki yeniden etiketle
if Lib.LoadFromFile('vendor-manual.pdf', '') = 1 then
begin
  WriteLn('Before: ', Lib.GetPageLabel(121));  // örn. A-1
  // 121. sayfada başlayan aralığı değiştir: App-a, App-b ...
  if Lib.AddPageLabels(121, 5, 1, 'App-') = 1 then
    Lib.SaveToFile('vendor-manual-relabeled.pdf');
  // Mevcut roma ve ondalık aralıklar hâlâ düzleştirilmiş yaprakta
  WriteLn('After: ', Lib.GetPageLabel(121));   // App-a
  WriteLn('Front: ', Lib.GetPageLabel(2));     // ii, değişmedi
end;

Bir /Nums dizisi nasıl yanlış anahtar olarak okunabilir?

Bir /Nums dizisi, kod onu tek tek eleman gezerken yanlış okunur; çünkü dizi [key0 value0 key1 value1 ...] biçiminde dönüşümlü çiftlerden oluşan düz bir dizidir ve yalnızca çift konumlar anahtardır. Eski NumTreeSet döngüsü her elemanı sayısal tür açısından sınıyordu; o anda sayı olan bir değer anahtarmış gibi karşılaştırılıyordu ve bir küçüktür isabeti ekleme noktasını tek numaralı bir indekse taşıyıp yeni çifti mevcut bir çiftin ortasına bırakabiliyordu, sonraki her çifti faz dışına iterek. EnumNumTree de aynı tek adımlı yürüyüşe sahipti. İkisi de artık çiftleri adım ikiyle geziyor, anahtarı X * 2'de değeri X * 2 + 1'de okuyor ve tam anahtar eşleşmesi değeri değiştirip Break ile çıkıyor. Adaletten yana söylemek gerekirse sayfa etiketi değerleri sözlüktür, bu yüzden ikinci hata /PageLabels üzerinde nadiren tetiklendi; ama yanlış adımla okuyan bir number tree yardımcısı, herhangi bir değer sayısal olduğu anda bozuktur ve aynı geçişte düzeltildi

PDFlibPas number tree'lerinde çift adımı düzeltmesi: bir /Nums dizisi anahtar ve değer girdilerinin dönüşümlü düz dizisidir; her elemanı sınamak yeni bir çifti tek numaralı bir indekse ekleyip sonraki çiftleri faz dışına itebilir, düzeltilmiş yürüyüş ise anahtarı X*2'de değeri X*2+1'de okur
Hata /PageLabels üzerinde nadiren tetiklendi, çünkü etiket değerleri sözlüktür; ama yanlış adımla okuyan bir number tree yardımcısı herhangi bir değer sayısal olduğu anda bozulur, iki yürüyüş de artık çift çift adım atıyor

Etiketleri geri okumak ve kayıpsız gidiş dönüş

TPDFlib.GetPageLabel(Page), 1 tabanlı bir sayfanın etiketini döndürür ve bilinmesinde fayda olan iki yedeği vardır. Hiç /PageLabels girdisi yoksa ondalık sayfa numarasını döndürür, böylece çağıran onu koşulsuz kullanabilir. Ağaç var ama sayfayı kapsayan aralık yoksa boş dize döndürür; dosya zorunlu 0 indeks girdisini atladığında olan tam olarak budur. Referans dokümantasyonu, etiketlerin doğru görüntülenmesi için 1. sayfada başlayan bir aralığın bulunması gerektiğini söyler ve kod o gerekliliği görünür kılar. Harf stilleri elektronik tablo sütunlarını değil spesifikasyonu izler: Z'den sonra AA, sonra BB gelir; taşıma yapmak yerine harf yinelenir

var
  P: Integer;
  Data: WideString;
begin
  // Görüntüleyicinin sayfa kutusunda göstereceğinin hızlı denetimi
  for P := 1 to Lib.PageCount do
    WriteLn(P, ' -> ', Lib.GetPageLabel(P));

  // 4 seçenek değeri yalnızca etiket aralıklarını PageLabelBegin kayıtları olarak dışa aktarır
  Data := Lib.ExportDocumentData(4);
  // İçe aktarma onları ClearPageLabels + AddPageLabels üzerinden oynatır
  Lib.ImportDocumentData(Data, 0);
end;

Toplu düzenlemeler için seçenek değeri 4 ile ExportDocumentData, her aralığı PageLabelNewIndex, PageLabelStart, PageLabelPrefix ve PageLabelNumStyle satırları taşıyan bir PageLabelBegin bloğu olarak yazar; ImportDocumentData ise gördüğü ilk etiket kaydını tam değişim sayar: bir kez ClearPageLabels çağırır ve sonra her kaydı AddPageLabels'e verir. Bu, özgün dosya /Kids ağacı kullanmış olsa bile metin gidiş dönüşünü deterministik kılar; çünkü temizleme tüm katalog girdisini kaldırır ve yeniden kurulan ağaç baştan tek yapraktır

Düzeltme hâlâ neyi garanti etmiyor?

Düzleştirme tek yönlüdür ve bulduğu sıraya güvenir. EnumNumTree çiftleri dosya sırasında toplar ve GetPageLabel, anahtarı sayfa indeksine eşit ya da ondan küçük olan son aralığı uygular; yaprakları sıra dışı olan yabancı bir dosya — §7.9.7'nin yasakladığı ama dolaşımda olan tür — siz aralıkları ClearPageLabels ile taze AddPageLabels çağrılarıyla yeniden kurana kadar yanlış etiketler verebilir. Etiketler ayrıca sayfa nesnelerine değil sayfa indekslerine bağlıdır, dolayısıyla sayfa sayısını ya da sırasını değiştiren her işlem aralıkları bulundukları yerde bırakır. Nesne numaralarını koruyarak sayfa değiştirmek gibi yerinde bir takas sayıyı ve dolayısıyla etiket hizasını korur; iç içe dubleks taramaları harmanlamak gibi bir birleştirme ise yeni yazılmış bir aralık setini hak eden yeni bir sayfa dizisi üretir

Burada anlatılan sayfa etiketi çağrıları, number tree işleme ve belge verisi dışa-içe aktarma, Delphi, C++Builder ve Lazarus için PDF Library for Delphi ile gelir; AddPageLabelsin referans girdisi stil değerlerini ve dönüş kodlarını belgeler