PDF puslapio atvaizdavimas „Windows“ įrenginio kontekste (angl. device context, DC) spaudinio peržiūrai sujungia tris skirtingas koordinačių sistemas vienoje kodo eilutėje, kurios retai sutampa. PDF puslapis matuojamas taškais (angl. points), o jo koordinatės prasideda apatiniame kairiajame kampe. Ekrano įrenginio kontekstas matuojamas pikseliais, jo pradžia yra viršutiniame kairiajame kampe, o mastelį pasirenka pats vartotojas. Spausdintuvo įrenginio kontekstas (tas, kurį peržiūra turėtų tiksliai prognozuoti) matuoja pikselius pagal įrenginio skiriamąją gebą, tačiau jo pradžios taškas yra spausdinamos srities kampe, o ne viso popieriaus lapo kampe. Jei suklysite bent vienoje iš šių sistemų, peržiūra ekrane atrodys puikiai, tačiau atspausdintas puslapis bus pasislinkęs, pakeisto mastelio arba nukirptas ties kraštu. Dažniausias simptomas yra forma su rėmeliu, kurios peržiūra rodoma centre, tačiau spausdinant viršutinė ir kairioji linijos yra nukerpamos, nes lazerinis spausdintuvas negali padengti dažais paskutinių kelių milimetrų lapo pakraštyje, o peržiūros lange tai nebuvo numatyta. „losLab PDF Library“ („PDFlibPas“) apima visą šį procesą su įrenginio konteksto atvaizdavimo iškvietimais, virtualaus spausdintuvo konfigūravimo sluoksniu ir peržiūros bitų žemėlapiais (angl. bitmaps), sugeneruotais pagal paties spausdintuvo metriką, kas leidžia užtikrinti, kad peržiūroje būtų tiksliai atsižvelgta į šias paraštes.
Popieriaus geometrija nėra spausdinama geometrija
Kiekvieną spaudinio taikinį apibūdina du stačiakampiai, o poslinkis tarp jų yra ta vieta, kurioje kyla dauguma peržiūros klaidų. Popieriaus stačiakampis reprezentuoja fizinį lapą. Spausdinamos srities stačiakampis yra mažesnis regionas, kurį spausdintuvas iš tikrųjų gali pasiekti, atimant aparatinę paraštę, kuri skiriasi priklausomai nuo spausdintuvo modelio, o kartais net ir nuo popieriaus dėklo. Bibliotekos spausdinimo sluoksnis matuoja abu šiuos dydžius. Pagrindinė klasė TPLPrinter pateikia PageWidth ir PageHeight spausdinamai sričiai, FullPageWidth bei FullPageHeight visam lapui ir PrintOffsetX bei PrintOffsetY poslinkiams tarp jų pradžios taškų – visi šie dydžiai nurodomi įrenginio pikseliais pagal skiriamąją gebą, kurią grąžina funkcija GetDPI. Teisinga peržiūra šiuos skaičius sumažina iki ekrano skiriamosios gebos, užuot tiesiog nupiešusi puslapį bet kokiame stačiakampyje, kurį turi valdymo elementas. Praleidus šį žingsnį, peržiūros lange bus daroma prielaida, kad paraštės lygios nuliui, o tai yra reikšmė, kurios nenaudoja joks realus spausdintuvas.
Ekrano peržiūra naudojant RenderPageToDC
Ekrano peržiūros elementui funkcija RenderPageToDC(DPI, Page, DC) nupiešia įkelto dokumento puslapį tiesiai į bet kurį GDI įrenginio kontekstą – nesvarbu, ar tai būtų TPaintBox drobė, atmintyje esantis bitų žemėlapis (angl. off-screen bitmap), ar metafailo įrenginio kontekstas. DPI argumentas nustato mastelį. Reikšmė 96 atitinka maždaug 100% dydį klasikinio ekrano atveju, o ją padvigubinus, atvaizduojamas puslapio dydis taip pat padvigubėja.
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;
Klastinga tai, kad DC atvaizdavimo kelias valdomas išliekančia bibliotekos būsena (angl. sticky library state), o ne kiekvieno iškvietimo parametrais. SetRenderDCOffset, SetRenderDCErasePage ir SetRenderCropType išlieka tol, kol kas nors juos pakeičia. Todėl miniatiūrų ciklas, paleistas vartotojui pakoregavus mastelį, paveldi bet kokį poslinkį ar apkarpymą, likusį iš ankstesnio kodo vykdymo. Dėl to peržiūra gali nukrypti tik tam tikrose navigacijos sekose, o tokią klaidą atkurti yra ypač sunku. Visų susijusių parametrų nustatymas piešimo apdorojimo pradžioje, kaip parodyta aukščiau, nieko nekainuoja ir visiškai pašalina šią klaidų klasę. Taip pat reikėtų nepamiršti kito daugiklio. Efektyvi išvesties skiriamoji geba yra atvaizdavimo mastelis, padaugintas iš DPI argumento. Nors numatytoji SetRenderScale reikšmė yra 1.0, ją pakeitus ji išlieka, todėl eksporto funkcija, kuri laikinai padidino mastelį, tyliai pakeis visų vėlesnių peržiūrų dydį, kol kas nors ją nustatys iš naujo.
Slenkamiems peržiūros langams ir daliniam perbraižymui yra skirtas atskiras variantas. RenderPageToDCClip priima kirpimo specifikaciją kartu su įrenginio kontekstu, todėl atnaujinant tik tam tikrą lango dalį yra perbraižoma tik ji, užuot iš naujo apdorojus visą puslapį. Esant dideliam masteliui didelio formato puslapiuose, tai lemia skirtumą tarp sklandaus vaizdo slinkimo ir vėluojančio ekrano perbraižymo.
Spausdinimo užduotis, atitinkanti peržiūrą
Spausdinimo procesas vyksta per virtualų spausdintuvą. Funkcija NewCustomPrinter nukopijuoja sisteminį spausdintuvą į privačią bibliotekos konfigūraciją, o SetupPrinter leidžia koreguoti šią kopiją nekeičiant visos sistemos DevMode parametrų: popierius nustatomas kaip 1 nustatymas (DMPAPER_* konstanta), o orientacija – kaip 11 nustatymas. Tai suteikia izoliaciją. Fone veikianti paslauga gali spausdinti A4 formato etiketes, kol pagrindinis sistemos spausdintuvas naudoja „Letter“ formatą, ir po to nieko nereikia atstatyti.
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;
Funkciją PrintOptions reikėtų išanalizuoti atidžiai. Ji grąžina parinkčių rodyklę (angl. options handle), kurią privalote perduoti funkcijai PrintDocument arba PrintPages; tai nėra bendra aplinkos būsena. Sukūrus parinktis ir pamiršus perduoti šią rodyklę, klaidos nebus rodomos, tačiau užduotis bus atspausdinta pagal numatytuosius nustatymus. Tai pastebima tik tada, kai tikimasi puslapio pritaikymo prie popieriaus lapo, o per didelis puslapis spausdinant yra tiesiog nukerpamas. Puslapio mastelio keitimo argumentas nusako šią politiką. Mastelio nekeitimas išsaugo matmenų tikslumą, kas svarbu formoms, kurios matuojamos liniuote. Parinktis „Fit-to-paper“ pritaiko viską prie lapo dydžio. Parinktis „Shrink-large-pages“ nepaliečia įprastų puslapių ir suveikia tik tada, kai puslapis viršija spausdinamą sritį, o tai paprastai yra tinkamiausias pasirinkimas įvairių dydžių dokumentų rinkiniui. Vėliavėlė „auto-rotate-and-center“ apdoroja gulsčius puslapius nenaudojant papildomo kodo kelio.
Programos, kurios jau valdo TPrinter per VCL dialogo langus, gali jį perduoti tiesiogiai. PrintDocumentToPrinterObject ir PrintPagesToPrinterObject priima sukonfigūruotą TPrinter egzempliorių, todėl vartotojui rodomas standartinis spausdinimo dialogo langas, o puslapių atvaizdavimą atlieka biblioteka. Šių dviejų būdų maišymas tame pačiame kodo kelyje gali vėl sukelti geometrijos nukrypimus, kuriuos ir bandome pašalinti, todėl pasirinkite vieną. Virtualaus spausdintuvo kelias labiau tinka automatizuotoms fono paslaugoms, o TPrinter kelias – interaktyvioms programoms.
Atrankinė spaudinio išvestis veikia taip pat. Funkcija PrintPages priima puslapių rėžio eilutę, todėl nurodžius virtualaus spausdintuvo pavadinimą, '2-5,12' ir parinkčių rodyklę, bus išspausdinti 2–5 ir 12 puslapiai išlaikant geometrijos taisykles. Ta pati sintaksė naudojama ir spausdinimo į failą variantuose. Šie spausdinimo į failą variantai yra praktiškas sprendimas automatizuotoje aplinkoje be prijungto fizinio įrenginio, pavyzdžiui, tikrinant geometrijos pasikeitimus kūrimo serveryje, kuriame nėra spausdintuvų eilių. Kiekvieno kūrimo metu eksportuokite tą patį dokumentą su tomis pačiomis parinktimis į failą, ir geometrijos pasikeitimas bus pastebėtas kaip failų skirtumas (angl. diff), o ne kaip kliento pranešimas apie klaidą po trijų savaičių.
Peržiūros bitų žemėlapiai su paties spausdintuvo metrika
Peržiūra, atvaizduota 96 DPI skiriamąja geba pagal numatytąjį puslapio dydį, atsako į neteisingą klausimą. Ji rodo, kaip puslapis atrodo ekrane, o ne tai, ką spausdintuvas iš tikrųjų perkels ant popieriaus. Funkcija GetPrintPreviewBitmapToString išsprendžia šią problemą, sukurdama peržiūros vaizdą iš to paties virtualaus spausdintuvo ir to paties parinkčių objekto, kaip ir būsima spausdinimo užduotis. Taip popieriaus dydis, orientacija, mastelio politika, pasukimas ir aparatinis poslinkis įtraukiami į generuojamą bitų žemėlapį. Grąžinamas vaizdas tiksliai atspindi tai, kas bus atspausdinta lape.
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;
Argumentas MaxDimension riboja ilgiausią bitų žemėlapio kraštinę. Reikšmė 1200 pikselių išlieka pakankamai aiški peržiūros dialogui ir reikalauja nedaug atminties net ir didelio formato inžineriniams brėžiniams, kurių pilno dydžio atvaizdavimas su 600 DPI spausdintuvo skiriamąja geba užimtų kelis gigabaitus.
Vartotojo pasirinktų spausdintuvo nustatymų išsaugojimas
Spausdinimo dialogo langai, kurie pamiršta savo nustatymus tarp sesijų, sukelia papildomų problemų vartotojams. DevMode funkcijų pora, GetPrinterDevModeToString and SetPrinterDevModeFromString, paverčia visą spausdintuvo tvarkyklės konfigūraciją į vientisą simbolių eilutę, kurią galite išsaugoti vartotojo nustatymuose ir atkurti kitos sesijos metu, įskaitant specifinius tvarkyklės nustatymus, kurių jokie bendrieji API neatvaizduoja. Spausdintuvą visada išsaugokite pagal pavadinimą, gautą iš GetPrinterNames, o ne pagal sąrašo indeksą. Indeksų tvarka keičiasi kiekvieną kartą pridėjus arba pašalinus spausdintuvą, todėl išsaugotas indeksas gali tyliai nukreipti į neteisingą įrenginį. Funkcija GetDefaultPrinterName padeda nustatyti numatytąjį įrenginį, jei išsaugotas spausdintuvas nebeegzistuoja.
Popieriaus dėklų parinkimas papildo nustatymų išsaugojimo galimybes. GetPrinterBins praneša apie popieriaus šaltinius, kuriuos pateikia tvarkyklė. Tai svarbu blankų spausdinimo procesuose, kai pirmasis puslapis imamas iš firminio blanko dėklo, o kiti puslapiai – iš paprasto popieriaus dėklo. Šiuos nustatymus vartotojai tikisi rasti išsaugotus kartu su visa kita konfigūracija, o užduotis, atspausdinta ant netinkamo popieriaus, laikoma klaida, net jei visi PDF baitai buvo visiškai teisingi.
Naudokite tą patį variklį peržiūrai ir spausdinimui
Paskutinis sprendimas lemia vizualinį tikslumą. Atvaizdavimo variklio pasirinkimas galioja tiek ekrano, tiek spausdintuvo tikslams, todėl kyla pagunda peržiūrai naudoti greitą variklį, o spausdinimui naudoti tikslų. Venkite to. Naudojant skirtingus variklius peržiūrai ir spausdinimui, vėl atsiranda vaizdo netikslumų, kuriuos bandėme pašalinti, ir jie išryškės tik ant popieriaus. Integruoto, „Cairo“ ir „PDFium“ variklių skirtumai aprašyti straipsnyje kelių atvaizdavimo variklių PDF rendering Delphi programoje; pasirinkite vieną ir naudokite jį abiejose pusėse.
Dokumentus, kurie yra per dideli, kad juos būtų galima patogiai įkelti prieš spausdinant, galima atverti naudojant tiesioginės prieigos kelią, aprašytą straipsnyje didelio PDF sujungimas, skaidymas ir tiesioginė prieiga, kuris atvaizduoja puslapius į įrenginio kontekstą tiesiai iš failo handle, nesukurdamas viso dokumento medžio. Pilną spausdinimo API aprašymą rasite „losLab PDF Library for Delphi“ produkto puslapyje losLab PDF Library for Delphi.