PDFium Component, tekrarlanabilir PDF çıktısını tek bir çağrıya indirger: TPdf.SaveAsReproducible, aynı içeriğin iki kez kaydedilmesinin bayt özdeş dosyalar vermesi için belgeyi yeniden yazar. Belge bilgi sözlüğünden /CreationDate ile /ModDatei kaldırır, trailer /IDsi yazmaz, nesne sırasını stabil tutar ve neyi değiştirdiğini bir TPdfReproducibleReport içinde bildirir. Şifreli dosyalar reddedilir ve imzalı dosyalar açık onay ister
İhtiyaç genellikle bir derleme hattında ortaya çıkar. Biri üretilen PDF'leri bir depoya ya da bir golden-file test setine işler ve hiçbir sayfada hiçbir şey değişmemişken her çalıştırma bir diff üretir. Ya da bir arşiv SHA-256 ile tekilleştirir ve aynı faturanın elli kopyasını sessizce saklar; çünkü her kayıt dosyaya yeni bir zaman damgalamıştır. Duvar saatini gömen bir PDF yazıcısı, masaüstü uygulaması için iyidir ve baytları kimlik sayan her şeye düşmandır
Aynı PDF'yi iki kez kaydetmek neden farklı baytlar üretir?
Değişmemiş bir belgenin iki kaydı tam olarak iki tür veri yüzünden farklıdır: belge bilgi sözlüğündeki zaman damgaları ve trailer'daki dosya tanımlayıcısı. Dikkatli bir yazıcının ürettiği geri kalan her şey — nesne numaralandırması, nesne sırası, cross-reference düzeni — deterministik yapılabilir ve PDFium Component'in eksiksiz yeniden yazım yolu bunu zaten yapar: nesneler artan nesne numarasıyla yazılır ve xref tablosu aynı sırayı izler
Zaman damgaları bariz şüphelidir. ISO 32000-1 §14.3.3, /Info sözlüğünde /CreationDate ile /ModDatei tanımlar ve yerel PDFium kaydı, belgeyi serileştirdiği her seferde taze bir /ModDate yazar. Dosya tanımlayıcısı ince olandır. ISO 32000-1 §14.4, /IDyi bayt dizilerinden oluşan iki elemanlı bir dizi olarak betimler; ilk eleman özgün dosyayı tanımlar, ikincisi dosya revize edildikçe değişir. Yazıcılar o dizileri tipik olarak dosyayla ilgili bir şeyi hash'leyerek türetir ve o bir şey zaman damgalı baytları içeriyorsa tanımlayıcı, belirsizliği miras alır
Delphi'de TPdf.SaveAsReproducible kullanmak
TPdf.SaveAsReproduciblein iki overload'u vardır: biri bir TStreame, biri bir dosya adına yazar; ve her ikisi de out Report: TPdfReproducibleReport ile varsayılanı False olan isteğe bağlı bir AllowSignedDocument parametresiyle Boolean döndürür. Dönüş değeri Report.Succeededı yansıtır; False olduğunda Report.ErrorMessage nedenini söyler. Dosya overload'u hedefin yanındaki bir geçici dosyaya yazar ve hedefi yalnızca başarılı bir kayıttan sonra değiştirir; böylece başarısız bir çalıştırma, önceki derleme çıktınızın durduğu yere yarım yazılmış bir PDF bırakmaz
var
Pdf: TPdf;
Report: TPdfReproducibleReport;
begin
Pdf:= TPdf.Create(nil);
try
Pdf.FileName:= 'invoice-2026-10.pdf';
Pdf.Active:= True;
if Pdf.SaveAsReproducible('build\invoice-2026-10.pdf', Report) then
Writeln(Format('%d -> %d bytes, %d objects kept, %d pruned, ' +
'%d timestamps stripped', [Report.SourceByteCount,
Report.OutputByteCount, Report.KeptObjectCount,
Report.PrunedObjectCount, Report.StrippedTimestampCount]))
else
Writeln('Reproducible save failed: ', Report.ErrorMessage);
finally
Pdf.Free;
end;
end;
Rapor alanları CI'da günlüğe yazılmaya değerdir. StrippedTimestampCount, /Infosu her iki tarihi taşıyan bir belge için normalde 2, hiç /Info sözlüğü olmayan bir belge için 0'dır. PrunedObjectCount, yerel kayıtta var olan ama catalog'tan ya da /Info sözlüğünden ulaşılabilir olmayan nesneleri sayar; tekrarlanabilir yeniden yazım onları atar, bu yüzden çıktının sıradan bir kayıtla aynı boyutta ya da daha küçük olmasını bekleyin
Zaman damgalarını silmek neden yetmedi
İlk uygulama iki zaman damgasını da kaldırdı ve yine de iki farklı dosya üretti. Sayfalar eşleşiyordu, /Info sözlüğü eşleşiyordu ve trailer'lar /IDnin ikinci elemanında ayrışıyordu. Kök neden sıralamaydı: yeniden yazım önce PDFium'dan yerel bir kayıt ister, sonra o baytları son işler ve trailer kurucusu /IDyi yerel çıktıyı hash'leyerek hesaplıyordu. O yerel çıktı hâlâ taze /ModDatei içeriyordu; böylece hash, dolayısıyla tanımlayıcı, zaman damgasının kendisi son dosyaya hiç ulaşmazken her çalıştırmada değiştiyordu
Bu, tekrarlanabilir derlemeler için genel bir kuralın güzel bir örneğidir: belirsiz bir değeri kaldırmak, ondan önce bir şey türetilmişse yeterli değildir. Düzeltme, /IDyi tamamen taşmayı bırakmaktı. TPdf.SaveAsReproducible, yalnızca /Size, /Root ve kaynağında varsa /Info içeren bir trailer yazar. Trailer'da zamana bağlı hiçbir şey kalmadığında aynı belgenin iki ardışık kaydı bayt baytına eşit karşılaşır; davranış tam olarak böyle doğrulanır
function SavesAreIdentical(Pdf: TPdf): Boolean;
var
A, B: TMemoryStream;
RA, RB: TPdfReproducibleReport;
begin
A:= TMemoryStream.Create;
B:= TMemoryStream.Create;
try
Result:= Pdf.SaveAsReproducible(A, RA) and
Pdf.SaveAsReproducible(B, RB) and
(A.Size = B.Size) and CompareMem(A.Memory, B.Memory, A.Size);
finally
B.Free;
A.Free;
end;
end;
Metinleri bozmadan sözlük anahtarlarını kaldırmak
Zaman damgaları, /Info nesne gövdesinin token bilinciyle taranmasıyla kaldırılır, metin aramasıyla değil. /ModDate için naif bir bayt değişimi, tesadüfen o metni içeren bir /Subject ya da /Keywords metnine de çarpar ve literal bir metinden bayt kesmek, yüksek sesle başarısız olmak yerine meta veriyi sessizce bozar. PDFium Component önce gövdenin bir tarama kopyasında literal metinleri ve yorumları nötralize eder; sonra yalnızca gerçek bir name token'inin başladığı yerde bir eşleşme kabul eder, o anahtarın değerinin nerede bittiğini bulmak için PDF nesne dil bilgisini kullanır ve bütün anahtar-değer çiftini atlar
Pratik sonuç, /Infodaki diğer her girişin — title, author, producer, özel anahtarlar — dokunulmadan hayatta kaldığıdır. Yalnızca tarih olarak tanımlanan iki anahtar kaldırılır ve o da yalnızca trailer'ın /Info girişinin gerçekte işaret ettiği sözlükten
/ID atlamak şifreli ve imzalı PDF'lere neye mal olur?
/IDyi atlamak düz PDF 1.x çıktısı için güvenlidir ama şifrelemeyi eler ve imzalara olanları değiştirir. ISO 32000-1'de trailer /IDsi, bir /Encrypt girişi bulunmadıkça isteğe bağlıdır; çünkü standart security handler, ilk tanımlayıcı dizisini şifreleme anahtarına karıştırır. /IDsiz bir dosya doğru şekilde şifrelenmiş bir dosya olamaz; bu yüzden TPdf.SaveAsReproducible denemez: trailer /Encrypt taşıdığında False döndürür ve Report.ErrorMessage, belgenin şifreli olduğunu söyler. Hattınız izin veriyorsa önce şifreyi çözün ya da şifreli çıktıları bayt-kimliği denetimlerinin dışında tutun. Hedefiniz PDF 2.0 ise ISO 32000-2'nin kuralı sıkılaştırdığına ve her dosyada /ID gerektirdiğine dikkat; tekrarlanabilir çıktı en doğal haliyle PDF 1.x hedeflerine uyar. Delphi'de kesin PDF sürüm uygunluğu yazısı, bileşenin sürüm hedeflerini nasıl hallettiğini anlatır
İmzalı belgeler ayrı bir sorundur. TPdf.SaveAsReproducible, eksiksiz ve artımsız bir yeniden yazımdır; bayt kaymaları oynar ve mevcut imzaların /ByteRangei, imzalanan baytları artık kapsamaz. İmzalı bir dosyanın tekrarlanabilir çıktısı bu yüzden, stabil baytlar karşılığında mevcut imzaların geçerliliğinden bilinçli olarak vazgeçmek demektir; yöntemin SignatureCount sıfırdan büyükken EPdfError yükseltmesinin nedeni budur — AllowSignedDocument = True geçirmediğiniz sürece
if Pdf.SignatureCount > 0 then
// Açık opt-in: yeniden yazım bayt kaymalarını oynatır; mevcut
// imzaların sonrasında doğrulamayı geçememesi beklenebilir
Ok:= Pdf.SaveAsReproducible('normalized.pdf', Report, True)
else
Ok:= Pdf.SaveAsReproducible('normalized.pdf', Report);
İmzalı özgün kayıt hakikatin kaydıysa onu olduğu gibi tutun ve tekrarlanabilir çıktıyı yalnızca türetilmiş kopyalar için kullanın; CI'da diff aldığınız normalize edilmiş bir sunum gibi. Korunan bir dosyaya, orada duranı rahatsız etmeden ek yapmak için şifreli bir PDF'nin Delphi'de neden sessizce yamalanamadığı yazısına bakın
Bayt kimliğine yaslanmadan önce bilinmesi gereken sınırlar
Bayt kimliği, aynı bileşen derlemesi tarafından kaydedilen aynı belge için geçerlidir ve tam olarak metodun dokunduğu şeyi kapsar. TPdf.SaveAsReproducible yalnızca iki /Info tarihini siler; kendi xmp:CreateDate ya da xmp:ModifyDateine sahip bir XMP meta veri stream'i, belgenin çoktan taşıdığı baytlar neyse o olarak taşınır. Bu, kayıtlar boyunca stabildir; ama XMP ile /Info tarihlerinin artık aynı şeyi betimlemediği anlamına gelir ve ikisinin uyuşmasını isteyen PDF/A profilleri için önemlidir. PDF/A Info ve XMP meta veri eşdeğerliği yazısı o kuralları açıklar. Farklı bir PDFium binary'si ya da bileşen sürümü nesneleri farklı serileştirebilir; makinalar arasında hash karşılaştıran her hattada ikisini de sabitleyin
O sınırlar içinde metot, CI diff'lerinin, golden-file testlerinin ve içerik adresli arşivlerin istediğini verir: aynı girdi, aynı baytlar. TPdf.SaveAsReproducible ile TPdfReproducibleReport, kayıt, optimizasyon ve uyumluluk API'sinin gerisiyle birlikte Delphi ve C++Builder için PDFium Component ile gelir
FAQ
TPdf.SaveAsReproducible sayfa içeriğini değiştirir mi?
Hayır. /Info sözlüğünden /CreationDate ile /ModDate anahtarlarını kaldırır, trailer /IDsi yazmaz ve catalog'tan ya da /Infodan ulaşılamayan nesneleri atar; sayfa içeriği değiştirilmeden yazılır
Şifreli bir PDF'den tekrarlanabilir çıktı üretebilir miyim?
Hayır. Şifreleme, tekrarlanabilir çıktının atladığı trailer /IDsine dayanır; bu yüzden metot, şifreli kaynaklar için açıklayıcı bir ErrorMessage ile False döndürür
İmzalı bir PDF neden AllowSignedDocument ister?
Tekrarlanabilir kayıt, bayt kaymalarını oynatan eksiksiz bir yeniden yazımdır; mevcut imzalar imzalı bayt aralıklarıyla eşleşmeyi bırakır. AllowSignedDocument = True geçmek o takası kabul ettiğinizi teyit ettirir