RTF, kimsenin planlamadığı yerlerde ortaya çıkacak kadar uzun zamandır piyasada: 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 DLL sürümü, doğrudan bu ardışık düzene yerleşen sanal bir cihaz bağlamını ortaya çıkarır
Mekanizma: sanal DC ve EM_FORMATRANGE
Zengin Metin denetimleri içeriklerini yalnızca fiziksel bir yazıcı için değil, herhangi bir aygıt bağlamı 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 değerini ilerletin; sayfa sayfa çıktı elde edersiniz. losLab PDF Kütüphanesinin GetCanvasDC'si, belirttiğiniz sayfa boyutlarına göre boyutlandırılmış bir bellek içi DC sağlar; içine bir sayfayı oluşturduktan sonra, LoadFromCanvasDc sonucu bir PDF sayfası olarak yakalar. Tüm süreç 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 boyutlarından daha küçük veya daha büyükse, sayfalandırma PDF'ye girenle uyuşmaz. A4 çıktısı için standart yaklaşım, DC'yi boyutlandırmak için kullanacağınız aynı ölçek yardımcılarını kullanarak RTF dosyasını yüklemeden önce denetimin piksel boyutlarını 96 DPI'da 210 x 297 mm ile eşleşecek şekilde ayarlamaktır
Delphi uygulaması
Aşağıda, kütüphanenin DLL sürümünü saran PDFlibAX_TLB içe aktarma birimi kullanılmıştır. Form bir TRichEdit ve bir düğme barındırır; formun OnCreate işleyicisi denetimi boyutlandırır ve RTF'yi yükler ve düğme tıklaması dönüştürme döngüsünü çalıştırır
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: TPDFLibrary;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
PdfDoc := TPDFLibrary.Create(Self);
// Sayfalandırmanın DC ile eşleşmesi için kontrolü ekran DPI'sında A4 boyutuna getirin
RichEdit1.Width := Round(ScaleX(210, mmPixel));
RichEdit1.Height := Round(ScaleY(297, mmPixel));
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'e göre boyutlandırılmış sanal bir DC elde edin
Dc := PdfDoc.GetCanvasDC(
Round(ScaleX(210, mmPixel)),
Round(ScaleY(297, mmPixel)));
// RTF içeriğinin sonraki sayfasını DC'ye aktarın
LastChar := PrintRtfBox(Dc, RichEdit1, LastChar);
// DC içeriklerini PDF belgesi olarak yakalayın
PdfDoc.LoadFromCanvasDc(96, 0);
PdfDocId := PdfDoc.SelectedPdfDocument;
PdfDoc.SaveToFile(
ExtractFilePath(Application.ExeName)
+ 'Output' + IntToStr(PageNumber) + '.pdf');
PdfDoc.RemovePdfDocument(PdfDocId);
Inc(PageNumber);
until LastChar = 0;
end;
function TForm1.PrintRtfBox(hDc: HDC; rtfBox: TRichEdit;
FirstChar: Integer): Integer;
var
RcDrawTo, RcPage: TRect;
Fr: TFormatRange;
NextCharPosition: Integer;
begin
RcPage.Left := 0;
RcPage.Top := 0;
RcPage.Right := rtfBox.Left + rtfBox.Width + 100;
RcPage.Bottom := rtfBox.Top + rtfBox.Height + 100;
RcDrawTo.Left := rtfBox.Left;
RcDrawTo.Top := rtfBox.Top;
RcDrawTo.Right := rtfBox.Left + rtfBox.Width;
RcDrawTo.Bottom := rtfBox.Top + rtfBox.Height;
Fr.hdc := hDc;
Fr.hdcTarget := hDc;
Fr.rc := RcDrawTo;
Fr.rcPage := RcPage;
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ı gösterir
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 ve benzeri adlara sahip bir PDF dosyası üretir. Bunun yerine tek bir çok sayfalı belge istiyorsanız, kütüphanenin sayfaya ekleme API'si, bunları daha sonra birleştirmenize olanak tanır veya döngüyü tek bir belge oturumu içinde AddPage öğesini çağıracak şekilde yeniden yapılandırabilirsiniz. Yukarıdaki her yineleme için SaveToFile ve ardından RemovePdfDocument deseni, en yüksek belleği bir sayfalık içeriğe bağlı tutar, bu da çok uzun RTF dosyaları için önemlidir
İnsanları yanıltan boyutlandırma ayrıntıları
LoadFromCanvasDc'ye giden 96 DPI bağımsız değişkeni, kütüphaneye DC'nin hangi ekran çözünürlüğünde işlendiğini bildirir, böylece PDF sayfası için doğru noktadan piksele eşlemeyi hesaplayabilir. Bunu yanlış yaparsanız, ekrandaki resim doğru görünse bile metin çıktıda yanlış boyutta görünür
RcPage.Right ve RcPage.Bottom değerine eklenen +100, denetimin görünür kenarının ötesindeki küçük bir kenar boşluğudur. Rich Edit sayfaları nerede böleceğine karar vermek için rcPage dikdörtgenini kullanır; kenar boşluğu olmadan, tam sınırda kalan bir çizgi iki sayfa boyunca kopyalanabilir. Bu sihirli bir sabit değil: sayfa sınırının son pikselde değil, denetimin düzen alanının tamamen içine düşecek kadar büyük olmasını istersiniz
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ü resimler ve çoğu Zengin Metin biçimlendirme özelliği herhangi bir ekstra işleme gerek kalmadan çalışır çünkü Rich Edit bunları yerel olarak işler. Şaşırtıcı olabilecek tek alan twip cinsinden ifade edilen özel paragraf aralığı veya ilk satır girintileri kullanan metindir: Rich Edit'in dahili koordinat sistemi twip (1/1440 inç) cinsindendir, TFormatRange'de ayarladığınız DC koordinatları mevcut DPI'da piksellerdedir. Denetim dahili olarak dönüştürür, ancak RTF'yi programatik olarak oluşturuyorsanız kenar boşluğu değerlerinizin doğru birimde olduğunu doğrulamalısınız
DPI farkındalığı ve yüksek DPI ekranlar
%150 ölçeklemede (144 DPI) çalışan bir ekranda, ScaleX(210, mmPixel) %100 ekrana göre daha büyük bir piksel sayısı döndürür. PDF Kütüphanesi GetCanvasDC'ye geçirdiğiniz piksel boyutlarını kaydeder ve PDF'deki fiziksel sayfa boyutunu geriye doğru hesaplamak için LoadFromCanvasDc içindeki DPI argümanını kullanır. Geçirdiğiniz DPI değeri, uygulamanızın çalıştığı DPI değeriyle eşleştiği sürece, çıktı sayfası boyutu, ekran ölçeklemesine bakılmaksızın doğru olacaktır
Uygulamanız DPI-farkında değilse (eski varsayılan), Windows ekran DC'sini ölçeklendirir ve piksel hesaplamalarınız yüksek DPI makinelerde yanlış olur. En basit düzeltme, uygulama bildiriminde DPI farkındalığını bildirmektir; uygulama daha sonra gerçek cihaz piksellerini alır ve LoadFromCanvasDc'ye geçirdiğiniz 96 GetDeviceCaps(GetDC(0), LOGPIXELSX)'ten elde edilen gerçek ekran DPI'sı ile değiştirilmelidir. Yukarıdaki kod örneği, %100 ölçekleme ortamı için uygun olduğundan ve örneği kısa tuttuğu için 96'yı sabit kodlar
Çı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ığı alt akış kullanımına bağlıdır. Rapor oluşturma sistemleri genellikle tek tek sayfalara ihtiyaç duyar çünkü son belgeyi daha sonra sayfaları birleştirerek veya yeniden sıralayarak bir araya getirirler. Baştan tek bir PDF istiyorsanız, kütüphane tek bir oturumda birden çok sayfası olan bir belge oluşturmanıza izin verir: döngü dışında belgeyi bir kez oluşturun, döngü içinde SaveToFile yerine sayfa ekleme yöntemini çağırın ve döngü bittikten sonra eksiksiz belgeyi kaydedin. Bu, ara dosyalardan kaçını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 Kütüphanesinin bir parçasıdır