Technický článek

Náhled tisku a výstup do kontextu zařízení v Delphi s PDF Library for Delphi

Vykreslení stránky PDF do kontextu zařízení Windows kvůli náhledu tisku sráží do jednoho řádku kódu tři souřadné soustavy a ty se málokdy shodnou. Stránka PDF se měří v bodech s počátkem vlevo dole. Obrazovkový DC se měří v pixelech s počátkem vlevo nahoře a s faktorem přiblížení, který si zvolíte. Tiskový DC, tedy ten, jejž má náhled předpovídat, měří pixely v rozlišení zařízení, ale klade počátek do rohu tisknutelné oblasti, ne do rohu archu. Spleťte kteroukoli z nich a náhled vypadá dobře, zatímco vytištěná stránka vyjde posunutá, přeškálovaná nebo u okraje oříznutá. Obvyklým příznakem je orámovaný formulář, jenž se v náhledu jeví vystředěný a vytiskne se s uříznutou horní a levou linkou, protože laserová tiskárna nedokáže dát toner do vnějších pár milimetrů a náhledu to nikdo neřekl. losLab PDF Library (PDF Library for Delphi) pokrývá celou tu cestu voláními pro vykreslení do kontextu zařízení, konfigurační vrstvou virtuální tiskárny a náhledovými bitmapami generovanými z metrik samotné tiskárny, což je ta část, díky níž je náhled o oné marži upřímný

Geometrie papíru není geometrie tisknutelné plochy

Každý tiskový cíl popisují dva obdélníky a posun mezi nimi je místem, kde žije většina chyb náhledu. Obdélník papíru je fyzický arch. Tisknutelný obdélník je menší oblast, na kterou tiskový engine skutečně dosáhne, zmenšená o hardwarovou marži lišící se podle modelu tiskárny a někdy i podle zásobníku. Tisková vrstva knihovny měří obojí. Podkladová třída TPLPrinter zpřístupňuje PageWidth a PageHeight pro tisknutelnou oblast, FullPageWidth a FullPageHeight pro celý arch a PrintOffsetX s PrintOffsetY pro mezeru mezi jejich počátky, vše v pixelech zařízení při rozlišení, které hlásí GetDPI. Poctivý náhled tato čísla přeškáluje dolů na rozlišení obrazovky, místo aby stránku namaloval do jakéhokoli obdélníku, jejž má zrovna ovládací prvek k dispozici. Vynechte tento krok a náhled tiše předpokládá nulovou marži, což je jediná hodnota, kterou žádná skutečná tiskárna nemá

Diagram PDF Library for Delphi: celý list papíru proti menšímu tisknutelnému obdélníku, přičemž PrintOffsetX a PrintOffsetY značí hardwarový okraj mezi jejich počátky
Obdélník papíru je fyzický list, zatímco tisknutelný obdélník je to, co tiskové jádro dosáhne; mezi jejich počátky je mezera, kde bydlí většina chyb náhledu

Náhled na obrazovce přes RenderPageToDC

Pro náhledový ovládací prvek na obrazovce vykreslí RenderPageToDC(DPI, Page, DC) stránku načteného dokumentu rovnou do libovolného kontextu zařízení GDI, ať už jde o plátno TPaintBox, bitmapu mimo obrazovku nebo DC metasouboru. Argument DPI nastavuje přiblížení. Hodnota 96 přibližně odpovídá zobrazení na 100 % na klasickém displeji a její zdvojnásobení zdvojnásobí vykreslenou velikost

procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
  // tyto tři jsou lepivý stav knihovny, ne parametry jednotlivého volání:
  FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
  FPdf.SetRenderDCErasePage(1);
  FPdf.SetRenderCropType(0);
  FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;

Past spočívá v tom, že cestu vykreslení do DC řídí lepivý stav knihovny, nikoli parametry jednotlivých volání. SetRenderDCOffset, SetRenderDCErasePage i SetRenderCropType vydrží, dokud je něco nezmění, takže smyčka generující náhledy, jež poběží poté, co uživatel upravil přiblížené zobrazení, zdědí libovolný posun či ořez, který po sobě předchozí cesta kódem nechala. Příznakem je náhled ujíždějící jen při konkrétních posloupnostech navigace, což je asi tak nejmizernější reprodukovatelnost, jaké může chyba dosáhnout. Nastavení veškerého relevantního stavu na začátku obsluhy překreslení, jak je uvedeno výše, nestojí nic a celou tuto třídu chyb odstraní. Poblíž se skrývá druhý násobitel. Efektivní výstupní rozlišení je měřítko vykreslení krát argument DPI, a přestože SetRenderScale má výchozí hodnotu 1.0, po změně rovněž vydrží, takže exportní funkce, jež jej nadzvedla, tiše přeškáluje každý další náhled, dokud jej něco nevrátí zpět

Rolovací prohlížeče a částečná překreslení mají vyhrazenou variantu. RenderPageToDCClip bere vedle kontextu zařízení i specifikaci ořezu, takže zneplatnění jednoho pásu okna překreslí jen tento pás místo opětovné rasterizace celé stránky. Při vysokém přiblížení na velkoformátových stránkách je to rozdíl mezi prohlížečem, který sleduje posuvník, a prohlížečem, jenž se za ním rozmazává

Tisková úloha, která odpovídá náhledu

Tisková strana pracuje přes virtuální tiskárnu. NewCustomPrinter naklonuje systémovou tiskárnu do konfigurace privátní pro knihovnu a SetupPrinter tento klon upravuje, aniž by sáhl na celosystémový DevMode: papír vstupuje jako nastavení 1 (konstanta DMPAPER_*) a orientace jako nastavení 11. Výhodou je izolace. Služba může tisknout štítky A4, zatímco výchozí tiskárna hostitele zůstává na Letter, a poté není třeba nic vracet do původního stavu

PDF Library for Delphi: Tok od výchozí tiskárny systému přes NewCustomPrinter a SetupPrinter k izolované tiskové úloze, která se nikdy nedotkne celostrojového DevMode
SetupPrinter přecílí na klon privátní pro knihovnu, takže služba může tisknout A4, zatímco výchozí tiskárna hostitele si nechá svůj Letter DevMode nedotčený
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);        // nastavení 1 = papír, DMPAPER_A4
    Pdf.SetupPrinter(Virt, 11, 1);       // nastavení 11 = orientace, 1 = na výšku
    Opt := Pdf.PrintOptions(1, 1, 'Monthly Report');  // na papír, otočit + vystředit
    Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
  finally
    Pdf.Free;
  end;
end;

PrintOptions si zaslouží pozorné čtení. Vrací handle voleb, který musíte předat do PrintDocument nebo PrintPages; nejde o okolní stav. Sestavit volby a pak zapomenout handle předat selže tiše. Úloha se vytiskne s výchozími hodnotami a nikdo si toho nevšimne, dokud se čekalo na politiku přizpůsobení papíru a místo toho vyjela předimenzovaná stránka oříznutá. Právě v argumentu škálování stránky tato politika sídlí. Žádné škálování zachovává rozměrovou přesnost, na čemž záleží u formulářů, které se přeměřují pravítkem. Přizpůsobení papíru přeškáluje na arch všechno. Zmenšení velkých stránek nechá běžné stránky být a zasáhne jen tehdy, když stránka přesáhne tisknutelnou oblast, což bývá u smíšené sady dokumentů správná výchozí volba. Příznak automatického otočení a vystředění vyřídí stránky na šířku bez druhé cesty kódem

Aplikace, které už spravují TPrinter přes dialogový tok VCL, jej mohou předat rovnou. PrintDocumentToPrinterObject a PrintPagesToPrinterObject přijímají nakonfigurovanou instanci TPrinter, čímž zůstává standardní tiskový dialog konfigurační plochou obrácenou k uživateli, zatímco knihovna obstará vykreslení stránek. Míchání obou přístupů v jedné cestě kódem má sklon znovu zavést tu geometrickou odchylku, kterou měl zbytek téhle práce zabít, takže si jeden vyberte. Cesta přes virtuální tiskárnu se hodí bezobslužným službám; cesta přes TPrinter interaktivním aplikacím

Výběrový výstup funguje stejně. PrintPages bere řetězec s rozsahem, takže předání názvu virtuální tiskárny, '2-5,12' a handlu voleb vytiskne stránky 2 až 5 a 12 s neporušenou geometrickou smlouvou, a stejná syntaxe řídí i varianty tisku do souboru. Tyto souborové varianty jsou praktickou odpovědí pro bezobslužné prostředí bez připojeného fyzického zařízení: regresní testování geometrie tisku na sestavovacím serveru, jenž nemá vůbec žádnou frontu ovladače. Vykreslujte při každém sestavení tentýž dokument přes tytéž volby do souborového artefaktu a z regrese geometrie se stane rozdíl místo zákaznického hlášení o tři týdny později

Náhledové bitmapy podle metrik samotné tiskárny

Náhled vykreslený při 96 DPI proti předpokládané velikosti stránky odpovídá na špatnou otázku. Ukazuje, jak stránka vypadá, ne co tato tiskárna položí na tento papír. GetPrintPreviewBitmapToString tu mezeru zavírá tím, že náhled staví ze stejné vlastní tiskárny a stejného handlu voleb jako pozdější úloha, takže do bitmapy vstupuje velikost papíru, orientace, politika škálování, otočení i hardwarový posun. Co se vrátí, je to, co arch ukáže

PDF Library for Delphi: Kontrast mezi náhledem na obrazovce s předpokládanou velikostí strany, který udává špatné okraje, a bitmapou věrnou tiskárně postavenou z vlastního handle tiskárny a voleb úlohy
GetPrintPreviewBitmapToString kreslí náhled ze stejného handle vlastní tiskárny a voleb jako skutečná úloha, takže bitmapa ukazuje okraje a rotaci, které list skutečně dostane
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 omezuje delší hranu bitmapy. Hodnota 1200 pixelů zůstává pro náhledový dialog ostrá a drží paměť při zemi i u konstrukčních výkresů velikosti E, kde by vykreslení v plném rozlišení při 600 DPI tiskárny šlo do gigabajtů

Zapamatování uživatelských voleb tiskárny

Tiskové dialogy, které mezi sezeními zapomínají svá nastavení, generují vlastní požadavky na podporu. Dvojice pro DevMode, GetPrinterDevModeToString a SetPrinterDevModeFromString, serializuje úplnou konfiguraci ovladače tiskárny do neprůhledného řetězce, jejž můžete schovat do uživatelských předvoleb a příští sezení obnovit, včetně voleb specifických pro ovladač, které se žádné obecné API nenamáhá modelovat. Tiskárnu si ukládejte podle názvu z GetPrinterNames, nikdy podle indexu v seznamu. Pořadí indexů se mění pokaždé, když se tiskárna přidá nebo odebere, takže uložený index tiše ukazuje na nesprávné zařízení, jakmile se seznam posune. GetDefaultPrinterName pokrývá záložní řešení pro případ, že zapamatované zařízení zmizelo úplně

Příběh o uchovávání nastavení dokresluje výběr zásobníku. GetPrinterBins hlásí zdroje papíru, které ovladač nabízí, na čemž záleží u postupů s hlavičkovým papírem, kde se první stránka tahá ze zásobníku s hlavičkou a zbytek z běžného papíru. To je politika, u níž uživatelé očekávají, že si ji aplikace zapamatuje spolu se vším ostatním, a tisková úloha, jež dosedne na nesprávný papír, se čte jako chyba, i kdyby byl každý bajt PDF správný

Držte jeden engine pro náhled i tisk

Věrnost tiše řídí ještě jedno poslední rozhodnutí. Výběr vykreslovacího enginu platí pro cíle na obrazovce i na tiskárně, takže je lákavé dělat náhled rychlým enginem a tisknout přesným. Odolejte tomu. Hnát náhled a úlohu různými enginy znovu zavádí přesně tu odchylku věrnosti, kterou měl náhled věrný tiskárně odstranit, a dělá to způsobem, jenž se projeví až na papíře. Kompromisy mezi vestavěným enginem, Cairo a PDFium váží článek vícemotorové vykreslování PDF v Delphi; vyberte si jeden a používejte jej na obou stranách

Dokumenty příliš velké na to, aby se před tiskem pohodlně načetly, lze otevřít cestou přímého přístupu popsanou v článku slučování, dělení a přímý přístup u velkých PDF, která vykresluje stránky do kontextu zařízení z handlu souboru bez stavby stromu dokumentu. Kompletní referenční příručka tiskového API je na produktové stránce losLab PDF Library for Delphi