v3.539.30dan önce losLab PDF Librarydeki TPDFlib.ImportAnnotationsFromFDFString, ayrıştırdığı FDF annotation girdilerinin sayısını döndürüyor ama hiçbirini belgeye eklemiyordu: her girdi sayılıyor, her girdi düşüyordu. v3.539.30dan beri FDF importerı anahtarları herhangi bir sırayla okuyor, /Recti doğru ve yerel ayarından bağımsız biçimde ayrıştırıyor ve eşleşen exporter annotationın gerçek /Rectini yazıyor; böylece export, import ve ikinci export bayt olarak özdeş bir FDF üretiyor. Bu notun geri kalanı, tek bir yanlış başlangıç ofsetinin kusursuz bir sessiz başarısızlığı nasıl ürettiğini, arkasına saklanmış üç başka kusuru ve importu dönüş değerine güvenmek yerine kendinizin nasıl doğrulayacağını anlatıyor
Senaryo sıradandır. Bir incelemeci sözleşmeye işaretler, yorumlar FDF dosyası olarak yola çıkar (Acrobat buna Export Comments der) ve sizin Delphi servisiniz onları ImportAnnotationsFromFDF ile temiz bir kopyaya birleştirir. Çağrı 7 döndürür, log "7 comments imported" der, iş yeşile döner ve çıktı PDFinde hiç yorum yoktur. Ne bir istisna ne bir uyarı; sayı da inandırıcı görünüyordu çünkü dosyadaki girdilerin gerçek sayısıydı. Bir hatanın alabileceği en kötü biçim budur: tek başarı sinyali, bildirdiği işten bağımsız hesaplanan bir sayaç olan fonksiyon
ImportAnnotationsFromFDFString neden başarı bildirip hiçbir şey eklemedi?
Importer her /Subtypeı boş dize olarak okuyordu ve annotationı oluşturan yardımcı boş subtypeta erken çıkarken çağıran yine de sonucu artırıyordu. Anahtar bulucu /Subtypeın hemen ardından gelen konumu döndürüyordu; orası değerden önceki boşluktur. ReadName o boşluktan başlıyor ve ilk boşluk karakterinde duruyordu, yani hiçbir şey okumadan duruyordu. AddAnnotationToPage subtypesız annotation kurmayı reddeder; izole bakılınca doğru savunmacı tercihtir ama dönüş değeri olmayan bir procedure idi ve Inc(Result) dışarıda duruyordu. Her koruma tek başına makuldü; birlikte "hiçbir şey çalışmıyordu"nu "her şey çalışıyordu"na çevirdiler. Düzeltme, ReadNamein boşlukları atlamasını, PDF name objectinin başındaki /yi şart koşmasını ve [, ( ile ) dahil herhangi bir sınırlayıcıda durmasını sağlar; böylece /Subtype/Text de /Subtype /Text de Text üretir
Dönüş değeri o düzeltmeden sonra da özen ister. v3.539.39a kadar ImportAnnotationsFromFDFString, /Annots dizisindeki her düzgün sözlük için sonucu artırmaya devam ediyordu; buna 0 tabanlı /Pagei aralık dışı olan ya da /Subtypeı eksik olan, yani atlanan girdiler de dahildi. PDFlibPas v3.539.40dan beri ImportAnnotationsFromFDFString ile ImportAnnotationsFromFDF, XFDF importu gibi gerçekten eklenen annotation sayısını döndürür: FDF yardımcısı AddAnnotationToPage artık Boolean döndürür ve sayaç yalnızca başarıda ilerler. Belgeyi ölçmek hâlâ daha güçlü kontroldür, çünkü eski sürümlerde de geçerlidir; aşağıdaki taslak da bu yüzden import öncesi ve sonrası her sayfada AnnotationCountu karşılaştırır
function TotalAnnotations(Lib: TPDFlib): Integer;
var
Page, Saved: Integer;
begin
Result := 0;
Saved := Lib.SelectedPage;
for Page := 1 to Lib.PageCount do
if Lib.SelectPage(Page) = 1 then
Inc(Result, Lib.AnnotationCount); // seçili sayfa başına, widgetlar dahil
Lib.SelectPage(Saved);
end;
var
Lib: TPDFlib;
Before, Reported, Added: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('contract.pdf', '');
Before := TotalAnnotations(Lib);
Reported := Lib.ImportAnnotationsFromFDF('review-comments.fdf');
Added := TotalAnnotations(Lib) - Before;
if Added <> Reported then // v3.539.40dan beri eşit
Writeln(Format('Importer reported %d, %d landed on a page', [Reported, Added]));
Lib.SaveToFile('contract-reviewed.pdf');
finally
Lib.Free;
end;
end;
İlkinin arkasındaki üç kusur daha
Yalnızca subtypı düzeltmek aynı fonksiyondaki üç kusuru daha ortaya çıkarırdı; hiçbir annotation bir sayfaya ulaşamadığı için her biri şimdiye kadar görünmez kalmıştı. Birincisi, ReadNumber konumunu value parameter olarak alıyordu; yani dört /Rect sayısını sırayla okumak aynı noktayı dört kez okumakti ve baştaki [yi de atlamıyordu, pratikte hiçbir şey okumuyordu. İkincisi, FindKey tüm aramalarda tek bir ileri hareketli imleci paylaşıyordu. Exporter /Subtype, /Rect, /Page, /Contents, /T, /Subj yazar; importer ise /Subtype, /Contents, /T, /Subj, /Page, /Rect sırasıyla arıyordu. İmleç bir kez /Contentsı geçince /Page ve /Rect araması mevcut girdiyi aşıyor ya da hiçbir şey bulamıyor ya da bir sonraki annotationın anahtarlarıyla eşleşiyordu. Kütüphane kendi çıktısını okuyamıyordu. Üçüncüsü, sayılar sistem ondalık ayracını izleyen PLStrToFloattan geçiyordu. ISO 32000-1 §12.7.7 FDFyi PDF object sözdizimi olarak tanımlar ve PDFde sözlük anahtarları sırasızdır (§7.3.7); dolayısıyla bir anahtar sırası varsayan her FDF ayrıştırıcısı, dosyayı hangi araç ürettiyse üretsin, kuruluşu gereği yanlıştır
Tamir edilen importer önce her girdiyi sınırlar. FindDictEnd, açılış <<dan eşleşen >>ya yürür; iç içe sözlükleri izler ve literal dize gövdelerini backslash kaçışlarıyla birlikte atlar, böylece (see section >> 4) gibi bir yorum içindeki >> girdiyi erken bitiremez. Ardından her anahtar araması girdinin kendi başında başlar ve sonuna kadar sınırlıdır; bu, anahtar sırasını önemsiz kılar ve bir annotationın başkasının /Pageini ödünç almasını durdurur. Anahtar eşleşmesi ayrıca ismin hemen ardından gelen sınırlayıcıyı kabul eder, çünkü /Contents(Hi) kadar /Contents (Hi) de geçerlidir; word-boundary kuralı ise /Subjnin /Subtypeın başıyla, /Tnin /Typela eşleşmesini engeller. ReadNumber artık konumunu var parameter olarak alır, boşlukları ve [yi atlar ve PLTryStrToFloatInvariant ile ayrıştırır; bozuk bir token üzerinde istisna fırlatmak yerine yumuşak başarısız olur. Dört dikdörtgen sayısından herhangi biri başarısız olursa, yarım okunmuş bir dikdörtgen üretmek yerine dördü de sıfıra döner
FDF round-tripleri her annotationı kendi yüksekliği kadar neden kaydırıyordu?
Eski exporter dikdörtgeni yanlış koordinat modelinde yazıyordu. Bir annotationın /Recti varsayılan user spacete [llx lly urx ury]dir (ISO 32000-1 §12.5.2, dikdörtgen tanımı §7.9.5te) ve FDF aynı diziyi taşır. ExportAnnotationsToFDFString ise kütüphanenin çizim koordinatlarında — SetOriginin kontrol ettiği uzayda — Left, Top, Width ve Height bildiren GetAnnotRectExi çağırıyor ve onları [L T L+W T+H] olarak serileştiriyordu. Importer, çalışmaya başladığında o dört değeri aynen PDF dikdörtgeni olarak geri yazıyordu; üst kenar sol alt köşenin yerine düşüyor ve her round-trip annotationı kendi yüksekliği kadar yukarı taşıyordu. Exporter artık annotationın kendi /Rect sayılarını kopyalar: üç ondalık, nokta ayracı, üs yok; ve yalnızca saklanan dizi eksikse ya da dört sayı içermiyorsa hesaplanan dikdörtgene döner
Bunu çivileyen regresyon testi kopyalamaya değer, çünkü importerın dönüş değeri üzerinde değil, belge üzerinde ve ikinci bir export üzerinde iddia bulunur. 2 olan beklenen sayıya dikkat: AddNoteAnnotation bir Text annotationı ve onun Popupını oluşturur ve ikisi de yola çıkar. Test ayrıca export ile importu virgül ondalık ayracı altında çalıştırır; hikâyenin öteki yarısı tam orada yaşar
var
Source, Target: TPDFlib;
FDF: AnsiString;
OldSep: Char;
begin
Source := TPDFlib.Create;
Target := TPDFlib.Create;
try
Source.NewPages(1); // artık iki sayfa
Source.SelectPage(2);
Source.AddNoteAnnotation(50.5, 60.25, 0, 80, 80, 120, 60,
'Reviewer', 'Check this', 0.25, 0.5, 0.75, 0);
Target.NewPages(1);
OldSep := FormatSettings.DecimalSeparator;
FormatSettings.DecimalSeparator := ','; // Alman ya da Fransız masaüstünü taklit et
try
FDF := Source.ExportAnnotationsToFDFString; // /Rect [50.5 ... yazmaya devam eder
Target.ImportAnnotationsFromFDFString(FDF);
finally
FormatSettings.DecimalSeparator := OldSep;
end;
Target.SelectPage(2);
Assert(Target.AnnotationCount = 2); // not ve popupı
Assert(Target.GetAnnotType(1) = 'Text');
Assert(Target.ExportAnnotationsToFDFString = Source.ExportAnnotationsToFDFString);
finally
Target.Free;
Source.Free;
end;
end;
FDF yolunun ne taşıdığını netleştirin. Importer her girdiyi /Type, /Subtype, /Rect, /Contents, /T ve /Subj taşıyan bir sözlük olarak yeniden kurar; renk, bayraklar, border stili, popup bağlantıları ve appearance streamler bu yola dahil değildir ve exporter Widget annotationlarını atlar, çünkü form alanları form-data metotlarına aittir. Hangi verinin hangi metotla yola çıktığının daha geniş haritası FDF, XFDF ve XFA form data alışverişi genel bakışındadır; gerçekten ne geldiğini incelemeniz gerekiyorsa GetAnnotType, GetAnnotTitle ve GetAnnotContentsEx gibi indeks başına okuyucular outline, annotation ve action introspection yazısında kapsanır
Eski exportlardan kalan virgüllü ondalıklı FDF ve XFDF dosyaları nasıl okunur?
FDF için cevap nettir: PDF sözdiziminde virgül sınırlayıcı değildir; dolayısıyla tam bir virgül içeren ve nokta içermeyen bir sayı tokeni ancak virgül yerel ayarlı bir makinede yazılmış ondalık olabilir. Önceki sürümler gerçekten böyle dosyalar yazıyordu, örneğin /Rect [10,500 20,250 40,750 60,125]; yeni ReadNumber o tek virgülü ayrıştırmadan önce noktaya çevirir. İki virgüllü ya da virgülle noktayı birlikte taşıyan bir token ise tahmin yürütmek yerine reddedilir. Okuyucu üs gösterimini de tüketmez; ISO 32000-1 §7.3.3le uyumludur: PDF sayıları asla üs kullanmaz
XFDF daha zordur, çünkü XML attributelerinde virgül ayracın kendisidir. Standart XFDF (ISO 19444-1) rect="50.5,80.25,70.75,100.125" ve dashes="4,2" yazar; v3.539.28 ve öncesi ise virgül yerel ayarlı sistemde rect="50,500 80,250 70,750 100,125" ile opacity="0,600" yazıyor ve standart bir opacity="0.6" okurken EConvertErrorla başarısız oluyordu. v3.539.29dan beri iki yön de invarianttır ve eski biçimi XFDFNormalizeLegacyDecimals yalnızca attribute boşluktan bölündüğünde tam olarak beklenen sayıda token veriyorsa (rect için dört, opacity ile width için bir) ve her token rakam-virgül-rakam biçimindeyse tanır. Standart bir rect asla eşleşmez: ya üç virgüllü tek tokendir ya da virgülle biten tokendir. dashes bilinçli olarak olduğu gibi bırakılır, çünkü 4,2 iki dash uzunluğu da olabilir eski biçimli 4.2 de; ikisini ayırt edebilecek bir kural yoktur
const
// Exporter sırasının dışında anahtarlar, artı eski bir virgül yerel ayarlı exporttan virgüllü ondalıklar
LegacyFDF: AnsiString = '%FDF-1.2'#10'1 0 obj'#10'<< /FDF << /Annots ['#10 +
'<< /Rect [10,500 20,250 40,750 60,125] /Page 0 /Contents (First) ' +
'/Subtype /Text /T (Alpha) /Type /Annot >>'#10 +
'] >> >>'#10'endobj'#10'trailer'#10'<< /Root 1 0 R >>'#10'%%EOF'#10;
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create; // taze bir belgenin bir sayfası vardır
try
Lib.ImportAnnotationsFromFDFString(LegacyFDF);
Assert(Lib.AnnotationCount = 1);
Assert(Lib.GetAnnotTitle(1) = 'Alpha');
// Nokta ondalıklarla XFDF olarak yeniden export: rect="10.500 20.250 40.750 60.125"
Writeln(Lib.ExportAnnotationsToXFDFString);
finally
Lib.Free;
end;
end;
Bir annotation import testi aslında neyi iddia etmeli?
Faydalı bir import testi hedef belgenin durumu üzerinde iddia bulunur, asla yalnızca importerın kendisi hakkındaki sözlerine değil. Test takımının hiçbir yeri FDF importundan sonra AnnotationCountu kontrol etmiyordu ve herkesin baktığı tek sayı olan dönüş değer, hatanın dokunmadığı tek sayıydı. Burada anlatılan her kusuru yakalayacak üç iddia vardı: beklenen sayfadaki annotation sayısı, GetAnnotType ya da GetAnnotContentsEx üzerinden geri okunan bir alan ve ilkiyle bayt bayt karşılaştırılan ikinci bir export. Aynı disiplin, belge yapısını toplu biçimde yeniden yazan her APIye uygulanır; yinelenen form alanlarının birleştirilmesi de buna dahildir: dönen toplama değil, oluşan ağaca bakın. FDF ve XFDF annotation metotları, dosya ve dize varyantlarıyla birlikte losLab PDF Library for Delphi and C++Builder içinde gelir; yorumların yolculuğu atlatması için v3.539.30 ve üzerini, dönen sayının eklenene eşit olması için v3.539.40 ve üzerini çalıştırın