Bir glif genişliğinin gömülü font programıyla uyuşmadığını söyleyen bir veraPDF raporu, hangi glif olduğunu ya da nedenini neredeyse hiç anlatmaz. PDFlibPas bu soruyu, her karakter kodunu gömülü cmap üzerinden bir glif indeksine çözümleyerek, program metriğini em başına 1000 birime normalleştirerek ve karşılaştırmayı orada yaparak cevaplar
Glif genişlikleri neden uyuşmaz?
Çünkü karşılaştırılan iki sayı farklı koordinat sistemlerinde yaşar ve PDF sözlüğü size dönüşümü söyleyen hiçbir şey içermez. Bir font sözlüğü /Widths'i glif uzayında yazar; PDF bunu emin binde birine sabitler (ISO 32000-1 §9.2.4). Gömülü TrueType programının içindeki hmtx tablosu ilerlemeleri (advance) font tasarım birimlerinde yazar ve head tablosu bunların kaçının bir em ettiğine karar verir: TrueType yüzlerin çoğu için 2048, CFF türevliler için 1000, arada başka bir şey de olabilir. Ham değerleri karşılaştırın ve külliyatınızdaki her 2048-upem font bozuk görünür. ISO 14289-1 §7.21.5, sözlük alanlarını okuyarak genişlik denetimi yapmaya çalışan herkes için tam olarak bu tuzağı kurar
PDFlibPas yüklemede normalleştirir. TPDFTrueTypeParser, genişlik dizisine Advance * 1000 div unitsPerEm değerini koyar; böylece Parser.GetWidth(GID) zaten PDF'in kullandığı emin binde birleri cinsinden cevap verir ve tasarım birimlerine ihtiyaç duyduğunuzda GetRawWidth ulaşılabilir kalır. Buna rağmen zor olan yarım hâlâ ortadadır: bir karakter kodundan glif indeksine ulaşmak. Basit bir TrueType font için yol, FontDescriptor içindeki Symbolic bayrağına, /Flags'in 3. bitine bağlıdır
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Sembolik yüzler doğrudan program cmap'i üzerinden adreslenir,
// geri dönüş olarak (3,0) yüksek bayt kuralıyla
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Sembolik olmayan: kod -> glif adı, kodlama üzerinden; ad -> Unicode
// Adobe Glyph List üzerinden; Unicode -> GID, program cmap'i üzerinden
UnicodeValue := GetGlyphUnicode(EncodingNames[Code and $FF]);
if UnicodeValue = 0 then
Continue;
GID := Parser.GetGlyphIndex(UnicodeValue);
end;
if (GID > 0) and (GID < Parser.GlyphCount) then
if Abs(PDFWidth - Parser.GetWidth(GID)) > 1 then
Inc(MismatchCount);
finally
Parser.Free;
end;
O parçadaki iki ayrıntı ağırlık taşır. Hoşgörü payı sıfır değil bir birimdir; çünkü normalleştirme tam sayı bölmesidir ve meşru üretilmiş bir dosya bir birim sapabilir — diagnostic 10036'nın raporladığı "emin binde biri içinde" ifadesi tam olarak budur. Ve GID < Parser.GlyphCount koruması süs değildir. GetWidth, render çağırıcıları için bağışlayıcı yazılmıştır: aralık dışı indeksi hmtx'teki son girdiye kıstırır ve tablo yoksa 750'ye geri düşer. Bağışlayıcılık render için doğrudur, denetim için yanlış; bu yüzden denetim, kıstırmaya güvenmek yerine genişlik sormadan önce indeksi reddeder
CIDFontType2 bir dolaylı katman daha ekler
PDFlibPas bileşik fontları aynı şekilde gezdirir; CID ile glif arasına /CIDToGIDMap girer. Genişlikler /W dizisinde gelir; ISO 32000-1 §9.7.4.3 bu diziye tek dizide serbestçe dönüşümlü iki biçim verir: bir başlangıç CID'i ve ardından ardışık genişlikler dizisi, ya da bir ilk CID, bir son CID ve aralığa uygulanan tek bir genişlik. Denetim ikisini de ayrıştırır, ortaya çıkan her çifti aynı karşılaştırmaya verir ve toplamı diagnostic 10037 altında raporlar. Bileşik fontların ayrıştığı yer eşleme adımıdır ve eksik harita diagnostic 10021'i, hiçbir genişlik okumadan önce önemli kılan da budur — yok ya da bozuk bir /CIDToGIDMap yalnızca §7.21.3.2'yi ihlal etmekle kalmaz, genişlik sorusunu cevapsız bırakır
// /CIDToGIDMap ya /Identity adıdır ya da big-endian 16 bitlik
// glif indeksleri akışıdır, CID başına bir tane (ISO 32000-1 bölüm 9.7.4.2)
Obj := DerefIndRef(FDoc, CIDFont.FindValueByKeyName('CIDToGIDMap'));
if (Obj is TPDFName) and (TPDFName(Obj).Name = 'Identity') then
begin
GID := CID;
Result := True;
end
else if Obj is TPDFStream then
begin
Data := TPDFStream(Obj).GetDecodedStream;
P := CID * 2 + 1; // Pascal dizeleri 1 tabanlıdır
if (P >= 1) and (P + 1 <= Length(Data)) then
begin
GID := (Integer(Byte(Data[P])) shl 8) or Integer(Byte(Data[P + 1]));
Result := True;
end;
end;
Font programı çözümlenmeyince denetçi ne yapmalı?
Hiçbir şey demek. ISO 14289-1 §7.21.4.2'nin istediği /CharSet ve /CIDSet tamlık sınamaları — diagnostic 10038 ve 10039 — fazla istekli bir doğrulayıcının yük hâline geldiği yerdir; çünkü "CharSet'iniz eksik" raporu, okuyan kişi için "Type 1 çözücümüz pes etti" demenden ayırt edilemez. PDFlibPas bu yüzden eksik girdiyi yalnızca üç şey de başarılı olduğunda raporlar: font programı çözümlenir, kod-glif eşlemesi çözülür ve kümenin kendisi çözümlenir. TPDFType1Decoder.LoadPFBFromString, hiçbir glif adı /CharSet dizisine karşı sınanmadan önce True döndürüp bir charstring sayısı üretmelidir; /CIDSet yolu, tek bit sınanmadan önce akışın şişmesini ve glif sayısının pozitif dönmesini ister. Yoldaki herhangi bir istisna kusura değil, "bulgu yok"a çöker
Bu, yanlış negatiflere bilinçli bir eğilimdir ve gömülmek yerine açıkça söylenmeye değer. Bozuk bir CFF tablosu, desteklenmeyen bir Type 1 varyantı ya da glif aralığından kısa bir /CIDSet, diagnostic yerine sessizlik üretir. Gerekçe şudur: PDF/UA denetimleri, araçları inşa etmemiş yazarlara iletilir ve yanlış suçlama, kaçırılmış bir bulgudan pahalıya gelir; yazar, uyumlu bir dosyanın uyumlu olduğunu kanıtlamaya bir gün harcar ve bütün rapora güvenmeyi bırakır. Matterhorn Protocol da aynı ayrımı başka biçimde yapar: makinenin karar verebileceği sınamaları insanın kararı gerekenlerden ayırır ve bu sınamalar Fonts kontrol noktasında (31) yaşar. Daha katı okumaya ihtiyacınız varsa PDFlibPas'ı hızlı kapı, özel bir doğrulayıcıyı ikinci görüş olarak çalıştırın — PDF/A ve PDF/UA preflight rehberinde anlatılan eşleştirme budur
Sayfa /Contents bir listedir, akış değil
İçerik akışı denetimindeki en pahalı tek hata, /Contents'i tek bir akış sanmaktır. ISO 32000-1 §7.7.3.3, bir sayfanın; parçaları arasındaki boşluklarla birleştirildiğinde sayfa programı olan akış dizisi tutmasına izin verir; üreticiler rastgele noktalardan böler ve bir BT bir üyede dururken onun eşleşen ET'si bir sonrakinde olabilir. Bir içerik işlemcisi durum tutar — işaretli-içerik iç içe derinliği, son Tf'in seçtiği font, metin-nesne bayrağı — ve Process girişte o durumu sıfırlar. Dizi üyesi başına bir kez çağırın ve ilk akıştan sonraki her akış geçerli fontsuz başlar; tam düzgün etiketlenmiş metin, etiketsiz ve fontsuz gürültü olarak okunur. PDFlibPas önce birleştirir, sonra bir kez işler
function ContentObjectData(FDoc: TSmartPDFDocument; Obj: TPDFObject): AnsiString;
var
I: Integer;
begin
Result := '';
Obj := DerefIndRef(FDoc, Obj);
if Obj is TPDFStream then
Result := TPDFStream(Obj).GetDecodedStream
else if Obj is TPDFArray then
for I := 0 to TPDFArray(Obj).Count - 1 do
Result := Result + ContentObjectData(FDoc, TPDFArray(Obj).Item[I]) + #10;
end;
// Bütün birleşimin üzerine tek Process çağrısı, asla üye başına bir çağrı
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Hangi Form XObject'ler gerçekten yapılandırılmamış sayılır?
Yalnızca bir sayfanın gerçekten çağırdıkları: işaretli içerik dışındaki bir çağrı noktasından gelen ve kendi içeriği metin gösterenler. Diagnostic 10040, ISO 14289-1 §7.20 maddesini nesne numarası başına üç bağımsız olgu kaydederek uygular — metni var, çağrıldı, işaretli içerik içinde çağrıldı — ve yalnızca ilk ikisinin kesişiminin üçüncü eksiğini raporlar. İki kestirmenin her biri, teslim edeceğiniz biçimde yanlıştır: /Resources'teki her metinli Formu işaretlemek, kimsenin çizim yapmadığı bir şablon kütüphanesini cezalandırır; çağrılan her Formu işaretlemek, metin taşımayan ve etiket gerektirmeyen vektör logoları cezalandırır. Çağrı noktası kaynak adıyla değil nesne numarasıyla çözülür; çünkü aynı Forma farklı sayfalarda farklı adlarla rastlanır. Eşlikçi diagnostic 10041, §7.21.8 için aynı birleştirilmiş programı gezdirir; kapsamdaki font üzerinden her metin gösteren işleneni çözümler ve .notdefe düşen kodları sayar — bu, taranmış görüntülerin arkasında kullanılan görünmez mod dahil, metin render modundan bağımsız olarak yasaktır. Kalan Formların nasıl sarılması gerektiği bir yapı ağacı sorusudur; etiketli PDF yapısı kurma makalesinde ele alınır
Hiç FontDescriptor olmayan fontlar
Gömülmemiş bir font bu denetimin meşru bir girdisidir, hata durumu değildir ve gömme sınamasının altındaki her yardımcı ondan kurtulmak zorundadır. PDFlibPas /FontDescriptor bulamadığında ya da FontFile, FontFile2, FontFile3 olmayan bir tanımlayıcı gördüğünde diagnostic 10020 kaydeder — ad Standart 14'ten biri olduğunda 10022; §7.21.4 NOT 5, onu muaf tutmayı reddeder — ve dosyanın gerisini gezmeye devam eder. Bir raporun bütün anlamı budur: yazar, her bulguyu tek geçişte ister, her çalıştırmada bir bulguyu değil. Bu yüzden genişlik, cmap, CharSet ve CIDSet yardımcılarına verilen tanımlayıcı başvurusu Nil olabilir ve her biri, önceki bir sınamanın denetimi durdurduğunu varsaymak yerine girişte bunu sınar. Eksik olanı gömmek ise çözümse, mekanikler var olan bir PDF'e eksik fontları gömme notundadır
Denetimi çalıştırmak
Tek çağrı, üretmek zorunda olmadığınız bir dosya üzerinde. TPDFlib.CheckFileCompliance bir uyumluluk testi seçicisi alır — ISO 14289-1:2014 altındaki PDF/UA-1 için 2 — ve ya sıfır ya da girdileri sayısal kod, iki nokta ve okunabilir mesaj olan bir dize-listesi tanıtıcısı döndürür. Burada tartışılan font ve içerik akışı bulguları o aralıkta 10020 ile 10041 arasını kaplar; 00xxx PDF/A kodlarından sayısal olarak ayrı tutulur ki karışık bir günlük okunabilir kalsın. Options'a 1 geçmek ilk bulguda kısa devre yapar; bunu yazar aracında değil, derleme kapısında istersiniz. Bellekte hâlâ açık bir belge için GetPDFUADiagnostics, disk turu atmadan eşdeğer incelemeyi çalıştırır
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2, PDF/UA-1 seçer; Options = 0 her bulguyu raporlar
Issues := PDF.CheckFileCompliance('delivery.pdf', '', 2, 0);
if Issues = 0 then
WriteLn('delivery.pdf: PDF/UA-1 conformant')
else
begin
Count := PDF.GetStringListCount(Issues);
for I := 1 to Count do
WriteLn(' ', PDF.GetStringListItem(Issues, I)); // örn. 10037 CIDFontType2 ...
end;
end;
Bunların hiçbiri makinede harici bir doğrulayıcı ikilisi gerektirmez; her derlemede çalışan bir sınama ile birinin aklına geldiğinde çalışan sınama arasındaki fark budur. Burada anlatılan uyumluluk ve diagnostic API'leri standart PDFlibPas Delphi PDF Library ile gelir; ürün sayfası, PDF/A, PDF/X ve PDF/E test paketlerinin yanında PDF/UA-1 için tam diagnostic kod tablosunu taşır