Teknik Makale

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

Bir tedarikçi kılavuzuna yeni bir etiket aralığı eklersiniz, ek bölüm A-1, A-2 yerine App-a, App-b okusun diye, kaydedersiniz ve dosyayı yeniden açarsınız. Sayfa kutusu hâlâ A-1 der. Kataloğu bir metin düzenleyicide açın ve /PageLabels sözlüğü nedeni gösterir: üreticiden kalma bir /Kids dizisi taşıyor ve hemen yanında, sizin aralığınızı tutan taze bir /Nums dizisi var. ISO 32000-1, bir sayı ağacı kökünde bu anahtarlardan birine ya da diğerine izin verir; kökte /Kids bulan bir okuyucu ona dalıp daha fazla bakmaz

PDF bir sayfa etiketi aralığını nasıl saklar?

Sayfa etiketleri, görüntüleyicinin sayfa kutusunda fiziksel sayfa indeksi yerine gösterdiği dizelerdir ve ISO 32000-1 §12.4.2, onları katalogdaki /PageLabels anahtarının altında bir sayı ağacında tutar. Her anahtar, bir etiketleme aralığının başladığı 0 tabanlı sayfa indeksidir. Her değer, üç girdiye kadar taşıyan bir etiket sözlüğüdür: numaralandırma stili için /S (D, R, r, A veya a), önek dizgesi için /P ve aralıktaki ilk sayfanın numarası için /St (varsayılanı 1'dir). Bir aralık, bir sonraki anahtara kadar sürer. Şartname ayrıca 0 sayfa indeksi için bir girdi ister; böylece belgedeki her sayfa bir aralığın içine düşer

PDFlibPas terimleriyle sayfa etiketi saklaması: /PageLabels sayı ağacının anahtarları her aralığı sıfır tabanlı başlangıç sayfasıyla işaretler, her değer /S stili, /P öneki ve /St ilk numarası taşıyan bir etiket sözlüğüdür ve kitap örneği roma ön kısım, Arap rakamlı gövde sayfaları ile A- ek bölümünü üç aralığa haritalar
Bir aralık bir sonraki anahtara kadar sürer, şartname 0 sayfa indeksi için bir değer ister ve GetPageLabel, anahtarı sayfaya eşit ya da altında olan son aralığı uygular; böylece her sayfa bir şeye çözümlenir

PDF Library for Delphi'de yazan taraf TPDFlib.AddPageLabels(Start, Style, Offset, Prefix) çağrısıdır ve bu sözlüğe akılda tutmaya değer üç kuralla oturur. Start kitaplıktaki her sayfa argümanı gibi 1 tabanlıdır ve ağaca Start - 1 olarak girer. Style 0'dan 5'e koşar: 0 hiç /S yazmaz, böylece etiket yalnızca önek olur (Cover olarak etiketlenmiş tek bir sayfa için kullanışlıdır); 1-5 ise D, R, r, A ve aya dönüşür. 0..5 dışında bir stil, çağrının 0 döndürmesini sağlar ve belgeye dokunmaz. Offset yalnızca sıfırdan büyük olduğunda /St olur; 0 geçirirseniz anahtar atlanır ve her okuyucu bunu 1 olarak yorumlar

Bir dizgi aracından çıkan tipik bir kitap üç aralık ister: ön kısım için küçük harf roma rakamları, gövde için Arap rakamları ve önekli bir ek bölüm

uses
  PDFlibrary;

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('handbook.pdf', '') <> 1 then
      Exit;
    // Sayfa 1-4: i, ii, iii, iv (küçük harf roma)
    Lib.AddPageLabels(1, 3, 1, '');
    // Sayfa 5-120: 1, 2, 3 ... (ondalık, 1'den yeniden başlar)
    Lib.AddPageLabels(5, 1, 1, '');
    // Sayfa 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;

Sayfa etiketleri PDF 1.3'te geldi; bu yüzden her çağrı, belgeden çıktı sürümünü en az 1.3'e yükseltmesini de ister. Sürümü LockSaveVersion ile kilitleydiyseniz bu istek yok sayılır ve kilit geçerli kalır; çünkü kitaplık, bilinçli olarak seçtiğiniz bir sürümü ezmez. Kilitleyorsanız, etiket yazan her işte 1.3 ya da üzerine kilitleyin

Kök /Kids taşıdığında yeni bir aralık neden kayboluyor?

Aralık kaybolur; çünkü ISO 32000-1 §7.9.7 (Tablo 37), bir sayı ağacının köküne ya /Kids ya /Nums verir, ikisini birden asla, ve okuyucular /Kids varsa onu seçer. Uzun belgeler yazan üreticiler ağacı çoğu zaman ara düğümlere böler — her biri bir /Limits çiftiyle — ve yalnızca /Kids taşıyan bir köke asarlar. Yalnızca /Numstan anlayan bir ekleme yordamı o köke bakar, dizi bulamaz, var olan /Kidsin yanına bir tane kurar ve yeni aralığı içine bırakır. Yazma başarılı olur. Dosya ayrıştırılabilir kalır. Sonra her görüntüleyici /Kidse iner, üreticinin özgün üç aralığını görür ve başıboş dizeye hiç dokunmaz; açılış paragrafındaki tedarikçi kılavuzu belirtisi tam olarak budur

Çıkış yolu, kökü ikinci bir giriş noktası uzatabilecek bir kap olarak görmeyi bırakmaktır. AddPageLabels kökte /Kids bulduğunda önce var olan ağacın her yaprağını soldan sağa gezer, her anahtarı ve etiket sözlüğünü toplar, sonra bu listeden tek bir düz /Nums dizisi kurar. Düz dizi eklenmeden önce kökten /Kids, /Limits ve arta kalan her /Nums silinir; yeni aralık ancak ondan sonra sıradan sıralı bir eklemeyle girer. /Limitsin silinmesi de önemlidir: Tablo 37 ona yalnızca ara ve yaprak düğümlerde izin verir ve onu tutan bir kök, artık /Nums taşısa bile bozuktur

PDFlibPas'ta sayı ağacı 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 /Kids ile /Limits'i temizler — Tablo 37 bir köke asla izin vermez
/Kids yanındaki başıboş bir /Nums her yüzeysel denetimi geçer: ekleme başarılı olur ve kaydedilen dosya tertemiz açılır; ama önce /Kids'e inen bir okuyucu — görüntüleyicilerin ve kitaplığın kendisinin yaptığı gibi — yeni aralığı asla bulamaz

Kolay kaçan bir yan etki var. Dosya size çoktan bozulmuş ulaşırsa — kökte iki anahtar da var çünkü bir önceki araç naif eklemeyi yapmış — yaprak gezintisi bir görüntüleyici gibi /Kidsi izler, bu yüzden başıboş dizinin aralıkları devralınmaz. Görüntüleyici de o aralıkları hiç göstermedi; yani düzenlemeden sonraki etiketler, okuyucuların çoktan gördüğüyle eşleşir

Düzleştirilen ağaç ardından dengeli /Kids düğümlerine yeniden kurulmaz ve bu özel ağaç için doğru kararın bu olduğunu düşünüyoruz. Dengeleme, binlerce hedefi ya da gömülü dosyası olan ad ağaçlarında karşılığını bulur. Bir sayfa etiketi ağacında her etiketleme aralığı için bir girdi vardır ve büyük bir başvuru kılavuzu bile nadiren birkaç düzineyi aşar; çoğu üretici, bölme işini kendi başına yapmadıkça zaten tek yaprak yazar. Yeniden dengeleme geçişi, her kayıtta yanlış gidebilecek kod ekler; karşılığında, bu kadar küçük bir ağaçta kimsenin ölçemeyeceği bir arama kazancı vardır

// /PageLabels kökü /Kids kullanan bir dosyada ek bölümü yeniden etiketle
if Lib.LoadFromFile('vendor-manual.pdf', '') = 1 then
begin
  WriteLn('Before: ', Lib.GetPageLabel(121));  // örn. A-1
  // Var olan aralıkla aynı başlangıç sayfası, bu yüzden değeri değiştirilir
  if Lib.AddPageLabels(121, 5, 1, 'App-') = 1 then
  begin
    WriteLn('After: ', Lib.GetPageLabel(121));  // App-a
    WriteLn('Front: ', Lib.GetPageLabel(2));    // ii, değişmedi
    Lib.SaveToFile('vendor-manual-relabeled.pdf');
  end;
end;

Yeni aralık, var olan bir anahtarla aynı sayfa indeksinde başladığı için ekleme, 120 sayfa indeksi için ikinci bir girdi eklemek yerine o anahtarın sözlüğünü değiştirir. Üreticinin yapraklarından gelen roma ve ondalık aralıkları, varsa /St değerleriyle birlikte özgün sözlükleriyle geçer

Yaprak gezintisi bozuk ağaçları da hayatta kılmak zorundadır. Kendi atalarından birine geri işaret eden bir /Kids girdisi ya da bir yaprağı paylaşan iki dal, hasarlı hatlardan çıkan dosyalarda karşınıza çıkar. Gezinti hangi düğümleri ziyaret ettiğini hatırlar; bir döngü bir kez yürütülür ve geride bırakılır, paylaşılan bir yaprak aralıklarını bir kez katar. Ad ağaçları için aynı kaygılar ad ağacı döngüleri, bozuk /Limits ve büyük yaprakları ele alma başlığında ele alınır

Bir /Nums dizisini çiftler halinde gezmek

Bir /Nums dizisi, [key0 value0 key1 value1 ...] biçiminde dönüşümlü girdilerden oluşan düz bir diziştir ve yalnızca çift konumlar anahtardır. Bu düzen klasik bir hataya davet çıkarır. Tek tek eleman atlayıp "bu bir sayı mı?" diye soran bir döngü, değerlerden biri sayısal olduğunda değerleri anahtar sanıp karşılaştırır. O karşılaştırma bir küçüktür testini kazanırsa ekleme noktası tek bir indekse düşer, yeni çift var olan bir çiftin ortasına girer ve sonraki her anahtar ile değer yer değiştirir

PDFlibPas sayı ağaçlarında çift adımı: bir /Nums dizisi dönüşümlü anahtar ve değer girdilerinden oluşan düz bir dizidir; her elemanı test eden bir gezinti yeni çifti tek bir indekse sokabilir ve sonraki çiftleri faz dışı kaydırabilir, çift gezintisi ise anahtarı X*2'de ve değeri X*2+1'de okur
Tek adımlı gezinti /PageLabels üzerinde nadiren yanlış yapar çünkü etiket değerleri sözlüktür; ama yanlış adımla okuyan bir sayı ağacı yardımcısı, herhangi bir değer sayısal olduğu an bozar — bu yüzden her iki gezinti de çiftler halinde atlar

Sayfa etiketi değerleri sözlüktür; bu yüzden hata /PageLabelsin kendisinde gizli kalır. Aynı yardımcı, değerleri düz sayı olan bir sayı ağacı için yeniden kullanıldığı gün yüzeye çıkar ve dizinin söz diziminde elinizde hangi tür olduğunu söyleyen hiçbir şey yoktur. PDF Library for Delphi hem yaprak gezintisinde hem eklemede anahtarları X * 2, değerleri X * 2 + 1 indeksinde okur ve tam anahtar eşleşmesi değeri değiştirip taramayı durdurur. Ham nesneler üzerine kendi sayı ağacı kodunuzu yazıyorsanız, her şeyden önce o adımı kopyalayın

GetPageLabel ile etiketleri geri okumak

TPDFlib.GetPageLabel(Page) 1 tabanlı bir sayfanın etiketini döndürür ve iki yedeği bilinçli olarak farklı davranır. Hiç /PageLabels girdisi olmayan bir belge ondalık sayfa numarasını döndürür; böylece çağıran işlevi gösterim için koşulsuz kullanabilir. Ağacı olan ama istenen sayfayı kapsayan aralığı olmayan bir belge boş bir dize döndürür. İkinci durum, 0 sayfa indeksi için zorunlu girdiyi atlayan bir dosyanın ürettiği şeydir: ilk birkaç sayfanın şartname anlamında etiketi yoktur ve boş dize bunu uydurma bir sayıdan daha dürüst bildirir. Etiket kümenizi her zaman 1. sayfadan başlatın

Harf stilleri hesap tablosu sütunlarını değil PDF şartnamesini izler. Zden sonra AA, sonra BB, sonra CC gelir; harf taşımak yerine tekrar eder, bu yüzden bir A aralığının 53. sayfası AAA olarak okunur. Roma rakamları R için büyük harf, r için küçük harf üretilir ve önek her iki durumda da değişmeden öne eklenir

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

  // Seçenek değeri 4 yalnızca etiket aralıklarını dışa aktarır; her aralık
  // için bir PageLabelBegin bloğu
  Data := Lib.ExportDocumentData(4);
  // ... metni düzenleyin ya da başka bir dosyaya taşıyın ...
  // İlk etiket kaydı var olan aralıkları temizler, ardından her kayıt
  // AddPageLabels ile yeniden oynatılır; uygulanan kayıt sayısını döndürür
  WriteLn(Lib.ImportDocumentData(Data, 4), ' label ranges applied');
end;

Toplu düzenlemelerde ExportDocumentData, seçenek değeri 4 ile her aralığı bir PageLabelBegin bloğu ve ardından PageLabelNewIndex (1 tabanlı başlangıç sayfası), PageLabelStart, isteğe bağlı PageLabelPrefix ve PageLabelNumStyle satırları olarak yazar; stiller LowercaseRomanNumerals ve DecimalArabicNumerals gibi adlar kullanır. ImportDocumentData gördüğü ilk etiket kaydını tam bir değiştirme sayar: bir kez ClearPageLabels çağırır — bu, /PageLabels girdisinin tamamını katalogdan siler — sonra her kaydı AddPageLabels çağrısına aktarır. Yeniden kurulan ağaç baştan tek yapraktır; bu yüzden /Kids biçimli bir özgün üzerinden yapılan metin gidiş dönüşü, yeniden etiketleme yolunun ürettiği aynı düz biçimde geri gelir

Yeniden etiketledikten sonra yaprak sırası ve sayfa taşımaları

Düzleştirme, aralıkları bulduğu sırada korur. §7.9.7 anahtarların artan sırada olmasını ister; ama sırasız yapraklı dosyalar dolaşımdadır ve GetPageLabel, anahtarı sayfa indeksine eşit ya da altında olan son aralığı o sırayla uygular. Aralıklarını [0 roman, 120 A-, 4 decimal] olarak bildiren, ondalık aralığı 1'den başlayan bir dosyada 122. sayfa ondalık aralığa çözümlenir ve A-2 beklerken 118 okursunuz. Yukarıdaki sayfa sayfa denetim böyle etiketler gösteriyorsa, bir AddPageLabels çağrısı daha ekleyerek etrafından dolanmayın. ClearPageLabels çağırın ve tam kümeyi yeniden artan sırada yazın; dört çağrı, hangi aralığın kazandığını akıl etmekten ucuzdur

Etiketler sayfa nesnelerine değil sayfa indekslerine bağlıdır. Sayfa sayısını ya da sırasını değiştiren her şey, aralıkları tam oldukları yerde bırakır; artık o indekslere oturan hangi sayfaysa ona işaret ederler. nesne numaralarını koruyarak sayfa değiştirme gibi yerinde bir takas sayıyı korur, etiketler hizalı kalır. iç içe çift yüzlü taramaları harmanlama gibi bir birleştirme yeni bir sayfa dizisi üretir ve o çıktı, birleştirmeden sonra 1. sayfadan başlayan taze yazılmış bir aralık kümesi almalıdır

Buradaki etiket çağrıları, /Kids düzleştirmesi ve belge verisi dışa ve içe aktarma, Delphi, C++Builder ve Lazarus'u hedefleyen PDF Library for Delphi ürününün parçasıdır; AddPageLabels için başvuru girdisi stil değerlerini ve dönüş kodlarını tek tabloda listeler