Kapak sayfası kesilmiş etiketli bir rapor doğru sayfa sayısıyla açılabilir ve yine de üç yerde bozuk olabilir: İçindekiler yer imi tıklandığında hiçbir şey yapmaz, erişilebilirlik denetleyicisi hiçbir sayfaya ait olmayan bir yapı elemanı bildirir ve katı bir doğrulayıcı serbest bir nesneye yapılan başvuruyu işaretler. Sayfa ağacı sağlamdır. ISO 32000-1 §7.7.3 yalnızca sayfanın /Kids içinden çıkmasını ve /Count değerinin bir azalmasını ister; bir dosya bu maddeyi sağlarken kataloğunun yarısı hâlâ kayıp sayfayı işaret edebilir
Yedi yapı tek bir sayfaya başvuru tutabilir
Bunların yalnızca biri sayfa ağacıdır. Gerisi kataloğa, ana hatlara, mantıksal yapıya ve diğer sayfaların ek açıklamalarına dağılmıştır; sayfa nesnesi dosyadan gidince hepsi null'a çözülür. Görüntüleyicinin o null ile ne yapacağı kendi tercihidir, bu yüzden aynı kırpılmış dosya iki okuyucuda farklı davranabilir
/Names/Destsaltındaki ad ağacı (§7.7.4, §12.3.2.3), adları ilk öğesi sayfa olan hedef dizilerine eşler- Kataloğun içinde doğrudan duran 1.2 öncesi
/Destssözlüğü, aynı türdeki dizileri ada göre anahtarlayarak tutar - Ana hat öğeleri (§12.3.3) bir sayfaya satır içi
/Destile ya da/S /GoTove bir/Ddizisi taşıyan bir/Aeylemi üzerinden ulaşır - Yapı elemanları (§14.7.2), işaretli içeriklerinin üzerinde bulunduğu sayfayı adlandıran bir
/Pganahtarı taşır;/Kalt öğeleri o sayfaya bağlı işaretli-içerik başvuruları ve nesne başvuruları (§14.7.4.3) olabilir ParentTree(§14.7.4.4), sayfa ve ek açıklama/StructParentsnumaralarını yapı elemanlarına geri eşler; bir eleman, kökten itibaren/Kzincirinin hiçbir yerinde görünmeden orada durabilir- Diğer sayfalardaki bağlantı ek açıklamaları (§12.5.6.5) sayfayı hedefleyen bir
/Destya da/GoToeylemi taşır; katalog/OpenActionda aynısını yapabilir
THotPDF.DeletePage, /Kids'e dokunmadan önce ne yapar?
Yüklü bir belgede THotPDF.DeletePage(PageIndex), önce bu yapıların hepsini süpürür ve sayfa ağacını en son yeniden yazar. İndeks sıfır tabanlıdır. Herhangi bir şey değişmeden önce iki koruma çalışır: aralık dışı bir indeks Invalid page number oluşturur, tek kalan sayfayı kaldırma isteği de bir istisna oluşturur; çünkü sıfır alt öğeli bir /Pages düğümü geçerli bir belge tanımlamaz
Süpürme sırası sabittir: /Names /Dests ağacı, eski usul katalog /Dests sözlüğü, /OpenAction, ana hatlar, ParentTree'siyle birlikte /StructTreeRoot ve son olarak kalan her sayfanın /Annots dizileri. Ancak ondan sonra sayfa nesnesi silinmiş işaretlenir, widget ek açıklamaları AcroForm alan ağacından ayrılır ve /Kids, /Count ile hayatta kalan her sayfanın /Parent'ı yeniden yazılır. Form tarafında widget gittikten sonra alt öğesiz kalan bir üst alan da kaldırılır; bu, /Fields'e kadar yukarı doğru gider, böylece kırpılmış bir form arkasında ekranda hiçbir şey olmayan bir alan girdisi tutmaz
DeletePages, diğer yüklü-belge sayfa işlemlerinin kullandığı gösterimle '1,3-5,7-' gibi 1 tabanlı bir aralık dizisi alır ve seçilen en yüksek sayfadan aşağı doğru siler. Yukarıdan aşağıya gitmek, sayfalar altınızda kaybolurken yazdığınız numaraları geçerli tutar. Her sayfayı kapsayan bir aralık, DeletePage'in son sayfayı reddetmesiyle aynı nedenle istisna oluşturur ve işlev kaç sayfa kaldırdığını döndürür
var
Pdf: THotPDF;
Removed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// LoadFromFile, yüklediği sayfa sayısını döndürür
if Pdf.LoadFromFile('tagged-report.pdf') > 0 then
begin
// Sıfır tabanlı: sayfa 0 kapaktır
Pdf.DeletePage(0);
// 1 tabanlı aralık; numaralar belgenin şu anki haline göredir
Removed := Pdf.DeletePages('3-4,9');
Pdf.SaveLoadedDocument('tagged-report-trimmed.pdf');
end;
finally
Pdf.Free;
end;
end;
Adlar kaybolur, yer imleri taşınır
Silinen sayfayı hedefleyen adlandırılmış hedefler kaldırılır. Onu hedefleyen yer imleri yeniden hedeflenir. /Names /Dests ağacında her düğüm ziyaret edilir ve her hedef, spesifikasyonun izin verdiği iki biçimde de sınanır: çıplak dizi ve /D anahtarlı sözlük; dizinin ilk öğesi silinen sayfa olduğunda ad/değer çifti gider. Ne /Names ne de /Kids bırakmış bir ara düğüm üstünden koparılır, böylece ağaçta içi boş yaprak kalmaz. Eski usul katalog /Dests sözlüğü aynı sınava anahtar anahtar girer ve silinen sayfada açılan bir katalog /OpenAction düşürülür; bu da açılış görünümünü görüntüleyicinin varsayılanına bırakır
Girdi kaybeden bir düğüm /Limits dizisini de kaybeder. Yeni en düşük ve en yüksek anahtarlar yeniden hesaplanmaz ve görüntüleyiciler adları onsuz çözer. §7.9.6 ara ve yaprak düğümlerde /Limits ister; bu yüzden hattınızda bir ön kontrol adımı bu kuralı katı uyguyorsa kırpılmış dosyayı aynı işin parçası olarak o adımdan geçirin
Ana hat, /First ve /Next üzerinden bir ziyaret listesiyle ve 128 derinlik sınırıyla gezilir; böylece kendisine döngü kuran bozuk bir /Next zinciri çağrıyı kilitleyemez. Silinen sayfayı hedefleyen her /Dest dizisi ya da /GoTo /D dizisi için ilk öğe, onu izleyen sayfayla değiştirilir; silinen sayfa son sayfaysa önündeki sayfayla değiştirilir. Sayfa başvurusundan sonraki görünüm parametreleri olduğu gibi kalır; böylece bir /XYZ atlaması sol, üst ve yakınlaştırma değerlerini korur ve bunları yeni sayfaya uygular
Bu asimetri kasıtlıdır. Hiçbir şeye çözülmeyen bir ad yasal ve zararsızdır; var olmayan bir hedefi adlandıran bir GoTo basitçe hiçbir şey yapmaz. null hedefli bir yer imi daha kötüdür: kenar çubuğunda durmaya devam eder, tıklanabilir görünür ve başarısız olur. En yakın hayatta kalan sayfaya inmek, kusurlu iki sonuçtan iyisidir. Yeniden hedefleme yalnızca açık hedef dizilerini kapsar. /Dest'i bir ad dizgesi olan ana hat öğesi ad ağacı üzerinden çözülür; süpürme o adı kaldırınca yer imi hiçbir yere gitmez, bu yüzden adlı yer imlerini yoğun kullanan belgeler silme sonrası bir kontrolü hak eder. Aşağıdaki kontrol adları ResolveLoadedNamedDestination ile okur; bu, yalnızca kök düğümün /Names dizisine bakar ve düz bir ad ağacı için anlamlı bir yanıt verir, /Kids'e bölünmüş bir ağaç ise her ad için -1 döndürür. Göz önünde olmayan /Count anlam bilimi dahil, ana hat yapısının kendisi yüklü bir PDF'e yer imleri ve adlandırılmış hedefler ekleme kılavuzunda ele alınır
// Varsaymak yerine sonucu kontrol edin.
// Memo1 kendi formunuzda bir TMemo'dur.
Pdf.DeletePage(0);
// ResolveLoadedNamedDestination yalnızca kök /Names dizisini okur,
// bu yüzden bu kontrol düz bir ad ağacı için anlamlıdır; /Kids
// kullanan bir ağaçta silinmiş ya da değil her ad için -1 döner
if Pdf.ResolveLoadedNamedDestination('cover') = -1 then
Memo1.Lines.Add('"cover" is not resolvable from the root /Names array');
// Kapağı işaret eden yer imi artık onu izleyen sayfaya çözülür;
// bu da silmeden sonra sıfır tabanlı 0 indeksidir
Memo1.Lines.Add(Format('Bookmark "Contents" -> page index %d',
[Pdf.GetLoadedBookmarkPageIndex('Contents')]));
Yapı ağacını ve ParentTree'yi budamak
Yalnızca silinen sayfa için var olmuş yapı elemanları kaldırılır ve birkaç sayfaya yayılan elemanlar /Pg anahtarını kaybeder ama alt öğelerini tutar. Gezinti /StructTreeRoot'tan başlar, /K üzerinden 128 derinliğe iner ve §14.7.2'nin izin verdiği hem dizi biçimini hem de /K'nin tek sözlük biçimini kabul eder. Her elemanın alt öğeleri, elemanın kendisi yargılanmadan önce budanır. Budama /K'sini boşalttıysa eleman kaldırılır ve üstü onu düşürür. Kendi /Pg'si silinen sayfayı adlandırıyor ama hâlâ alt öğeleri ve bir /P üstü varsa yalnızca /Pg gider; çünkü bir elemandaki /Pg, kendi sayfasını adlandırmayan işaretli-içerik alt öğeleri için yalnızca varsayılan sayfadır ve o alt öğeler büyük olasılıkla başka sayfalarda yaşar. /Pg'si silinen sayfa olan ve altında hiçbir şey bulunmayan bir eleman tümüyle kaldırılır; /Pg'si sayfayı adlandıran işaretli-içerik başvuru sözlükleri son kuralın kapsamına girer
ParentTree aynı işleme tabi tutulur; bunun kolayca gözden kaçan bir nedeni var: bir elemana ParentTree'den ulaşılabilir ve başka hiçbir yerden ulaşılamayabilir. O numara ağacı /StructParents tam sayılarını tek bir elemana ya da bir eleman dizisine eşler. Her değer yukarıdaki kurallarla budanır, kaldırılan değerler dizilerinden düşer, dizisi boşalan bir /Nums çifti silinir ve ne /Nums ne de /Kids taşıyan bir düğüm koparılır. Bu adımı atlarsanız yetimler, /Pg üzerinden serbest bırakılmış bir sayfayı işaret etmeye devam eder. Etiket sırasında metin çıkaran biri için bu doğrudan bir sonuçtur: yapı-sıralı metin çıkarma tam olarak bu ağaçları gezinir ve null /Pg'li bir eleman, okuma sırasından düşen bir paragraftır
Kalan sayfalardaki bağlantılar neden yeniden hedeflenmek yerine kaldırılıyor?
Kalan bir sayfada bulunup /Dest dizisi ya da /GoTo eylemi silinen sayfayı hedefleyen bir bağlantı ek açıklaması, o sayfanın /Annots içinden kaldırılır ve etiketi de onunla birlikte gider. /Obj'si ek açıklamayı adlandıran OBJR sözlüğü yapı elemanından çıkarılır; o OBJR elemanın tek alt öğesiyse eleman da kaldırılır ve ParentTree yeniden budanır. OBJR'i geride bırakmak §14.7.4.3'ü bozar; bu madde /Obj'nin gerçek bir nesneye başvurmasını bekler ve bir PDF/UA denetçisi nesne başvurusu hiçbir yere varmayan bir Link elemanı görür
Yer imleri taşınır; bağlantılar taşınmaz. Gövde metninde "sayfa 3'e bak" diyen bir çapraz başvuru, sayfa 3 gidince yanlış olur ve onu sessizce sayfa 4'e çevirmek bozuk bir bağlantıyı yanıltıcı bir bağlantıya dönüştürür. Sonraki bölüme inen bir yer imi gezinmedir; gövde bağlantısı ise içerik hakkında bir önermedir. Kaldırma, iki seçenek arasında daha güvenli olandır. İş akınız bu bağlantıların hayatta kalmasını gerektiriyorsa hedeflerini DeletePage'i çağırmadan önce yeniden yazın; çağrıdan sonra zaten /Annots dizilerinden çıkmış olurlar
Artımlı kayıtlar ve doğrudan-alt-öğe tuzağı
DeletePage, BeginIncrementalUpdate ile SaveIncrementalUpdate arasında da aynı şekilde davranır. Eklenen bölüm, süpürmenin değiştirdiği nesneleri ve kendi nesne numarasına sahip olan kaldırılmış nesneler için serbest girdileri tutar; bunlara sayfa ve silinen bağlantı ek açıklamaları dahildir. Özgün %%EOF'un üstündeki her şey, §7.5.6'nın gerektirdiği gibi bayt bayt kalır
Pdf := THotPDF.Create(nil);
try
Pdf.BeginIncrementalUpdate('tagged-report.pdf');
Pdf.DeletePage(0);
// Doğrudan bir /MCR alt öğesi kaybeden yapı elemanları,
// daha kısa bir /K dizisiyle yeniden yazılmış biçimde eklenir
Pdf.SaveIncrementalUpdate('tagged-report-trimmed.pdf');
finally
Pdf.Free;
end;
Yazılı elle tutulan bir budayıcının yanıldığı nokta tam olarak burasıdır. İşaretli-içerik başvuruları (/Type /MCR) ve nesne başvuruları (/Type /OBJR) tipik olarak üst elemanlarının /K dizisi içinde duran doğrudan sözlüklerdir. Doğrudan bir nesnenin nesne numarası ve xref girdisi yoktur; bu yüzden tek başına serbest bırakılamaz. Bariz hamle, yine de her kaldırılan alt öğesini "serbest bırakmak"tır ve doğrudan bir nesneyi onu tutan dolaylı nesneye eşleyen her yazıcı, sonra tutanı serbest bırakır; tutan da hayatta kalan yapı elemanıdır. O eleman null olarak çıkar ve hiç dokunulmamış sayfalardaki etiketli içerik etiketsiz kalır. HotPDF, kaldırılan bir nesneyi yalnızca nesne numarası varsa serbest listeye gönderir; doğrudan bir alt öğe için içeren elemanı değişmiş işaretler, böylece eleman alt öğesi gitmiş halde yeniden yazılır ve serbest liste temiz kalır
Aynı titizlik, DeletePage'in yüklü bir belgede neye dokunmadığına da karar verir. Silinen sayfanın içerik akışları, XObject'leri ve widget olmayan ek açıklamaları nesne olarak kalır; çünkü yüklü bir dosya herhangi birini kalan bir sayfayla paylaşıyor olabilir ve silme anında aksini kanıtlamanın ucuz bir yolu yoktur. Sayfa ağacı başvurusunu düşürmek doğru bir dosya için yeterlidir. Bu artıkların hâlâ kaç bayta mal olduğunu ölçmek ayrı bir iştir ve onun aracı nesne bağımlılık grafiği ile tutulan-bayt analizidir
DeletePage mi DeleteLoadedPage mi?
Kullanıcının istediği her sayfa kaldırma için DeletePage kullanın. THotPDF.DeleteLoadedPage(PageIndex) hafif seçenektir: aynı iki korumayı uygular, yüklü sayfa listesini kaydırır, /Kids ve /Count'u yeniden yazar, işlenmiş sayfa önbelleğini geçersiz kılar ve OnLoadedDocumentModified'i tetikler. Ad ağacına, ana hatlara, yapı ağacına ya da diğer sayfaların ek açıklamalarına dokunmaz ve sayfa nesnesini silinmiş işaretlemez. ComposeLoadedPagesNUp tam ona uyan iştir: kaynak sayfaları form XObject'leri olarak çizen taze oluşturulmuş yapraklar ekler, sonra her özgünü DeleteLoadedPage(0) ile düşürür; çünkü yapraklar sayfaların içeriğine başvurur, sayfa nesnelerine değil. Etiketli, yer imli, çapraz bağlı bir dosyada "bu sözleşmeden 7. sayfayı kaldır" için DeletePage, SaveLoadedDocument ile kaydediyor ya da SaveIncrementalUpdate ile ekliyor olmanızdan bağımsız olarak her başvuruyu tutarlı bırakan çağrıdır
Her iki yöntem, aralık tabanlı DeletePages ve yukarıda kullanılan artımlı güncelleme yolu, HotPDF Delphi Component'in parçasıdır; bu bileşen, makinede görüntüleyici çalışma zamanı olmaksızın yüklü PDF'leri Delphi ve C++Builder koduyla düzenler