PDFium Component, BuildReflowDocument'ı kullanarak sabit düzenli bir PDF'yi yeniden akıtılabilen (reflow) anlamsal bir modele dönüştürür ve o modeli ToHtml üzerinden kendi kendine yeten HTML olarak dışa aktarır. Başlıklar başlık olarak kalır, liste öğeleri liste öğesi olarak kalır ve sayfada tespit edilen tablolar, üstbilgi hücreleri ve birleştirmeleri (span) korunmuş gerçek tablo işaretlemesi olarak çıkar. Çıktıda hiçbir şey harici bir betiğe (script) veya stil sayfasına başvurmaz
Bunu istemenin nedeni, bir PDF sayfasının konumlandırılmış gliflerden oluşan bir küme olmasıdır — bu, bir telefon ekranı, bir ekran okuyucu veya bir arama dizini için tam olarak yanlış olan şeydir. Bunu düz metin çıkararak çözmeye yönelik her girişim, belgeyi okunabilir kılan yapıyı kaybeder ve sayfaları görüntüye dönüştürerek çözmeye yönelik her girişim de metni tamamen kaybeder. Bir yeniden akış modeli her ikisini de korur: sözcükleri ve aralarındaki ilişkileri
Anlamsal bilgi nereden gelir?
Her şey, bileşendeki tek metin ve anlambilim kaynağı olan GetStructuredText'ten başlar. PDF bir yapı ağacı taşıdığında — ISO 32000-1'in 14.7 maddesinde tanımlanan etiketli PDF — model, üreticinin kaydettiği mantıksal hiyerarşiyi izler. Taşımadığında, ki gerçek dünyadaki çoğu PDF taşımaz, model, okuma sırası amaçları için zaten hesaplanmış fiziksel yerleşim sırasına geri döner
Bu seçim katı bir sınır korur: mevcut olanın yanıtlayabileceği sorulara yanıt vermek için ikinci bir PDF ayrıştırıcısı veya ikinci bir render motoru getirilmez. Altındaki okuma sırası mekaniği, yapılandırılmış metin blokları ve okuma sırası yazısında anlatılmıştır ve yeniden akış modeli, onun yerini almak yerine üzerine bir anlamsal katmandır
Her düğüm, bilgisinin nereden geldiğini kaydeder, bu yüzden bir tüketici, belgenin bildirdiği bir başlığı, yerleşim sezgisel yöntemlerinin (heuristic) çıkarsadığı bir başlıktan ayırt edebilir. Güvene duyarlı işlem hatları, tüm düğümleri eşit derecede yetkili saymak yerine bu alanı okumalıdır
Düz bir ağaç ve neden bir nesne ağacı değil?
Model, önce-sıra (pre-order) düzleştirilmiş bir ağaçtır: özyinelemeli bir kayıt veya sahiplikli bir nesne grafiği yerine, her düğümün bir ParentIndex ve bir Depth taşıdığı bir düğüm dizisi. Sayfalar, başlıklar, paragraflar, listeler, liste öğeleri, figürler, altyazılar, tablolar, satırlar ve hücrelerin hepsi o tek doğrusal dizide yaşar
İki fayda ortaya çıkar. Tüketiciler diziyi özyineleme olmadan sırayla akıtabilir, bu da HTML, Markdown veya bir ağaç görünümü üretmeyi basit bir döngü haline getirir. Ve düzen, bir ABI sınırı boyunca özyinelemeli yönetilen türleri ele alma biçimleri farklı olan Delphi, C++Builder ve Free Pascal arasında taşınabilir kalır. Dinamik dizilerden oluşan özyinelemeli bir kayıt, tam olarak her yerde derlenen ama her birinde ince bir biçimde farklı davranan türden bir yapıdır
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfReflowOptions;
Doc: TPdfReflowDocument;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.LoadDocument;
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.DetectTables := True;
Options.IncludeCss := True; // satır içi stil bloğu, harici dosya yok
Options.MaxNodes := 200000; // kapalıya-hata (fail-closed) bütçe
Options.MaxCharacters := 4000000;
Doc := Pdf.BuildReflowDocument(Options);
for I := 0 to High(Doc.Nodes) do
case Doc.Nodes[I].Kind of
prnkHeading:
Writeln(Format('%sH%d: %s', [StringOfChar(' ', Doc.Nodes[I].Depth),
Doc.Nodes[I].HeadingLevel, Doc.Nodes[I].Text]));
prnkParagraph:
Writeln(Format('%sp: %s', [StringOfChar(' ', Doc.Nodes[I].Depth),
Copy(Doc.Nodes[I].Text, 1, 60)]));
prnkTable:
Writeln(Format('table on page %d', [Doc.Nodes[I].PageNumber]));
end;
Writeln(Format('%d node(s), %d table(s), %d character(s)',
[Length(Doc.Nodes), Doc.TableCount, Doc.CharacterCount]));
finally
Pdf.Free;
end;
end;
Tablolar iki kez görünmekten nasıl alıkonur?
Tablo tespiti, bir sayfa için yapılandırılmış metin toplandıktan sonra çalışır; bu, belirgin bir tehlike yaratır: aynı hücre içeriği hem metin bloklarında hem de tespit edilen tabloda bulunur. İkisini de yayınlamak, her tablonun kendi içeriğini gevşek paragraflar olarak yeniden takip ettiği bir HTML üretir
Bunu çözen kural geometriktir. Tespit edilen bir tablo, bir metin bloğunun alanının yarısından fazlasını kapladığında, tablo düğümü ona katılmak yerine o bloğun yerini alır. Bir satır içindeki hücre indeksleme, kovalara (bucket) sayarak oluşturulur, bu yüzden modeli oluşturmak, her satır için her hücreyi yeniden taramak yerine hücre artı satır sayısında doğrusal kalır; bu, tek bir sayfanın yüzlerce hücre taşıyabildiği finansal belgelerde önemlidir
Tespit edilen yapı, tespit olduğu konusunda dürüsttür. Çizgili bir tablo, yalnızca boşlukla hizalanmış bir tablodan daha güvenilir bir şekilde tanınır ve düğümün güven değeri bunu yansıtır. Yanlış bir tablonun tablo olmamasından daha iyi olduğu içerikler için tespiti açık tutun; yanlış bir tablonun daha kötü olduğu arşiv dönüşümü için güven değerine göre kapı koyun
Kendi kendine yeten kalan HTML dışa aktarmak
ToHtml, zaten oluşturulmuş modelde gezinir ve hiçbir zaman PDFium'a geri dönmez, bu yüzden iki kez dışa aktarmak ekstra bir maliyete yol açmaz ve aynı modelden farklı bir sonuç üretemez. Metin ve öznitelik değerleri tek tip olarak kaçışlanır, başlık seviyeleri HTML'nin gerçekten tanımladığı h1'den h6'ya kadar olan aralığa sıkıştırılır ve üstbilgi hücreleri, RowSpan ve ColumnSpan yazıldığı gibi geçer
İsteğe bağlı CSS, düz bir satır içi stil bloğudur. Hiçbir betik, hiçbir web yazı tipi ve hiçbir türden harici kaynak yoktur; çıktıyı bir e-postaya, bir yardım görüntüleyicisine veya kum havuzuna alınmış (sandboxed) bir tarayıcı denetimine gömmeyi güvenli kılan şey de budur:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // sayfa sınırlarını görünür tut
Options.PreserveLineBreaks := False; // paragrafları tarayıcının sarmasına izin ver
Html := Pdf.BuildReflowDocument(Options).ToHtml;
Bytes := TEncoding.UTF8.GetBytes(string(Html));
Stream := TFileStream.Create('report.html', fmCreate);
try
if Length(Bytes) > 0 then
Stream.WriteBuffer(Bytes[0], Length(Bytes));
finally
Stream.Free;
end;
end;
PreserveLineBreaks, üzerinde düşünmeye en değer seçenektir. Bir PDF satır sonu, sabit bir sayfa genişliği için verilmiş bir dizgi kararıdır, bu yüzden onu dar bir ekranda korumak, yeniden akışın çözmek için var olduğu sorunun tam da kendisini yeniden üretir. Şiir, kod listeleri ve adresler için satır sonlarını koruyun; düz yazı için bırakın
Bütçeler, iptal ve sayfa durumu
Karakterlerin, düğümlerin, tabloların ve hücrelerin her birinin bir tavanı vardır ve her biri ayırmadan sonra değil önce denetlenir, bu yüzden bozuk veya kötü niyetli bir belge, başka bir şey durana kadar belleği tüketmek yerine temiz bir şekilde başarısız olur. İptal belirteci; sayfa, blok, tablo, satır ve hücre sınırlarında denetlenir, bu da bin sayfalık bir belgenin iptal edilen bir taramasını duyarlı tutar
Özellikle GUI uygulamaları için önemli bir davranış vardır: tüm belge taraması, etkin sayfayı geri yükleyen bir kapsam içinde çalışır, bu yüzden başarı, bütçe hatası ve iptal, çağıranın geçerli sayfasını dokunulmadan bırakır. Kullanıcının 340. sayfaya bakarken dışa aktarmasına izin veren bir görüntüleyici, sonrasında kendini hâlâ 340. sayfada bulur
Yeniden akış ne için iyidir, ne için değildir?
Yeniden akış çıktısı, arama dizinleme, erişilebilir okuma görünümleri, mobil görüntüleme ve içerik geçişi için mükemmel bir girdidir. Sadakati koruyan bir dönüştürücü değildir: mutlak konumlar, tam yazı tipleri, vektör görsel içerik ve kesin sayfa geometrisi, tasarım gereği amacının dışındadır. Bir işin sayfanın aynı görünmesine ihtiyacı olduğunda onu render edin; başka bir yerde okunabilir olmasına ihtiyacı olduğunda onu yeniden akıtın
Özellikle yardımcı teknoloji için, yeniden akış modeli, erişilebilir bir okuyucu oluşturma yazısında anlatılan okuma özellikleriyle eşleşir ve gerçek bir yapı ağacı taşıyan belgeler belirgin şekilde daha iyi modeller üretir; bu, PDF/UA yapı ağacı doğrulaması yazısında anlatıldığı gibi etiketlemeyi kaynağında doğrulamak için iyi bir gerekçedir
Yeniden akış, yapılandırılmış metin, etiketleme doğrulaması ve render etme, Delphi, C++Builder ve Lazarus genelinde tek bir belge nesnesini paylaşır; tam API Delphi için PDFium Component sayfasında anlatılmıştır