HotPDF, koordinatlardan değil bildirimsel bir ağaçtan sayfalanmış bir belge oluşturabilir. Bölümlerden (section), yığınlardan (stack), metinden, listelerden ve tablolardan bir THPDFDOMDocument oluşturur, bunu THPDFDOMRenderer'a verirsiniz ve render edici ölçer, sayfalar, sayfa süslerini (page furniture) çizer ve istendiğinde sonucu erişilebilir kılan PDF/UA yapı ağacını üretir. Yerleşim kodu hiçbir zaman bir y koordinatı hesaplamaz
Koordinat odaklı bir rapor üreteci bakımı yapmış herkes bunun neden önemli olduğunu bilir. İlk sürüm çalışır. Ardından bir müşteri adresi üç satıra çıkar, bir tablo satır kazanır, yerelleştirilmiş bir başlık satır kaydırır ve sonrasındaki her y konumu yanlış olur. Düzeltmeler, iş mantığına dağılmış elle yapılan sayfa sonu denetimleri olarak birikir ve iki yıl sonra gelen etiketli PDF gereksinimi, bir paragrafın ne olduğu hakkında hiçbir fikri olmayan koda sonradan eklenemez
Ağaç neyin sahibidir ve sahiplik neden katıdır?
DOM, her seviyede tekli sahipliği zorunlu kılar: belge kendi bölümlerinin sahibidir, bir bölüm kendi gövdesinin, üstbilgisinin ve altbilgisinin sahibidir, yığınlar, kapsayıcılar ve tablolar da kendi çocuklarının sahibidir. Yeniden kullanım, Clone üzerinden veya kayıtlı bir fabrika (factory) üzerinden gerçekleşir, aynı nesneyi hiçbir zaman iki ebeveyne eklemek yoluyla değil. Bu kural bir formalite değildir. Ağaçta iki kez görünen bir bileşen, farklı kısıtlarla iki kez ölçülür ve söküm (teardown) sırasında iki kez serbest bırakılırdı
Çağıran kod için pratik sonuç, yardımcı (helper) fonksiyonların yeni örnekler döndürmesidir. RegisterComponent ile bir fabrika kaydetmek ve CreateComponent'i çağırmak, size her seferinde yeni bir bileşen üreten adlandırılmış bir tarif verir; bir imza bloğu veya bir yasal altbilgi gibi tekrar eden süslerin ağaca dahil olma yolu budur
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // PDF/UA yapı ağacını üret
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // punto cinsinden A4
Section.PageHeight := 842;
Section.MarginLeft := 56;
Section.MarginTop := 56;
Section.MarginRight := 56;
Section.MarginBottom := 56;
Section.Style.FontName := 'Helvetica';
Section.Style.FontSize := 10;
Section.Body.AddHeading('Annual maintenance report', 1);
Section.Body.AddText('Every asset inspected during the reporting ' +
'period is listed below, grouped by site.');
Section.Body.AddSpacer(12);
Table := THPDFDOMTable.Create('assets');
Table.AddColumn(3); // ağırlıklar, mutlak genişlikler değil
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // üstbilgi satırı
Row[0].Text := 'Asset';
Row[1].Text := 'Last service';
Row[2].Text := 'Status';
for I := 0 to High(Assets) do
begin
Row := Table.AddRow(16);
Row[0].Text := Assets[I].Name;
Row[1].Text := Assets[I].ServiceDate;
Row[2].Text := Assets[I].Status;
end;
Section.Body.Add(Table);
end;
Sayfalama, karesel maliyetten nasıl kaçınır?
Bir ağacı sayfalamanın saf (naive) yolu, sığmayan her şeyi klonlayıp bir sonraki sayfaya taşımaktır. On bin satırlık bir tabloda, bu, kalan satırları sayfa başına bir kez klonlar ve doğrusal bir belgeyi karesel bir belgeye dönüştürür
HotPDF bunun yerine dar kapsamlı böler. Üst düzey render edici, gövde çocuklarında indekse göre gezinir ve hiçbir zaman bütün bir bölümü veya gövdeyi klonlamaz. Yalnızca gerçekten bir sayfa sınırına yayılan iç içe yığınlar ve kapsayıcılar, etkilenen alt ağaçlarını klonlatır ve iki ağır yaprak (leaf) türü, bir kopya yerine bir imleç (cursor) taşır: bir metin devamı hâlâ borçlu olduğu kaynak karakter aralığını saklar, bir tablo devamı ise henüz yerleştirmediği satır dilimini saklar. Uzun belgeler doğrusal kalır ve uzun paragraflar, bir kez veya beş kez kesilsinler, aynı maliyete sahiptir
Ölçüm, yan etkiler konusunda dürüst kalır. THPDFLayoutElement.Measure'ın çizim yan etkilerinden arınmış olması gerekir ve gerçek yerleştirme her zaman THotPDF.PlaceLayoutElement üzerinden çalışır; bu, yerleştirilen parçayı yeniden ölçen, taşma (overflow) sahipliğini kuran ve tanılama bilgilerini kaydeden aynı merkezi yordamdır. DOM render edicisi yalnızca yeni sayfa ilkesine, sayfa süslerine, boşluklara ve devamların yaşam süresine karar verir
Sonsuz bir belgeyi önleyen tablo üstbilgisi kuralları
Sayfalar arasında tablo üstbilgilerini tekrarlamak basit görünür ama iki başarısızlık modunu gizler. HotPDF, üstbilgi satırlarının yalnızca ardışık satırların ilk grubunda görünmesini ve ilk bölünmenin tüm üstbilgi satırlarına artı en az bir gövde satırına sığmasını gerektirir. İkinci kural olmadan, kalan alandan daha uzun bir üstbilgi, yalnızca üstbilgiyi içeren bir sayfa üretir, ardından aynı sayfa sonsuza kadar tekrar eder
Devam sayfaları üstbilgiyi yeniden çizer ve bu yeniden çizilen kopya, içerik olarak değil bir yapı öğesi (artifact) olarak işaretlenir; bu, hem erişilebilirlik hem de metin çıkarma için doğru cevaptır. Orijinal üstbilgi satırı, mantıksal tablo yapısında tam olarak bir kez kalır. Bunu atlarsanız, bir ekran okuyucu sütun başlıklarını verilerin ortasında yeniden duyurur ve bir metin çıkarıcı, gövde satırları arasına yinelenen bir üstbilgi satırı ekler
Devam derinliği üzerinde savunma amaçlı bir tavan da vardır, çünkü özel bir bileşen Split'i her zaman eşdeğer bir kuyruk (tail) döndürecek şekilde uygulamakta serbesttir. Render edici sınırı, kuyruğu ayırdıktan sonra ve bir sonraki sayfayı başlatmadan önce denetler ve geçerli yineleme kuyruğu kendi finally bloğunda serbest bırakır, bu yüzden hatalı davranan bir üçüncü taraf bileşeni bir diski doldurmak yerine tanılanabilir bir hatayla başarısız olur
Tek mantıksal öğe, çok sayfa parçası
Otomatik etiketlemenin, sayfalama modeli ile yapı modelinin uyuşmak zorunda olduğu yerdir. İki sayfaya bölünmüş bir paragraf tek bir mantıksal paragraftır, bu yüzden tek bir yapı öğesi olarak kalmalıdır. Ancak işaretli içerik tanımlayıcıları (marked content identifier) sayfa başınadır, bu yüzden her görünür parça, göründüğü sayfada kendi MCID'sine ihtiyaç duyar
HotPDF bunu, tek bir yapı öğesi tutup her parça için onun /K dizisine bir işaretli içerik referansı ekleyerek çözer; sayfayı ve tanımlayıcıyı belirleyen /Pg ve /MCID çifti ile. O MCID için ParentTree yuvası, aynı öğeye geri işaret eder. Bu, tam olarak ISO 14289'un beklediği şeydir ve devam klonlarının sıradan klonlardan farklı olmasının nedeni budur: sıradan bir Clone yeni mantıksal içerik anlamına gelir ve yeni bir anlamsal kimlik alır, oysa dahili devam klonu devam ettirdiği bileşenin kimliğini devralır
Öğe yeniden kullanımı, bileşen işaretçisine göre sıralanmış ve ikili karşılaştırmayla aranan bir anlamsal kimlik dizini üzerinden aranır; bu, büyük ağaçlarda aramayı logaritmik tutar. Dizin yalnızca sahip olmayan (non-owning) referanslar tutar; yapı nesnelerinin yaşam süresinin kendisi PDF nesne grafiğinde kalır
Render edicinin baştan zorunlu kıldığı yapı kuralları
GenerateStructure etkinleştirildiğinde, birkaç PDF/UA kuralı, dosya var olduktan sonra değil ağaç render edilirken denetlenir. Başlıklar 1. seviyeden başlar ve seviye atlayamaz. LI yalnızca L içinde, Lbl ve LBody ise yalnızca LI içinde görünebilir. TR bir tabloya, TH ve TD ise bir satıra aittir. Alternatif metni olmayan bir figür, PDF/UA modunda reddedilir
Burada bilinçli seçim erken reddetmektir. Belge yazıldıktan sonra eksik bir alternatif metni bildiren bir doğrulayıcı, size on bin adet ekstrenin yeniden üretilmesi gerektiğini söyler; bileşeni reddeden bir render edici ise, onu üreten veri hâlâ kapsamdayken hangi bileşen olduğunu söyler. Uygunluk (conformance) doğrulaması yine de işlem hattında ayrı bir adım olarak yerini korur; bunun mekaniği PDF/A, PDF/X ve PDF/UA doğrulama yazısında ele alınmıştır
var
Pdf: THotPDF;
Renderer: THPDFDOMRenderer;
Stats: THPDFDOMRenderStatistics;
begin
Pdf := THotPDF.Create(nil);
Renderer := THPDFDOMRenderer.Create;
try
Pdf.FileName := 'maintenance-report.pdf';
Pdf.BeginDoc;
Stats := Renderer.Render(Doc, Pdf);
Pdf.EndDoc;
Writeln(Format('%d page(s), %d placement(s), %d split(s)',
[Stats.PageCount, Stats.PlacementCount, Stats.SplitCount]));
Writeln(Format('structure elements=%d marked content=%d artifacts=%d',
[Stats.StructureElementCount, Stats.MarkedContentCount,
Stats.ArtifactCount]));
Writeln(Format('deepest continuation chain: %d',
[Stats.MaximumContinuationDepth]));
finally
Renderer.Free;
Doc.Free;
Pdf.Free;
end;
end;
İstatistik kaydı ilk bakışta göründüğünden daha kullanışlıdır. Bir şablon değişikliğinden sonra SplitCount'un keskin biçimde yükselmesi genellikle bir bileşenin kapsayıcısından daha uzun ölçülmeye başladığı anlamına gelir. MaximumContinuationDepth'in yavaşça yükselmesi, Split'i sayfa başına çok az ilerleme kaydeden bir bileşen için erken uyarıdır. Ve ArtifactCount'u devam sayfalarının sayısıyla karşılaştırmak, tekrarlanan üstbilgilerin gerçekten yapı öğesi (artifact) olarak etiketlendiğini doğrular
DOM, doğrudan API'nin yanında nereye oturur?
DOM, doğrudan çizimin yerini almaz; aynı sayfa nesnelerinin üzerine oturur. Render edicinin yerleştirdiği her şey, THotPDF üzerindeki doğrudan çağrılarla iç içe geçebilir; bu, bir raporun tam bir konumda elle yerleştirilmiş bir imza görüntüsü gibi tek bir öğeye ihtiyaç duyduğu durumlarda önemlidir. Sayfa kapatma, AddPage ve EndDoc'un kontrolünde kalır, bu yüzden anlık boşaltma (flush) modu bellekte tamamlanmış hiçbir sayfa tutmaz ve yerleşik bellek, mevcut devamlar, yazı tipi kaynakları ve olağan belge nesne grafiği tarafından yönetilmeye devam eder
İçerik veri odaklı ve yerleşim kural odaklı olduğunda DOM'u seçin, sabit görsel içerik (artwork) için ise doğrudan çizimi koruyun. Şu anki sıkıntınız özellikle tablo sayfalaması ise, önce PDF'de tablo oluşturma yazısındaki daha dar kapsamlı yaklaşımı okumaya değer; hizalama gibi metin düzeyi davranışlar ise metin hizalama yazısında anlatılmıştır
Bildirimsel yerleşim, otomatik etiketleme ve doğrudan çizim API'si, Delphi ve C++Builder için aynı bileşende gelir; eksiksiz özellik listesi HotPDF Delphi PDF bileşeni sayfasındadır