Teknik Makale

losLab PDF Kütüphanesi ile Delphi'de RTF'den PDF'e Dönüştürme

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