Tekninen artikkeli

RTF-tiedostojen muuntaminen (Transforming RTF files into) PDF:ksi (PDF): Kehittäjän (developers) opas (guide)

RTF on ollut (has been around) olemassa riittävän kauan (long enough), jotta (that it) se (it) ilmaantuu (shows up in) paikkoihin, joita kukaan ei (no one planned for) suunnitellut: vanhoihin raporttigeneraattoreihin (legacy report generators), sähköpostien (mail merge pipelines) yhdistämisputkiin (pipelines), laillisten (legal) asiakirjojen arkistoihin (archives that), jotka (that) ovat ajalta (predate) ennen nykyaikaisia (modern) tekstinkäsittelyohjelmia (word processors). Sen (Converting it to PDF) muuntaminen (Converting it to PDF on the fly) PDF:ksi lennosta (on the fly) on toistuva vaatimus (recurring requirement, and the), ja menetelmä, joka (and the approach that) oikeasti toimii Windowsissa, ei ole (is not a) omistettu (dedicated) RTF-jäsennin (parser), vaan (but the) renderöintireitti, jonka Windows (Windows itself already provides) itse jo (already provides through) tarjoaa TRichEdit- (TRichEdit) ja (and) EM_FORMATRANGE-komentojen kautta (through). losLab PDF Library (losLab PDF Library DLL edition) DLL -versio paljastaa (exposes a) virtuaalisen laitekontekstin (virtual device context), joka liittyy (slots directly into that) suoraan (directly) kyseiseen putkeen (pipeline)

Mekanismi: virtuaalinen DC ja EM_FORMATRANGE

Rich Edit -kontrollit voivat (can) sivuttaa (paginate their) sisältönsä (content) mille tahansa (any) laitekontekstille (device context), ei vain fyysiselle tulostimelle (physical printer). EM_FORMATRANGE-viesti kertoo kontrollille, että sen on (tells the control to lay out a) aseteltava merkkialue tiettyyn DC:hen, ja palauttaa (returns the) viimeisen sen sovittaman (managed to fit) merkin (character) sijainnin (position of the last character it). Kutsu sitä (Call it) toistuvasti, siirtäen (advancing) cpMin-arvoa joka kerta, ja (and you get) saat (get) sivukohtaisen tulosteen (page-by-page output). losLab PDF Libraryn GetCanvasDC tarjoaa (provides an) muistissa olevan DC:n, joka on mitoitettu (sized to whatever) määrittämiesi sivumittojen (page dimensions you specify) kokoiseksi; sen jälkeen kun sivu on renderöity (after rendering a page into it, LoadFromCanvasDc) siihen, LoadFromCanvasDc nappaa (captures the) tuloksen (result as a) PDF-sivuna (PDF page). Se (That) on koko (whole) putki (pipeline)

Yksi asia on syytä tehdä oikein alusta alkaen: TRichEdit-kontrolli on mitoitettava (must be sized to match the) kohdesivua (target page) vastaavaksi. Jos (If the) kontrolli on (is) pienempi tai (or) suurempi (larger) kuin DC:n (DC) mitat (dimensions), sivutus (pagination will not line up with what) ei vastaa sitä, mitä (what ends up in the) PDF:ään päätyy. A4-tulosteelle vakiotapa on asettaa kontrollin pikselimitat vastaamaan 210 x 297 mm:ää 96 DPI:llä ennen RTF-tiedoston (RTF file) lataamista, käyttäen (using the same scale helpers you) samoja skaalausapureita (scale helpers you will use to), joita käytät DC:n mitoittamiseen (size the DC)

Delphi-toteutus (implementation)

Seuraavassa käytetään PDFlibAX_TLB-tuontiyksikköä (import unit), joka käärii kirjaston (wraps the DLL edition of the library) DLL-version (DLL edition). Lomake isännöi TRichEdit-komponenttia ja painiketta; lomakkeen OnCreate-käsittelijä mitoittaa kontrollin (sizes the control) ja (and) lataa (loads the) RTF:n, ja painikkeen napsautus (button click drives the) ohjaa (drives the) muunnossilmukkaa (conversion loop)

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);
  // Mitoita kontrolli A4-kokoiseksi näytön DPI:llä, jotta sivutus vastaa DC:tä
  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
    // Hanki A4-kokoiseksi mitoitettu virtuaalinen DC
    Dc := PdfDoc.GetCanvasDC(
      Round(ScaleX(210, mmPixel)),
      Round(ScaleY(297, mmPixel)));
    // Renderöi RTF-sisällön seuraava sivu DC:hen
    LastChar := PrintRtfBox(Dc, RichEdit1, LastChar);
    // Nappaa DC:n sisältö PDF-asiakirjana
    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;  // ilmoittaa (signals) viimeisen sivun
end;

end.

Mitä silmukka (loop is doing) tekee

PrintRtfBox täyttää TFormatRange-rakenteen ja välittää (passes it to the) sen (it to the) Rich Edit -kontrollille SendMessage-komennon (via SendMessage) kautta (via). Kontrolli renderöi (renders characters) merkkejä alkaen kohdasta cpMin, pysähtyy (stopping when the DC fills), kun (when the) DC täyttyy, ja palauttaa (and returns the) ensimmäisen (first character) sen (that) merkin (character) sijainnin, joka ei (did not fit) mahtunut (fit). Kun paluuarvo (return value) on yhtä suuri (equals or exceeds the) tai suurempi (exceeds the) kuin koko tekstin (text length) pituus, jokainen merkki (every character has been rendered) on renderöity (rendered) ja (and the) funktio (function returns) palauttaa (returns) nollan, mikä lopettaa (which terminates the) repeat...until -silmukan

Jokainen iteraatio (iteration produces one) tuottaa (produces) yhden PDF-tiedoston (PDF file named), jonka (named) nimi on Output1.pdf, Output2.pdf, ja niin edelleen (and so on). Jos haluat sen sijaan (instead, the) yhden monisivuisen (single multi-page document) asiakirjan, kirjaston sivunliittämis-API:n (page-append API lets you assemble them) avulla voit koota (assemble them after the fact, or you can) ne jälkikäteen, tai voit jäsentää silmukan uudelleen (restructure the loop to) kutsuaksesi (call AddPage within a) AddPage-metodia yhden asiakirjaistunnon (single document session) sisällä (within a). Yllä (above) oleva iteraatiokohtainen (per-iteration SaveToFile followed by RemovePdfDocument pattern above) SaveToFile -malli, jota seuraa RemovePdfDocument, pitää (keeps peak memory bounded at) huippumuistin (peak memory) rajattuna yhden (one) sivun (page's worth of) sisällön kokoiseksi, millä (which matters for) on (matters for) merkitystä erittäin pitkien RTF-tiedostojen kohdalla (very long RTF files)

Mitoituksen yksityiskohtia (details that trip people up), jotka saavat (trip people) ihmiset (people) kompastumaan (trip up)

LoadFromCanvasDc-kutsulle (argument to) annettava 96 DPI:n (DPI argument) argumentti (argument) kertoo (tells the library at what) kirjastolle, millä näyttöresoluutiolla (screen resolution the DC was rendered) DC renderöitiin (rendered), jotta se voi laskea oikean pistestä-pikseliin-kartoituksen (correct point-to-pixel mapping for the PDF page) PDF-sivulle. Jos tämä menee väärin (Get this wrong and text will), teksti näkyy tulosteessa väärän kokoisena (wrong size), vaikka kuva näyttäisi (image looks correct on) oikealta näytöllä (screen)

Kohteisiin RcPage.Right ja (and) RcPage.Bottom lisätty +100 on pieni marginaali kontrollin näkyvän reunan (visible edge) ulkopuolella (beyond the). Rich Edit käyttää rcPage-suorakulmiota päättääkseen, mistä (where to split) sivut (pages) jaetaan; ilman marginaalia (margin) linja (line that falls exactly at the boundary can be duplicated across two pages), joka (that) osuu tarkalleen (exactly at the) rajalle, voi monistua (be duplicated) kahdelle (across two) sivulle. Se ei ole (It is not a magic constant) taikavakio: haluat sen (you want it large enough that the) olevan (it) riittävän suuri (large enough), jotta sivun raja osuu puhtaasti (cleanly inside the) kontrollin asettelualueen (layout area rather than on the) sisälle (inside the) viimeisen pikselin (last pixel) sijaan (rather than on the)

Lopuksi kontrollin on jo (must already be) oltava liitettynä (attached to a visible form window when) näkyvään lomakeikkunaan, kun (when FormCreate runs so that) FormCreate suoritetaan (runs), jotta (so that its window handle is valid) sen ikkunakahva (window handle) on (is valid before the) kelvollinen ennen (before the) ensimmäistä (first call to) SendMessage-kutsua (call to SendMessage). Ajonaikana dynaamisesti luotu TRichEdit (TRichEdit created dynamically at runtime needs an) vaatii eksplisiittisen (explicit) HandleNeeded-kutsun (call) ennen (before the) renderöintisilmukan alkamista, jos (if the) lomaketta ei ole (has not yet been shown) vielä (yet) näytetty

Fonttien ja RTF-ominaisuuksien käsittely (Handling fonts and RTF features)

Koska renderöinnin suorittaa (rendering is done by the) Windowsin (Windows) Rich Edit -moottori, fonttien korvaaminen (font substitution) noudattaa samoja sääntöjä, joita se käyttää näyttämisessä (display and printing) ja (and) tulostamisessa. Koneelle asennetut fontit, joihin RTF-tiedostossa (referenced in the RTF file that are installed on the machine will) viitataan, renderöityvät uskollisesti (faithfully); puuttuvat (missing) fontit korvataan (will be substituted) hiljaisesti, mikä (which can shift line lengths and) voi siirtää rivipituuksia ja (and pagination) sivutusta (pagination). Tuotantoerämuunnoksissa (production batch conversion this is) tämä (this) on (is worth testing) testaamisen (testing) arvoista: lataa asiakirja (document) kullakin (with each typeface your RTF sources use and) RTF-lähdeasiakirjoissasi käytetyllä kirjasintyypillä (typeface) ja (and) varmista, että tulosteen (output page count matches what you expect from a) sivumäärä (page count) vastaa manuaalisesta tulostuksen (manual print preview) esikatselusta (preview) odottamaasi (expect)

Taulukot (Tables), upotetut (embedded) kuvat (images) ja useimmat Rich Text -muotoiluominaisuudet toimivat ilman minkäänlaista (without any) lisäkäsittelyä (extra handling because Rich Edit renders them natively), koska Rich Edit renderöi (renders them natively) ne natiivisti. Yksi (The one area that) alue, joka (that can be surprising is text that uses custom paragraph spacing or) voi (can be) olla yllättävä (surprising is text that), on (is text that uses custom paragraph spacing or) teksti (text that uses custom), joka käyttää twipseinä ilmaistua (expressed in twips) mukautettua kappaleväliä tai ensimmäisen (first-line) rivin (first-line) sisennystä (indents expressed in twips): Rich Editin (Rich Edit's internal coordinate system is in twips) sisäinen koordinaattijärjestelmä on twipseinä (1/1440 tuumaa (inch)), kun taas DC-koordinaatit (DC coordinates you set in TFormatRange are in pixels at the), jotka asetat TFormatRange-kohteeseen, ovat (are in) pikseleinä nykyisellä (current DPI) DPI:llä. Kontrolli muuntaa (converts internally, but if you are constructing the) sisäisesti, mutta (but if you are constructing the) jos (if you are constructing the RTF programmatically you should verify that your margin values are in the) luot RTF:n (RTF programmatically) ohjelmallisesti (programmatically), sinun tulee (you should) varmistaa (verify that), että (that your) marginaaliarvosi (margin values) ovat (are in the) oikeassa yksikössä (right unit)

DPI-tietoisuus (DPI awareness and) ja korkean (high-DPI) DPI:n näytöt (displays)

Näytöllä, joka (display running at) toimii (running at) 150 %:n skaalauksella (144 DPI), ScaleX(210, mmPixel) palauttaa suuremman pikselimäärän (pixel count) kuin 100 %:n näytöllä (display). PDF Library tallentaa pikselimitat (pixel dimensions you pass to), jotka välität (pass to) GetCanvasDC-kutsulle, ja käyttää DPI-argumenttia (DPI argument in) LoadFromCanvasDc-kutsussa laskeakseen (back-calculate the) PDF:n fyysisen (physical page size in the) sivukoon taaksepäin (back-calculate the). Niin (As long as the DPI value you pass matches the DPI your application is running at, the output page size will be correct regardless of the) kauan (As long as the DPI value you pass matches the) kuin (as the) välittämäsi (pass) DPI-arvo (DPI value) vastaa (matches the) sovelluksesi (application) käyttämää (running at) DPI:tä, tulostesivun (output page size) koko on (will be) oikea (correct) näytön (display scaling) skaalauksesta riippumatta

Jos (If your) sovelluksesi on DPI-tietämätön (DPI-unaware) (vanha (old default) oletus (default)), Windows skaalaa (scales the) näytön DC:n ja (and your pixel calculations will be) pikselilaskelmasi (calculations) menevät pieleen korkean DPI:n koneilla. Yksinkertaisin (simplest fix) korjaus (fix is to declare) on ilmoittaa (declare DPI awareness in the application manifest) DPI-tietoisuus (DPI awareness in the) sovelluksen manifestissa (application manifest); sovellus (application then receives) saa (receives) tällöin todellisia (true device pixels and the) laitepikseleitä, ja (and the) 96, jonka välität (pass to) LoadFromCanvasDc-kutsulle, tulisi (should be replaced with the actual display DPI obtained from) korvata todellisella näytön (actual display DPI obtained from) DPI:llä, joka on saatu (obtained from) GetDeviceCaps(GetDC(0), LOGPIXELSX) -kutsusta. Yllä oleva (The code sample above) koodiesimerkki (code sample) kovakoodaa 96:n, koska se on sopiva 100 %:n skaalausympäristölle (scaling environment and keeps the) ja pitää esimerkin lyhyenä (keeps the example short)

Tulosteen rakenne (Output structure: one file per page versus a combined document): yksi (one) tiedosto per (file per) sivu (page) versus yhdistetty asiakirja (combined document)

Yllä (above writes each) oleva (above writes each) silmukka kirjoittaa jokaisen (each page to a separate) sivun erilliseen (separate) PDF-tiedostoon. Se, onko (Whether that is what you want depends on the) tämä sitä (that is what you want), mitä haluat, riippuu myöhemmästä käytöstä (downstream use). Raporttien luomisjärjestelmät (Report-generation systems often need) tarvitsevat (need individual pages because they) usein (often) yksittäisiä sivuja, koska ne kokoavat lopullisen asiakirjan (final document later by merging or reordering) myöhemmin (later by) yhdistämällä (merging or) tai (or) järjestämällä (reordering pages) sivuja uudelleen. Jos haluat yhden PDF:n heti (from the start, the), kirjasto antaa (lets you) sinun luoda yhdessä istunnossa (in a single session) asiakirjan (document with), jossa on (with multiple pages) useita sivuja: luo asiakirja kerran (once outside the loop) silmukan ulkopuolella (outside the loop), kutsu sivun (page-add method instead of) lisäysmetodia SaveToFile-kutsun sijaan silmukan sisällä (inside the loop), ja (and) tallenna koko asiakirja (complete document after the) silmukan poistumisen (loop exits) jälkeen (after the). Tämä (This avoids the) välttää (avoids the) välitiedostot (intermediate files and is the) ja (and is the) on (is the) oikea rakenne useimmille (for most single-document conversion) yhden (single-document) asiakirjan muunnosskenaarioille (conversion scenarios)

Suurille RTF-tiedostoille kannattaa (is worth adding some progress feedback in the) lisätä jonkinlaista edistymispalautetta (progress feedback in the loop, since the) silmukkaan (in the loop), koska (since the) muunnosnopeus (conversion rate is roughly proportional to page count and a) on karkeasti (roughly proportional to) verrannollinen sivumäärään ja (and a) 200-sivuinen (200-page document can take a few) asiakirja (document) voi kestää (can take a) muutaman sekunnin (few seconds). repeat...until -rakenne on (is easy to) helppo laajentaa (extend: track the): seuraa (track the) merkkisiirtymää (character offset in a) edistymispalkin (progress bar update after each iteration) päivityksessä jokaisen iteraation jälkeen käyttämällä (using LastChar divided by the) LastChar-muuttujaa (LastChar) jaettuna kokonaismerkkimäärällä (total character count from), joka on (from) saatu (from) RichEdit1.GetTextLen -kutsusta

Tässä esitetyt (shown here are part of the) GetCanvasDC- (GetCanvasDC) ja (and) LoadFromCanvasDc-metodit (methods) ovat osa losLab PDF -kirjastoa (Library) Delphille ja C++Builderille