Tekninen artikkeli

PDF-asiakirjojen tulostaminen Delphissä PDFium Component -komponentilla

PDF-koordinaatit ovat pisteissä, tulostimen koordinaatit ovat laiteyksiköissä, eikä näillä kahdella ole mitään tekemistä toistensa kanssa, ennen kuin muunnat ne tarkoituksellisesti. Tuo epäsuhta on useimpien huonojen tulosteiden juuri Delphi-sovelluksissa: koodi lähettää oikean tiedoston, mutta sivu tulostuu rajattuna, venytettynä tai tyhjänä. PDFium Component käsittelee hahmontamisen puolen siististi; tulostimen putkisto on vakio-VCL:ää. Nämä kaksi sopivat yhteen vaatimattoman koodimäärän kanssa, kunhan ymmärrät, mitä molemmat osapuolet odottavat

Kuinka hahmonna-sitten-tulosta -liukuhihna toimii

PDFium Component ei puhu suoraan tulostimille. Kuvio on: hahmonna sivu TBitmap-objektiin haluamallasi resoluutiolla, ja siirrä sitten tuo bittikartta tulostimen kankaalle StretchDIBits-funktiolla. TPdf.RenderPage palauttaa kutsujan omistaman bittikartan, joten hallitset pikselimittoja. Välitä [rePrinting]-asetus optiosarjaan, ja PDFium vaihtaa hahmonnuspolun, joka jättää pois vain näytölle tarkoitetut efektit, kuten LCD-alipikselivihjeistyksen, ja käsittelee sivun MediaBox-ominaisuutta oikein tulostetta varten. Jätä rePrinting pois, ja se, mitä lähetät tulostimeen, on näyttöhahmonnus, joka näyttää hyvältä näytöllä, mutta tuottaa tyypillisesti pehmeämmän tulosteen korkean DPI:n tulostimilla, koska 96 DPI:n näyttöjä varten tehdyt vihjeistyspäätökset eivät sovi 300 tai 600 DPI:n tulostukseen

TPdf.Active on ainoa portti, joka on tarkistettava ennen minkään sivuominaisuuden koskettamista. Komponentti nielee latausvirheet hiljaa: Active := True -määrityksen asettaminen vioittuneelle tai salasanasuojatulle tiedostolle ei nosta poikkeusta; se jättää Active-ominaisuuden yksinkertaisesti tilaan False. Tarkista se aina määrityksen jälkeen. PageCount- tai PageWidth-ominaisuuden lukeminen inaktiivisesta asiakirjasta palauttaa nollan, mikä tuottaa hiljaisia nollatoimintoja, joita on erittäin vaikea diagnosoida, kun ne saavuttavat taustatulostimen

Renderoi- sitten tulosta -putki Delphissä PDFium Componentilla: ladattu PDF-sivu renderoituu TPdf.RenderPagen kautta rePrinting-lipulla tulostimen kokoiseksi TBitmapiksi, sitten StretchDIBits siirtää sen tulostinkankaalle, verrattuna pehmeään näytölle renderoituun tulosteeseen, kun rePrinting jätetään pois
PDFium Component ei koskaan puhu spoolerille suoraan. RenderPage tuottaa bittikartan, StretchDIBits siirtää sen tulostinkankaalle, ja rePrinting valitsee paperille tarkoitetun renderöintipolun

Minimaalinen tulostussilmukka

Yksinkertaisin toimiva tapaus lataa tiedoston, avaa tulostustyön, iteroi sivut ja sulkee sen. Ainoa knoppiyksityiskohta on, että Printer.NewPage-metodia ei saa kutsua ennen ensimmäistä sivua, mistä FirstPage-lippu johtuu. StretchDIBits-siirto menee GetDIBSizes- ja GetDIB-funktioiden kautta vetääkseen laiteriippumattomat bitit bittikarttakahvasta, ja maalaa ne sitten tulostimen kankaalle koko sivun kokoisena:

procedure PrintPdfFile(const FileName: string);
var
  Pdf: TPdf;
  I: Integer;
  Bitmap: TBitmap;
  InfoHeaderSize, ImageSize: DWORD;
  InfoHeader: PBitmapInfo;
  Image: Pointer;
  FirstPage: Boolean;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;
    if not Pdf.Active then
      Exit;  // lataus epäonnistui hiljaa; peräänny

    Printer.Title := Pdf.Title;
    Printer.BeginDoc;
    try
      FirstPage := True;
      for I := 1 to Pdf.PageCount do
      begin
        if FirstPage then
          FirstPage := False
        else
          Printer.NewPage;

        Pdf.PageNumber := I;

        // Hahmonna tulostimen resoluutiolla; rePrinting säätää hahmonnuspolkua
        Bitmap := Pdf.RenderPage(
          0, 0,
          Printer.PageWidth,
          Printer.PageHeight,
          ro0,
          [rePrinting]
        );
        try
          GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);
          InfoHeader := AllocMem(InfoHeaderSize);
          try
            Image := AllocMem(ImageSize);
            try
              GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);
              StretchDIBits(
                Printer.Canvas.Handle,
                0, 0, Printer.PageWidth, Printer.PageHeight,
                0, 0, Bitmap.Width, Bitmap.Height,
                Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY
              );
            finally
              FreeMem(Image);
            end;
          finally
            FreeMem(InfoHeader);
          end;
        finally
          Bitmap.Free;
        end;
      end;
    finally
      Printer.EndDoc;
    end;
  finally
    Pdf.Active := False;
    Pdf.Free;
  end;
end;

Printer.PageWidth- ja Printer.PageHeight-arvojen välittäminen bittikartan mittoina tarkoittaa, että hahmonnat tulostimen natiivilla pikselikoolla, mikä ottaa jo huomioon laitteen DPI:n. StretchDIBits-kutsu kartoittaa sitten nuo pikselit 1:1 sivulle. Tämä antaa sinulle parhaan saavutettavissa olevan tarkkuuden ilman nimenomaista DPI-aritmetiikkaa, mutta se toimii vain silloin, kun PDF-sivu ja fyysinen paperi sattuvat olemaan samankokoisia. Kun ne eroavat, tarvitset eksplisiittistä skaalausta

Skaalaus, kun sivun ja paperin koot eroavat toisistaan

A4-muotoinen muotokuva-PDF-sivu ei automaattisesti mahdu US Letter -tulostimeen, ja maisemasivu, joka syötetään pystysuuntaiseen tulostimeen, leikkaantuu. Vakiolähestymistapa on laskea tasainen skaalauskerroin tulostimen pikselien ja PDF-pisteiden suhteesta ja soveltaa sitä sitten molempiin mittoihin, jotta kuvasuhde säilyy. Pdf.PageWidth ja Pdf.PageHeight paljastavat nykyiset sivumitat pisteinä, joissa yksi piste on 1/72 tuumaa. Kertominen tavoite-DPI:llä ja jakaminen 72:lla muuntaa pikseleiksi kyseisellä resoluutiolla. Ota Min X- ja Y-suhteista saadaksesi suurimman skaalan, joka mahtuu yhä tulostusalueen sisälle:

Sovita-sivulle -skaalaus PDF-tulostukseen Delphissä: 595 × 842 pisteen PDF-sivu istuu skaalattuna tulostinpikselien tulostuskelpoisen alueen sisään, muunnettuna pisteet kertaa Dpi jaettuna 72:lla, skaalattuna leveys- ja korkeussuhteen pienemmällä, rajattuna sovita-kutistamalla ja mitoitettuna kohdebittikarttaan
Pisteistä tulee pikseleitä pisteinä kertaa DPI jaettuna 72:lla, ja pienempi suhde voittaa, joten mikään ei leikkaudu. Rajaus 1.0:aan pitää sovituksen vain pienentävänä
// Sovita PDF-sivu tulostusalueelle, säilyttäen kuvasuhde
var
  ScaleX, ScaleY, Scale: Double;
  DestWidth, DestHeight: Integer;
  Dpi: Integer;
begin
  Dpi := 300;  // kohderesoluutio
  Pdf.PageNumber := PageIndex;

  ScaleX := Printer.PageWidth  / (Pdf.PageWidth  * Dpi / 72);
  ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
  Scale  := Min(ScaleX, ScaleY);

  // Rajoita arvoon 1.0 vain kutistamista varten (ei suurennusta)
  if Scale > 1.0 then Scale := 1.0;

  DestWidth  := Round(Pdf.PageWidth  * Dpi / 72 * Scale);
  DestHeight := Round(Pdf.PageHeight * Dpi / 72 * Scale);

  Bitmap := Pdf.RenderPage(0, 0, DestWidth, DestHeight, ro0,
    [rePrinting, reAnnotations]);
  // ... siirrä StretchDIBits-funktiolla kuten yllä
end;

Hahmontaminen arvolla Dpi = 300 sopii useimpiin toimistotulostimiin. 600 DPI:llä yhden A4-sivun bittikartta nousee karkeasti 34 megapikseliin, mikä on noin 100 Mt 32-bittisenä bittikarttana; laadun paraneminen tavallisille tekstiasiakirjoille on minimaalista, ja sivukohtainen muistikustannus on merkittävä. Pidä 600 DPI tulostusliikkeitä tai vektoriraskaita teknisiä piirustuksia varten, joissa sillä on todella väliä

Toisen koodilohkon reAnnotations-lippu on riippumaton rePrinting-lipusta. Sisällytä se, kun käyttäjä odottaa leimojen, korostusten ja kommenttilaatikoiden näkyvän paperilla. Jätä se pois, jos tuloste on vain sisältöä varten. Molempia lippuja voi yhdistää vapaasti

Sivun kierto

PDFium tallentaa sivun kierron PDF-tiedostoon /Rotate-merkintänä, joka on saatavilla Pdf.PageRotation-ominaisuuden kautta ja joka palauttaa TRotation-arvon (ro0, ro90, ro180, ro270). Tulostimen koordinaattijärjestelmä kääntää 90 ja 270 asteen kierrot suhteessa näyttöön. Jos välität raa'an PageRotation-arvon suoraan RenderPage-metodille ilman minkäänlaista säätöä, muotokuva-asiakirjaan upotetut maisemasivut tulostuvat ylösalaisin useimmissa Windowsin tulostinohjaimissa. Korjaus on yksinkertainen vaihto ennen hahmonnuskutsua: kartoita ro90 arvoon ro270 ja ro270 takaisin arvoon ro90, jättäen ro0 ja ro180 ennalleen

Varmista tämä toiminta tietyllä kohdetulostimellasi ennen julkaisua. Ohjaimen toiminta kiertoon liittyen ei ole yhtenäistä toimittajien välillä, ja jotkut ohjaimet soveltavat omaa kiertokorjaustaan GDI-tasolla. Jos näet kaksinkertaisen kierron, poista vaihto; jos et näe lainkaan korjausta, lisää se. Sekasuuntainen asiakirja, jossa vuorottelevat pysty- ja vaakasuuntaiset sivut, on nopein tapa havaita kumpi tahansa vikatila testauksen aikana

Muistin hallinta pitkässä tulostustyössä

Jokainen RenderPage-kutsu varaa uuden TBitmap-objektin, jonka kutsuja omistaa ja joka on vapautettava. Yllä olevassa silmukassa try/finally Bitmap.Free -lohko käsittelee tämän oikein yhden sivun osalta kerrallaan. Älä kerrytä bittikarttoja sivujen yli: 300 DPI:n hahmonnus 200-sivuisesta asiakirjasta kuluttaisi gigatavuja ennen kuin ensimmäinen sivu saavuttaa taustatulostimen. Vapauta jokainen bittikartta ennen seuraavalle sivulle siirtymistä

AllocMem / FreeMem -pari siirtolohkon sisällä noudattaa samaa sääntöä. GetDIBSizes kertoo sinulle, kuinka paljon muistia DIB-otsikko ja pikselidata tarvitsevat; varaat, täytät, maalaat ja vapautat kaiken yhden sivun laajuudessa. Jommankumman lohkon vuotamisen salliminen saa tulostustyön kuluttamaan prosessin keon loppuun asiakirjoissa, jotka ovat pidempiä kuin muutama kymmenen sivua

Jos sinun on suoritettava tulostustöitä taustasäikeessä, pidä TPdf ja kaikki VCL-tulostinkutsut samassa säikeessä. TPdf itse ei ole säikeistysturvallinen sellaisten ilmentymien välillä, jotka jakavat PDFium DLL:n globaalin tilan; turvallisin malli on yksi TPdf per säie, ja kukin lataa oman kopionsa tiedostosta

Tässä esitetyt hahmonnus- ja asiakirja-API:t ovat osa PDFium Component -komponenttia Delphille ja C++Builderille