Bir yaprak dizisini yamalayıp tek bir ad ekle, girdi yine de eksik okunabilir. PDFium Component buna TPdfTreeEditor ile yanıt verir; ISO 32000 ad ağaçları ve numara ağaçları için genel bir editör: ağacı sıkı doğrulamayla yükler, girdileri bellek içinde düzenler, doğru /Limits ile dengeli bir düğüm kümesini yeniden kurar ve doğrulanmış bir artımlı revizyon yazar
Bu unit'i motive eden hata, yalnızca bayt okuyarak teşhis edebileceğin türden. Bir araç, bir yaprak /Names dizisine anahtar ve değer ekler, yeni bir revizyon yazar ve her hex dump anahtarın dosyada olduğunu doğrular. Acrobat yine de ek dosyanın var olmadığını söyler. Naif anlamda hiçbir şey bozuk değildir. Ağaç, arama algoritmasının inebileceği bir ağaç olmaktan çıkmıştır sadece
Yamalı bir yaprak dizisi aramayı neden bozar?
Çünkü arama yaprakların taranması değil, /Limits üzerinden ikili bir iniştir. ISO 32000-1 §7.9.6 ad ağacını şöyle tanımlar: /Kids değerleri /Names tutan yapraklara inen bir kök; bu dizi dönüşümlü anahtar ve değer listesidir, anahtarlar tüm ağaç boyunca bayt karşılaştırmasıyla sıralıdır ve kök olmayan her düğüm kendi en küçük ve en büyük anahtarı olarak /Limits bildirir. Uyumlu bir okuyucu istenen anahtarı her kid aralığıyla karşılaştırır ve tam olarak tek bir çocuk açar. Bir yaprağa anahtar ekleyip eski /Limits değerine dokunmazsan iniş, onu gerçekten tutan düğümün yanından dümdüz geçer
Numara ağaçları (§7.9.7) /Nums ve tamsayı anahtarlarla aynı biçime sahiptir ve gerçek trafik taşırlar: §12.4.2'deki /PageLabels bir numara ağacıdır ve §7.7.4'ün katalog /Names kategorileri sana /Dests, /EmbeddedFiles ve /JavaScript değerlerini ad ağacı olarak verir. Bu yüzden dört değişmez bir arada tutmak zorundadır, teker teker değil. Anahtarlar ağaç sırasında kesin artan olmalı, her seviye kendi gerçek anahtar aralığıyla eşleşen limitler yayımlamalı, kardeş aralıklar çakışmamalı ve kök nesne numarası ile generasyon tam olarak oldukları yerde kalmalıdır; çünkü katalog ve diğer her referans veren, dolaylı referansla o kökü gösterir. Delphi'de PDF ekleriyle çalışmak üzerine notumuz aynı yapının tüketici tarafını gösterir
TPdfTreeEditor neyi yüklemeyi reddeder?
Beklediğinden fazlasını ve bilerek. TPdfTreeEditor.TryLoad düğüm grafiğini TPdfSparseDictionaryReader üzerinden yürür, her çocuğu referansın adlandırdığı tam generasyonda ReadDictionary ile çözer; hangi revizyonun aktif olduğunu kabul etmek yerine. İyi biçimli olduğu kanıtlanamayan bir ağaç, tek bir düzenlemeye izin verilmeden önce reddedilir; bu da bozuk bir girdinin makul görünümlü bozuk bir çıktıya dönüşmesini engeller
- Döngüler ve tekrarlanan referanslar, ikisi de
ptviCycleolarak raporlanır; böylece zinciri yukarı doğru gösteren bir/Kidsgirdisi özyinelemek yerine sonlanır - Karışık düğüm biçimleri:
/Kidsile birlikte/Namesya da/Numstaşıyan bir sözlükptviInvalidNodeShapeolarak raporlanır; boş bir kök olmayan yaprak da öyle - Sıralanmamış ya da yinelenen anahtarlar,
ptviUnsortedKeyveptviDuplicateKeyolarak raporlanır; tek bir yaprak içinde değil tüm gezinme boyunca kontrol edilir - Gerçekte mevcut anahtarlarla çelişen limitler,
ptviInvalidLimitsolarak raporlanır - Tam olarak tek bir eksiksiz PDF nesnesi olmayan ham bir değer,
ptviInvalidValueolarak raporlanır - Bütçe aşımları:
ptviDepthLimit,ptviNodeLimit,ptviEntryLimitveptviDeadlineExceeded; hemTPdfTreeEditOptionshem merkezi parser bütçesiyle sınırlanır
TPdfTreeEditOptions.Default bu ağaç-yerel sınırları 1.000.000 girdi, 100.000 düğüm, derinlik 64, yaprak başına 64 girdi ve düğüm başına 32 kid olarak ayarlar. Yaprak ve kid rakamları yalnızca koruma değildir; bunlar ağaç yeniden kurulurken daha sonra kullanılan fan-out değerleridir, bu yüzden kayıt ikisinin de en az iki olmasını doğrular
uses
FPdfCompress;
var
Options: TPdfTreeEditOptions;
RootRef: TPdfTreeObjectReference;
Editor: TPdfTreeEditor;
Report: TPdfTreeValidationReport;
begin
Options := TPdfTreeEditOptions.Default;
RootRef.ObjectNumber := EmbeddedFilesRootObject; { katalog /Names /EmbeddedFiles }
RootRef.Generation := EmbeddedFilesRootGeneration;
if not TPdfTreeEditor.TryLoad(Source, ptkName, RootRef, Options,
Editor, Report) then
begin
Writeln('tree rejected: ', Report.ErrorMessage);
Exit;
end;
try
{ değer, tek bir eksiksiz PDF nesnesinin ham baytlarıdır }
Editor.UpsertName('invoice-2026-09',
'<< /Type /Filespec /F (invoice.pdf) /EF << /F 42 0 R >> >>');
Editor.RemoveName('draft-2025-11');
finally
{ Editor.Free, aşağıdaki kayıttan sonra }
end;
end;
Girdi değerleri ham PDF nesne baytları olarak kalır
Bir girdi değeri olduğu gibi saklanır ve bu, editörü senin üretmediğin dosyalara doğrultmayı güvenli kılan tek tasarım kararıdır. TPdfTreeEntry, NameKey, NumberKey ve RawValue tutar; RawValue tek bir eksiksiz PDF nesnesinin ham bayt dizisidir: bir sözlük, bir dizi, bir string, bir stream referansı ya da çıplak bir dolaylı referans. Hiçbir şey bir Delphi variant'ına, TStrings ya da türlenmiş bir kayda çözülmez. Kazanç, unit'in hiçbir şey bilmediği değerlerin kayıpsız round-trip edilmesidir. Satıcı extension'ı taşıyan bir /Filespec, alışılmadık bir fit türü olan bir hedef dizisi, özel anahtarlı bir /JavaScript eylem sözlüğü — hepsi yeniden kurulumdan bayt bayt sağ çıkar, çünkü editörün içerikleri hakkında hiçbir zaman bir fikri olmadı. Ayrıca değişiklik API'sini kanıtlanabilir bir şeye küçültür: UpsertName ve UpsertNumber yalnızca verilen değerin tam olarak tek bir eksiksiz nesne olarak ayrıştığını doğrulamak zorundadır; RemoveName, RemoveNumber ve Clear yalnızca girdi listesine dokunur
Kök yeniden numaralandırılmadan dengeli bir ağaç nasıl yeniden kurulur?
Tek bir sabit noktayla iki geçişte. BuildBalancedObjects önce sıralı girdi listesini en fazla MaxLeafEntries içeren yapraklara böler, sonra her seviyeyi en fazla MaxKidsPerNode içeren ebeveynlere katlar ve tek bir düğüm kalana kadar tekrarlar. Limitler aşağıdan yukarı hesaplanır; bir yaprak ilk ve son anahtarını alır, bir iç düğüm ilk çocuğunun alt limitini ve son çocuğunun üst limitini alır, böylece çakışan kardeş aralıkları yapım gereği oluşamaz
Sabit nokta kimliktir. Nihai kök her zaman ağacın yüklendiği nesne numarasını ve generasyonu alır; böylece katalog referansı geçerli kalır ve belgedeki başka hiçbir şeyin yeniden yazılması gerekmez. Diğer her düğüm, yalnızca düğüm türü eşleştiğinde ve tam anahtar aralığı eşleştiğinde önceden yüklenmiş bir düğümün referansını yeniden kullanır; aksi halde aktif maksimum nesne numarası artı birden tahsis edilen taze bir numara alır. Bu kombinasyon değişmemiş alt ağaçların mevcut nesnelerini göstermesini sağlarken, içeriği taşınan herhangi bir düğümün sessizce yeniden yorumlanmak yerine yeni bir nesne olarak yazılmasını garanti eder. Aynı referans-yeniden kullanım disiplini, kardeş unit içindeki outline editörünü ve sayfa yeniden eşlemesini de yönetir
var
Serialized: TPdfTreeSerializedObjects;
I: Integer;
begin
{ FirstNewObjectNumber kaynak revizyonundan gelir }
if Editor.BuildBalancedObjects(MaximumObjectNumber + 1,
Serialized, Report) then
begin
Writeln('nodes ', Report.NodeCount, ' depth ', Report.MaximumDepth);
for I := 0 to High(Serialized) do
Writeln(Serialized[I].Reference.ObjectNumber, ' ',
Serialized[I].Reference.Generation, ' obj');
end
else
for I := 0 to High(Report.Issues) do
Writeln('issue ', Ord(Report.Issues[I].Kind), ' at obj ',
Report.Issues[I].ObjectNumber, ': ', Report.Issues[I].Message);
end;
Revizyonu yaz, sonra sonucu geri oku
Bir kayıt, çıktı yeniden açılıp karşılaştırılmadan tamamlanmış sayılmaz. SaveIncremental kaynak revizyon bilgilerini okur, belge kataloğunun kendisi olan bir kökü reddeder, dengeli düğüm kümesini artı katalog nesnesini serileştirir ve her şeyi AppendPdfSparseUpdate'a devreder; bu da §7.5.6'ya göre yeni bir revizyon eklerken mevcut /Info referansını ve belge /ID dizisini korur
Sonra kendi yazarlarına kopyalamaya değer kısım gelir. Editör TPdfTreeEditor.TryLoad'ı bu kez hedef stream'e karşı, aynı kök referansı ve aynı seçeneklerle yeniden çağırır ve yeniden yüklenen girdi sayısını, her anahtarı ve RawValue değerini yazmayı amaçladığı şeyle karşılaştırır. Yeniden yükleme başarısız olursa ya da herhangi bir karşılaştırma farklıysa, hedef stream geri verilmek yerine sıfırlanır; böylece kısmen yazılmış bir revizyon asla başarı olarak elden çıkarılmaz. Doğrulama, çıktıyı sparse lazy PDF nesne indeksi üzerine yazımızda anlatılan aynı sınırlı sparse yol üzerinden okur; büyük dosyalarda kontrolü karşılanabilir tutan da budur
var
Dest: TFileStream;
begin
Dest := TFileStream.Create(OutputName, fmCreate);
try
if Editor.SaveIncremental(Source, Dest, Report) then
Writeln('wrote ', Report.NodeCount, ' nodes, ',
Report.EntryCount, ' entries')
else
{ hedef zaten sıfırlandı, yarı yazılmış hiçbir şey tutulmadı }
Writeln('save refused: ', Report.ErrorMessage);
finally
Dest.Free;
Editor.Free;
end;
end;
Şifreli kaynaklar reddedilir ve bu dürüstlüktür
Güncel uygulama şifreli bir kaynağı doğrudan reddeder. Neden savunmacı değil spesifiktir: bir ağaç yeniden kurulumu yeni string nesneleri getirir ve yeni stream referansları getirebilir; §7.6 bunların belge anahtarı ve sahip nesne numarası ile generasyonuyla nesne başına şifrelenmesini gerektirir. /Encrypt trailer'ını kopyalayıp düz metin stringler eklemek, açılan ve yeni anahtarlarının tam olduğu yerde çöp olarak okunan bir dosya üretir; açık bir reddedişten daha kötüdür. Açıkça söylemeye değer iki sınır daha var. Editör kaynak stream'in bir anlık görüntüsü üzerinde çalışır, bu yüzden editör onun üzerinde canlıyken kaynak değiştirilmemelidir; ayrıca belge kataloğunu bir ağaç kökü olarak ele almayı reddeder, çünkü katalog ağaç düğümünün kendisi değil, içinde ağaçlar bulunan bir sözlüktür. /EmbeddedFiles, /Dests, /JavaScript ya da /PageLabels yazan Delphi, C++Builder ya da Lazarus kodu bakıyorsan ve yaprak dizilerini elle yamalıyorsan, ağaç editörü ve doğrulanmış artımlı kaydı PDFiumPas Delphi PDFium component ile birlikte gelir