Teknik Makale

Delphi'de HTML'den PDF'e: HTML5 Sayfalı Ortam Yolu

HotPDF, HTML'i PDF sayfalarına THPDFHTMLImporter.RenderHTML5 aracılığıyla dönüştürür; bu, gerçek bir DOM, gerçek bir CSS önceliklendirmesi, tablolar, sayfa sonları ve HTML form denetimlerinden isteğe bağlı AcroForm üretimi içeren sürümlü bir sayfalı ortam profilidir. Tamamen süreç içinde çalışır: tarayıcı motoru yok, Chromium yok, harici dönüştürücü yok ve varsayılan olarak hiçbir dosya veya ağ erişimi yok

İyileştirilmiş bir RenderHTML yerine ayrı bir giriş noktasının bulunma nedeni uyumluluktur. Eski yöntem yıllardır üretim uygulamalarında faturalar ve mektuplar render ediyor ve tam bir önceliklendirmeye sahip esnek bir DOM, eski davranışa göre ayarlanmış belgelerin çıktısını değiştirir. Bu nedenle eski yöntem kendi tam anlambilimini korur ve yeni olan her şey, profil sürümü HTML5ProfileVersion tarafından bildirilen ve desteklenen kilometre taşları HTML5ProfileMilestones tarafından listelenen RenderHTML5'in arkasında yaşar

HTML5 Profili Aslında Neler Ekliyor?

Pratikte dört yetenek önemlidir. html ve body'nin metin düğümleri değil kapsayıcı olduğu yapısal bir DOM. Doğru önceliğe sahip bir CSS önceliklendirmesi. Tablo düzeni. Ve "sayfalı ortam"ın anlamı olan açık sayfa sonu denetimi: HTML kaydırılabilir bir görüntü alanı için tasarlanmıştır ve onu sayfalara dönüştürmek, içeriğin nerede bölünmesine izin verileceğine karar vermeyi gerektirir

Kapsayıcı meselesi bir uygulama ayrıntısı gibi görünür, ama aslında çalışmakla çalışmamak arasındaki farktır. Kök öğeler metin kapsayıcıları olarak ele alınırsa, tüm DOM ayrıştırma sırasında tek bir metin bloğuna çöker ve sonrasındaki her stil sayfası kuralı, sayfa sonu, tablo ve form alt ağacı hiçbir şeye uygulanır. Yapılandırılmış bir belgeden tek, farksız bir metin duvarı üreten her HTML render motoru tam olarak bu hatayı yapmıştır

Önceliklendirme Sırası, Ayrıntılı Olarak

CSS önceliği tek bir sıralama değildir. HotPDF, eşleşen kurallardan gelen normal bildirimleri önce özgüllük sırasına, sonra kaynak sırasına göre uygular, üzerine satır içi normal bildirimleri uygular, ardından eşleşen kurallardan gelen !important bildirimlerini ve son olarak satır içi !important'ı uygular. Bu sırayla dört geçiş

HotPDF'in Delphi'de her CSS özelliğini, normal kural eşleştirmesinden satır içi !important bildirimlerine kadar dört sıralı önceliklendirme geçişiyle çözümlediğini gösteren diyagram
Sonraki geçişler beraberlikleri kasıtlı olarak kazanır: satır içi bildirimler stil sayfalarını geçersiz kılar ve !important normal şekilde bildirilen her şeyi geçersiz kılar

Bunu "sonuncu kazanır" mantığına indirgeyen uygulamalar, yaygın durumları doğru, ilginç olanları yanlış yapar; bu da hepsini bir örnek yanlış yapmaktan daha kötüdür, çünkü hata yalnızca temel bir stil sayfasını geçersiz kılmak için !important kullanan şablonlarda ortaya çıkar — tam olarak önceliklendirmenin çalışmasını bekleyen kişiler tarafından yazılan şablonlarda:

uses
  HPDFDoc, HPDFHTMLImport;

var
  Pdf: THotPDF;
  Importer: THPDFHTMLImporter;
  Html, Css: WideString;
begin
  Pdf := THotPDF.Create(nil);
  Importer := THPDFHTMLImporter.Create(Pdf);
  try
    Pdf.FileName := 'statement.pdf';
    Pdf.BeginDoc;

    Importer.Margin := 56;              // punto
    Importer.BaseFontName := 'Arial';
    Importer.BaseFontSize := 10;
    Importer.CreateAcroForms := False;  // denetimleri render et, alan oluşturma

    Css := '@page { margin: 56pt } ' +
           'table { border-collapse: collapse } ' +
           'tr.total { page-break-inside: avoid; font-weight: bold }';
    Html := LoadTemplate('statement.html');

    if not Importer.RenderHTML5(Html, Css) then
      raise Exception.Create('nothing was emitted - check the markup');

    Pdf.EndDoc;

    Writeln(Format('profile=%d nodes=%d rules=%d layout ops=%d',
      [Importer.Statistics.ProfileVersion,
       Importer.Statistics.ParsedNodeCount,
       Importer.Statistics.CSSRuleCount,
       Importer.Statistics.LayoutOperationCount]));
  finally
    Importer.Free;
    Pdf.Free;
  end;
end;

İkinci argüman, belgenin taşıdığı her şeyle birlikte uygulanan bir yazar stil sayfasıdır; bu, her şablonu düzenlemeden sayfa kenar boşlukları ve dul satır denetimi gibi kurumsal kuralları uygulamanın temiz yoludur

İçe Aktarıcı Neden Hiçbir Şey Getirmez?

Dış referansları çözen bir HTML'den PDF'e dönüştürücü, belge üretimi kostümü giymiş bir sunucu taraflı istek sahteciliği (SSRF) motorudur. Onu kullanıcı tarafından sağlanan biçimlendirmeye yönlendirin, seve seve http://169.254.169.254/ adresini veya uygulama sunucunuzdan bir dosya URL'sini getirir ve sonucu birinin indireceği bir PDF'e gömer

Bu yüzden HotPDF varsayılan olarak ne dosya ne de ağ okur. Görüntüler ve diğer kaynaklar yalnızca iki şekilde gelir: biçimlendirme içinde data: URI'leri olarak, ya da kodunuzun bir URI'nin ne anlama geldiğine karar verip baytları sağladığı OnResource geri çağrısı aracılığıyla:

HotPDF'in Delphi HTML içe aktarıcısının her kaynak URI'sini yerel olarak çözümlediğini, veri URI'lerini ve OnResource geri çağrılarını kabul ederken dosya ve ağ getirmelerini sayım ve bayt üst sınırlarının arkasında reddettiğini gösteren diyagram
Yalnızca yerel olarak gelen baytlar sayfaya ulaşır, reddedilen referanslar sayacı artırır ve hiçbir getirme işlemi süreçten dışarı çıkmaz
procedure TReportForm.SupplyResource(Sender: TObject;
  const URI: WideString; ResourceKind: THPDFHTMLResourceKind;
  var Data: TBytes; var ContentType: string; var Handled: boolean);
begin
  Handled := False;
  // Yalnızca izin verilenler listesindeki yerel bir varlık dizininden sun.
  // URI'yi asla bir HTTP istemcisine geçirme ve onu önce doğrulamadan
  // asla bir yolla birleştirme
  if StartsText('asset:', URI) then
  begin
    Data := LoadAssetBytes(Copy(URI, 7, MaxInt));
    ContentType := 'image/png';
    Handled := Length(Data) > 0;
  end;
end;

Gelen her kaynak yine de üç ayrı üst sınıra karşı sayılır: kaç kaynak, her birinin kaç baytı ve toplamda kaç bayt. Kendisinden istenen her şeyi sadakatle döndüren bir geri çağrı, aynı 50 MB'lık görüntüye iki yüz kez referans veren bir belge tarafından belleği tüketmeye ikna edilemez

Dört Bütçe, Bir Başarısızlık Modu

Ayrıştırıcı düğümleri, CSS kuralları, düzen işlemleri ve kaynaklar birbirinden bağımsız ölçülür, çünkü birbirinden bağımsız başarısız olurlar. Bir belge küçük olup düzenlemesi patolojik derecede pahalı olabilir; bir başkası sığ olup seçici eşleştirmeyi patlatacak şekilde tasarlanmış bir stil sayfasına sahip olabilir. Bunlardan herhangi birinin tükenmesi, bütçe tükenmesinin bozuk girdiden ayrı olarak yakalanabilmesi için özel bir istisna sınıfı olan EHPDFHTMLImportBudget'ı yükseltir

İstatistikler her RenderHTML5 çağrısının başında sıfırlanır, bu yüzden sayılar içe aktarıcı nesnesinin ömrünü değil tek bir içe aktarmayı tanımlar. Güvenilmeyen şablonları render eden bir hizmette, belge başına ParsedNodeCount, LayoutOperationCount ve ResourceRejectCount değerlerini günlüğe kaydedin: yükselen bir reddetme sayısı genellikle bir şablon yazarının geri çağrınızın tanımadığı varlıklara referans verdiği anlamına gelir ve üst sınıra yakın bir düzen işlemi sayısı, bir sonraki biraz daha büyük belgenin başarısız olacağı anlamına gelir

HTML İçe Aktarma ile Düzen API'si Arasında Seçim Yapmak

İçerik Delphi geliştiricisi olmayan kişiler tarafından HTML olarak yazıldığında, şablonlar koddan daha sık değiştiğinde veya aynı içerik e-posta olarak da gönderilmesi gerektiğinde HTML içe aktarma öne çıkar. etiketli çıktıyla bildirimsel PDF düzeni başlığında anlatılan bildirimsel düzen ağacı ise belge kendi kodunuz tarafından verilerden üretildiğinde, PDF/UA yapısının otomatik olarak yayımlanması gerektiğinde veya sayfalama üzerinde hassas denetim yazım kolaylığından daha önemli olduğunda öne çıkar

Bunlar birbirini dışlamaz. Yaygın bir şekil, bir raporun gövdesi için HTML içe aktarma yaparken başlık, altbilgi ve imza bloklarını doğrudan çizmektir, çünkü bunlar hiçbir şablonun sahiplenmemesi gereken sabit bir geometridir. Tablolar, her ikisini de ölçmeye değer olan tek alandır: karmaşık finansal tablolar bazen PDF'de tablo oluşturma başlığında anlatılan özel tablo yolu üzerinden daha iyi sayfalanır

CreateAcroForms'u bilinçli olarak ayarlayın. Kapalıyken, HTML form denetimleri yazdırılmış bir ekstrenin istediği gibi statik görsel olarak render edilir. Açıkken, doldurulabilir bir formun istediği gibi gerçek, etkileşimli AcroForm alanlarına dönüşür ve bunu izleyen alan davranışı yüklenmiş bir PDF'e form alanları ekleme başlığında ele alınmıştır

HTML içe aktarma, bildirimsel düzen ve doğrudan çizim API'si, Delphi ve C++Builder'da aynı belge nesnesini hedefler; eksiksiz özellik listesi HotPDF Delphi PDF bileşeni sayfasında bulunur