Teknik Makale

Delphi'de Etiketli Çıktıyla Bildirimsel PDF Yerleşimi

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