HotPDF Component, THPDFHTMLImporter.RenderHTML5 HTML'i Delphi'de PDF'e dönüştürürken CSS multi-column metnini dizer. Sayfalı HTML motoru, column-count ile column-width'i içerik kutusuna göre çözer, her multicol kapsayıcının son parçasını ikili aramayla dengeler, önceki sayfaları sütun sütun sayfa dibine kadar doldurur ve column-fill, column-span: all ile column-rule destekler
Bunu istemenin olağan nedeni sıkıcı ama gerçektir: iki sütunlu bir ürün bülteni, üç dar sütuna dizilmiş bir hükümler ve koşullar eki ya da pazarlama ekibinin HTML'de tasarladığı basılı bültene benzemesi gereken bir sürüm notları PDF'i. Sütun desteği olmadan içe aktarıcı her paragrafı tek geniş satır ölçüsünde yığar, satır uzunluğu ikiye katlanır ve belge bir günlük dosyası gibi okunur. Desteğiyle, tarayıcı ön izlemesini süren aynı stil sayfası PDF'i de sürer ve zor kısım sayfalara bölmeye dönüşür; tarayıcıların ekranda çoğunlukla hiç düşünmek zorunda kalmadığı kısım
HotPDF kaç sütun kullanacağına nasıl karar verir?
HotPDF sütun sayısını, CSS Multi-column Layout Module Level 1 §3.4'teki sözde-algoritmayla aynı kurallarla çözer; dolgu ve kenarlıklar çıkarıldıktan sonraki içerik kutusu genişliği W'ye uygulanır. Bildirilmiş bir column-count olduğu gibi alınır. Yalnızca column-width verildiğinde motor, en az bir sütun olacak biçimde N = Trunc((W + gap) / (column-width + gap)) hesaplar. İkisi de mevcutken sonuç ikisinin küçüğüdür; bildirilen sayı türetilen sayıyı tavanlayabilir ama hiç şişiremez
Somut bir örnek: 483pt içerik kutusu (56pt yan kenar boşluklu A4, yuvarlanmış) ile column-width: 140pt; column-gap: 12pt, Trunc(495 / 152) = 3 sütun verir; her biri (483 - 2 * 12) / 3 = 153pt genişliğindedir. Aynı kurala column-count: 2 eklemek onu iki sütuna düşürür, çünkü küçük olan kazanır. normal boşluk anahtar sözcüğü, kapsayıcı yazı tipi boyutunun 1em'ine çözülür ve bu çözümleme ayrıştırma zamanında değil düzen zamanında olur; aynı kuralda column-gap'ten sonra bildirilmiş bir font-size bile doğru boşluğu üretir
Korumalardan biri spesifikasyonda yoktur ve orası bilerek ayarlanmıştır. Sütun genişliği hesaplandıktan sonra 12pt altındaki her değer kapsayıcıyı tek sütuna geri çöker. column-count: 9999 diyen bir stil sayfası — kazayla olsun güvenilmeyen bir şablondan gelmiş olsun — aksi hâlde negatif ya da mikroskobik genişlikli binlerce sütun ve her satıra bir glif sığdırmaya çalışan bir satır kırıcı üretirdi. Tek sütuna düşmek çıktıyı okunur tutar ve düzen maliyetini sınırlı tutar. Sıfır ya da negatif sayılar hiç oraya varamaz: doğrulayıcı column-count için yalnızca auto ya da pozitif bir tamsayı kabul eder; column-width ile column-gap sırasıyla auto ya da normal, ya da nokta ya da em cinsinden bir uzunluk alır
Delphi'de çok sütunlu bir HTML bölümünü PDF'e render etmek
Çok sütunlu çıktı, içe aktarıcıda bir anahtar gerektirmez. THPDFHTMLImporter, içine çizdiği THotPDF belgesiyle kurulur; BeginDoc çoktan çalışmış olmalıdır ve sütun özellikleri normal kaskadla gelir — ya işaretlemeye ya da RenderHTML5'in ikinci argümanı olarak verilen yazar stil sayfasına yazılmıştır
uses
SysUtils, HPDFDoc, HPDFHTMLImport;
procedure BuildBulletin(const FileName: string);
var
Pdf: THotPDF;
Importer: THPDFHTMLImporter;
Html, Css: WideString;
begin
Pdf := THotPDF.Create(nil);
Importer := THPDFHTMLImporter.Create(Pdf);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Importer.Margin := 56;
Importer.BaseFontName := 'Arial';
Importer.BaseFontSize := 10;
Css := 'section.news { column-width: 140pt; column-gap: 12pt; ' +
' column-rule: 0.5pt solid #999999 } ' +
'section.news h2 { column-span: all }';
Html := '<section class="news">' +
'<p>Release 4.2 ships the new export pipeline...</p>' +
'<p>Licensing changes take effect on 1 March...</p>' +
'<h2>Known issues</h2>' +
'<p>Import of legacy archives & templates...</p>' +
'</section>';
if not Importer.RenderHTML5(Html, Css) then
raise Exception.Create('Nothing was emitted');
Pdf.EndDoc;
finally
Importer.Free;
Pdf.Free;
end;
end;
Son paragraftaki çift kaçışa dikkat: Pascal dizgesi içindeki &, ampersandın HTML varlığıdır ve içe aktarıcı onu tek karaktere geri çözer. Kaskadın geri kalanı, bütçeler ve kaynak callback'leri, HotPDF HTML5 sayfalı medya içe aktarımına genel bakışta anlatıldığı gibi çalışır; sütunlar aynı profildeki bir düzen modundan ibarettir
Sütunları ikili aramayla dengelemek
HotPDF sütunları, açgözlü bir doldurmanın kalan her satırı hâlâ yerleştirdiği en küçük sütun yüksekliğini arayarak dengeler. Açgözlü geçiş basittir: satırları, sıradaki satır aday yüksekliği aşacak kadar ilerleyinceye dek birinci sütuna yürütür, sonra ikinci sütunda sürdürür ve böyle gider. Arama alt sınır olarak en uzun tek satırla, üst sınır olarak bütün satır yüksekliklerinin toplamıyla başlar, her turda aralığı ikiye böler ve sınırlar 0.05pt içine girdiğinde durur
İkili arama yalnızca yüklem monotonken çalışır ve burada öyledir: daha uzun bir sütun, daha kısa olandan daha az satır tutamaz; yükseklik büyürken "her satır sığar" yüklemi false'tan true'ya tam bir kez döner. Bu özellik yakınsamayı garanti eder ve bulunan yükseklikteki son açgözlü doldurmanın, uydurma bir ek sayfa tetikleyecek öksüz bir satır bırakmak yerine her satırı tükettiğini garanti eder. Sonucun bir ayrıntısını da açıklar: dengeli bant, satır kutularının izin verdiği kadar kısadır; sütunlar eşit olmak zorunda değildir, çünkü satırlar bölünemez ve son sütun ötekilerden bir iki satır yukarıda bitebilir
Sütunlar bir sayfa sonunu aştığında ne olur?
Geçerli sayfaya sığmayan sütunlar sırayla sayfa dibine kadar doldurulur ve yalnızca son sayfa parçası dengelenir. Bu, CSS Multi-column Layout Level 1 §7.1'in parçalanmış bağlamlarda column-fill: balance için öngördüğü davranıştır ve basılı bir bültenin görünümü de budur: son sayfa dışında her sayfada tam sütunlar; son sayfada kalan metin, boş bir sütunun yanında tek uzun sütun bırakmak yerine eşit bölünür
Her sayfa parçası aynı parçalayıcıdan bağımsız geçer. Kalan satırlar mevcut yüksekliğin N sütununa sığıyorsa parça dengelenir; sığmıyorsa her sütun sayfa dibine kadar dolar ve döngü yeni bir sayfa açar. Motorun break-inside: avoid kararları ve kapsam ölçümü için kullandığı bütün kapsayıcının doğal yüksekliği, sınırsız kapasiteli dengeli bant yüksekliğidir; böylece taze bir sayfada dengeli bir blok olarak sığacak kapsayıcı dilimlenmek yerine bütün hâliyle oraya taşınır. Tek bir satır bütün bir sayfadan uzun olduğunda motor kısıtı gevşetir, satırı yine de bir sayfanın en üstüne yerleştirir ve multi-column özneli bir hdkPaginationConstraint teşhisi raporlar
column-fill: auto ve sabit yükseklikli kutular
column-fill: auto ile kesin bir height birlikteyken HotPDF sütunları sırayla doldurur ve her sütunun yüksekliği, kalan sayfa alanı ile kapsayıcı yüksekliğinin dolgusu ve kenarlıkları çıkarılmış hâlinin küçüğüdür. Bu minimum göründüğünden daha önemlidir. Yalnızca kalan sayfa alanını kullanmak, sayfada kutudan fazla yer kaldığında birinci sütunun her satırı yutmasına izin verirdi. Kesin bir yükseklik yokken column-fill: auto, tarayıcılar gibi dengelemeye döner; çünkü kısıtsız bir multicol kapsayıcının doldurulacak bir sütun yüksekliği yoktur. İlk uygulama o yedeği kaçırmıştı ve 27 regresyon kontrolünden biri yakalamıştı
column-span: all ve iç içe paragraflar
HotPDF, CSS Multi-column Layout Level 1 §6.1'i izleyerek column-span: all'u çok sütunlu bir kapsayıcının doğrudan çocuklarında destekler. Yayılan bir çocuk kapsayıcıyı parçalara böler: önündeki çocuklar bir sütun bandı kurar, yayılan öğe tam genişlik boyunca render edilir ve arkasındaki çocuklar yeni bir bant başlatır. Her bant kendi içinde dengelenir, doldurulur, çizgilenir ve sayfalanır; üç sütunlu bir bölümün ortasındaki başlık böylece aralarında tam genişlikli bir başlık bulunan iki dengeli bant üretir — insanların taslaklarda gerçekten çizdiği düzen
Oraya varmak önce daha az görünür bir sorunu düzeltmeyi gerektirdi. <div style="column-count:2"> gibi, birkaç <p> çocuğu olan bir kapsayıcı, her paragrafı tek bir üst üste sütunda render ediyordu; çünkü her paragraf kendi metin bloğu olarak diziliyor ve ebevendeki sütun bildirimi hiç uygulanmıyordu. HotPDF artık iç içe blok çocukların metnini kapsayıcı için tek bir akışta topluyor. Toplayıcı bunu yaparken her blok sınırında katı bir satır sonu eklemek zorunda; atlanırsa bir paragrafın son sözcüğüyle sonrakinin ilk sözcüğü aynı satırda birleşir — test metni tesadüfen noktalama ile bitiyorsa kolayca gözden kaçan bir hata
Birleşik akışın bilinçli bir kapsamı vardır; yalnızca bütün alt ağaç düz metin akışıyken kullanılır. Form kontrolleri, görseller, tablolar, listeler ya da herhangi bir yerinde <hr> barındıran bir kapsayıcı sıradan blok yolunu korur ve tek sütunda render edilir; birleşik metin toplayıcısı onların geometrisini ifade edemez. Tabloların özellikle kendi sayfalama kuralları vardır — HotPDF'te rowspan yönetimi ve yinelenen tablo başlıkları makalesinde işlenir — ve onları bir sütun bandına karıştırmak o garantileri bozar
Sütun çizgisi nereye gelir ve nasıl test edilir
HotPDF column-rule'u, CSS Multi-column Layout Level 1 §4'ün anlattığı gibi her boşluğun orta çizgisine — bir sütunun sağ kenarı artı boşluğun yarısı — her sayfa parçasında bandın tepesinden dibine çizer. Genişlik 1pt'ye varsayılandır. Renk, bildirilmemişse ya da currentColor'sa öğenin metin rengine varsayılandır. Stil anahtar sözcüğü olmayan column-rule kısayolu solid sayılır; border kısayolunun yaygın okunuşuyla uyumludur, none ile hidden ise çizgiyi bastırır
Sütun geometrisi, PDF'i ayrıştırmadan gözlenebilir. THPDFHTMLImporter'daki OnLineLayout olayı çizilen her satır için bir THPDFHTMLLineLayout kaydıyla bir kez tetiklenir ve motor her sütunu ayrı çizdiği için her satırın X alanı sütununun sol kenarıdır. Komşu sütunlar tam olarak sütun genişliği artı boşluk kadar farklıdır; regresyon testinde 12pt boşluğu 20pt boşluktan ayırmayı kolaylaştıran da budur
type
TColumnProbe = class
public
Lines: TStringList;
constructor Create;
destructor Destroy; override;
procedure LineDrawn(Sender: TObject; const Line: THPDFHTMLLineLayout);
end;
constructor TColumnProbe.Create;
begin
inherited Create;
Lines := TStringList.Create;
end;
destructor TColumnProbe.Destroy;
begin
Lines.Free;
inherited;
end;
procedure TColumnProbe.LineDrawn(Sender: TObject;
const Line: THPDFHTMLLineLayout);
begin
// PageIndex sıfır tabanlıdır; X, satırın sütununun sol kenarıdır
Lines.Add(Format('page=%d x=%.1f y=%.1f %s',
[Line.PageIndex, Line.X, Line.Y, string(Line.Text)]));
end;
// Bağlantı, RenderHTML5'ten önce:
// Probe := TColumnProbe.Create;
// Importer.OnLineLayout := Probe.LineDrawn;
Bu olaya karşı test yazmaktan iki pratik not. PageIndex sıfır tabanlıdır; elle yazılan bir assertion'ın ilk yanlış aldığı şey budur. X hizalama kaymalarını da içerir; ortalanmış ya da sağa hizalı metin için sütun konumlarını sola hizalı test verilerinde karşılaştırın. Çizginin kendisi olayda görünmez; konumunu öne sürmek için sıkıştırılmamış içerik akışında dikey m ile l operatörlerini tarayın
Bilmeye değer sınırlar ve tuzaklar
Birkaç davranış yaklaşımdır ve bunları düzen incelemesinden sonra değil önce bilmek iyidir. Birleşik sütun akışının içinde paragraf aralığı, her blok sınırında kapsayıcı satır yüksekliğinin bir satırıyla yaklaşıklandırılır; sütun bandı içindeki paragraf başına kenar boşlukları tam olarak yeniden üretilmez. column-span: all bir kapsayıcıyı böldüğünde her parça kapsayıcı dolgusunu ve arka plan kutusunu yineler; parçalar görsel olarak süreklidir ama katı CSS anlamında tek kutu değildir. none ile hidden dışındaki her column-rule-style değeri doğrulamayı geçer ama çizgi solid olarak konturlanır
Desteklenmeyen değerler sessizce başarısız olmaz. StrictCSS'i True yapmak her teşhisi bir EHPDFHTMLUnsupportedCSS istisnasına çevirir; OnDiagnostic ise toplu işin onları günlüğe yazıp sürdürmesine izin verir. Katı modun sayfalama teşhisine de uygulandığına dikkat edin; bir sayfadan uzun satırı olan belge, kısıtı sessizce gevşetmek yerine StrictCSS altında istisna fırlatır
procedure TReportJob.HtmlDiagnostic(Sender: TObject;
const Diagnostic: THPDFHTMLDiagnostic);
begin
case Diagnostic.Kind of
hdkUnsupportedProperty, hdkUnsupportedValue:
FLog.Add('CSS ' + Diagnostic.Subject + ': ' + Diagnostic.MessageText);
hdkPaginationConstraint:
FLog.Add('Layout relaxed (' + Diagnostic.Subject + '): ' +
Diagnostic.MessageText);
else
FLog.Add(Diagnostic.MessageText);
end;
end;
// Şablon yazımı: motorun yok sayacağı her şeyde hızlı başarısız ol
Importer.StrictCSS := FValidatingTemplate;
Importer.OnDiagnostic := HtmlDiagnostic;
Katı mod, şablon yazılırken doğru ayardır; column-span: both gibi bir yazım hatası, başlığın sütunların içinde sessizce akmasına izin vermek yerine anında reddedilir. Üretimde OnDiagnostic üzerinden günlükleme genellikle daha iyi takastır; müşteri kaynaklı HTML'deki tek tuhaf bildirim bütün belgeye mal olmamalı. Özdeş teşhisler (aynı tür, özne ve değer) MaxDiagnostics listesine sığdıkları sürece bir kez bildirilir; liste 256 girdiyle varsayılandır, böylece kaskadın yüzlerce öğe için ziyaret ettiği bir bildirim günlüğü boğmaz
SSS
HotPDF, columns kısayolunu destekler mi?
Evet. columns kısayolu, herhangi bir sırada en fazla bir pozitif tamsayı sayı ile bir sütun uzunluğu kabul eder ve auto her iki longhand'ı sıfırlar. Çözülen değerler, tekil özelliklerle aynı sayı ve genişlik kurallarını izler
Çok sütunlu bir kapsayıcı görsel ya da tablo tutabilir mi?
Tutabilir ama HotPDF o zaman o kapsayıcıyı tek sütunda render eder. Sütun akışı yalnızca tüm torunlar düz metin akışıyken uygulanır; çünkü görsellerin, tabloların, listelerin ve form kontrollerinin kendi kutu geometrisi ile sayfalama kuralları gerekir
Son sayfa neden dengeli sütunlar gösterirken önceki sayfalar tamdır?
Bu, sayfalı medyada column-fill: balance için CSS kuralıdır: yalnızca son parça dengelenir. Önceki sayfalar her sütunu sayfa dibine kadar doldurur; belgenin ortasında yer boşa gitmez
Multi-column düzen, HTML5 sayfalı medya profili ve HTML-to-PDF hattının geri kalanı Delphi ve C++Builder için HotPDF Component'in parçasıdır; bileşen doğrudan PDF çizimini, formları, şifrelemeyi ve metin çıkarmayı da kapsar