Teknisk artikel

PDF Library for Delphi udskriftsforhåndsvisning og Device-Context-output i Delphi

At gengive en PDF-side på en Windows-enhedskontekst til forhåndsvisning ved udskrivning sætter tre koordinatsystemer i samme kodelinje, og de er sjældent enige. PDF-siden måles i punkter med origo nederst til venstre. Skærmens DC måles i pixels med origo øverst til venstre og en zoomfaktor, du vælger. Printerens DC, den som forhåndsvisningen skal forudsige, måler pixels ved enhedens opløsning, men placerer sit origo i hjørnet af det udskrivbare område, ikke i hjørnet af arket. Ram bare én af dem forkert, og forhåndsvisningen ser fin ud, mens den udskrevne side kommer ud forskudt, skaleret eller beskåret langs en kant. Det sædvanlige symptom er en formular med ramme, der vises centreret og udskrives med de øverste og venstre streger skåret af, fordi laserprinteren ikke kan lægge blæk i de yderste få millimeter, og ingen fortalte forhåndsvisningen det. losLab PDF Library (PDF Library for Delphi) dækker hele stien med gengivelseskald til enhedskontekster, et konfigurationslag med virtuelle printere og forhåndsvisningsbitmaps genereret ud fra printerens egne mål, og det sidste er det, der gør forhåndsvisningen ærlig omkring den margen

Papirets geometri er ikke det udskrivbare områdes geometri

To rektangler beskriver ethvert udskrivningsmål, og forskydningen mellem dem er dér, de fleste fejl i forhåndsvisninger bor. Papirrektanglet er det fysiske ark. Det udskrivbare rektangel er den mindre region, printmotoren rent faktisk kan nå, indrykket af en hardwaremargen, der er forskellig fra printermodel til printermodel og undertiden fra bakke til bakke. Bibliotekets udskrivningslag måler begge. Den underliggende klasse TPLPrinter blotlægger PageWidth og PageHeight for det udskrivbare område, FullPageWidth og FullPageHeight for hele arket og PrintOffsetX sammen med PrintOffsetY for afstanden mellem deres origoer, alt sammen i enhedspixels ved den opløsning, GetDPI melder. En ærlig forhåndsvisning skalerer netop de tal ned til skærmopløsning i stedet for at male siden ind i det rektangel, kontrollen nu tilfældigvis har. Springer du det trin over, antager forhåndsvisningen stiltiende en margen på nul, og det er den ene værdi, ingen rigtig printer bruger

PDF Library for Delphi-diagram over det fulde papirark versus det mindre udskrivbare rektangel, med PrintOffsetX og PrintOffsetY, der markerer hardwaremargenen mellem deres origo
Papirrektanglet er det fysiske ark, mens det udskrivbare rektangel er det, print-motoren kan nå, og afstanden mellem deres origopunkter er, hvor de fleste preview-fejl bor

Forhåndsvisning på skærmen gennem RenderPageToDC

Til en forhåndsvisningskontrol på skærmen tegner RenderPageToDC(DPI, Page, DC) en side af det indlæste dokument direkte på enhver GDI-enhedskontekst, hvad enten det er et TPaintBox-lærred, en bitmap uden for skærmen eller en metafil-DC. DPI-argumentet sætter zoomen. 96 svarer nogenlunde til en visning på 100 % på en klassisk skærm, og en fordobling fordobler den gengivne størrelse

procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
  // disse tre er klæbrig bibliotekstilstand, ikke parametre per kald:
  FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
  FPdf.SetRenderDCErasePage(1);
  FPdf.SetRenderCropType(0);
  FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;

Fælden er, at DC-gengivelsesstien styres af klæbrig bibliotekstilstand, ikke af parametre per kald. SetRenderDCOffset, SetRenderDCErasePage og SetRenderCropType bliver hver især hængende, indtil noget ændrer dem, så en miniature-løkke, der kører, efter at brugeren har justeret den zoomede visning, arver hvilken forskydning eller beskæring den forrige kodesti nu efterlod. Symptomet er en forhåndsvisning, der kun driver i bestemte navigationsforløb, hvilket er omtrent så elendigt at reproducere, som en fejl kan blive. At sætte al den relevante tilstand i toppen af paint-handleren, som ovenfor, koster ingenting og fjerner hele klassen. En anden multiplikator gemmer sig lige i nærheden. Den effektive outputopløsning er gengivelsesskalaen gange DPI-argumentet, og selv om SetRenderScale som standard står på 1,0, bliver den også hængende, når den først er ændret, så en eksportfunktion, der har skruet på den, omskalerer stille og roligt enhver senere forhåndsvisning, indtil noget sætter den tilbage

Fremvisere med rulning og delvise gentegninger har en dedikeret variant. RenderPageToDCClip tager en clip-specifikation sammen med enhedskonteksten, så en invalidering af ét bånd i vinduet kun gentegner det bånd i stedet for at rasterisere hele siden igen. Ved høj zoom på storformatsider er det forskellen på en fremviser, der følger rullebjælken, og en, der smører sig ud bag den

Et printjob, der matcher forhåndsvisningen

Udskrivningssiden arbejder gennem en virtuel printer. NewCustomPrinter kloner en systemprinter ind i en biblioteksprivat konfiguration, og SetupPrinter justerer den klon uden at røre maskinens fælles DevMode: papir går ind som indstilling 1 (en DMPAPER_*-konstant) og orientering som indstilling 11. Gevinsten er isolering. En tjeneste kan udskrive A4-etiketter, mens værtens standardprinter bliver stående på Letter, og intet skal gendannes bagefter

PDF Library for Delphi: Flow fra systemets standardprinter gennem NewCustomPrinter og SetupPrinter til et isoleret printjob, der aldrig rører den maskinomfattende DevMode
SetupPrinter retargeter en biblioteks-privat klon, så en tjeneste kan udskrive A4, mens værtens standardprinter beholder sit Letter DevMode urørt
var
  Pdf: TPDFlib;
  Virt: WideString;
  Opt: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.LoadFromFile('report.pdf', '') <> 1 then
      raise Exception.Create('load failed');
    Virt := Pdf.NewCustomPrinter(Pdf.GetDefaultPrinterName);
    Pdf.SetupPrinter(Virt, 1, 9);        // indstilling 1 = papir, DMPAPER_A4
    Pdf.SetupPrinter(Virt, 11, 1);       // indstilling 11 = orientering, 1 = stående
    Opt := Pdf.PrintOptions(1, 1, 'Monthly Report');  // tilpas til papir, autorotér + centrér
    Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
  finally
    Pdf.Free;
  end;
end;

PrintOptions fortjener en omhyggelig læsning. Den returnerer et options-handle, som du skal sende videre til PrintDocument eller PrintPages; det er ikke omgivende tilstand. At bygge indstillingerne og så glemme at sende handlet videre fejler tavst. Jobbet udskrives med standardværdier, og ingen bemærker det, før en politik om tilpasning til papiret var forventet, og en overdimensioneret side i stedet kom ud beskåret. Argumentet om sideskalering er dér, den politik bor. Ingen skalering bevarer den dimensionelle nøjagtighed, hvilket betyder noget for formularer, der bliver målt op mod en lineal. Tilpasning til papiret omskalerer alt til arket. Formindsk store sider lader normale sider være i fred og træder kun til, når en side overskrider det udskrivbare område, hvilket som regel er det rigtige standardvalg til et blandet sæt dokumenter. Flaget for autorotering og centrering håndterer liggende sider uden en ekstra kodesti

Programmer, der allerede styrer en TPrinter gennem VCL'ens dialogforløb, kan overdrage den direkte. PrintDocumentToPrinterObject og PrintPagesToPrinterObject tager imod den konfigurerede TPrinter-instans, hvilket bevarer standardudskrivningsdialogen som den brugervendte konfigurationsflade, mens biblioteket står for sidegengivelsen. At blande de to tilgange i én kodesti plejer at genindføre netop den geometridrift, resten af dette arbejde skulle slå ihjel, så vælg én. Vejen med den virtuelle printer passer til tjenester uden opsyn; TPrinter-vejen passer til interaktive programmer

Selektivt output fungerer på samme måde. PrintPages tager en intervalstreng, så hvis du sender den virtuelle printers navn, '2-5,12' og options-handlet, udskrives side 2 til 5 og 12 med geometrikontrakten intakt, og den samme syntaks driver print-til-fil-varianterne. De filvarianter er det praktiske svar til et miljø uden opsyn, hvor ingen fysisk enhed er tilsluttet: regressionstest af printgeometri på en byggeserver, der slet ikke har nogen driverkø. Gengiv det samme dokument gennem de samme indstillinger til et filartefakt ved hver build, og en geometriregression bliver til en diff i stedet for en kundehenvendelse tre uger senere

Forhåndsvisningsbitmaps med printerens egne mål

En forhåndsvisning gengivet ved 96 DPI mod en antaget sidestørrelse besvarer det forkerte spørgsmål. Den viser, hvordan siden ser ud, ikke hvad denne printer vil lægge på dette papir. GetPrintPreviewBitmapToString lukker det hul ved at bygge forhåndsvisningen ud fra samme brugerdefinerede printer og samme options-handle som det endelige job, så papirstørrelse, orientering, skaleringspolitik, rotation og hardwareforskydningen alle går ind i bitmappen. Det, der kommer tilbage, er det, arket vil vise

PDF Library for Delphi: Kontrast mellem en skærmforhåndsvisning med antaget sidestørrelse, der fejlagtigt angiver margener, og en printerægte bitmap bygget fra jobbets egen printer- og options-handle
GetPrintPreviewBitmapToString tegner previewet fra det samme brugerdefinerede printer- og options-handle som det endelige job, så bitmappen viser margener og rotation, som arket virkelig får
procedure ShowPrinterTruePreview(Pdf: TPDFlib; const Virt: WideString; Opt: Integer);
var
  Data: AnsiString;
  Strm: TMemoryStream;
  Bmp: TBitmap;
begin
  Data := Pdf.GetPrintPreviewBitmapToString(Virt, 1, Opt, 1200, 0);
  Strm := TMemoryStream.Create;
  try
    Strm.WriteBuffer(PAnsiChar(Data)^, Length(Data));
    Strm.Position := 0;
    Bmp := TBitmap.Create;
    try
      Bmp.LoadFromStream(Strm);
      PreviewImage.Picture.Assign(Bmp);
    finally
      Bmp.Free;
    end;
  finally
    Strm.Free;
  end;
end;

Argumentet MaxDimension sætter loft over bitmappens lange kant. 1200 pixels forbliver skarpt i en forhåndsvisningsdialog og holder hukommelsesforbruget beskedent selv for ingeniørtegninger i E-format, hvor en gengivelse i fuld opløsning ved printerens 600 DPI ville løbe op i gigabyte

At huske brugerens printervalg

Udskrivningsdialoger, der glemmer deres indstillinger mellem sessioner, genererer supportsager helt af sig selv. DevMode-parret, GetPrinterDevModeToString og SetPrinterDevModeFromString, serialiserer en printers fulde driverkonfiguration til en uigennemsigtig streng, du kan gemme i brugerindstillingerne og gendanne næste session, inklusive de driverspecifikke valgmuligheder, intet generisk API gider modellere. Gem printeren efter navn fra GetPrinterNames, aldrig efter listeindeks. Rækkefølgen i indekset ændrer sig, hver gang en printer tilføjes eller fjernes, så et gemt indeks peger stille og roligt på den forkerte enhed, næste gang listen forskyder sig. GetDefaultPrinterName dækker reserveløsningen, når den huskede enhed er forsvundet helt

Valg af bakke runder historien om vedvarende indstillinger af. GetPrinterBins melder de papirkilder, en driver blotlægger, hvilket betyder noget for arbejdsgange med brevpapir, hvor side ét trækker fra brevpapirbakken og resten fra almindeligt papir. Det er en politik, brugere forventer, at programmet husker sammen med alt det andet, og et printjob, der lander på det forkerte papir, læses som en fejl, selv når hver eneste byte i PDF'en var korrekt

Hold én motor på tværs af forhåndsvisning og udskrivning

En sidste beslutning styrer stille og roligt troværdigheden. Valget af gengivelsesmotor gælder for både skærm- og printerdestinationer, så fristelsen er at vise forhånd med en hurtig motor og udskrive med en præcis. Modstå den. At drive forhåndsvisningen og jobbet gennem forskellige motorer genindfører præcis den drift i troværdighed, som en printertro forhåndsvisning blev bygget for at fjerne, og den gør det på en måde, der kun viser sig på papir. Afvejningerne mellem den indbyggede motor, Cairo og PDFium bliver vejet i PDF-gengivelse med flere motorer i Delphi; vælg én, og brug den på begge sider

Dokumenter, der er for store til at indlæse bekvemt før udskrivning, kan åbnes gennem den direct access-sti, der beskrives i fletning, opdeling og direct access for store PDF'er, som gengiver sider til en enhedskontekst ud fra et fil-handle uden at bygge dokumenttræet. Den komplette API-reference for udskrivning findes på produktsiden for losLab PDF Library til Delphi