Üretilmiş bir raporu bir ekran okuyucuyla açın; alışılmış başarısızlıklar bir dakika içinde ortaya çıkar: her sayfada "Sayfa 3 / 40" sesli okunur, şirket logosu arkasından hiçbir şey gelmeyen "grafik" olarak duyurulur ve bir tablo, sütun adları olmayan uzun bir sayı dizisi olarak çıkar. PDF Library for Delphi üçünü de otomatik yapı etiketlemeyle ortadan kaldırır: SetAutoTagMode'u bir kez çağırın ve sıradan çizim çağrıları, belge çizilirken paragrafları, başlıkları, şekilleri, tabloları ve okuyucunun atlayacağı sayfa gereçlerini üretsin
Elle etiketleme yolu hâlâ duruyor. Her çizim çağrısını BeginTag ile EndTag içine sarabilirsiniz ve olağandışı yapılı belgeler için doğru araç da bu. Sıradan bir rapor, fatura veya ekstre içinse elle etiketleme, erişilebilirliği ürünün ömrü boyunca bir şey çizen her kod yolunda kimsenin bir çifti unutmamasına bağlar. Bir uyumluluk gereksinimini buna oynamayız
SetAutoTagMode neyi açar
SetAutoTagMode bir bit maskesi alır ve çağrıdan önce yürürlükte olan modu döndürür. Her bit bir içerik türünü kapsar:
AUTOTAG_TEXT(1): metin paragraf olur, kaydedilmiş bir başlık varsa başlık olurAUTOTAG_FURNITURE(2): sabit üst ve alt başlıklar ile sayfa numaraları, okuyucunun atladığı yapı öğelerine dönüşürAUTOTAG_FIGURE(4): çizilen bir görsel şekil olur; dekoratif olarak bildirdiyseniz yapı öğesi olurAUTOTAG_TABLE(8): çizilen tablolar satırlarını ve hücrelerini yapı ağacına taşırAUTOTAG_DEFAULT(15): dördü birden; yeni her rapor kodu için önerdiğimiz değer
Modu açmak aynı zamanda belgeyi etiketli olarak işaretler ve bu adım göründüğünden daha önemlidir. ISO 32000-1 §14.7.1'e göre bir okuyucu, kataloğun /MarkInfo sözlüğü /Marked true demedikçe dosyayı etiketsiz sayar. Bir dosya tam ve doğru bir yapı ağacı taşıyıp yine de hiç yapısı yokmuş gibi duyurulabilir; çünkü yardımcı teknolojiye ona bakmasını söyleyen tek bayrak eksiktir. Ağaç oradadır. Onu okuyan hiçbir şey yoktur
Metin etiketleme çağrı başına çalışır. Her DrawText, DrawWrappedText ya da DrawTextBox çağrısı tek bir öğe olur; böylece beş DrawText çağrısıyla satır satır çizilen bir paragraf, okuyucuya beş tek satırlık paragraf olarak ulaşır. Yerleşim kodunuz satırları zaten kendisi bölüyorsa bunu değiştirmeye değer: bütün paragrafı DrawWrappedText'e verin ve tek çağrının tek paragraf üretmesini sağlayın
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // sol üst köken, y aşağı doğru büyür
Lib.SetDocumentLanguage('en-US');
Lib.SetAutoTagMode(AUTOTAG_DEFAULT); // herhangi bir RegisterHeading çağrısından önce
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.RegisterHeading(1, 'Annual service report');
Lib.DrawText(72, 96, 'Annual service report'); // H1 olarak etiketlenir
Lib.SetTextSize(11);
Lib.DrawWrappedText(72, 130, 450,
'Every unit installed before 2024 was inspected. ' +
'Units that failed the torque check were serviced on site.'); // tek P
// ... kalan sayfalar ...
// Var olan her sayfaya uygulanır; sayfalar çizildikten sonra çağırın
Lib.AddPageFooter('', 'Page &P of &N', 36, 1.0, 1); // Pagination yapı öğesi
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Üçüncü satırdaki yorum boşa değil. RegisterHeading bir başlık düzeyini yalnızca metin etiketleme hâlâ açıkken kuyruğa alır ve SetAutoTagMode çalıştığında kuyruktaki düzeyi temizler. Önce başlığı kaydedip modu sonra açarsanız başlık, hiçbir yerde sizi uyaran bir hata olmadan sıradan bir paragraf olarak çıkar
Başlık, kendinden önce çizilen görseli neden bekler?
RegisterHeading düzeyi sıradaki metin parçasına adlandırır ve onu yalnızca metin tüketebilir. Araya bir görsel çizilirse görsel şekil olur ve başlık, ardından gelecek metin için kuyrukta kalır. Bariz uygulama, kuyruktaki düzeyi herhangi bir etiketin tüketmesine izin verir; o zaman bölüm başlığını kaydetme, bir banner görseli çizme ve başlığı çizme sıralaması, H2 olarak etiketlenmiş bir banner ve paragrafa düşürülmüş gerçek bir başlık üretir. Başlıklara göre gezinen bir okuyucu bir resme konar
Kuyruğun çalışma biçiminden iki şey daha çıkar. Başlık düzeyi, RegisterHeading'e hangi dizeyi geçirdiğinizden bağımsız olarak sıradaki hangi metin gelirse ona yapışır: yerleşim kodunuz başlığın üstünde küçük bir "Bölüm 3" vurgusu çiziyorsa başlık o vurgu olur. Altıdan derin düzeyler H6 olarak yazılır; çünkü okuyucu ötesini bilmez. Title bağımsız değişkeni kendi işini yapmaya devam eder: DrawTOC'nin sonra içindekiler tablosuna çevireceği ana hattı besler
Görseller aynı "tek öğede harcanır" muamelesini görür. RegisterFigure sıradaki görselin taşıyacağı açıklamayı sağlar; RegisterDecoration ise sıradaki görseli anlamı olmayan bir çizgi, kenarlık ya da arka plan olarak bildirir ve bu, Layout yapı öğesi olarak yazılır (ISO 32000-1 §14.8.2.2). Her bildirim tam olarak tek bir görsel tarafından tüketilir; böylece sonraki bir görsel, önceki bir görsel için yazılmış açıklamayı asla miras almaz. Yanlış resme yapışan alt metin, hiç olmamasından daha kötüdür; çünkü okuyucu yanlış bir şeyi tam bir kendinden eminlikle söyler
Hangi görsellerin dekoratif olduğunu çıkarsamaya bilerek girişmedik. Bir görsel ya okuyucunun tanımlaması gereken içeriktir ya atlaması gereken; hangisi olduğunu yalnızca çağıran kod bilir. Kaçınılacak durum üçüncüsüdür: hiçbir bildirim olmadan çizilen bir görsel, açıklaması olmayan bir şekil olur; bu PDF/UA'yı geçmez ve ekran okuyucuyu "grafik" deyip ardından susacak şekilde durdurur. Rapor kodunuzda DrawImage için grep çalıştırırsanız her eşleşmenin hemen üstünde bir RegisterFigure ya da RegisterDecoration olmalı
Lib.RegisterHeading(2, 'Gearbox inspection'); // sıradaki metin için kuyrukta
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0); // görseli yükler ve seçer
Lib.DrawImage(72, 160, 360, 270); // etiketli bir Figure olur
Lib.RegisterDecoration; // anlamsız çizgi
Lib.AddImageFromFile('divider.png', 0);
Lib.DrawImage(72, 450, 360, 4); // bir layout yapı öğesinin içinde çizilir
Lib.DrawText(72, 480, 'Gearbox inspection'); // H2 ancak şimdi belirir
Açıklama, belgedeki başka herhangi bir dizeden fazla özen hak eder. Görme engelli bir okuyucu resmin yerine açıklamayı alır ve aldığı şeyin tamamı budur. "Grafik" ona hiçbir şey söylemez; "Bölgelere göre üç aylık gelir, üçüncü çeyrekte doğu bölgesi en yüksek" ise gören bir okuyucunun resimden iki saniyede aldığını söyler
Tablolar: başlık satırları bildirilir, asla tahmin edilmez
Tablo biti açıkken DrawTableRows tabloyu, satırlarını ve hücrelerini yapı ağacına yazar; böylece okuyucu, ızgarayı birbiriyle ilgisiz metinler olarak okumak yerine bir değerin hangi sütunda durduğunu söyleyebilir. SetTableHeaderRowCount baştaki kaç satırın başlık olduğunu söyler. O satırlar sütun kapsamı taşıyan TH hücreleri olarak yazılır; okuyucunun, kullanıcının üzerine geldiği değerden önce "Tork" duyurabilmesini sağlayan da budur
Başlık satırı sayısı varsayılan olarak sıfırdır ve bu bir tercihti. Bir tablonun ilk satırı çoğu zaman gerçekten başlıktır; tahmin etmek çoğu zaman doğru olurdu. Yanlış tahminin maliyeti ise tek taraflıdır: başlık sanılan bir veri satırı, sütunundaki her değerin önünde sesli okunur; bu da hiç başlığı olmayan bir tablodan çok daha kafa karıştırıcıdır. Sizden tek satır kod, o hata modundan daha ucuzdur
Başlık satırlarını bildirmek ile onları her sayfada yinelemek ayrı kararlardır. SetTableHeaderRowCount anlamsaldır ve sayfada hiçbir şeyi değiştirmez. Yineleme yerleşimdir ve DrawTaggedTableRows'un RepeatHeaderRows bağımsız değişkenine aittir. İkisi tek ayar olsaydı doğru başlık etiketleri istemek, hiç istemediğiniz yinelenen başlık bantlarını çizmeye de başlardı ve sayfa sayfa denetlenmiş bir yerleşim kayardı. RepeatHeaderRows olarak geçilen satırlar da başlık olarak etiketlenir; aynı şeyi iki kez söylemek zorunda kalmazsınız
var
TableID: Integer;
begin
TableID := Lib.CreateTable(40, 3);
Lib.SetTableHeaderRowCount(TableID, 1); // satır 1 başlık bandı
Lib.SetTableCellContent(TableID, 1, 1, 'Part');
Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
// ... veri satırlarını doldur ...
// 1..40 satırlarını 600pt'lik banda çiz, her sayfada bir başlık satırını tekrarla
Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;
Otomatik bir belgenin içinde elle etiketlenmiş bölümler
Otomatik etiketleme, elle açtığınız bir etiket hâlâ açıkken kenara çekilir. Bu, kodunuzun bir belgenin bir bölümünü kendisinin tanımlayıp geri kalanını kütüphaneye bırakmasını, ikisinin birbirinin içine yerleşmeden sağlar; çoğu gerçek belgenin istediği düzen de budur. Kapak sayfası ile imza bloğu yalnızca sizin anladığınız bir yapı taşır; aralarındaki iki yüz sayfalık gövde metni taşımaz
Hangi karışımı kullanırsanız kullanın üç kural çıktıyı geçerli tutar. Bir yapı öğesinin içinde hiçbir şey etiketlenmez; çünkü bir yapı öğesinin marked-content dizisi aynı zamanda yapı öğesi taşıyamaz ve bunu deneyen dosya PDF/UA doğrulamasını geçemez. Boş metin hiçbir öğe açmaz; böylece boş dizeyle yapılan başıboş bir DrawText, okuyucunun boş olarak duyuracağı bir öğe bırakamaz. Ve içeride başka çizim çağrılarıyla çizen bir kütüphane çağrısı dış düzeyde tek bir etiket açar; parçaları asla kendilerine ait ikinci bir öğenin içine gömülüp kalmaz
Kaydettikten sonra, güvenmeden önce ağacı geri okuyun. Okuma API'si 1 tabanlıdır ve bu bizi de yakaladı: ilk denetim döngümüz 0'dan StructTreeItemCount - 1'e kadar döndü, başta boş bir kayıt aldı, son öğeye hiç ulaşmadı ve bir süre bir paragrafın kayıp olduğuna ikna olduk. Hiçbir şey kayıp değildi. Döngü iki uçta da bir kaymıştı, hepsi bu
var
I: Integer;
begin
// Elle etiketlenmiş bir alıntı; otomatik etiketleme buna karışmaz
Lib.BeginTag('BlockQuote', '', '');
Lib.DrawWrappedText(72, 520, 450,
'All readings were taken with the unit at operating temperature.');
Lib.EndTag;
Lib.SaveToFile('report.pdf');
// Ağacı geri okuyun: indeksler 1'den StructTreeItemCount'a kadar gider
if Lib.LoadFromFile('report.pdf', '') = 1 then
for I := 1 to Lib.StructTreeItemCount do
Writeln(Lib.GetStructTreeItemText(I, 2), ' ', // çözümlenmiş tür
Lib.GetStructTreeItemText(I, 4)); // varsa /Alt
end;
Bu tür bir döküm hızlı bir sağlamlık sınamasıdır, karar değil. Görmek istediğiniz şey, ilk paragrafın önünde bir H1, her şeklin açıklamasını taşıması ve boş /Alt'lı bir şekil olmamasıdır
Çizim sırası ile okuma sırasının ayrıştığı yer
Otomatik etiketleme öğeleri, içeriğin çizildiği sırayla kaydeder. Yerleşim kodunuz gövdeden önce kenar çubuğunu çiziyorsa ağacın tuttuğu sıra odur ve ekran okuyucunun okuduğu sıra da odur. Görsel sıra ile okuma sırasının gerçekten ayrıştığı ya da başka yapı türlerine ya da öğe başına dile ihtiyaç duyan belgeler için elle etiketleme API'si daha uygundur; etiketli PDF ve erişilebilirlik yapısı gezinmesi rolleri, kapsamları ve başlık bağlarını ayrıntılı olarak ele alır
Kendinizin çizmediği sayfalar için, işaretli içeriği olmayan içe aktarılmış sayfalar gibi, AutoTagPages sonradan çalışır: kendisine verilen sayfalardaki medyan metin boyutunu gövde metni sayar, belirgin biçimde büyük dizileri başlık sayar ve diğer boyamaları yapı öğesi olarak işaretler. Yeniden üretemeyeceğiniz içerik için doğru araç budur. Kendi kodunuzun çizdiği her şey için çizim anında etiketlemeyi tercih ederiz; çünkü orada kütüphane, başlığın ne olduğunu font boyutundan tahmin etmek yerine açıkça öğrenir
Belge tamamlandığında varsaymak yerine bir denetleyiciden geçirin: PDF/A ve PDF/UA preflight notları ürettiğiniz yapı hakkında nasıl karar alacağınızı gösterir; veri kümesi tabanlı rapor dışa aktarımı yazısı ise bu çağrıların, yerleşimini veriden üreten bir rapor motorunda nerede durduğunu gösterir
PDF Library for Delphi, Delphi, C++Builder ve Lazarus için doğal Pascal ile yazılmıştır ve dağıtılacak harici bir PDF çalışma zamanı yoktur; dolayısıyla etiketli çıktı, sayfayı çizen kodun kendisinden gelir. Eksiksiz API başvurusu ve platform listesi PDF Library for Delphi ürün sayfasındadır