Izrisovanje strani PDF v kontekst naprave (Device Context - DC) sistema Windows za predogled tiskanja postavi tri koordinatne sisteme v isto vrstico kode, ti pa se le redko ujemajo. Stran PDF se meri v točkah z izhodiščem v spodnjem levem kotu. Zaslonski DC se meri v slikovnih pikah z izhodiščem v zgornjem levem kotu in izbranim faktorjem povečave. Tiskalniški DC, ki naj bi ga predogled predvidel, meri slikovne pike pri ločljivosti naprave, vendar postavi svoje izhodišče v kot tiskalnega območja, ne v kot lista. Če gre karkoli od tega narobe, je predogled videti v redu, medtem ko je natisnjena stran premaknjena, spremenjena po velikosti ali obrezana vzdolž roba. Običajen simptom je obrazec z obrobo, ki je v predogledu sredinsko poravnan, natisne pa se z odrezanimi zgornjimi in levimi črtami, saj laserski tiskalnik ne more nanašati črnila na zadnjih nekaj milimetrov, predogled pa o tem ni bil obveščen. Knjižnica losLab PDF Library (PDFlibPas) pokriva celotno pot s klici za izrisovanje v kontekst naprave, konfiguracijsko plastjo za virtualni tiskalnik in bitnimi slikami predogleda, ustvarjenimi na podlagi lastnih meritev tiskalnika, kar zagotavlja, da predogled natančno prikazuje dejanske robove.
Geometrija papirja ni enaka tiskalni geometriji
Vsak cilj tiskanja opredeljujeta dva pravokotnika, odmik med njima pa je mesto, kjer se skriva večina hroščev pri predogledu. Pravokotnik papirja predstavlja fizični list. Tiskalni pravokotnik pa je manjše območje, ki ga tiskalna naprava dejansko lahko doseže, zamaknjeno za strojni rob, ki se razlikuje glede na model tiskalnika in včasih celo glede na pladenj. Knjižnična plast za tiskanje meri oba pravokotnika. Podredno razred TPLPrinter izpostavlja lastnosti PageWidth in PageHeight za tiskalno območje, FullPageWidth and FullPageHeight za celoten list ter PrintOffsetX in PrintOffsetY za razmik med njunima izhodiščema, vse v slikovnih pikah naprave pri ločljivosti, ki jo vrne funkcija GetDPI. Zanesljiv predogled te iste številke prilagodi ločljivosti zaslona, namesto da bi stran izrisal v poljuben pravokotnik, ki ga ima kontrolnik na voljo. Če ta korak preskočite, predogled tiho predvideva rob nič, kar pa je vrednost, ki je ne uporablja noben pravi tiskalnik.
Predogled na zaslonu prek RenderPageToDC
Za zaslonski kontrolnik predogleda funkcija RenderPageToDC(DPI, Page, DC) izriše stran naloženega dokumenta neposredno v kateri koli GDI kontekst naprave, naj bo to platno TPaintBox canvas, bitna slika zunaj zaslona ali DC datoteke metafile. Parameter DPI določa povečavo. Vrednost 96 ustreza približno 100-odstotnemu prikazu na klasičnem zaslonu, podvojitev te vrednosti pa podvoji velikost izrisanega elementa.
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// these three are sticky library state, not per-call parameters:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
Past je v tem, da pot izrisa DC vodi trajno stanje knjižnice in ne posamezni klici parametrov. Metode SetRenderDCOffset, SetRenderDCErasePage in SetRenderCropType ohranijo svoje nastavitve, dokler jih nekaj ne spremeni. Zato zanka za sličice, ki se izvede po tem, ko je uporabnik prilagodil povečavo pogleda, podeduje kakršen koli odmik ali obrezovanje, ki ga je zapustila prejšnja koda. Simptom tega je predogled, ki se premakne le pri določenih navigacijskih zaporedjih, kar je eden najbolj zapletenih hroščev za poustvarjanje. Nastavitev vseh ustreznih stanj na vrhu rutine za izris, kot je prikazano zgoraj, ne stane nič in odpravi to težavo. V bližini pa se skriva še en množitelj. Dejanska izhodna ločljivost je zmnožek merila izrisa in parametra DPI; čeprav je privzeta vrednost za SetRenderScale 1.0, tudi ta nastavitev ostane nespremenjena, ko jo spremenite, zato funkcija za izvoz, ki jo je povečala, tiho spremeni merilo vsakega poznejšega predogleda, dokler jo nekaj ne ponastavi.
Za drsne bralnike in delna ponovna izrisovanja obstaja namenska različica. Funkcija RenderPageToDCClip poleg konteksta naprave sprejme tudi specifikacijo območja izreza, tako da razveljavitev enega pasu okna ponovno izriše le ta del, namesto da bi ponovno rasterizirala celotno stran. Pri visoki povečavi na straneh velikega formata je to razlika med bralnikom, ki tekoče sledi drsniku, in tistim, ki zaostaja za njim.
Tiskalno opravilo, ki se ujema s predogledom
Tiskanje poteka prek virtualnega tiskalnika. Funkcija NewCustomPrinter klonira sistemski tiskalnik v zasebno konfiguracijo knjižnice, SetupPrinter pa prilagodi ta klon brez spreminjanja sistemskih nastavitev DevMode: papir se nastavi z nastavitvijo 1 (konstanta DMPAPER_*), usmerjenost pa z nastavitvijo 11. Prednost tega je popolna izolacija. Storitev lahko tiska nalepke formata A4, medtem ko privzeti tiskalnik gostitelja ostane nastavljen na Letter, po zaključku pa ni treba ničesar obnavljati.
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); // setting 1 = paper, DMPAPER_A4
Pdf.SetupPrinter(Virt, 11, 1); // setting 11 = orientation, 1 = portrait
Opt := Pdf.PrintOptions(1, 1, 'Monthly Report'); // fit to paper, auto-rotate + center
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
Funkcija PrintOptions zahteva natančno razumevanje. Vrne ročico (handle) možnosti, ki jo morate prenesti v PrintDocument ali PrintPages; to ni del splošnega stanja okolja. Če ustvarite možnosti in nato pozabite posredovati ročico, se izvedba konča brez opozorila o napaki. Opravilo se natisne s privzetimi nastavitvimi, kar opazite šele takrat, ko se namesto pričakovanega prilagajanja velikosti natisne prevelika stran, ki je obrezana. Parameter za prilagajanje velikosti strani določa to politiko. Brez prilagajanja se ohrani natančna dimenzijska natančnost, kar je pomembno za obrazce, ki se merijo z ravnilom. Prilagoditev papirju (Fit-to-paper) spremeni velikost vsega na list papirja. Možnost za pomanjšanje velikih strani (Shrink-large-pages) pusti običajne strani nedotaknjene in posreduje le, ko stran preseže tiskalno območje, kar je običajno pravilna privzeta nastavitev za mešane dokumente. Zastavica za samodejno vrtenje in centriranje pa poskrbi za ležeče strani brez potrebe po dodatni kodi.
Aplikacije, ki že upravljajo TPrinter prek pogovornih oken VCL, ga lahko predajo neposredno. Funkciji PrintDocumentToPrinterObject in PrintPagesToPrinterObject sprejmeta konfiguriran primerek TPrinter, s čimer ohranita standardno pogovorno okno za tiskanje kot uporabniški vmesnik, medtem ko knjižnica poskrbi za izrisovanje strani. Mešanje obeh pristopov v eni kodi običajno ponovno povzroči odstopanje geometrije, ki smo ga želeli odpraviti, zato izberite le enega. Pot z virtualnim tiskalnikom je primerna za storitve brez nadzora, pot s TPrinter pa za interaktivne aplikacije.
Selektivni izpis deluje na enak način. PrintPages sprejme niz z obsegom strani, tako da posredovanje imena virtualnega tiskalnika, niza '2-5,12' in ročice možnosti natisne strani od 2 do 5 ter stran 12 z ohranjeno geometrijo. Enaka sintaksa velja tudi za različice tiskanja v datoteko. Te različice za tiskanje v datoteko so praktična rešitev za okolja brez nadzora, kjer ni priključene fizične naprave: npr. za regresijsko testiranje geometrije tiskanja na gradilnem strežniku brez tiskalniške vrste. Z izrisom istega dokumenta z enakimi možnostmi v datotečni artefakt pri vsaki gradnji se morebitno odstopanje geometrije pokaže kot razlika v kodi (diff) in ne kot prijava naprave s strani stranke čez tri tedne.
Bitne slike predogleda z lastnimi meritvami tiskalnika
Predogled, izrisan pri 96 DPI glede na predvideno velikost strani, daje odgovor na napačno vprašanje. Prikazuje namreč, kako je stran videti, ne pa tega, kaj bo tiskalnik dejansko natisnil na papir. Funkcija GetPrintPreviewBitmapToString premosti to vrzel tako, da zgradi predogled na podlagi istega prilagojenega tiskalnika in ročice možnosti kot končno opravilo, tako da se velikost papirja, usmerjenost, politika prilagajanja velikosti, vrtenje in strojni odmik upoštevajo pri ustvarjanju bitne slike. Rezultat, ki ga prejmete, natančno prikazuje končni izgled natisnjenega lista.
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;
Argument MaxDimension omeji daljšo stranico bitne slike. Vrednost 1200 slikovnih pik zagotavlja oster prikaz v pogovornem oknu predogleda in ohranja nizko porabo pomnilnika celo pri inženirskih načrtih velikosti E, kjer bi izris polne ločljivosti pri 600 DPI tiskalnika zahteval več gigabajtov pomnilnika.
Shranjevanje uporabnikovih nastavitev tiskalnika
Pogovorna okna za tiskanje, ki pozabijo svoje nastavitve med sejami, povzročajo nepotrebne zahtevke za podporo. Par funkcij DevMode, GetPrinterDevModeToString in SetPrinterDevModeFromString, serializira celotno konfiguracijo gonilnika tiskalnika v nepregleden niz, ki ga lahko shranite v uporabniške nastavitve in obnovite ob naslednji seji, vključno s specifičnimi možnostmi gonilnika, ki jih splošni API-ji ne podpirajo. Tiskalnik vedno shranite po imenu iz funkcije GetPrinterNames in nikoli po indeksu seznama. Vrstni red indeksov se spremeni vsakič, ko dodate ali odstranite tiskalnik, zato shranjeni indeks ob naslednji spremembi seznama tiho pokaže na napačno napravo. Funkcija GetDefaultPrinterName pa ponuja nadomestno možnost, če shranjena naprava sploh ne obstaja več.
Izbira pladnja dopolnjuje zgodbo o shranjevanju nastavitev. Funkcija GetPrinterBins vrne vire papirja, ki jih ponuja gonilnik, kar je pomembno pri delu z dopisnimi papirji, kjer se prva stran vzame iz pladnja z dopisnim papirjem, preostale pa iz navadnega papirja. To je nastavitev, za katero uporabniki pričakujejo, da jo bo aplikacija shranila skupaj z vsem ostalim, saj se tiskalno opravilo, ki konča na napačnem papirju, šteje za napako, četudi je bil vsak bajt zapisa PDF povsem pravilen.
Uporaba enakega pogona za predogled in tiskanje
Zadnja odločitev tiho vpliva na natančnost prikaza. Izbira pogona za izrisovanje velja tako za zaslon kot za tiskalnik, zato se pogosto pojavi skušnjava, da bi za predogled uporabili hiter pogon, za tiskanje pa natančnega. Uprite se temu. Izvajanje predogleda in tiskalnega opravila prek različnih pogonov ponovno uvede natanko tisto odstopanje pri natančnosti prikaza, ki ga je zanesljiv predogled tiskalnika moral odpraviti, in to na način, ki se pokaže šele na papirju. Primerjave med vgrajenim pogonom ter pogonoma Cairo in PDFium so opisane v članku večpogonsko izrisovanje PDF v Delphiju; izberite enega in ga uporabite na obeh straneh.
Dokumente, ki so preveliki za enostavno nalaganje pred tiskanjem, lahko odprete prek poti za neposreden dostop, opisane v članku združevanje, razdeljevanje in neposreden dostop do velikih datotek PDF, ki izriše strani v kontekst naprave iz ročice datoteke, ne da bi zgradil celotno drevo dokumenta. Celotna referenca API-ja za tiskanje je na voljo na strani izdelka losLab PDF Library za Delphi.