Bir arşiv alım kapısı, masadaki her görüntüleyicide sorunsuz açılan bir grup "PDF/A-2b" dosyasını reddetti. Tedarikçi dosyaların uyumlu olduğuna yemin ediyordu. Değillerdi: her birinin catalog içinde gömülü bir JavaScript action'ı vardı; çıplak gözün kolay kolay yakalayamayacağı ve veraPDF gibi tam bir PDF/A doğrulayıcısının anında işaretleyeceği türden bir şey. Sorun şuydu: kimse dosya başına tek bir evet-hayır sorusuna cevap vermek için Delphi batch servisine Java araç zinciri bağlamak istemiyordu. PDFium Component içindeki ValidatePdfACompliance işte bu boşluğu doldurur ve bir içerik akışını asla tam ayrıştırmadan sonuca nasıl vardığını anlamaya değer
PDFium'un kendisi bunu neden cevaplayamaz
İlk dürüst nokta şu: paketle gelen pdfium.dll içinde hiç PDF/A yeteneği yoktur. Genel yüzeyde ConvertToPDFA yoktur, OutputIntent yazıcısı yoktur, XMP API'si yoktur. Bu kütüphanedeki PDF/A ile ilgili her şey — hem yazma tarafı hem denetleme tarafı — FPdfPdfa.pas içinde saf Pascal olarak yaşar ve bayt düzeyinde ayrıştırma ile incremental update üzerinden çalışır. Yani doğrulayıcıyı çağırdığınızda Chromium'un renderer'ına soru sormuyorsunuz. Dosyanın yapısal baytları üzerinde çalışan bir Pascal token tarayıcısı çalıştırıyorsunuz
Açık API bilinçli biçimde küçüktür. Bir fonksiyon akışı 0 konumundan okur ve bir kayıt döndürür:
function ValidatePdfACompliance(Source: TStream): TPdfAValidationResult;
type
TPdfAValidationResult = record
Conformance: TPdfAConformance; // pacUnknown, pacNone, pac1b, pac2u, ...
Issues: TPdfAValidationIssues; // a set of TPdfAValidationIssue
function IsCompliant: Boolean; // True only when level <> unknown/none
end; // AND Issues is empty
IsCompliant, kapıda gerekli olan kuralı kodlar: dosya, yalnızca gerçek bir uyumluluk seviyesi algılandığında ve issue kümesi boş olduğunda geçer. Ayrıştırma başarılı olsa ama pdfaid işareti bulunmasa sonuç pacNone olur ve bu açıkça geçer sayılmaz. batch preflight report CLI dışarıdan tam da bunu vurgular: tanınmayan bir dosyada bulgu listesinin boş olması, temiz rapor anlamına gelmez
Her token taramasından önce stream gövdelerini temizlemek
İşte en önemli uygulama ayrıntısı ve kendi tarayıcınızı yazarsanız en kolay yanlış yapacağınız nokta. Denetleyici ihlalleri, /JavaScript, /LZWDecode, /BM gibi sınırlandırılmış ad token'larını arayarak bulur. Ham dosya baytlarını tararsanız gömülü ikili stream gövdeleri — sıkıştırılmış görseller, ICC profilleri, font programları — rastgele bu token'lara benzeyen bayt dizileri içerir. Üç bayt JPEG içinde buna benzedi diye /AA ya da /3D bulundu diye rapor üretirsiniz. Bu, yanlış pozitif fabrikasıdır
Çözüm PdfStructureBytes yordamıdır: dosyayı dolaşır ve her stream ile endstream anahtar sözcüğü arasındaki baytları boşluklara çevirir, sözlük yapısını olduğu gibi bırakır. Taramaya ancak bundan sonra başlanır. Doğrulayıcıdaki her ad-token denetimi bu temizlenmiş kopya üzerinde çalışır. Bu makaleden yalnızca tek bir fikir alacaksanız, onu alın. Aynı disiplin PDF/UA doğrulayıcısında da yansıtılmıştır; iki standardın evrimi bağımsız olduğu için yordamın kendi kopyasını tutar
29 issue ve her birinin anlamı
TPdfAValidationIssue, belgelenmiş bir sözleşmedir. Sıra numaraları dondurulmuştur; çünkü DUnitX testleri, demolar ve rapor katmanı bunlara bağlıdır. Bu yüzden yeni bulgular yalnızca listenin sonuna eklenir. v1.63.0 itibarıyla 29 üyesi vardır. Bunlar birkaç aileye ayrılır:
- Metadata ve kimlik:
pvaiMissingXmpMetadata,pvaiMissingPdfAIdentifier,pvaiMissingTrailerId(ISO 19005-1 6.1.3),pvaiMissingXmpDates - Renk ve çıktı:
pvaiMissingOutputIntent,pvaiMissingIccProfileve hem DeviceRGB hem DeviceCMYK göründüğündepvaiMixedDeviceColorSpaces(6.2.3.3) - Her bölüm için katı yasaklar:
pvaiEncryptionPresent(bir/Encryptsözlüğü açıkça yasaktır),pvaiJavaScriptPresent,pvaiForbiddenAction,pvaiAdditionalActions,pvaiLzwUsed,pvaiXfaPresent,pvaiNeedAppearancesTrue,pvaiForbiddenAnnotation - Fontlar:
pvaiFontNotEmbeddedve daha katı olanpvaiUnembeddedFontile birlikte,pvaiUnicodeMappingMissingolmadan Level U iddiası için/ToUnicode - Etiketleme: A düzeyinde bir uygunluk iddiası etiketli yapı taşımıyorsa
pvaiLevelAStructureMissing
24 ile 29. sıralara eklenen en yeni altı üye, incelemecilerin gerçekten tökezlediği ince durumları kapsar: pvaiTrappedTrue (Info dictionary içinde bir /Trapped /True; değer False veya Unknown olmak zorunda olduğu için bu bir "false friend" durumudur), pvaiForbiddenActionSubtype (yalnızca anotasyon değil, bir action olarak Sound veya Movie kullanımı), pvaiTransparentColorSpace (Normal dışı blend mode ya da /CA//ca değeri 1.0 olmayan durumlar), pvaiAnnotationDictViolation, pvaiUnembeddedFont ve pvaiMixedDeviceColorSpaces
Bölüm farkındalıklı engelleme: A-1 katıdır, A-2 ve A-3 gevşer
PDF/A tek bir kural kitabı değildir. PDF/A-1'in yasakladığı üç şey, PDF/A-2'den itibaren açıkça izinlidir: saydamlık (bir /Transparency group ya da etkin bir /SMask, 6.4), optional content (/OCProperties, 6.1.13) ve gömülü dosyalar (/EmbeddedFiles ya da /EF, 6.1.11). Bunların üçünü de her dosya için işaretleyen saf bir doğrulayıcı, tamamen geçerli PDF/A-2 belgelerini topluca reddeder
Bu yüzden doğrulayıcı, bölüm numarasını pdfaid işaretinden PdfAPartOf aracılığıyla okur ve bu denetimleri PartNo = 1 arkasına koyar. Yeni saydamlık issue'ları için blend-mode ve annotation-alpha denetimleri de yalnızca part 1 için geçerlidir:
if PartNo = 1 then
begin
if PdfHasName(Struct, '/BM') then
if not PdfHasBMNormal(Struct) then // only /Normal or /Compatible allowed
Include(Result.Issues, pvaiTransparentColorSpace);
if PdfHasCaNotOne(Struct, '/CA') or PdfHasCaNotOne(Struct, '/ca') then
Include(Result.Issues, pvaiTransparentColorSpace);
end;
Bir muhafazakâr varsayılan ayrıca anılmayı hak ediyor: hiç pdfaid işareti yoksa bölüm 1, yani en katı bölüm kabul edilir. Gerekçe şudur: kimliği belirlenemeyen bir dosya gevşekçe geçirilmemeli, en sıkı kurallara göre tutulmalıdır. JavaScript, yasaklı action'lar, LZW, XFA, NeedAppearances, yasaklı annotation'lar ve gömülmemiş fontlar bütün bölümlerde yasak kaldığı için bu denetimler asla kapının arkasına alınmaz
Hiçbir şey saklanmasın diye object stream'leri açma
PDF 1.5, cross-reference stream ile object stream'i (/Type /ObjStm) getirdi ve bunlar saf bir bayt tarayıcısı için kör nokta oluşturur. Catalog, OutputIntent, action dictionary, akış olmayan her şey ObjStm içinde Flate ile sıkıştırılmış olabilir. Ham yapıyı tararsınız, bunların hiçbirini görmezsiniz ve aslında hiç temiz olmayan bir dosyayı temiz diye rapor edersiniz
PdfExpandObjectStreams, bu boşluğu kapatır. Herhangi bir denetim çalışmadan önce doğrulayıcı Data := PdfExpandObjectStreams(Data) yapar. Bu yordam bütün ObjStm nesnelerini bulur, içerilen nesne numaraları ile ofsetleri almak için /N ve /First başlıklarını okur, gövdeyi PdfInflate ile açar (RTL zlib; Delphi'de System.ZLib, FPC'de zstream) ve içerilen her nesneyi baytların bir kopyasının sonuna sıradan N 0 obj ... endobj olarak ekler. Var olan token denetimleri, kendi mantıklarında hiçbir değişiklik olmadan bu nesneleri artık bulabilir
Bunu kırılgan değil temiz yapan iki kısıt vardır. Stream nesneleri — Metadata, ICC profile ve font programları — object stream içinde yaşayamaz; yalnızca stream olmayan sözlükler yaşayabilir. Bu yüzden genişletme yalnızca sözlüklerle uğraşır ve eklenen nesneler, gövde temizleme geçişini bozacak bir stream anahtar sözcüğü taşımaz. Ayrıca eklenen içerik %%EOF sonrasına indiği için, startxref içinden yapılan ters arama hâlâ özgün trailer'ı bulur. Cross-reference stream trailer'ı zaten daha önce, v1.49.3 içinde, Root, Size ve ID değerlerini düz metin xref-stream sözlüğünden okuyarak ele alınmıştı; bu konu nesne ve cross-reference stream doğrulaması yazısında incelenir. Object-stream çalışmasının yalnızca inflate adımını eklemesi yeterli oldu; type-2 xref girdilerini çözmeye ya da PNG predictor'ı geri sarmaya gerek kalmadı
Bayt düzeyindeki denetleyicinin dürüst sınırları
Bu bir preflight aracıdır, sertifikalı doğrulayıcı değil ve sınırlar gerçektir. Font gömme denetimi bir sayım sezgisidir ve doğru hale gelmesi, bilinmeye değer bir düzeltme gerektirdi. İlk denetim PdfCountName('/FontDescriptor') kullanıyordu, ancak her font iki /FontDescriptor token'ı katkısı yapar: biri font dictionary'sindeki başvuru, diğeri descriptor nesnesi içindeki /Type değeri. Yani sayım, N gömülü program karşısında 2N oluyordu ve test her zaman true çıkıyordu. Düzeltme PdfCountDescriptorRefs yordamıdır; bu yordam yalnızca /FontDescriptor N G R başvuru biçimini sayar, font başına bir tane, ve yalnızca gömülü programlar gerçekten daha azsa pvaiUnembeddedFont yükseltir:
K := PdfCountDescriptorRefs(Struct); // one per font dict
Emb := PdfCountName(Struct, '/FontFile')
+ PdfCountName(Struct, '/FontFile2')
+ PdfCountName(Struct, '/FontFile3');
if (K > 0) and (Emb < K) then
Include(Result.Issues, pvaiUnembeddedFont);
Bu düzeltilmiş haliyle bile kabadır: her descriptor içinde bir tür FontFile bulunan karma belge, bireysel olarak uyumsuz bir fontun yine de gözden kaçmasına izin verebilir. Object stream genişletmenin bilinen bir yan etkisi de vardır; AcroForm /DR tarafından taşınan /Helv gibi standard-14 varsayılan kaynakları görünür hale getirir ve sezgi bunları, gerçekte render için hiç kullanılmadıkları halde veraPDF izin verdiği halde, gömülü değil diye dürüstçe rapor eder. İçerik akışı operatör düzeyi denetimler (6.2.10) ise bütünüyle kapsam dışıdır; çünkü bunlar bayt taraması değil tam içerik ayrıştırması gerektirir. Doğrulayıcıyı, işaret eklemenin düzeltemeyeceği ihlalleri yakalayan hızlı ve bağımlılıksız ilk kapı olarak görün; nihai sertifikasyon için tam doğrulayıcıyı ayırın
Bu, hikâyenin denetleme yarısıdır. SaveAsPdfA tarafında XMP'yi, OutputIntent'i ve sRGB ICC profilini enjekte eden ve etiketli yapı taşımayan bir Level A isteğini dürüstçe düşüren tamamlayıcı yazma tarafı, aynı bayt düzeyi makine üzerine kuruludur. Her iki yarı da, harici çalışma zamanı kurulumuna ihtiyaç duymayan saf Pascal PDF/A uygulaması üzerine kurulu tek bir VCL paketi olan Delphi için PDFium Component içinde birlikte sunulur