V minulém dílu (o základech tisku) se vyskytla zmínka o tiskových službách. Ty umožňují postoupit v úrovni abstrakce tisku ještě výše a nemuset zbytečně řešit technické detaily.
21.3.2007 09:00 | Lukáš Jelínek | přečteno 20332×
Za "normálních okolností" musí uživatel přemýšlet o tom, jaké vlastnosti má ta která tiskárna, má-li jich k dispozici více. Například potřebuje vědět, která tiskárna vytiskne stránku o velikosti A3, umí tisknout oboustranně nebo barevně. V praxi to znamená zjistit si předem potřebné informace a podle toho pak zvolit tiskárnu, na kterou se tisková úloha pošle.
Lepší je ovšem abstraktní pohled - máme nějaké dostupné tiskárny (místní či vzdálené, nebo i ryze virtuální, tvořené pouhým programem) a tiskovou úlohu s určitými parametry. Nyní jde o to, aby tisková úloha putovala na nejvhodnější tiskárnu a nemuselo se o tom příliš přemýšlet.
Standardní knihovny Javy obsahují kompletní aparát pro abstraktní pohled na tisk. Říká se mu tiskové služby a využívá se skrze aplikační rozhraní Java Print Service API. Podívejme se na to, co všechno umožňuje:
Není toho málo, implementace infrastruktury není jednoduchá, ale z hlediska použití v aplikacích je k dispozici velmi silná nástroj pro řízení tisku. Návrh JPS API vychází z protokolu IPP (Internet Printing Protocol).
Tisk s využitím tiskových služeb se poměrně značně liší od klasických metod tisku, třeba i té, kterou jsme používali minule. Základní postup při využití tiskových služeb je zhruba tento:
Rozdíl oproti klasickému postupu při tisku je jasně viditelný. Celý proces je sice poněkud náročnější a zdlouhavější, ale získáváme naopak větší flexibilitu a méně otrocké práce při tisku.
Zatímco při klasickém tisku je víceméně dáno, jak vypadá tištěný dokument (je to vždy grafický kontext, tedy virtuální plocha, na kterou se kreslí). Ne tak zde - možnosti tiskových služeb jsou mnohem větší.
Při specifikaci datového formátu se používá standard MIME. Jeho pomocí určujeme, jaký datový typ bude dokument mít. V základní podobě máme k dispozici tyto formáty:
text/plain
)text/html
)application/postscript
, PDF - application/pdf
a PCL - application/vnd.hp-PCL
)image/gif
, JPEG - image/jpeg
a PNG - image/png
)application/octet-stream
)Každý z popsaných formátů umožňuje používat různé zdroje dat (bajtová pole, streamy, URL apod.). Ne každý formát podporuje všechny zdroje. Než vytvoříme/získáme objekt pro práci s formátem, musíme tedy kromě formátu znát i zdroj dat.
Formát je reprezentován třídou DocFlavor
z balíku javax.print
. Instanci vytvoříme
buď klasicky konstruktorem nebo využijeme některou z podtříd, připravených
pro různé zdroje dat. Zde je několik příkladů získání instance DocFlavor
:
DocFlavor df; df = new DocFlavor("image/gif", "java.io.InputStream"); df = new DocFlavor("text/html; charset=utf-8", "java.io.Reader"); df = new DocFlavor.BYTE_ARRAY("application/octet-stream"); df = DocFlavor.URL.POSTSCRIPT;
První příkaz vytvoří konstruktorem instanci určenou pro obrázek GIF získávaný ze vstupního streamu. Druhý řádek je podobný, ukazuje ovšem HTML data včetně uvedení znakové sady (to je důležité, jinak se používá výchozí sada, což nemusí být vždy v pořádku) a data načítá z textově orientovaného streamu. Dále tu je instance vytvořená pomocí konstruktoru podtřídy (v tomto případě pro bajtové pole), určená pro automaticky detekovaný formát. A konečně poslední příklad plně využívá hotové objekty - v tomto případě se použije instance pro postscriptová data z URL.
Pokud známe předem (při implementaci programu) typ dat a jejich zdroj, používáme přednostně již vytvořené objekty nebo alespoň specializované třídy. Je to rychlejší než volat základní konstruktor s textovými parametry.
Požadavky na tiskovou službu, a také další vlastnosti tisku, se vyjadřují pomocí atributů. Každý atribut je
instancí třídy implementující rozhraní javax.print.attribute.Attribute
. Takových
tříd je velké množství. Většinou nás bude zajímat například MediaSizeName
(název
velikosti média), PrinterResolution
(rozlišení tiskárny), ColorSupported
(podpora
barev v tiskárně) nebo třeba PagesPerMinute
(počet stránek za minutu). Většina atributů
odpovídá příslušné reprezentaci v IPP.
S atributy je to ještě trochu složitější. Obecně se dají zařadit do pěti kategorií (podle účelu), každá z nich je představována jedním rozhraním. Atribut může patřit do více kategorií současně.
DocAttribute
)PrintJobAttribute
)PrintServiceAttribute
)PrintRequestAttribute
)SupportedValuesAttribute
)
Všechny kategorie mají svůj smysl. Ještě se k nim dostaneme. Další důležitou
věcí je sdružování atributů do sad. Základním rozhraním sady je AttributeSet
,
od něho jsou odvozena další rozhraní, například PrintRequestAttributeSet
(sada požadavků na tiskovou službu).
Nejdříve se podíváme na využití atributů k vyhledání tiskové služby odpovídající daným požadavkům. Tady je krátký příklad, jak sestavit sadu atributů:
PrintRequestAttributeSet as = new HashPrintRequestAttributeSet(); as.add(MediaSize.ISO_A4); as.add(Sides.DUPLEX); as.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
Jak je snad na první pohled zřejmé, takto sestavíme požadavek na tisk na stránku
A4, s možností oboustranného (duplexního) tisku a s rozlišením 300 x 300 DPI.
Lze používat jen atributy implementující rozhraní PrintRequestAttribute
- z toho
vyplývá, že některé hodnoty nelze přímo vyžadovat. Tam patří třeba rychlost
tisku (PagesPerMinute
; jedná se stejně jen o informativní hodnotu), ale také
umístění tiskárny (PrinterLocation
; sice by bylo určitě lákavé automaticky zabránit tisku na
tiskárnu umístěnou ve firemní pobočce v Austrálii, nicméně umístění tiskárny
je stejně jen textový atribut - rozhodnout musí uživatel).
Samostatnou zmínku si zaslouží speciální kategorie atributů - atributy
podporovaných hodnot. Slouží k vyjádření hodnot podporovaných jinými atributy.
Tak například atribut JobPrioritySupported
říká, jaké hodnoty priorit úloh
(JobPriority
) tisková služba podporuje (ne všechny hodnoty z obecného rozsahu musí být
vždy podporovány, případně nemusejí být podporovány žádné).
Nyní nastal pravý čas zjistit, které tiskové služby vyhovují určeným požadavkům. Stačí jednoduše provést dotaz a pak už si jen vybírat z dostupných služeb:
DocFlavor fd = ... PrintRequestAttributeSet as = ... PrintService sa[] = PrintServiceLookup.lookupPrintServices(df, as);
Kdo má to štěstí, že mu takový dotaz vrátí větší počet služeb, může si pak vybrat nejvhodnější službu (viz dále). Mnohdy ale člověk může být rád, že vůbec nějaká služba prošla jeho sítem.
Celkem snadno se ale stane, že nevyhoví žádná služba - třeba když zadáme požadavek na duplexní tisk a žádná dostupná tiskárna ho nepodporuje. To je prostě smůla, takže nezbývá než si to někde v programu poznamenat a zkoušet dál s menšími požadavky. Lze to i opačně - začít s málem a postupně přidávat další a další požadavky, dokud nějaké služby zbývají.
Pokud si vystačíme s výchozí tiskárnou, stačí místo zmíněné metody zavolat
lookupDefaultPrintService()
. Ovšem pozor - nikde není řečeno, že nějaká tisková
služba vůbec musí být k dispozici.
Ještě zbývá jedna důležitá věc, a to zjišťování atributů určité tiskové služby.
To může sloužit například k ručnímu výběru služby uživatelem (například podle
zmíněného umístění tiskáry, rychlosti tisku, aktuálního stavu tiskárny atd.).
Jednoduše zavoláme instanci tiskové služby (PrintService
) metodu getAttributes()
a pak už pomocí metody get()
přistupovat k atributům, nebo použít u služby přímo metodu
getAttribute()
se stejným výsledkem. Třída PrintService
má ještě pár dalších
zajímavých metod, které se mohou hodit při zkoumání, co služba umí.
I když to není úplně nutné, velice se hodí vytvořit si něco, čemu se dá říkat
"tiskový dokument". Jedná se vlastně o propojení zdroje tiskových dat, formátu
tisku a tiskových atributů. Tiskový dokument je představován rozhraním Doc
(z balíku javax.print
). Ve většině případů nemusíme rozhraní implementovat,
vyhoví totiž předem připravená třída SimpleDoc
, obsahující vše potřebné.
Vytvoření instance SimpleDoc
je triviální. Zavolá se konstruktor a tomu se
předá zdroj dat, formát a atributy dokumentu. U těchto atributů stojí za to se
chvilku zdržet. Říkají totiž, jaké parametry se mají při tisku nastavit.
Služba by je pochopitelně měla podporovat - nemá smysl se snažit tisknout ze
zásobníku, který tiskárna nemá.
Pokud atributy nepotřebujeme používat, stačí místo sady použít null
- není
třeba zbytečně vytvářet prázdnou sadu. Celý postup při vytvoření dokumentu
může vypadat například takto:
HashDocAttributeSet as = new HashDocAttributeSet(); as.add(PrintQuality.DRAFT); as.add(OrientationRequested.LANDSCAPE); SimpleDoc sd = new SimpleDoc(data, df, as);
Atributy v příkladu určují použití kvalitativního režimu Draft a orientaci na šířku. Pozor na to, že některé atributy (například právě orientaci) lze použít jen u některých formátů - u jiných nemá smysl.
Zbývá už jen málo - vytvořit úlohu a poslat ji zvolené službě. Úloha jako
takové se získá velmi snadno. Stačí instanci tiskové služby (PrintService
)
zavolat metodu createPrintJob()
. Stejně jednoduše se pak úloha spustí - pouhým
zavoláním metody print()
. Jediné, na co se nesmí zapomenout, jsou požadované
atributy služby (jako při jejím hledání). Nemusí se použít, pokud nejsou potřeba.
SimpleDoc sd = ... PrintRequestAttributeSet as = ... PrintService ps = ... DocPrintJob job = ps.createPrintJob(); try { job.print(sd, as); } catch (PrintException e) { ... }
Všimněte si, že metoda print()
může vyhodit výjimku, kterou musíme ošetřit.
Navíc tato metoda obecně může (ale nemusí) pracovat asynchronně, proto její
opuštění vůbec neznamená, že se něco vytisklo. Pro sledování stavu implementujeme
rozhraní PrintJobListener
a přidáme ho do úlohy (metodou addPrintJobListener()
).
Pak např. metoda printJobCompleted()
znamená úspěšné dokončení úlohy,
printJobFailed()
selhání a tak podobně. Obdobně lze pomocí implementace
rozhraní PrintJobAttributeListener
sledovat změnu atributů úlohy (například
změnu stavu).
Pro některé účely se hodí vytvořit si vlastní tiskovou službu a pak ji používat. Může sloužit například k přímému exportu do některého grafického formátu.
Nejjednodušší možností je použít třídu StreamPrintServiceFactory
. Ta umožňuje
vyhledat službu odpovídající daným požadavkům. Vypadá to takto:
OutputStream os = ... StreamPrintServiceFactory fa[] = StreamPrintServiceFactory.lookupStreamPrintServiceFactories(df, "application/pdf"); if (fa.length > 0) { StreamPrintService ps = fa[0].getPrintService(os); if (ps != null) { ... } }
Příklad ukazuje vyhledání služby, která umožňuje (pro daný formát dokumentu) výstup do PDF. Je-li služba nalezena, připojí se na výstupní stream, který může představovat například otevřený soubor, síťové spojení, rouru do jiného vlákna apod.
Existuje ještě další možnost - naimplementovat si přímo celou tiskovou službu.
Ta by se pak, pokud by měla být zahrnuta do vyhledávání podle požadavků,
musela zaregistrovat zavoláním statické metody registerService()
třídě PrintServiceLookup
. Jen pro zajímavost, lze implementovat a registrovat
také vlastní vyhledávače služeb (instance PrintServiceLookup
; registrují se
pomocí registerServiceProvider()
), ale to už je poměrně okrajová záležitost.
Zatím zůstávala opomenuta jedna otázka - jak propojit klasickou tiskovou metodu (z minulého dílu seriálu) s tiskovými službami. Jde to snadno a existují dokonce dvě rozdílné cesty, jak toho dosáhnout.
První z nich vychází z původního základu, avšak využije mechanismus tiskových služeb. Tady je příklad z minulého dílu upravený pro tiskové služby:
protected void doPrint(Printable pt) { PrinterJob pj = PrinterJob.getPrinterJob(); pj.setPrintable(pt); PrintService sa[] = PrinterJob.lookupPrintServices(); if (sa.length > 0) { try { pj.setPrintService(sa[0]); pj.print(); } catch (PrinterException e) { JOptionPane.showMessageDialog(this, "Při tisku došlo k chybě: " + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE); } } }
Pokud bychom chtěli nastavovat atributy tisku, stačilo by vytvořit prázdnou sadu,
pomocí známých dialogů (metody pageDialog()
a printDialog()
, zde ve verzích
s předáním sady atributů) nastavit požadované hodnoty a pak sadu předat metodě
print()
.
Druhou, v podstatě ještě jednodušší metodou je použít postup určený přímo
pro tiskové služby a jako formát zvolit DocFlavor.SERVICE_FORMATTED.PRINTABLE
.
Při vytváření instance SimpleDoc
se pak jako zdroj dat předá instance
implementovaného rozhraní Printable
. Podobně bychom si počínali i při použití
rozhraní Pageable
(použil by se formát DocFlavor.SERVICE_FORMATTED.PAGEABLE
).
Opět krátký příklad:
Printable pt = ... PrintService sa[] = PrintServiceLookup.lookupPrintServices( DocFlavor.SERVICE_FORMATTED.PRINTABLE, null); if (sa.length > 0) { SimpleDoc sd = new SimpleDoc(pt, DocFlavor.SERVICE_FORMATTED.PRINTABLE, null); ... }
V příkladu se nepoužívají žádné atributy. Pokud bychom je chtěli použít, prázdné reference by se nahradily sadou atributů jako v příslušných příkladech z tohoto článku.
Přístě konečně přijde řada na to, co kvůli dvěma článkům o tisku muselo počkat - tedy na úvod do JavaBeans. Věřím, že i tato oblast Javy bude neméně zajímavá, jako právě popsané záležitosti tisku.