Formát RTF je tu už dostatečně dlouho na to, aby se objevoval v místech, kde to nikdo neplánoval: starší generátory sestav, systémy pro hromadnou korespondenci, archivy právních dokumentů, které předcházejí moderním textovým procesorům. Jeho převod do PDF za běhu je opakujícím se požadavkem a přístup, který ve Windows skutečně funguje, není dedikovaný parser RTF, ale vykreslovací cesta, kterou samotná Windows již poskytují prostřednictvím TRichEdit a EM_FORMATRANGE. Verze DLL knihovny losLab PDF Library zpřístupňuje kontext virtuálního zařízení, který do této pipeline přímo zapadá
Mechanismus: virtuální DC a EM_FORMATRANGE
Ovládací prvky Rich Edit mohou stránkovat svůj obsah pro jakýkoli kontext zařízení, nejen pro fyzickou tiskárnu. Zpráva EM_FORMATRANGE říká ovládacímu prvku, aby rozmístil rozsah znaků do daného DC, a vrací pozici posledního znaku, který se mu podařilo umístit. Zavolejte ji opakovaně a pokaždé posuňte cpMin, a získáte výstup stránku po stránce. Funkce GetCanvasDC v knihovně losLab PDF Library poskytuje DC v paměti dimenzované na jakékoli vámi zadané rozměry stránky; po vykreslení stránky do něj zachytí LoadFromCanvasDc výsledek jako stránku PDF. To je celá pipeline
Jedna věc, kterou je třeba vyřešit hned na začátku: velikost ovládacího prvku TRichEdit musí odpovídat cílové stránce. Pokud je ovládací prvek menší nebo větší než rozměry DC, stránkování se nebude shodovat s tím, co skončí v PDF. Pro výstup ve formátu A4 je standardním přístupem před načtením souboru RTF nastavit rozměry ovládacího prvku v pixelech tak, aby odpovídaly 210 x 297 mm při 96 DPI, pomocí stejných pomocníků pro měřítko, které použijete k určení velikosti DC
Implementace v Delphi
Následující kód používá importní unitu PDFlibAX_TLB, která obaluje verzi DLL knihovny. Formulář hostí TRichEdit a tlačítko; obslužná rutina OnCreate formuláře upraví velikost ovládacího prvku a načte RTF, a kliknutí na tlačítko řídí konverzní smyčku
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);
// Upravit velikost ovládacího prvku na A4 při DPI obrazovky, aby stránkování odpovídalo 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
// Získat virtuální DC o velikosti A4
Dc := PdfDoc.GetCanvasDC(
Round(ScaleX(210, mmPixel)),
Round(ScaleY(297, mmPixel)));
// Vykreslit další stránku obsahu RTF do DC
LastChar := PrintRtfBox(Dc, RichEdit1, LastChar);
// Zachytit obsah DC jako dokument PDF
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; // signalizuje poslední stránku
end;
end.
Co smyčka dělá
Funkce PrintRtfBox vyplní strukturu TFormatRange a předá ji ovládacímu prvku Rich Edit prostřednictvím SendMessage. Ovládací prvek vykresluje znaky počínaje od cpMin, zastaví se, když se DC zaplní, a vrátí pozici prvního znaku, který se nevešel. Když se vrácená hodnota rovná nebo překračuje celkovou délku textu, byl vykreslen každý znak a funkce vrátí nulu, čímž ukončí smyčku repeat...until
Každá iterace vytvoří jeden soubor PDF s názvem Output1.pdf, Output2.pdf a tak dále. Pokud místo toho chcete jeden vícestránkový dokument, rozhraní API knihovny pro připojování stránek vám je umožní sestavit dodatečně, nebo můžete restrukturalizovat smyčku tak, aby volala AddPage v rámci jedné relace dokumentu. Výše uvedený vzor použití SaveToFile v každé iteraci následovaný RemovePdfDocument udržuje maximální využití paměti omezené na obsah jedné stránky, na čemž záleží u velmi dlouhých souborů RTF
Detaily týkající se velikosti, na kterých se lidé zaseknou
Argument 96 DPI pro LoadFromCanvasDc říká knihovně, při jakém rozlišení obrazovky bylo DC vykresleno, takže může vypočítat správné mapování bodů na pixely pro stránku PDF. Pokud to uděláte špatně, text se na výstupu zobrazí ve špatné velikosti, i když obraz vypadá na obrazovce správně
Přidání +100 k RcPage.Right a RcPage.Bottom je malý okraj za viditelným okrajem ovládacího prvku. Rich Edit používá obdélník rcPage k rozhodnutí, kde rozdělit stránky; bez okraje se může řádek, který padne přesně na hranici, duplikovat na dvou stránkách. Není to magická konstanta: chcete ji dostatečně velkou, aby hranice stránky padla čistě dovnitř oblasti rozvržení ovládacího prvku spíše než na poslední pixel
A konečně, ovládací prvek už musí být připojen k viditelnému oknu formuláře, když běží FormCreate, aby byl jeho identifikátor okna platný před prvním voláním SendMessage. Dynamicky vytvořený TRichEdit za běhu potřebuje explicitní volání HandleNeeded před začátkem vykreslovací smyčky, pokud formulář ještě nebyl zobrazen
Zpracování písem a funkcí RTF
Protože vykreslování provádí engine Windows Rich Edit, nahrazování písem se řídí stejnými pravidly, jaká používá pro zobrazení a tisk. Písma odkazovaná v souboru RTF, která jsou na stroji nainstalována, se vykreslí věrně; chybějící písma se tiše nahradí, což může posunout délky řádků a stránkování. Pro produkční dávkovou konverzi stojí za to toto výslovně otestovat: načtěte dokument s každým typem písma, který používají vaše zdroje RTF, a potvrďte, že počet výstupních stránek odpovídá tomu, co očekáváte od ručního náhledu tisku
Tabulky, vložené obrázky a většina funkcí formátování Rich Text fungují bez jakéhokoli dalšího zacházení, protože je Rich Edit vykresluje nativně. Jedinou oblastí, která může být překvapivá, je text, který používá vlastní rozestupy odstavců nebo odsazení prvního řádku vyjádřené v twipech: vnitřní souřadnicový systém Rich Editu je v twipech (1/1440 palce), zatímco souřadnice DC, které nastavíte v TFormatRange, jsou v pixelech při aktuálním DPI. Ovládací prvek je převádí interně, ale pokud RTF konstruujete programově, měli byste ověřit, že hodnoty vašich okrajů jsou ve správné jednotce
Povědomí o DPI a displeje s vysokým DPI
Na displeji běžícím při 150% škálování (144 DPI) vrátí ScaleX(210, mmPixel) větší počet pixelů než na displeji se 100%. Knihovna PDF Library zaznamenává jakékoliv rozměry v pixelech, které předáte GetCanvasDC, a k následnému zpětnému výpočtu pro fyzickou velikost PDF dokumentu se obrátí u proměnné LoadFromCanvasDc podle předaného argumentu DPI. Dokud je přitom navíc argument s proměnnou DPI podle dané relace nastaven roven zrovna parametru DPI z režimu, ze kterého Vaše spouštěcí aplikace operuje, celková hodnota ve výstupu pro tisk odpovídá přesně požadované fyzické stránce bez návaznosti ohledně parametru pro rozlišení na výstupu obrazovky
Bude-li chybět údaj s parametrem odkazujícím se jako DPI u (dříve spíše standardní nastavení jako starý výchozí formát pro DPI-unaware) aplikace, Microsoft Windows na displejích pro DC zmenší zobrazení podle monitorů podporujících škálu pro zobrazení ze skupiny u tzv. high-DPI (s vysokým DPI) a pro propočet na tyto parametry ve snaze stanovení pixelů obdrží špatné parametry v chybné velikosti. V tu chvíli jako ten nejobyčejnější opravený přepis pro řešení funguje určení deklarace parametru pod označením DPI z deklarovaného seznamu u application manifestu; program si následně načítá správně vyhodnocené informace týkající se hodnoty přímo z pixelů podle fyzického zobrazení příslušného monitoru s tím, že oněch hodnotových 96 při propisu skrz proměnnou pro LoadFromCanvasDc bude žádoucí přepsat do správně odpovídající hodnoty za parametr displeje pro zobrazení DPI obdrženou právě přes aplikaci GetDeviceCaps(GetDC(0), LOGPIXELSX). Názorná úkazka se zde uvádí s pevně definovaným kódem nastaveným na 96 z důvodu snahy vytvořit pochopitelný kratší (krátký) přepis kódu k zachování pochopitelnosti kvůli demonstraci chování v nastavení se zaměřením zprostředkovat škálu za standardního propočtu ve 100 % prostředí
Struktura výstupu: jeden soubor na stránku versus kombinovaný dokument
Smyčka uvedená výše zapisuje každou ze stran ve stavu nezávisle oddělených dokumentů coby souborů ve formátu PDF. Jestli si tento způsob takovýmto způsobem požadujete odnést, bude nadále určováno zamýšleným modelem podle uplatnění v rámci downstream (následných postupech). Procesy určené k zhotovení reportů bývají zpravidla vyžadovány při nezávisle samostatných listech za sebou jdoucích jednotlivých postupných kroků pro další nakládání z důvodu, jelikož ke kompilaci konečného uceleného výstupu většinou přistupují nakonec společně se sloučením (mergingem), čímž nakonec určují, s jakým finálním výsledkem pak dokument přes reordering stran bude působit jako výsledek. V případě, kdy uvažujete raději naopak s výchozím nastavením se strukturou od základu se zapojeným jediným PDF (jako jediný ucelený) výstup s formátem rovnou zpočátku od začátku, se knihovna navíc navrhuje sama k dispozici ohledně variant o vyprodukování více stránek sdružených naráz formou k obalení s jedinou spouštěcí relací k obsloužení, stačí tak jen vytvořit dokument naráz samostatně jednorázově pro změnu těsně v místě před volanou cyklovou procedurou a po implementaci z metod uvnitř pro připojování s SaveToFile namísto spouštění dojde (dočkáme se propojení za ucelení takového kroku od procedury na komplet po vystoupení k vnějšímu okraji vně) k ukončení nad celou vytvořenou entitou se smyčkou po uložení kompletně dodělaných dat společně z úkonů od ukončení odchozího povelu venku ze smyčky. V takovém případě nezavoláme vůbec pro existenci nějakou z vložených vrstev v mezipřechodech při pomocných prozatímních souborech mezi voláními a proto se naopak dočkáme jako přesně ideálního výchozího stavu k nasazení převážně u větších objemů do celků pod parametry spíše odpovídající pro provedení formou o jednokusových dokumentech přes nasazení pro uplatnění a konverzi s těmito typicky převáděnými strukturovanými variantami souborů s příběhovými záchyty (scénáři)
U rozsáhlých souborů RTF stojí za to do smyčky přidat nějakou zpětnou vazbu o průběhu, protože rychlost konverze je zhruba úměrná počtu stránek a zpracování 200stránkového dokumentu může trvat několik sekund. Strukturu repeat...until lze snadno rozšířit: sledujte offset znaků v aktualizaci indikátoru průběhu (progress bar) po každé iteraci pomocí hodnoty LastChar dělené celkovým počtem znaků z RichEdit1.GetTextLen
Zde zobrazené metody GetCanvasDC a LoadFromCanvasDc jsou součástí knihovny losLab PDF Library pro Delphi a C++Builder