RTF, kimsenin planlamadığı yerlerde ortaya çıkacak kadar uzun ömürlüdür: eski rapor oluşturucular, adres mektup birleştirme boru hatları, modern kelime işlemcilerinden önceki yasal belge arşivleri. Bunu anında PDF'e dönüştürmek yinelenen bir gereksinimdir ve Windows'ta gerçekten işe yarayan yaklaşım özel bir RTF ayrıştırıcısı değil, Windows'un kendisinin TRichEdit ve EM_FORMATRANGE aracılığıyla zaten sağladığı işleme yoludur. losLab PDF Library, bu ardışık düzene doğrudan yerleşen sanal bir device context ortaya koyar
Mekanizma: sanal DC ve EM_FORMATRANGE
Rich Edit denetimleri içeriklerini yalnızca fiziksel bir yazıcı için değil, herhangi bir device context için sayfalara ayırabilir. EM_FORMATRANGE mesajı, denetime bir karakter aralığını belirli bir DC'ye yerleştirmesini söyler ve sığdırmayı başardığı son karakterin konumunu döndürür. Bunu art arda çağırın, her seferinde cpMin'i ilerletin; sayfa sayfa çıktı elde edersiniz. losLab PDF Library'nin GetCanvasDC'si, belirlediğiniz sayfa ölçülerine göre boyutlandırılmış bellek içi bir DC sağlar; bir sayfayı içine render ettikten sonra LoadFromCanvasDC, sonucu yeni tek sayfalık bir PDF belgesi olarak yakalar. Ardışık düzenin tamamı budur
En baştan doğru yapılması gereken bir şey var: TRichEdit denetimi hedef sayfayla eşleşecek şekilde boyutlandırılmalıdır. Denetim DC ölçülerinden küçük ya da büyükse sayfalandırma, PDF'ye girenle uyuşmaz. A4 çıktısı için standart yaklaşım, RTF dosyasını yüklemeden önce denetimin piksel ölçülerini, DC'yi boyutlandırmak için kullandığınız aynı milimetre-to-piksel dönüşümüyle, daha sonra LoadFromCanvasDC'ye geçireceğiniz DPI'da 210 x 297 mm'ye eşitlemektir
Delphi uygulaması
Aşağıdaki kod, kütüphanenin ActiveX sürümü için type library içe aktarımı olan PDFlibAX_TLB içe aktarma birimini kullanır. Form bir TRichEdit ve bir düğme barındırır; formun OnCreate işleyicisi denetimi boyutlandırıp RTF'yi yükler, düğme tıklaması ise dönüştürme döngüsünü sürer
unit MainUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, PDFlibAX_TLB, ActiveX;
type
TForm1 = class(TForm)
RichEdit1: TRichEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
function PrintRtfBox(hDc: HDC; rtfBox: TRichEdit;
FirstChar: Integer): Integer;
end;
var
Form1: TForm1;
PdfDoc: IPDFlibrary;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
PdfDoc := CoPDFlibrary.Create;
// Sayfalandırmanın DC ile eşleşmesi için denetimi ekran DPI'sında A4 boyutuna getirin
RichEdit1.Width := MulDiv(210, Screen.PixelsPerInch * 10, 254);
RichEdit1.Height := MulDiv(297, Screen.PixelsPerInch * 10, 254);
RichEdit1.Lines.LoadFromFile(
ExtractFilePath(Application.ExeName) + 'document.rtf');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Dc: HDC;
PageNumber, LastChar, PdfDocId: Integer;
begin
PageNumber := 1;
LastChar := 0;
repeat
// A4 boyutunda sanal bir DC alın
Dc := PdfDoc.GetCanvasDC(
MulDiv(210, Screen.PixelsPerInch * 10, 254),
MulDiv(297, Screen.PixelsPerInch * 10, 254));
// RTF içeriğinin sonraki sayfasını DC'ye render edin
LastChar := PrintRtfBox(Dc, RichEdit1, LastChar);
// DC içeriklerini bir PDF belgesi olarak yakalayın
PdfDoc.LoadFromCanvasDC(Screen.PixelsPerInch, 0);
PdfDocId := PdfDoc.SelectedDocument;
PdfDoc.SaveToFile(
ExtractFilePath(Application.ExeName)
+ 'Output' + IntToStr(PageNumber) + '.pdf');
PdfDoc.RemoveDocument(PdfDocId);
Inc(PageNumber);
until LastChar = 0;
// Denetimin DC için cache'lediği biçimlendirme verisini bırakmasını sağlayın
SendMessage(RichEdit1.Handle, EM_FORMATRANGE, 0, 0);
end;
function TForm1.PrintRtfBox(hDc: HDC; rtfBox: TRichEdit;
FirstChar: Integer): Integer;
var
Fr: TFormatRange;
Dpi, PageWidthTw, PageHeightTw, NextCharPosition: Integer;
begin
// rc ile rcPage piksel değil twip'tir (1/1440 inç): sayfa boyutunu, DC'yi ve
// denetimi boyutlandıran aynı DPI ile çevirin
Dpi := Screen.PixelsPerInch;
PageWidthTw := MulDiv(rtfBox.Width, 1440, Dpi);
PageHeightTw := MulDiv(rtfBox.Height, 1440, Dpi);
Fr.hdc := hDc;
Fr.hdcTarget := hDc;
Fr.rcPage := Rect(0, 0, PageWidthTw, PageHeightTw);
Fr.rc := Fr.rcPage; // bütün sayfaya çiz; kenar boşlukları için daraltın
Fr.chrg.cpMin := FirstChar;
Fr.chrg.cpMax := -1;
NextCharPosition :=
SendMessage(rtfBox.Handle, EM_FORMATRANGE, 1, LPARAM(@Fr));
if NextCharPosition < Length(rtfBox.Text) then
Result := NextCharPosition
else
Result := 0; // son sayfayı işaret eder
end;
end.
Döngü ne yapıyor
PrintRtfBox, TFormatRange yapısını doldurur ve SendMessage aracılığıyla Zengin Metin denetimine geçirir. Denetim cpMin'den başlayarak karakterleri işler, DC dolduğunda durur ve sığmayan ilk karakterin konumunu döndürür. Dönüş değeri toplam metin uzunluğuna eşit veya bu uzunluğu aştığında, her karakter işlenmiş olur ve işlev sıfır döndürerek repeat...until döngüsünü sonlandırır
Her yineleme Output1.pdf, Output2.pdf diye giden bir PDF dosyası üretir. Bunun yerine tek bir çok sayfalı belge istiyorsanız, AddToFileList artı MergeFileList sayfa başına dosyaları sonradan bir araya getirebilir; ya da döngüyü, her yeni sayfa belgesini MergeDocument ile birincisine katlayacak şekilde yeniden kurabilirsiniz. Yukarıdaki yineleme başına SaveToFile ve ardından RemoveDocument deseni, tepe belleği bir sayfalık içerikte sınırlı tutar; çok uzun RTF dosyaları için bu önemlidir
İnsanları yanıltan boyutlandırma ayrıntıları
LoadFromCanvasDC'ye giden DPI bağımsız değişkeni, kütüphaneye kaç pikselin bir inç yaptığını söyler: sayfa, tuval genişliğinin DPI'ya bölümü kadar 72 punto genişlikte çıkar; yükseklik için de aynısı. Bunu yanlış alırsanız görüntü ekranda doğru görünürken metin çıktıda yanlış boyutta belirir
rc ve rcPage'in birimi, bu tekniğin çoğu Delphi örneğinin yanlış aldığı ayrıntıdır. EM_FORMATRANGE iki dikdörtgeni de, çizim yaptığı DC piksellerle çalışsa da twip cinsinden okur; inçin 1/1440'ı. Onları piksel değerleriyle doldurursanız denetim, 96 DPI'da on beş kat küçük bir sayfaya metin dizer: geçiş başına bir avuç sözcük ve on sayfalık bir belgeyi yüzlerce neredeyse boş PDF'ye çeviren bir döngü. DC'yi boyutlandıran DPI ile MulDiv(Pixels, 1440, Dpi) üzerinden çevirmek, sayfa kırılımını tuvalin gerçekte bittiği yere koyar. rcPage bütün sayfadır; rc metnin işgal edebileceği alandır, dolayısıyla kenar boşlukları rc'nin onun içinde daraltılmasından gelir; o da twip ile. Döngü bittiğinde nil lParam'lı bir EM_FORMATRANGE daha, denetime DC için cache'lediği biçimlendirme verisini serbest bırakmasını söyler
Son olarak, denetim FormCreate çalıştırıldığında görünür bir form penceresine eklenmiş olmalıdır, böylece pencere tanıtıcısı SendMessage'a ilk çağrı yapılmadan önce geçerli olur. Çalışma zamanında dinamik olarak oluşturulan bir TRichEdit form henüz gösterilmediyse işleme döngüsü başlamadan önce açık bir HandleNeeded çağrısına ihtiyaç duyar
Yazı tiplerini ve RTF özelliklerini işleme
Oluşturma Windows Rich Edit motoru tarafından yapıldığı için, yazı tipi değiştirme aynı görüntüleme ve yazdırma kurallarını izler. Makinede yüklü olan RTF dosyasında başvurulan yazı tipleri aslına uygun olarak oluşturulur; eksik olan yazı tipleri sessizce değiştirilir, bu da satır uzunluklarını ve sayfalandırmayı değiştirebilir. Üretim toplu dönüştürmesi için bunu açıkça test etmeye değer: RTF kaynaklarınızın kullandığı her bir yazı karakteri ile bir belge yükleyin ve çıktı sayfa sayısının manuel bir baskı önizlemesinden beklediğinizle eşleştiğini onaylayın
Tablolar, gömülü görüntüler ve çoğu Zengin Metin biçimlendirme özelliği, Rich Edit onları doğal olarak render ettiği için ek işlem olmadan çalışır. Şaşırtabilecek tek alan, twip cinsinden ifade edilen özel paragraf aralığı ya da ilk satır girintisi kullanan metindir: Rich Edit twip (1/1440 inç) ile ölçer, TFormatRange'deki dikdörtgenlerle aynı birim; altındaki DC ise geçerli DPI'da piksellerle çalışır. Denetim çizerken birini diğerine eşler; ama RTF'yi programatik kuruyorsanız kenar boşluğu değerlerinizin de twip olduğunu doğrulamalısınız
DPI farkındalığı ve yüksek DPI ekranlar
%150 ölçeklemeyle (144 DPI) çalışan bir ekranda, milimetre-to-piksel dönüşümü %100'lük ekrandan daha büyük bir piksel sayısı döndürür. PDF Library, GetCanvasDC'ye geçirdiğiniz piksel ölçülerini kaydeder ve PDF'deki fiziksel sayfa boyutunu geriye hesaplamak için LoadFromCanvasDC'nin DPI bağımsız değişkenini kullanır. Geçirdiğiniz DPI değeri, milimetreleri piksele çevirirken kullandığınız değer olduğu sürece çıktı sayfa boyutu, ekran ölçeklemesinden bağımsız olarak doğrudur
Uygulamanız DPI-farkında değilse (eski varsayılan), Windows ekran DC'sini ölçekler ve piksel hesaplarınız yüksek DPI makinelerde yanlış olur. En basit çözüm, uygulama manifestosunda DPI farkındalığı bildirmektir; uygulama böylece gerçek aygıt piksellerini alır ve örneğin LoadFromCanvasDC'ye geçirdiği Screen.PixelsPerInch değeri, %100 ölçekleme varsaymak yerine gerçek ekran DPI'sını bildirir
Çıktı yapısı: tek bir birleştirilmiş belgeye karşı sayfa başına bir dosya
Yukarıdaki döngü her sayfayı ayrı bir PDF dosyasına yazar. Bunun istediğiniz şey olup olmadığı akış aşağısı kullanıma bağlıdır. Rapor üretim sistemleri çoğu zaman tek tek sayfalara ihtiyaç duyar; çünkü nihai belgeyi daha sonra sayfaları birleştirerek ya da yeniden sıralayarak kurarlar. Baştan tek bir PDF istiyorsanız kütüphane onu tek oturumda kurmanıza izin verir: ilk LoadFromCanvasDC çağrısının oluşturduğu belgenin ID'sini saklayın, sonraki her geçişte onu SelectDocument ile seçin ve SaveToFile yerine yeni sayfanın belge ID'siyle MergeDocument çağırın; sonra döngü çıktıktan sonra bir kez kaydedin. Bu, ara dosyaları ortadan kaldırır ve çoğu tek belgeli dönüştürme senaryosu için doğru yapıdır
Büyük RTF dosyaları için, dönüştürme hızı kabaca sayfa sayısıyla orantılı olduğundan ve 200 sayfalık bir belge birkaç saniye sürebildiğinden döngüye bir ilerleme geri bildirimi eklemeye değer. repeat...until yapısının genişletilmesi kolaydır: her yinelemeden sonra RichEdit1.GetTextLen ile toplam karakter sayısına bölünen LastChar değerini kullanarak bir ilerleme çubuğu güncellemesinde karakter sapmasını izleyin
Burada gösterilen GetCanvasDC ve LoadFromCanvasDC yöntemleri, Delphi ve C++Builder için losLab PDF Library'nin parçasıdır