RTF existuje dostatočne dlho, aby sa objavil na miestach, ktoré nikto neplánoval: staré generátory zostáv, kanály hromadného odosielania, archívy právnych dokumentov, ktoré predchádzajú moderným textovým procesorom. Konverzia na PDF za behu je opakujúcou sa požiadavkou, a prístup, ktorý naozaj funguje na Windows, nie je vyhradený RTF parser, ale renderovacia cesta, ktorú samotný Windows už poskytuje cez TRichEdit a EM_FORMATRANGE. DLL edícia knižnice losLab PDF vystavuje virtuálny kontext zariadenia, ktorý sa priamo zaradí do tohto kanálu.
Mechanizmus: virtuálne DC a EM_FORMATRANGE
Ovládacie prvky Rich Edit môžu stránkovať svoj obsah pre akýkoľvek kontext zariadenia, nielen pre fyzickú tlačiareň. Správa EM_FORMATRANGE hovorí ovládaciemu prvku, aby rozložil rozsah znakov do daného DC a vráti pozíciu posledného znaku, ktorý sa mu podarilo umiestniť. Zavolajte ho opakovane, pričom zakaždým pokročíte s cpMin, a dostanete stránkový výstup. Funkcia GetCanvasDC knižnice losLab PDF poskytuje DC v pamäti s rozmermi ľubovoľnej stránky, ktorú zadáte; po vykreslení stránky doň LoadFromCanvasDc zachytí výsledok ako stránku PDF. To je celý kanál.
Jedna vec, ktorú treba zariadiť vopred: ovládací prvok TRichEdit musí byť dimenzovaný zodpovedajúci cieľovej stránke. Ak je ovládací prvok menší alebo väčší ako rozmery DC, stránkovanie sa nezhoduje s tým, čo skončí v PDF. Pre výstup A4 je štandardným prístupom nastaviť pixelové rozmery ovládacieho prvku tak, aby zodpovedali 210 x 297 mm pri 96 DPI pred načítaním súboru RTF, pomocou rovnakých pomocníkov pre škálovanie, ktoré použijete na dimenzovanie DC.
Implementácia v Delphi
Nasledujúci príklad používa importnú jednotku PDFlibAX_TLB, ktorá obaluje DLL edíciu knižnice. Formulár obsahuje TRichEdit a tlačidlo; obsluha OnCreate formulára nastaví veľkosť ovládacieho prvku a načíta RTF a kliknutie na tlačidlo riadi konverzný cyklus.
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);
// Size the control to A4 at screen DPI so pagination matches the DC
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
// Obtain a virtual DC sized to A4
Dc := PdfDoc.GetCanvasDC(
Round(ScaleX(210, mmPixel)),
Round(ScaleY(297, mmPixel)));
// Render the next page of RTF content into the DC
LastChar := PrintRtfBox(Dc, RichEdit1, LastChar);
// Capture the DC contents as a PDF document
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; // signals last page
end;
end.
Čo robí slučka
PrintRtfBox vyplní štruktúru TFormatRange a odovzdá ju ovládaciemu prvku Rich Edit cez SendMessage. Ovládací prvok vykreslí znaky začínajúce od cpMin, zastaví sa, keď sa DC naplní, a vráti pozíciu prvého znaku, ktorý sa nezmestil. Keď sa vrátená hodnota rovná alebo presahuje celkovú dĺžku textu, každý znak bol vykreslený a funkcia vráti nulu, čo ukončí slučku repeat...until.
Každá iterácia vytvorí jeden súbor PDF s názvom Output1.pdf, Output2.pdf atď. Ak namiesto toho chcete jeden viac stránkový dokument, API knižnice pre pridávanie stránok vám umožní zostaviť ich neskôr, alebo môžete reštrukturovať slučku, aby volala AddPage v rámci jednej relácie dokumentu. Vzor SaveToFile za iteráciou nasledovaný RemovePdfDocument uchová maximálnu pamäť na jednu stránku obsahu, čo záleží pri veľmi dlhých RTF súboroch.
Detaily rozmerov, ktoré ľudí prekvapujú
Argument 96 DPI pre LoadFromCanvasDc hovorí knižnici, pri akom rozlíšení obrazovky bol DC vykreslený, aby mohla vypočítať správne mapovanie bodov na pixely pre stránku PDF. Ak to dostanete zle, text sa v výstupe zobrazí v nesprávnej veľkosti, hoci obrázok vyzerá správne na obrazovke.
Hodnota +100 pridaná k RcPage.Right a RcPage.Bottom je malý okraj za viditeľným okrajom ovládacieho prvku. Rich Edit používa obdĺžnik rcPage na rozhodnutie, kde rozdeliť stránky; bez okraja môže byť riadok, ktorý padne presne na hranicu, duplikovaný na dvoch stránkach. Nie je to magická konštanta: chcete ju dostatočne veľkú, aby hranica stránky padla čisto vo vnútri oblasti rozloženia ovládacieho prvku, nie na poslednom pixeli.
Napokon, ovládací prvok musí byť už pripojený k viditeľnému oknu formulára, keď sa spustí FormCreate, aby bol jeho popisovač okna platný pred prvým volaním SendMessage. TRichEdit vytvorený dynamicky za behu potrebuje explicitné volanie HandleNeeded pred spustením renderovacieho cyklu, ak formulár ešte nebol zobrazený.
Práca s písmami a funkciami RTF
Pretože renderovanie vykonáva engine Windows Rich Edit, substitúcia písem riadi rovnakými pravidlami ako pre zobrazenie a tlač. Písma odkazované v súbore RTF, ktoré sú nainštalované na stroji, sa vykreslia presne; chýbajúce písma sa nahradia ticho, čo môže posunúť dĺžky riadkov a stránkovanie. Pre produkčnú dávkovú konverziu sa to oplatí testovať explicitne: načítajte dokument s každým typom písma, ktorý vaše zdroje RTF používajú, a potvrďte, že počet stránok výstupu zodpovedá tomu, čo očakávate od manuálneho náhľadu tlače.
Tabuľky, vložené obrázky a väčšina formátovacích funkcií Rich Text fungujú bez akéhokoľvek ďalšieho spracovania, pretože Rich Edit ich vykresľuje natívne. Jednou oblasťou, ktorá môže byť prekvapujúca, je text, ktorý používa vlastné medzery medzi odsekmi alebo odsadenia prvého riadku vyjadrené v twips: interný súradnicový systém Rich Edit je v twips (1/1440 palca), zatiaľ čo súradnice DC, ktoré nastavujete v TFormatRange, sú v pixeloch pri aktuálnom DPI. Ovládací prvok konvertuje interne, ale ak programovo zostavujete RTF, mali by ste overiť, že vaše hodnoty okrajov sú v správnej jednotke.
Povedomie o DPI a displeje s vysokým DPI
Na displeji bežiacom pri 150% škálovaní (144 DPI) ScaleX(210, mmPixel) vráti väčší počet pixelov ako na 100% displeji. Knižnica PDF zaznamená akékoľvek pixelové rozmery, ktoré odovzdáte do GetCanvasDC, a použije argument DPI v LoadFromCanvasDc na spätný výpočet fyzickej veľkosti stránky v PDF. Pokiaľ hodnota DPI, ktorú odovzdávate, zodpovedá DPI, pri ktorom vaša aplikácia beží, veľkosť výstupnej stránky bude správna bez ohľadu na škálovanie displeja.
Ak vaša aplikácia nie je DPI-vedomá (staré predvolenie), Windows škáluje obrazovkový DC a vaše pixelové výpočty budú nesprávne na strojoch s vysokým DPI. Najjednoduchšou opravou je vyhlásiť povedomie o DPI v manifeste aplikácie; aplikácia potom dostane skutočné pixely zariadenia a hodnota 96, ktorú odovzdávate do LoadFromCanvasDc, by mala byť nahradená skutočným DPI displeja získaným z GetDeviceCaps(GetDC(0), LOGPIXELSX). Ukážka kódu vyššie pevne kóduje 96, pretože je vhodná pre prostredie so 100% škálovaním a zachováva príklad krátky.
Štruktúra výstupu: jeden súbor na stránku oproti kombinovanému dokumentu
Slučka vyššie zapíše každú stránku do samostatného súboru PDF. Či to je to, čo chcete, závisí od následného použitia. Systémy generovania zostáv často potrebujú jednotlivé stránky, pretože neskôr zostavujú finálny dokument zlúčením alebo preusporiadaním stránok. Ak chcete jeden PDF od začiatku, knižnica vám umožní vytvoriť dokument s viacerými stránkami v jednej relácii: vytvorte dokument raz mimo slučky, zavolajte metódu pridávania stránok namiesto SaveToFile vo vnútri slučky a uložte kompletný dokument po ukončení slučky. Toto sa vyhýba medzisúborom a je to správna štruktúra pre väčšinu scenárov konverzie jedného dokumentu.
Pre veľké RTF súbory sa oplatí pridať nejakú spätnú väzbu o pokroku v slučke, keďže rýchlosť konverzie je zhruba úmerná počtu stránok a 200-stránkový dokument môže trvať niekoľko sekúnd. Štruktúra repeat...until je ľahko rozšíriteľná: sledujte posun znakov v aktualizácii ukazovateľa priebehu po každej iterácii pomocou LastChar vydeleného celkovým počtom znakov z RichEdit1.GetTextLen.
Tu zobrazené metódy GetCanvasDC a LoadFromCanvasDc sú súčasťou knižnice losLab PDF pre Delphi a C++Builder.