Tekninen artikkeli

Vie TDataSet ja raportit PDF-muotoon Delphissä

TDataSetin tai raportin viemiseksi PDF-muotoon Delphissä losLab PDF Library tarjoaa kaksi polkua. PDFlibTableExport sovittaa minkä tahansa TDataSet-komponentin — FireDAC-kyselyn, ClientDataSetin tai muistitaulukon — sivutettavaksi PDF-taulukoksi, ja kolme siltaa kohdassa Addons välittävät valmiin FastReport-, QuickReport- tai ReportBuilder-raportin samalle PDF-kirjoittimelle. Molemmat tuottavat aidon PDF-tiedoston ilman tulostinajuria ja ilman näkyvää ikkunaa

Nämä kaksi ongelmaa vaikuttavat samanlaisilta, mutta eivät ole. DBGridillä tai raa'alla kyselytuloksella ei ole omaa asettelua, joten sen vieminen tarkoittaa sellaisen keksimistä: sarakkeet, otsikot, sivunvaihdot. FastReport- tai ReportBuilder-asiakirja sisältää jo valmiiksi suunnitellun asettelun, joten sen vieminen tarkoittaa jonkun toisen piirtokomentojen uskollista toistamista PDF-avaruuteen. losLab PDF Library pitää nämä asiat erillisissä yksiköissä juuri siksi, että niiden vikaantumistavat eroavat toisistaan, ja tämän artikkelin loppuosa käy läpi kunkin niistä ja kohdat, joissa ne lakkaavat olemasta luotettavia

Miten TDataSet viedään PDF-muotoon Delphissä?

PDFlibTableExport muuntaa tietojoukon taulukoksi yhdellä kutsulla. Viejä käy kenttäluettelon läpi kerran, ohittaa binaariset blob-kentät automaattisesti — tyypeillä ftBlob, ftGraphic, ftBytes ja vastaavilla ei ole hyödyllistä solutekstiä —, tasaa numeeriset sarakkeet oikealle ja maalaa valinnaisen seeprakuvioinnin tyylitellyn otsikkorivin päälle. Konepellin alla se rakentaa ruudukon CreateTable-funktiolla, täyttää solut SetTableCellContent-funktiolla ja renderoi DrawTableRows-funktiolla, joka on sama julkinen taulukko-API, jota ohjaisit käsin. Helppokäyttöinen kääre PDFlibExportDataSet määrittää millimetrit ja vasemman yläkulman alkupisteen puolestasi, joten kutsuja toimittaa vain sivun

uses
  Data.DB, FireDAC.Comp.Client, FireDAC.Stan.StorageBin,
  PDFlibrary, PDFlibTableExport;

var
  MemTable: TFDMemTable;
  PDF: TPDFlib;
  Options: TPDFlibTableExportOptions;
begin
  MemTable := TFDMemTable.Create(nil);
  PDF := TPDFlib.Create;
  try
    MemTable.LoadFromFile('customer.FDS');   // any TDataSet works here
 
    PDF.SetOrigin(1);
    PDF.SetMeasurementUnits(1);              // millimetres
    PDF.SetPageSize('A4');
    PDF.AddStandardFont(4);

    Options := DefaultTableExportOptions;
    Options.Title := 'Customers';
    Options.ColumnWidth := 32;               // 11 fields fit on A4 at 32 mm
    Options.RepeatHeader := True;            // redraw the header on each page

    PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
  finally
    PDF.Free;
    MemTable.Free;
  end;
end;

Automaattinen blob-kenttien ohitus on oletusarvo, ei pakkopaita. Kun tarvitset kenttäkohtaista hallintaa — mukautetun sarakkeen otsikon, kapeamman leveyden tai sellaisen sisäisen avainsarakkeen jättämisen pois, joka ei ole blob-kenttä —, luo TPDFlibTableExporter suoraan ja kytke sen OnFieldFilter-tapahtuma, joka käynnistyy kerran kenttää kohden ja antaa sinulle muokattavan määrityksen. Aseta Include-arvoksi False jättääksesi kentän pois tai aseta ColumnWidth ja DisplayLabel korvaamaan oletusasetukset. Tämä tapahtumakäsittelijä on myös paikka, jossa jätät pois leveän muistiokentän (memo), jota et halua paisuttavan sivua

Sivutus: piirtotilan välittäminen sivujen välillä

DrawTableRows kantaa sivutustilaa, ja sen toimintaperiaatteen ymmärtäminen on koko homman ydin. Kutsut sitä antamalla ensimmäisen ja viimeisen rivin; jos annat viimeisen rivin arvoksi alle 1, se tarkoittaa piirtämistä taulukon loppuun asti antamasi korkeuden rajoissa. Funktio palauttaa todellisuudessa piirretyn korkeuden, ja GetTableLastDrawnRow ilmoittaa viimeisen rivin, joka mahtui. Tämä pari on se tila, jonka siirrät sivulta toiselle: kun viimeksi piirretty rivi on vähemmän kuin kokonaismäärä, avaat uuden sivun ja jatkat sitä seuraavalta riviltä. Mitään ei mitata uudelleen — jatko jatkuu täsmälleen siitä, mihin edellinen kutsu jäi

// Miten viejä jatkaa pitkää taulukkoa sivujen yli
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
  DrawHeight := Y - Options.BottomMargin;
  // Viimeinen rivi = 0 tarkoittaa "piirrä loppuun asti", DrawHeightin rajoissa
  PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
  LastDrawn := PDF.GetTableLastDrawnRow(TableID);
  if LastDrawn >= TotalRows then
    Break;                       // koko taulukko mahtui tälle sivulle
  if LastDrawn < Row then
    Break;                       // turva: ei edistymistä, keskeytä
  PDF.NewPage;
  Row := LastDrawn + 1;          // jatka ensimmäisestä piirtämättömästä rivistä
  Y := PageHeight - Options.Top;
end;

Silmukka on myös paikka, jossa toistuva otsikko sijaitsee. Jokaisella jatkosivulla viejä voi valinnaisesti piirtää rivin 1 — otsikon — uudelleen ennen tietorivejä, ja se käyttää ensimmäisen DrawTableRows-kutsun palauttamaa korkeutta siirtääkseen rungon sen alapuolelle. Tämän vuoksi DrawTableRows palauttaa korkeuden eikä seuraavan rivin koordinaattia: palautusarvo antaa sinun pinota uudelleenpiirretyn otsikon ja jatkuvan rungon ilman, että kummankaan kokoa tarvitsee kovakoodata

Miten FastReport- tai ReportBuilder-raportti viedään PDF-muotoon?

Raporttisillat toimivat päinvastoin: ne eivät koskaan keksi asettelua, vaan toistavat sen. Jokainen silta kytkeytyy moottoriinsa sen natiiviin vientiliitäntään. PDFlibFRExport periytyy luokasta TfrxCustomExportFilter ja vastaanottaa FastReportin teksti- (memo), kuva-, muoto- ja viivaobjektit muuntaen jokaisen PDFlibPas-primitiiviksi. PDFlibRBDevice periytyy luokasta TppFileDevice ja käy läpi ReportBuilderin DrawCommand-luettelon. PDFlibQRExport valitsee täysin kolmannen reitin, jota käsitellään seuraavassa osiossa. Kaikki kolme toimivat ilman käyttöliittymää (headless), mikä on ainoa syy käyttää niitä palvelimella

Headless-ajolla on hintansa, ja FastReport on varoittava esimerkki. TfrxReport.Export reitittää sivut esikatselusivujen kautta, jotka tarkistavat suodattimen ShowDialog-ominaisuuden, jonka oletusarvoksi periytyy True. Koneella, jossa ei ole interaktiivista työpöytää, modaalinen valintaikkuna palauttaa peruutustuloksen ja vienti epäonnistuu hiljaisesti. Aseta ShowDialog-ominaisuuden arvoksi False ennen Export-kutsun tekemistä, niin raportti renderoituu hiljaisesti. ReportBuilderissa on vastaavat kytkimet — AllowPrintToFile ja ShowPrintDialog —, ja QuickReport, joka ajaa kaiken Prepare-kutsun kautta, ei tarvitse valintaikkunoiden vaimennusta lainkaan

var
  Exporter: TPDFlibFRExport;
begin
  Report.PrepareReport;                  // build the pages first
  Exporter := TPDFlibFRExport.Create(nil);
  try
    Exporter.FileName := 'invoice.pdf';
    Exporter.ShowDialog := False;        // headless: skip the modal, no cancel
    Report.Export(Exporter);
  finally
    Exporter.Free;
  end;
end;

Kolme moottoria, kolme koordinaattijärjestelmää

Jokainen silta tekee oman koordinaattilaskentansa, koska jokainen moottori mittaa maailmaa eri tavalla. PDFlibFRExport käsittelee FastReport-objektien sijainteja pikseleinä suhteessa 96 PPI:hin ja skaalaa ne kertoimella 96/25.4 päästäkseen millimetreihin. PDFlibRBDevice lukee ReportBuilderin piirtokomennot millimetrin tuhannesosina ja jakaa ne tuhannella, ja se rekisteröi itsensä ppRegisterDevice-kutsulla, joten ppReport.DeviceType-asetuksen asettaminen arvoon 'PDFlibPas' ja Print-kutsun tekeminen riittää reitittämään tulostuksen sen kautta. PDFlibQRExport ei tee lainkaan koordinaattilaskentaa: valmis QuickReport-sivu on jo EMF-metatiedosto, joten silta tallentaa jokaisen sivun virtaan ja syöttää sen ImportEMFFromStream-funktiolle antaen kirjaston laitekonteksti- ja metatiedostojen renderointipolun sijoittaa jokaisen kirjoitusmerkin ja viivan. FastReport- ja ReportBuilder-sillat puolestaan tuottavat natiiveja vektoripiirto-primitiivejä ja natiivia tekstiä

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);
    // Valmistelee raportin ja liittää jokaisen sivun yhdellä kutsulla; silta
    // muuntaa kunkin sivun EMF:n PDF:ksi ImportEMFFromStream-funktion kautta.
    PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
  finally
    PDF.Free;
  end;
end;

Mihin tarkkuus päättyy

Tunne rajat ennen kuin sitoudut käyttämään näitä siltoja työnkulussasi. Tietojoukon viejä ohittaa binaarisarakkeet hiljaisesti, joten raportti, jonka on näytettävä upotettu kuva, vaatii toisenlaisen lähestymistavan kuin suora taulukko. QuickReport-polku tekee EMF-muunnoksen kautta tekstistä vektorikuvioita — sivu näyttää oikealta, mutta se ei sisällä valittavaa tai haettavaa tekstiä eikä sivutettua PDF-rakennetta saavutettavuutta varten. FastReport ja ReportBuilder säilyttävät todellisen tekstin, mutta tyypitetyt käsittelijät kattavat yleiset näkymät — teksti (memo), kuva, muoto, viiva — ja turvautuvat rajauslaatikkoon tai ohittavat erikoiset objektit, joten runsaasti tekstiä, viivakoodeja tai liukuvärityksiä sisältävä raportti menettää yksityiskohtia. Tämä ei ole vika, vaan käännöskerroksen rehellinen raja

Yksi toiminnallinen varoitus on ylitse muiden. Mikään näistä kolmesta moottorista ei tule losLab PDF Libraryn mukana, ja sillat on tarkoituksella jätetty pois päärakennuksesta — ne kääntyvät vain sellaisesta projektista, jonka etsintäpolulla on ja FastReport VCL 6.x, QuickReport 8 tai ReportBuilder 20. Metatiedosto- ja DrawCommand-rajapinnat, joihin ne kohdistuvat, ovat pysyneet vakaina useiden moottoriversioiden välillä, mutta olet itse vastuussa versioiden yhteensopivuudesta. Kun saat tämän oikein, nämä kaksi polkua kattavat koko kirjon ad-hoc DBGrid-vedoksesta suunniteltuun laskuun. Sekä taulukonviejä että raporttisillat toimitetaan losLab PDF Library -kirjaston mukana Delphille ja C++Builderille