Ha egy PDF-oldalt nyomtatási előnézethez Windows-eszközkontextusra rajzolunk, azzal három koordináta-rendszer kerül ugyanabba a kódsorba, és ezek ritkán értenek egyet. A PDF-oldal pontban mérődik, az origó a bal alsó sarokban. A képernyő DC pixelben mérődik, az origó a bal felső sarokban, a nagyítási tényezőt pedig Ön választja meg. A nyomtató DC – az, amelyet az előnézetnek meg kellene jósolnia – az eszköz felbontásán mér pixelt, de az origóját a nyomtatható terület sarkába teszi, nem a lap sarkába. Ha ezek közül bármelyiket elrontja, az előnézet szépnek látszik, a nyomtatott oldal viszont eltolva, átméretezve vagy valamelyik szélén levágva jön ki. A szokásos tünet egy keretes űrlap, amely középre igazítva néz ki az előnézetben, majd a felső és a bal oldali vonal leszelve kerül papírra, mert a lézernyomtató nem tud tintát vinni a külső néhány milliméterre, és ezt senki nem közölte az előnézettel. A losLab PDF Library (PDF Library for Delphi) a teljes útvonalat lefedi: eszközkontextusra rajzoló hívásokkal, virtuálisnyomtató-konfigurációs réteggel és a nyomtató saját metrikáiból készített előnézeti bittérképekkel – utóbbi az a rész, amitől az előnézet őszinte lesz ezzel a margóval kapcsolatban
A papírgeometria nem azonos a nyomtatható geometriával
Bármely nyomtatási célt két téglalap ír le, és a legtöbb előnézeti hiba a kettő közti eltolásban lakik. A papírtéglalap a fizikai lap. A nyomtatható téglalap az a kisebb terület, amelyet a nyomtatómotor ténylegesen el tud érni, befelé húzva egy hardveres margóval, amely nyomtatómodellenként, néha tálcánként is más. A könyvtár nyomtatási rétege mindkettőt megméri. A mögöttes TPLPrinter osztály a nyomtatható területre a PageWidth és PageHeight értékeket, a teljes lapra a FullPageWidth és FullPageHeight értékeket, az origóik közti résre pedig a PrintOffsetX és PrintOffsetY értékeket adja meg, mindegyiket eszközpixelben, azon a felbontáson, amelyet a GetDPI jelent. Az őszinte előnézet ugyanezeket a számokat skálázza le képernyőfelbontásra, ahelyett hogy az oldalt abba a téglalapba festené, amely a vezérlőnek éppen a rendelkezésére áll. Hagyja ki ezt a lépést, és az előnézet szó nélkül nulla margót feltételez – ez az egyetlen érték, amelyet valódi nyomtató soha nem használ
Képernyő-előnézet a RenderPageToDC hívással
Képernyős előnézeti vezérlőhöz a RenderPageToDC(DPI, Page, DC) a betöltött dokumentum egy oldalát közvetlenül bármelyik GDI-eszközkontextusra rajzolja, legyen az egy TPaintBox vászna, egy képernyőn kívüli bittérkép vagy egy metafájl DC. A DPI argumentum állítja a nagyítást. A 96 klasszikus kijelzőn nagyjából a 100%-os nézetnek felel meg, a duplázása pedig duplájára növeli a kirajzolt méretet
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// ez a három ragadós könyvtárállapot, nem híváson belüli paraméter:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
A csapda az, hogy a DC-rajzolási útvonalat ragadós könyvtárállapot vezérli, nem híváson belüli paraméterek. A SetRenderDCOffset, a SetRenderDCErasePage és a SetRenderCropType mind megmarad, amíg valami meg nem változtatja, így az a bélyegképciklus, amely azután fut, hogy a felhasználó módosította a nagyított nézetet, örökli azt az eltolást vagy vágást, amelyet az előző kódútvonal ott hagyott. A tünet olyan előnézet, amely csak bizonyos navigációs sorrendekben csúszik el, és ennél nyomorúságosabban reprodukálható hiba kevés van. Ha a fenti módon az összes érintett állapotot beállítja a festéskezelő elején, az semmibe nem kerül, és az egész hibaosztályt eltünteti. A közelben egy második szorzó is rejtőzik. A tényleges kimeneti felbontás a rajzolási lépték és a DPI argumentum szorzata, és bár a SetRenderScale alapértéke 1.0, egyszeri módosítás után ez is megmarad, így egy exportfunkció, amely megemelte, csendben átméretez minden későbbi előnézetet, amíg valami vissza nem állítja
A görgethető megjelenítőknek és a részleges újrafestésnek külön változata van. A RenderPageToDCClip az eszközkontextus mellett vágási megadást is elfogad, így az ablak egyetlen sávjának érvénytelenítése csak azt a sávot festi újra, ahelyett hogy az egész oldalt újraraszterezné. Nagy nagyításon, nagy formátumú oldalakon ez a különbség a görgetősávot követő megjelenítő és a mögötte elmaszatolódó megjelenítő között
Nyomtatási feladat, amely illeszkedik az előnézethez
A nyomtatási oldal virtuális nyomtatón keresztül működik. A NewCustomPrinter egy rendszernyomtatót klónoz a könyvtár saját konfigurációjába, a SetupPrinter pedig ezt a klónt állítja anélkül, hogy a gépszintű DevMode-hoz hozzányúlna: a papír az 1-es beállításként megy be (egy DMPAPER_* konstans), a tájolás pedig a 11-es beállításként. A nyereség az elszigetelés. Egy szolgáltatás nyomtathat A4-es címkéket, miközben a gazdagép alapértelmezett nyomtatója Letter méreten marad, és utólag semmit nem kell visszaállítani
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); // 1-es beállítás = papír, DMPAPER_A4
Pdf.SetupPrinter(Virt, 11, 1); // 11-es beállítás = tájolás, 1 = álló
Opt := Pdf.PrintOptions(1, 1, 'Monthly Report'); // papírhoz igazítás, auto-forgatás + középre
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
A PrintOptions figyelmes olvasást érdemel. Egy beállításleírót ad vissza, amelyet át kell adnia a PrintDocument vagy a PrintPages hívásnak; ez nem környezeti állapot. Ha felépíti a beállításokat, majd elfelejti átadni a leírót, a dolog némán elbukik. A feladat alapértékekkel nyomtat, és senki nem veszi észre, amíg papírhoz igazítást várt volna valaki, és helyette egy túlméretes oldal levágva jött ki. Az oldalskálázási argumentum az, ahol ez a szabály lakik. A skálázás nélküli mód megőrzi a méretpontosságot, ami számít azoknál az űrlapoknál, amelyeket vonalzóval mérnek le. A papírhoz igazítás mindent a laphoz méretez át. A nagy oldalak zsugorítása a normál oldalakat békén hagyja, és csak akkor lép közbe, ha egy oldal túllóg a nyomtatható területen – vegyes dokumentumkészlethez rendszerint ez a helyes alapértelmezés. Az automatikus forgatás és középre igazítás jelzője második kódútvonal nélkül kezeli a fekvő oldalakat
Azok az alkalmazások, amelyek a VCL párbeszédfolyamatán át már kezelnek egy TPrinter példányt, közvetlenül átadhatják azt. A PrintDocumentToPrinterObject és a PrintPagesToPrinterObject elfogadja a beállított TPrinter példányt, így a felhasználó felé a szokásos nyomtatási párbeszéd marad a konfigurációs felület, miközben az oldalak kirajzolását a könyvtár végzi. A két megközelítés keverése egyetlen kódútvonalon hajlamos visszahozni pontosan azt a geometriai elcsúszást, amelynek kiirtására ez az egész munka irányult, ezért válasszon egyet. A virtuális nyomtatós út a felügyelet nélküli szolgáltatásokhoz illik, a TPrinter út az interaktív alkalmazásokhoz
A szelektív kimenet ugyanígy működik. A PrintPages tartománysztringet vár, tehát a virtuális nyomtató nevének, a '2-5,12' értéknek és a beállításleírónak az átadása a 2–5. és a 12. oldalt nyomtatja ki érintetlen geometriai szerződéssel, és ugyanez a szintaxis hajtja a fájlba nyomtató változatokat is. Ezek a fájlváltozatok jelentik a gyakorlati választ olyan felügyelet nélküli környezetben, ahol nincs fizikai eszköz: a nyomtatási geometria regressziós tesztelését olyan build-kiszolgálón, amelynek egyáltalán nincs illesztőprogram-sora. Rajzolja ki ugyanazt a dokumentumot ugyanazokkal a beállításokkal fájlba minden buildnél, és a geometriai regresszió diffé válik ahelyett, hogy három héttel később ügyfélbejelentés lenne belőle
Előnézeti bittérképek a nyomtató saját metrikáival
Az a 96 DPI-n, feltételezett oldalméret ellenében rajzolt előnézet rossz kérdésre válaszol. Azt mutatja meg, hogyan néz ki az oldal, nem azt, hogy ez a nyomtató mit tesz erre a papírra. A GetPrintPreviewBitmapToString ezt a rést zárja be azzal, hogy az előnézetet ugyanabból az egyedi nyomtatóból és ugyanabból a beállításleíróból építi fel, mint a későbbi feladat, így a papírméret, a tájolás, a skálázási szabály, a forgatás és a hardveres eltolás mind beleszól a bittérképbe. Ami visszajön, az az, amit a lap mutatni fog
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;
A MaxDimension argumentum a bittérkép hosszabbik élét korlátozza. Az 1200 pixel előnézeti párbeszédhez éles marad, és a memóriaigényt még E méretű műszaki rajzoknál is szerényen tartja, ahol a nyomtató 600 DPI-jén készített teljes felbontású rajzolás gigabájtokra rúgna
A felhasználó nyomtatóválasztásainak megjegyzése
Azok a nyomtatási párbeszédek, amelyek munkamenetek között elfelejtik a beállításaikat, maguk termelik a támogatási jegyeket. A DevMode-páros, a GetPrinterDevModeToString és a SetPrinterDevModeFromString, egy nyomtató teljes illesztőprogram-konfigurációját átlátszatlan sztringgé sorosítja, amelyet elrakhat a felhasználói beállítások közé és a következő munkamenetben visszatölthet, beleértve azokat az illesztőprogram-specifikus opciókat is, amelyeket egyetlen általános API sem modellez. A nyomtatót a GetPrinterNames szerinti néven tárolja, soha ne listaindex szerint. Az indexsorrend minden nyomtató hozzáadásakor vagy eltávolításakor megváltozik, így egy elmentett index a lista következő elmozdulásakor csendben rossz eszközre mutat. A GetDefaultPrinterName fedezi a tartalék esetet, amikor a megjegyzett eszköz teljesen eltűnt
A tálcaválasztás kerekíti le a megőrzés történetét. A GetPrinterBins jelenti azokat a papírforrásokat, amelyeket egy illesztőprogram közzétesz, és ez számít a fejléces papíros munkameneteknél, ahol az első oldal a fejléces tálcából, a többi pedig sima készletből jön. Ezt a szabályt a felhasználók minden mással együtt elvárják az alkalmazástól, és a rossz készletre landoló nyomtatási feladat hibának olvasódik akkor is, ha a PDF minden bájtja helyes volt
Ugyanaz a motor az előnézetben és a nyomtatásban
Egy utolsó döntés csendben megszabja a hűséget. A rajzolómotor kiválasztása a képernyős és a nyomtatói célra egyaránt érvényes, így kísértő az előnézetet gyors motorral, a nyomtatást pedig pontossal futtatni. Álljon ellen. Ha az előnézet és a feladat különböző motoron megy át, azzal pontosan az a hűségeltérés jön vissza, amelynek eltüntetésére a nyomtatóhű előnézet készült, méghozzá olyan módon, hogy csak papíron mutatkozik meg. A beépített, a Cairo és a PDFium motor közti mérlegelés a többmotoros PDF-rajzolás Delphiben cikkben szerepel; válasszon egyet, és mindkét oldalon azt használja
Azok a dokumentumok, amelyek túl nagyok ahhoz, hogy nyomtatás előtt kényelmesen betöltődjenek, a nagy PDF-ek egyesítése, szétvágása és közvetlen elérése cikkben leírt közvetlen elérési útvonalon nyithatók meg, amely fájlleíróból, a dokumentumfa felépítése nélkül rajzolja az oldalakat eszközkontextusra. A teljes nyomtatási API-referencia a losLab PDF Library for Delphi termékoldalon található