Durant cette étude, nous allons voir comment imprimer facilement une image (qui peut être en réalité que du texte) sur une seule feuille de papier, comment gérer une impression sur plusieurs pages et comment tirer profit de l'élégance du modèle graphique Java2D pour générer facilement une boîte de dialogue affichant un aperçu de l'impression.
La plate-forme Java permet également d'imprimer des composants de l'interface utilisateur. Il suffit généralement de faire appel à la méthode print() du composant à imprimer.
Afin de bien maîtriser l'ensemble des informations contenues dans cette étude, reportez-vous à l'étude qui porte sur le Graphisme 2D.
§
Dans ce chapitre, nous allons aborder ce qui est probablement la situation d'impression la plus courante : une image 2D. Bien entendu, l'image peut contenir du texte de différentes polices, voire n'être constituée que de texte. Pour créer une impression, il convient de prendre en compte au moins deux éléments :
Pour créer un travail d'impression, utilisez la classe java.awt.print.PrinterJob :
Printable canevas = ... ;
PrinterJob travail = PrinterJob.getPrinterJob();
travail.setPrintable(canevas);
Attention : Il existe une classe PrintJob qui gère les impressions avec le style du JDK1.1. Cette classe est maintenant obsolète. Ne la confondez pas avec la classe PrinterJob.
Avant de commencer un nouveau travail d'impression, n'oubliez pas d'appeler la méthode printDialog() pour afficher une boîte de dialogue d'impression. Cette boîte de dialogue permet à l'utilisateur de sélectionner l'imprimante, la plage des pages à imprimer et divers paramètres d'impression.
Il est possible de collecter les paramètres d'impression dans un objet d'une classe qui implémente l'interface javax.print.attribute.PrintRequestAttributSet dans la méthode printDialog(). Java fournit une classe javax.print.attribute.HashPrintRequestAttributSet dans ce but.
...
HashPrintRequestAttributSet attributs = new HashPrintRequestAttributSet();
travail.printDialog(attributs);
if (travail.printDialog(attributs)) { try { travail.print(); } catch (PrinterException exception) { ... }
Au cours de l'impression, la méthode print() de la classe PrinterJob effectue des appels répétés à la méthode print() de l'objet implémentant l'interface Printable associé à la tâche.
Etant donné que le travail d'impression ne sait pas combien de pages vous souhaitez imprimer, il se contente d'appeler plusieurs fois la méthode print(). Tant que la méthode print() renvoie la valeur Printable.PAGE_EXISTS, le travail d'impression continue de produire des pages. Lorsque la méthode print() renvoie Printable.NO_SUCH_PAGE, le travail d'impression s'arrête.
Attention ! Le nombre de pages que le travail d'impression passe à la méthode print() commence par 0.
§
Par conséquent, le travail d'impression ne sait pas précisément combien de pages il faut imprimer, avant que l'impression totale ne soit terminée. C'est pour cette raison que la boîte de dialogue d'impression affiche une plage d'impression correspondant aux pages 1 à 1. Vous verrez dans le prochain chapitre comment éviter cet inconvénient en fournissant un objet java.awt.print.Book au travail d'impression.
Pendant le processus d'impression, le travail d'impression appelle plusieurs fois la méthode print() de l'objet Printable. Ce travail d'impression a l'autorisation d'effectuer plusieurs appels pour la même page. Par conséquent, il ne faut pas compter les pages dans la méthode print(), mais plutôt se fonder sur le paramètre de nombre de pages.
Il existe une bonne raison pour que le travail d'impression puisse appeler plusieurs fois la méthode print() pour la même page. Certaines imprimantes, en particulier les imprimantes matricielles et à jet d'encre, utilisent un effet de bandes. Elles impriment une bande à la fois, avancent la feuille de papier, puis impriment la bande suivante. Cela fournit au travail d'impression une technique pour gérer la taille du fichier d'impression.
Si le travail d'impression demande à l'objet Printable d'imprimer une bande, il définit une zone de clipping dans le contexte graphique correspondant à la bande sélectionnée, puis appelle la méthode print(). Ses opérations d'affichage seront alors restreintes au rectangle de la bande, et seuls les éléments graphiques qui se trouvent dans cet bande seront affichés.
Votre méthode print() n'a pas forcément besoin d'être au courant de ce procédé, à une seule exception près : elle ne doit pas interférer avec le rectangle de clipping.
Attention : L'objet Graphics que votre méthode print() récupère est également restreint à la zone imprimable, c'est-à-dire que les marges y sont imprimées. Si vous remplacez la zone de clipping, il se peut que vous imprimiez en dehors des marges. En particulier dans un contexte graphique d'impression, vous devez respecter la zone de clipping. Appelez donc clip() et non setClip(), pour réduire la zone de clipping. Si vous êtes amenés à réduire la zone de clipping, vérifiez que vous appelez bien getClip() au début de votre méthode print() et que vous restaurez cette zone de clipping.
Pour en savoir plus sur le cliping, revoir les cours sur le Graphisme 2D.
§
Le paramètre PageFormat de la méthode print() contient des informations sur la page imprimée. Ainsi, les méthodes getWidth() et getHeight() renvoient la taille du papier, mesurée en points.
Les méthodes getWidth() et getHeight() de la classe PageFormat fournissent la taille complète du papier. Vous ne pouvez pas imprimer à n'importe quel endroit d'une page. La plupart du temps, les utilisateurs sélectionnent des marges, et même s'ils ne le font pas, les imprimantes ont besoin d'un faible espace pour entraîner la feuille sur laquelle elles impriment ; cet espace n'est par conséquent pas imprimable.
Les méthodes getImageableWidth() et getImageableHeight() fournissent les dimensions de la zone réellement imprimable. Cependant les marges doivent être symétriques : il vous faut également connaître le coin supérieur gauche de la zone graphique, qui peut être obtenu grâce aux méthodes suivantes : getImageableX() et getImageableY().
int print(Graphics surface, PageFormat page, int numéro) { surface.translate(page.getImageableX(), page.getImageableY()); ... }
PageFormat page = travail.pageDialog(attributs);
L'un des onglets de la boîte de dialogue d'impression contient la boîte de dialogue de configuration de la page. Vous souhaiterez peut-être donner aux utilisateurs le choix de définir un format de page avant l'impression, en particulier si votre programme présente un affichage en WYSIWYG des pages à imprimer. La méthode pageDialog() renvoie un objet PageFormat contenant les réglages de l'utilisateur.
Avant la version SDK 1.4, le système d'impression utilisait des boîtes de dialogue de configuration d'impression et de mise en page natives de la plate-forme hôte. Pour afficher une boîte de dialogue native, appelez la méthode printDialog() sans aucun paramètre. Il n'existe aucune méthode permettant de rassembler les paramètres utilisateur dans un ensemble d'attributs
L'un des avantages potentiels qui existent à utiliser une boîte de dialogue d'impression native réside dans le fait que certains pilotes d'imprimante possèdent des fonctions spéciales qui ne sont pas accessibles par le biais d'une boîte de dialogue inter-plates-formes.
PageFormat défaut = travail.defaultPage();
PageFormat page = travail.pageDialog(défaut);
Je vous propose de mettre en oeuvre une petite application qui permet d'afficher un ensemble de formes sur un écran et sur une page imprimée :
package impression; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.geom.*; import java.awt.print.*; import javax.print.attribute.*; import javax.swing.*; public class Imprimante extends JFrame { private PanneauImpression canevas = new PanneauImpression(); private PrintRequestAttributeSet attributs = new HashPrintRequestAttributeSet(); private JToolBar barre = new JToolBar(); public Imprimante() { super("Impression"); add(canevas); add(barre, BorderLayout.NORTH); barre.add(new AbstractAction("Imprimer") { public void actionPerformed(ActionEvent e) { try { PrinterJob travail = PrinterJob.getPrinterJob(); travail.setPrintable(canevas); if (travail.printDialog(attributs)) { travail.print(attributs); } } catch (PrinterException ex) { JOptionPane.showMessageDialog(Imprimante.this, ex); } } }); barre.add(new AbstractAction("Mise en page") { public void actionPerformed(ActionEvent e) { PrinterJob travail = PrinterJob.getPrinterJob(); travail.pageDialog(attributs); } }); setSize(330, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Imprimante(); } private class PanneauImpression extends JComponent implements Printable { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); dessinePage((Graphics2D) g); } @Override public int print(Graphics g, PageFormat page, int numéro) throws PrinterException { if (numéro>=1) return Printable.NO_SUCH_PAGE; Graphics2D surface = (Graphics2D) g; surface.translate(page.getImageableX(), page.getImageableY()); surface.draw(new Rectangle2D.Double(0, 0, page.getImageableWidth(), page.getImageableHeight())); dessinePage(surface); return Printable.PAGE_EXISTS; } private void dessinePage(Graphics2D surface) { surface.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); CubicCurve2D pétale = new CubicCurve2D.Double(150, 130, 10, -20, 290, -20, 150, 130); surface.setStroke(new BasicStroke(5)); surface.draw(pétale); surface.setFont(new Font("SansSerif", Font.BOLD+Font.ITALIC, 64)); surface.drawString("Bonjour !", 10, 190); } } }
Dans la pratique, vous ne passerez pas d'objet Printable brut à un travail d'impression. Il vaut mieux obtenir un objet d'une classe qui implémente l'interface java.awt.print.Pageable. La plate-forme Java fournit une classe de ce type, appelée java.awt.print.Book (ou livre). Un livre est composé de plusieurs sections, chacune de ces sections étant un objet Printable.
Book livre = new Book(); PrinterJob travail = PrinterJob.getPrinterJob(); PageFormat page = travail.pageDialog(attributs); Printable couverture = ... ; Printable corps = ... ;Ensuite la méthode setPageable() sert à passer l'objet Book au travail d'impression :
livre.append(couverture, page); // ajoute une seule page livre.append(corps, page, nombre); ...
travail.setPageable(livre);
Le travail d'impression sait maintenant exactement combien de pages il devra imprimer. La boîte de dialogue d'impression affiche alors un nombre de pages correct et l'utilisateur peut sélectionner toutes ces pages ou un de leurs sous-ensembles.
Attention, lorsque le travail d'impression appelle la méthode print() des sections Printable, il passe le nombre de pages courant du livre, et non celui de chaque section, comme étant le nombre de pages courant. C'est assez compliqué, puisque chaque section doit connaître le nombre de pages de toutes les sections précédentes, et dans l'ordre, pour que le paramètre de nombre de pages reste valable.
Du point de vue du programmeur, le plus grand problème soulevé par l'utilisation de la classe Book est qu'il faut connaître le nombre de pages que chaque section comprendra au moment de l'impression. Votre classe Printable a donc besoin d'un algorithme de mise en page qui calcule la mise en page finale des pages imprimées.
Avant que l'impression ne commence, appelez cet algorithme pour calculer les sauts de page et le décomptage des pages. Vous pouvez aussi conserver les informations de mise en page pour qu'elles soient accessibles plus facilement pendant l'impression.
L'utilisateur pouvant avoir modifié le format de la page, il convient de se prémunir contre cette éventualité. Si cela se produit, vous devez recalculer la mise en page, même si les données que vous souhaitez imprimer n'ont pas été modifiées.
Je vous propose de mettre en oeuvre une application qui recense les photos présentes dans un répertoire et de pouvoir les imprimer chacune sur une feuille différente et dont la largeur de la photo correspond à la zone imprimable de la page.
package impression; import java.awt.*; import java.awt.event.*; import java.awt.print.*; import java.io.*; import java.text.MessageFormat; import javax.print.attribute.*; import javax.swing.*; public class Imprimante extends JFrame { private File[] fichiers = new File("C:/Photos/").listFiles(); private Box panneau = Box.createVerticalBox(); private JScrollPane ascenceur = new JScrollPane(panneau, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); private String html = "<html><table>" +"<tr><td>{0}</td></tr>" +"<tr><td>{1} x {2}</td></tr>" +"<tr><td>{3} octets</td></tr>" +"<tr><td>{4, date, full}</td></tr>" +"</table></html>"; private JToolBar barre = new JToolBar(); public Imprimante(){ super("Photos"); for (File fichier : fichiers) { Image image = new ImageIcon(fichier.getPath()).getImage(); int largeur = image.getWidth(null); int hauteur = image.getHeight(null); Icon icône = new ImageIcon(image.getScaledInstance(200, -1, Image.SCALE_DEFAULT)); String intitulé = MessageFormat.format(html, fichier.getName(), largeur, hauteur, fichier.length(), fichier.lastModified()); JLabel étiquette = new JLabel(intitulé, icône, JLabel.LEFT); étiquette.setOpaque(true); étiquette.setBackground(Color.GREEN); panneau.add(Box.createVerticalStrut(5)); panneau.add(étiquette); } add(ascenceur); add(barre, BorderLayout.NORTH); barre.add(new AbstractAction("Imprimer") { public void actionPerformed(ActionEvent e) { try { Book pages = new Book(); PrintRequestAttributeSet attributs = new HashPrintRequestAttributeSet(); PrinterJob travail = PrinterJob.getPrinterJob(); PageFormat page = travail.getPageFormat(attributs); for (File fichier : fichiers) pages.append(new Impression(new ImageIcon(fichier.getPath()).getImage()), page); travail.setPageable(pages); if (travail.printDialog(attributs)) { travail.print(attributs); } } catch (PrinterException ex) { JOptionPane.showMessageDialog(Imprimante.this, ex); } } }); setSize(400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Imprimante(); } private class Impression implements Printable { private Image image; public Impression(Image image) { this.image = image; } @Override public int print(Graphics g, PageFormat page, int numéro) throws PrinterException { if (numéro>=fichiers.length) return Printable.NO_SUCH_PAGE; Graphics2D surface = (Graphics2D) g; double ratio = (double)image.getWidth(null) / image.getHeight(null); int largeur = (int) page.getImageableWidth(); int hauteur = (int) (page.getImageableWidth() / ratio); surface.drawImage(image, (int)page.getImageableX(), (int)page.getImageableY(), largeur , hauteur, null); return Printable.PAGE_EXISTS; } } }
Il est possible d'intervenir sur les réglages propres au papier sur lequel l'impression va se faire. Nous pouvons ainsi choisir le format du papier à utiliser et définir la zone d'impression. La classe java.awt.print.Paper est prévue à cet effet et possède deux méthodes pour spécifier les valeurs que nous venons d'évoquer :
Cette classe Paper possède en plus toutes les méthodes de lecture que nous connaissons déjà : getWidth(), getHeight(), getImageableHeight(), getImageableWidth(), getImageableX() et getImageableY().
Une fois que votre papier est créé et parfaitement configuré, vous devez l'intégrer ensuite au système de page à imprimer au moyen de la méthode setPaper() de la classe PageFormat.
PrintRequestAttributeSet attributs = new HashPrintRequestAttributeSet();
PrinterJob travail = PrinterJob.getPrinterJob();
PageFormat page = travail.getPageFormat(attributs);
Paper papier = new Paper();
papier.setSize(595, 842); // format A4
papier.setImageableArea(10, 10, papier.getWidth()-20, papier.getHeight()-20); // marge de 10 points de par et d'autre de la zone d'impression
page.setPaper(papier);
Tant que nous y sommes, nous pouvons aussi choisir l'orientation (mode portrait ou mode paysage) de l'impression sur la page au moyen de la méthode setOrientation() de la classe PageFormat. Vous devez alors préciser l'un des paramètres suivants :
page.setOrientation(PageFormat.LANDSCAPE); // mode paysage.
A titre d'exemple, je vous propose de reprendre l'application précédente. Cette fois-ci, les photos sont imprimées en mode paysage avec une marge plus petite de 10 points.
package impression; ... public class Imprimante extends JFrame { ... barre.add(new AbstractAction("Imprimer") { public void actionPerformed(ActionEvent e) { try { Book pages = new Book(); PrintRequestAttributeSet attributs = new HashPrintRequestAttributeSet(); PrinterJob travail = PrinterJob.getPrinterJob(); PageFormat page = travail.getPageFormat(attributs); Paper papier = new Paper(); papier.setSize(595, 842); papier.setImageableArea(10, 10, papier.getWidth()-20, papier.getHeight()-20); page.setPaper(papier); page.setOrientation(PageFormat.LANDSCAPE); ... } catch (PrinterException ex) { JOptionPane.showMessageDialog(Imprimante.this, ex); } } }); setSize(400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } ... private class Impression implements Printable { ... @Override public int print(Graphics g, PageFormat page, int numéro) throws PrinterException { if (numéro>=fichiers.length) return Printable.NO_SUCH_PAGE; Graphics2D surface = (Graphics2D) g; double ratio = (double)image.getWidth(null) / image.getHeight(null); int hauteur = (int) page.getImageableHeight(); int largeur = (int) (page.getImageableHeight() * ratio); surface.drawImage(image, (int)page.getImageableX(), (int)page.getImageableY(), largeur , hauteur, null); return Printable.PAGE_EXISTS; } } }
La plupart des programmes professionnels possèdent un mécanisme d'aperçu qui vous permet d'examiner vos pages à l'écran avant de les imprimer, pour ne pas gaspiller de papier si les paramètres de l'impression ne sont pas corrects. Les classes d'impression de la plate-forme Java ne fournissent pas de boîtes de dialogue d'aperçu standard. Mais il est assez simple d'en concevoir une.
L'astuce est de concevoir une boîte de dialogue qui prend en compte, soit un objet Printable, soit un Book, avec un objet PageFormat. La boîte doit posséder, par ailleurs, de boutons Suivant et Précédent afin de parcourir l'ensemble des pages à imprimer. Pour finir, la méthode paintComponent() de la boîte de dialogue appelle la méthode print() de l'objet Printable pour la page requise.
Normalement, la méthode print() dessine le contexte de la page sur le contexte graphique d'impression. Cependant, nous devons passer le contexte d'écran graphique, éventuellement après un changement d'échelle, de sorte que la page imprimée tout entière tienne à l'intérieur d'un petit rectangle de l'écran.
La classe Book possède deux méthodes qui peuvent s'avérer utiles pour la mise en oeuvre d'un aperçu : la méthode getNumberOfPages() qui comme son nom l'indique permet de connaître le nombre de pages à imprimer, et la méthode getPrintable(int) qui retourne un objet Printable afin de permettre sa visualisation sur un panneau quelconque. Il faut alors préciser la page à afficher. Pour afficher l'aperçu, il suffit tout simplement de lancer la méthode print() de l'interface Printable en spécifiant juste le contexte graphique que nous désirons prendre en compte, celui donc qui correspond au panneau à afficher.
La méthode print() ne sait jamais quand elle produit réellement des pages imprimées. Elle se contente de dessiner dans le contexte graphique, en produisant par conséquent un affichage microscopique sur l'écran. C'est une démonstration très convaincante de la puissance du modèle graphique de Java 2D.
Je vous propose simplement de reprendre l'application précédente à laquelle je vais rajouter un aperçu avant impression.
package impression; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.print.*; import java.io.*; import java.text.MessageFormat; import javax.print.attribute.*; import javax.swing.*; public class Imprimante extends JFrame { private File[] fichiers = new File("C:/Photos/").listFiles(); private Box panneau = Box.createVerticalBox(); private JScrollPane ascenceur = new JScrollPane(panneau, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); private String html = "<html><table>" +"<tr><td>{0}</td></tr>" +"<tr><td>{1} x {2}</td></tr>" +"<tr><td>{3} octets</td></tr>" +"<tr><td>{4, date, full}</td></tr>" +"</table></html>"; private JToolBar barre = new JToolBar(); private Book pages = new Book(); private PrintRequestAttributeSet attributs = new HashPrintRequestAttributeSet(); private PrinterJob travail = PrinterJob.getPrinterJob(); private PageFormat page = travail.getPageFormat(attributs); private Aperçu boîte; public Imprimante(){ super("Photos"); for (File fichier : fichiers) { Image image = new ImageIcon(fichier.getPath()).getImage(); int largeur = image.getWidth(null); int hauteur = image.getHeight(null); Icon icône = new ImageIcon(image.getScaledInstance(200, -1, Image.SCALE_DEFAULT)); String intitulé = MessageFormat.format(html, fichier.getName(), largeur, hauteur, fichier.length(), fichier.lastModified()); JLabel étiquette = new JLabel(intitulé, icône, JLabel.LEFT); étiquette.setOpaque(true); étiquette.setBackground(Color.GREEN); panneau.add(Box.createVerticalStrut(5)); panneau.add(étiquette); pages.append(new Impression(new ImageIcon(fichier.getPath()).getImage()), page); } add(ascenceur); add(barre, BorderLayout.NORTH); barre.add(new AbstractAction("Aperçu avant impression") { public void actionPerformed(ActionEvent e) { if (boîte==null) boîte = new Aperçu(); boîte.setVisible(true); } }); barre.add(new AbstractAction("Imprimer") { public void actionPerformed(ActionEvent e) { try { travail.setPageable(pages); if (travail.printDialog(attributs)) { travail.print(attributs); } } catch (PrinterException ex) { JOptionPane.showMessageDialog(Imprimante.this, ex); } } }); setSize(400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Imprimante(); } private class Impression implements Printable { private Image image; public Impression(Image image) { this.image = image; } @Override public int print(Graphics g, PageFormat page, int numéro) throws PrinterException { if (numéro>=fichiers.length) return Printable.NO_SUCH_PAGE; Graphics2D surface = (Graphics2D) g; double ratio = (double)image.getWidth(null) / image.getHeight(null); int largeur = (int) page.getImageableWidth(); int hauteur = (int) (page.getImageableWidth() / ratio); surface.drawImage(image, (int)page.getImageableX(), (int)page.getImageableY(), largeur , hauteur, null); return Printable.PAGE_EXISTS; } } private class Aperçu extends JDialog { private int pageCourante = 0; private Panneau panneau = new Panneau(); private JToolBar barre = new JToolBar(); private String libellé = "Page {0}/{1}"; private JTextField numéro = new JTextField(MessageFormat.format(libellé, pageCourante+1, pages.getNumberOfPages())); public Aperçu() { super(Imprimante.this, "Aperçu avant impression"); add(panneau); barre.setFloatable(false); barre.add(new AbstractAction("Précédente") { public void actionPerformed(ActionEvent e) { if (pageCourante>0) { pageCourante--; numéro.setText(MessageFormat.format(libellé, pageCourante+1, pages.getNumberOfPages())); repaint(); } } }); barre.add(new AbstractAction("Suivante") { public void actionPerformed(ActionEvent e) { if (pageCourante<pages.getNumberOfPages()-1) { pageCourante++; numéro.setText(MessageFormat.format(libellé, pageCourante+1, pages.getNumberOfPages())); repaint(); } } }); numéro.setEditable(false); numéro.setHorizontalAlignment(JTextField.CENTER); barre.add(numéro); add(barre, BorderLayout.SOUTH); setSize(300, 300); } private class Panneau extends JComponent { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D surface = (Graphics2D) g; double px = page.getWidth(); double py = page.getHeight(); double sx = getWidth()-1; double sy = getHeight()-1; double xoff, yoff; double échelle; if (px/py < sx/sy) { // centrer horizontalement échelle = sy / py; xoff = 0.5*(sx-échelle*px); yoff = 0; } else { // centrer verticalement échelle = sx /px; xoff = 0; yoff = 0.5*(sy-échelle*py); } surface.translate(xoff, yoff); surface.scale(échelle, échelle); Rectangle2D contour = new Rectangle2D.Double(0, 0, px, py); surface.setPaint(Color.WHITE); surface.fill(contour); surface.setPaint(Color.BLACK); surface.draw(contour); Printable aperçu = pages.getPrintable(pageCourante); try { aperçu.print(surface, page, pageCourante); } catch (PrinterException ex) { surface.draw(new Line2D.Double(0, 0, px, py)); surface.draw(new Line2D.Double(0, px, 0, py)); } } } } }
Jusqu'à présent, nous avons vu comment imprimer une image en 2D. Toutefois, l'API d'impression offre une flexibilité bien plus importante. L'API définit un certain nombre de types de données et vous permet de trouver des services d'impression capables de les imprimer. Parmi les types de données, on trouve :
Source de données | Type de données | Type MIME |
---|---|---|
IMPUT_STREAM URL BYTE_ARRAY |
GIF | image/gif |
JPEG | image/jpeg | |
PNG | image/png | |
POSTSCRIPT | application/postscript | |
application/PDF | ||
TEXT_HTML_HOST | text/html (utilisant l'encodage de l'hôte) | |
TEXT_HTML_US_ASCII | text/html; charset=us-ascii | |
TEXT_HTML_UTF_8 | text/html; charset=utf-8 | |
TEXT_HTML_UTF_16 | text/html; charset=utf-16 | |
TEXT_HTML_UTF_16LE | text/html; charset=utf-161e (little-endian) | |
TEXT_HTML_UTF_16BE | text/html; charset=utf-161e (big-endian) | |
TEXT_PLAIN_HOST | text/plain (utilisant l'encodage de l'hôte) | |
TEXT_PLAIN_US_ASCII | text/plain; charset=us-ascii | |
TEXT_PLAIN_UTF_8 | text/plain; charset=utf-8 | |
TEXT_PLAIN_UTF_16 | text/plain; charset=utf-16 | |
TEXT_PLAIN_UTF_16LE | text/plain; charset=utf-161e (little-endian) | |
TEXT_PLAIN_UTF_16BE | text/plain; charset=utf-161e (big-endian) | |
PCL | application/vnd.hp-PCL (langage de contrôle d'imprimante Hewlett Packard) | |
AUTOSENSE | application/octet-stream (données d'imprimante brutes) | |
READER STRING CHAR_ARRAY |
TEXT_HTML | text/html; charset=utf-16 |
TEXT_PLAIN | text/plain; charset=utf-16 | |
SERVICE_FORMATTED | PRINTABLE | N/A |
PAGEABLE | N/A | |
RENDERABLE_IMAGE | N/A |
Supposons que vous souhaitiez imprimer une image GIF située dans un fichier. Vous devez découvrir s'il existe un service d'impression capable de gérer la tâche. La méthode lookupPrintServices() de la classe javax.print.PrintServiceLookup renvoie un tableau d'objets javax.print.PrintService capables de gérer le type de données du document.
DocFlavor type = DocFlavor.INPUT_STREAM.GIF; PrintService[ ] services = PrintServiceLookup.lookupPrintServices(type, null); ...
Le deuxième paramètre de la méthode lookupPrintServices() vaut null pour indiquer que nous ne souhaitons pas contraindre la recherche en spécifiant des attributs d'imprimante. Nous traiterons de ces attributs ultérieurement.
L'API propose des services d'impression destinés aux types de données des documents de base, comme les images et graphisme 2D. Toutefois, si vous essayez d'imprimer du texte ou des documents HTML, la recherche renverra un tableau vide.
Par la suite, récupérer un travail d'impression du service au travers de l'interface javax.print.DocPrintJob :
DocFlavor type = DocFlavor.INPUT_STREAM.GIF; PrintService[ ] services = PrintServiceLookup.lookupPrintServices(type, null);
DocPrintJob travail = services[i].createPrintJob(); ...
Pour l'impression, vous avez besoin d'un objet qui implémente l'interface javax.print.Doc. L'API fournit une classe javax.print.SimpleDoc destinée à cet objectif. Le constructeur de SimpleDoc exige l'objet source des données, le type de données des documents, ainsi qu'un ensemble optionnel d'attributs. Par exemple :
InputStream flux = new FileInputStream(nomFichier);
Doc document = new SimpleDoc(flux, type, null); ...
Enfin, vous pouvez imprimer :
travail.print(document, null);
Comme avant, le paramètre null peut être remplacé par un ensemble d'attributs.
§
Vous remarquez que cette procédure d'impression diffère quelque peu de celle de la section précédente. Il n'existe aucune interaction utilisateur par le biais des boîtes de dialogue d'impression. Par exemple, vous pouvez implémenter un mécanisme d'impression côté serveur dans lequel les utilisateurs soumettent de tâches d'impression par le biais d'un formulaire Web.
Je vous propose de mettre une toute petite application en mode console qui permet de récupérer une photo du disque dur et de l'imprimer automatiquement en demandant à l'utilisateur de choisir son imprimante.
package impression; import java.io.*; import javax.print.*; import javax.swing.*; public class Imprimante { public static void main(String[] args) throws IOException, PrintException { InputStream fichierImage = new FileInputStream("C:/Photos/Papillon.jpg"); DocFlavor type = DocFlavor.INPUT_STREAM.JPEG; Doc document = new SimpleDoc(fichierImage, type, null); PrintService[ ] services = PrintServiceLookup.lookupPrintServices(type, null); PrintService service = (PrintService)JOptionPane.showInputDialog(null, "Choisissez votre imprimante ?", "Imprimer", JOptionPane.QUESTION_MESSAGE, null, services, null); DocPrintJob travail = service.createPrintJob(); travail.print(document, null); } }
Un service d'impression imprime des données sur une imprimante. Un service d'impression de flux génère ces mêmes données d'impression, mais il les transmet à un flux, par exemple pour une impression retardée ou parce que leur format peut être interprété par d'autres programmes. En particulier, si ce format est de type PostScript, il est utile d'enregistrer les données d'impression dans un fichier puisque plusieurs programmes peuvent traiter ce type de fichiers.
L'API comprend un service d'impression de flux capable de produire des données PostScript à partir d'images et de Graphisques 2D. Vous pouvez utiliser ce service sur tous les systèmes, même s'il n'existe pas d'imprimantes locales.
DocFlavor type = DocFlavor.SERVICE_FORMATTED.PRINTABLE; String typeMIME = "application/postscript"; StreamPrintServiceFactory[ ] production = StreamPrintServiceFactory.lookupStreamPrintServiceFactories(type, typeMIME);
...
La classe StreamPrintServiceFactory ne possède pas de méthodes qui vous aideraient à distinguer un facteur d'un autre, vous prendrez donc simplement production[0]. Appelez ensuite la méthode getPrintService() avec un paramètre de flus de sortie, afin d'extraire un objet de type javax.print.StreamPrintService :
OutputStream flux = new FileOutputStream(nomFichier); StreamPrintService service = production[0].getPrintService(flux);
...
La classe StreamPrintService implémente l'interface PrintService. Ainsi, pour obtenir une impression, il vous suffit de suivre les étapes du chapitre précédent :
DocPrintJob travail = service.createPrintJob();
Doc document = new SimpleDoc(flux, type, null); travail.print(document, null);
A la différence des autres programmes d'impression, celui-ci fonctionne bien sous Linux. Vous pouvez utiliser GhostScript (GSview pour Windows) pour afficher ou imprimer le résultat.
package impression; import java.io.*; import javax.print.*; public class Imprimante { public static void main(String[] args) throws IOException, PrintException { InputStream fichierImage = new FileInputStream("C:/Photos/Papillon.jpg"); DocFlavor type = DocFlavor.INPUT_STREAM.JPEG; Doc document = new SimpleDoc(fichierImage, type, null); DocFlavor typeFichier = DocFlavor.SERVICE_FORMATTED.PRINTABLE; String typeMIME = "application/postscript"; StreamPrintServiceFactory[ ] production = StreamPrintServiceFactory.lookupStreamPrintServiceFactories(typeFichier, typeMIME); OutputStream fichierImpression = new FileOutputStream("C:/Photos/Papillon.ps"); StreamPrintService service = production[0].getPrintService(fichierImpression); DocPrintJob travail = service.createPrintJob(); travail.print(document, null); } }
L'API du service d'impression contient un ensemble complexe d'interfaces et de classes permettant de spécifier diverses sortes d'attributs. Il existe quatre groupes importants d'attributs.
Les deux autres contiennent des informations sur l'imprimante ainsi que sur le status de la tâche.
Pour décrire les divers attributs, il existe une interface javax.print.attribute.Attribute contenant des sous-interfaces :
Chaque classe d'attributs implémente une ou plusieurs de ces interfaces.
Par exemple, les objets de la classe javax.print.attribute.standard.Copies permettent de décrire le nombre d'exemplaires d'une impression. Cette classe implémente à la fois les interfaces PrintRequestAttribute et PrintJobAttribute. A l'évidence, une requête d'impression peut contenir une requête visant à imprimer plusieurs exemplaires. A l'inverse, un attribut de la tâche d'impression peut concerner le nombre de copies qui ont été véritablement imprimées. Ce chiffre pourrait être inférieur, par exemple dans le cas où l'imprimante affiche des limites ou parce qu'il manque du papier.
L'interface SupportedValuesAttribute signale qu'une valeur d'attribut ne reflète pas une requête réelle ou des données d'état, mais bien la capacité d'un service.
Par exemple, il existe une classe javax.print.attribute.standard.CopiesSupported qui implémente l'interface SupportedValuesAttribute. Un objet de cette classe pourrait ainsi décrire le fait qu'une imprimante accepte uniquement de 1 à 99 copies pour une impression.
Outre les interfaces et classes de chaque attribut, l'API du service d'impression définit des interfaces et des classes pour les ensembles d'attributs. Il existe d'ailleurs une super-interface javax.print.attribute.AttributeSet contenant quatre sous-interfaces :
Pour chacune de ces interfaces, il existe une classe d'implémentation, qui produit les cinq classes :
Par exemple, vous construisez un ensemble d'attributs de requête d'impression comme ceci :
PrintRequestAttributeSet attributs = new HashPrintRequestAttributesSet();
Pourquoi alors disposer de toutes ces interfaces ? Elles permettent en fait de vérifier l'utilisation correcte des attributs.
Par exemple, un DocAttributeSet n'accepte que les objets qui implémentent l'interface DocAttribute. Toute tentative d'ajouter un autre attribut entraîne une erreur de type "runtime" (erreur d'exécution).
Un ensemble d'attributs constitue une sorte très spécialisée de carte, dans lequel les clés sont de type Class et où les valeurs appartiennent à une classe qui implémente l'interface Attribute. Par exemple, si vous insérez l'objet new Copies(10) dans un ensemble d'attributs, sa clé est l'objet Copies.class. Cette clé est appelée la catégorie de l'attribut.
attributs.add(new Copies(10));
AttributeSet attributs = travail.getAttributes();
Copies copies = (Copies)attributs.get(Copies.class);
AttributeSet attributs = travail.getAttributes();
int nombre = copies.getValue();
Les classes d'attributs possédant un nombre fini de valeurs étendent la classe javax.print.attribute.standard.EnumSyntax (comme PrintQuality) qui fournit un certain nombre de méthodes permettant de configurer ces énumérations de manière sûre pour les caractères. Ne vous inquiétez pas du mécanisme lors de l'utilisation d'un attribut de ce type ajoutez simplement les valeurs nommées aux ensembles d'attribut :
attributs.add(PrintQuality.HIGH);
Pour vérifier la valeur d'un attribut, effectuez l'opération suivante :
if (attributs.get(PrintQuality.class) == PrintQuality.HIGH) {
...
}
Le tableau ci-dessous recense les attributs d'impression (issus du paquetage javax.print.attribute.standard). La deuxième colonne contient la superclasse de la classe d'attribut (par exemple, IntegerSyntax pour l'attribut Copies) ou l'ensemble des valeurs d'énumération pour les attributs contenant un ensemble fini de valeurs. Les quatres dernières colonnes indiquent si la classe d'attributs implémente les interfaces DocAttribute (DA), PrintJobAttribute (PJA), PrintRequestAttribute (PRA) et PrintServiceAttribute (PSA).
Attribut | Superclasse ou constantes d'énumération | DA | PJA | PRA | PSA |
---|---|---|---|---|---|
Chromaticity | MONOCHROME, COLOR | X | X | X | |
ColorSupported | SUPPORTED, NOT_SUPPORTED | X | |||
Compression | COMPRESS, DEFLATE, GZIP, NONE | X | |||
Copies | IntegerSyntax | X | X | ||
DateTimeAtCompleted | DateTimeSyntax | X | |||
DateTimeAtCreation | DateTimeSyntax | X | |||
DateTimeAtProcessing | DateTimeSyntax | X | |||
Destination | URISyntax | X | X | ||
DocumentName | TextSyntax | X | |||
Fidelity | FIDELITY_TRUE, FIDELITY_FALSE | X | X | ||
Finishing | NONE, STAPLE, EDGE_STITCH, BIND, SADDLE_STITCH, COVER, ... | X | X | X | |
JobHoldUntil | DateTimeSyntax | X | X | ||
JobImpressions | IntegerSyntax | X | X | ||
JobImpressionsCompleted | IntegerSyntax | X | |||
JobKOctets | IntegerSyntax | X | X | ||
JobKOctetsProcessed | IntegerSyntax | X | |||
JobMediaSheets | IntegerSyntax | X | X | ||
JobMediaSheetsCompleted | IntegerSyntax | X | |||
JobMessageFromOperator | TextSyntax | X | |||
JobName | TextSyntax | X | X | ||
JobOriginating-User-Name | TextSyntax | X | |||
JobPriority | IntegerSyntax | X | X | ||
JobSheets | STANDARD, NONE | X | X | ||
JobState | ABORTED, CANCELED, COMPLETED, PENDING, PENDING_HELD, PROCESSING, PROCESSING_STOPPED | X | |||
JobStateReason | ABORTED_BY_SYSTEM, DOCUMENT_FORMAT_ERROR, ... | ||||
JobStateReasons | HashSet | X | |||
MediaName | ISO_A4_WHITE, ISO_A4_TRANSPARENT, NA_LETTER_WHITE, NA_LETTER_TRANSPARENT | X | X | X | |
MediaPrintableArea | MediaPrintableArea(int x, int y, int largeur, int hauteur, int unité) => unité ( MediaPrintableArea.INCH, MediaPrintableArea.MM) |
X | X | X | |
MediaSize | ISO.A0 - ISO.A10, ISO.BO - ISO.B10, ISO.C0 - ISO.C10, NA.LETTER, NA.LEGAL, ... | ||||
MediaSizeName | ISO_A0 - ISO_A10, ISO_BO - ISO_B10, ISO_C0 - ISO_C10, NA_LETTER, NA_LEGAL, ... | X | X | X | |
MediaTray | TOP, MIDDLE, BOTTOM, SIDE, ENVELOPE, LARGE_CAPACITY, MAIN, MANUAL | X | X | X | |
MultipleDocumentHandling | SINGLE_DOCUMENT, DOCUMENT_NEW_SHEET, SEPARATE_DOCUMENTS_COLLATED_COPIES, SEPARATED_DOCUMENTS_UNCOLLATED_COPIES | X | X | ||
NumberOfDocuments | IntegerSyntax | X | |||
NumberOfInterveningJobs | IntegerSyntax | X | |||
NumberUp | IntegerSyntax | X | X | X | |
OrientationRequested | PORTRAIT, LANDSCAPE, REVERSE_PORTRAIT, REVERSE_LANDSCAPE | X | X | X | |
OutputDeviceAssigned | TextSyntax | X | |||
PageRanges | SetOfInteger | X | X | X | |
PagesPerMinute | IntegerSyntax | X | |||
PagesPerMinuteColor | IntegerSyntax | X | |||
PDLOverrideSupported | ATTEMPTED, NOT_ATTEMPTED | X | |||
PresentationDirection | TORIGHT_TOBOTTOM, TORIGHT_TOTOP, TOBOTTOM_TORIGHT, TOBOTTOM_TOLEFT, TOLEFT_TOBOTTOM, TOLEFT_TOTOP, TOTOP_TOLEFT | X | X | ||
PrinterInfo | TextSyntax | X | |||
PrinterIsAcceptingJobs | ACCEPTING_JOBS, NOT-ACCEPTING_JOBS | X | |||
PrinterLocation | TextSyntax | X | |||
PrinterMakeAndModel | TextSyntax | X | |||
PrinterMessageFromOperator | TextSyntax | X | |||
PrinterMoreInfo | URISyntax | X | |||
PrinterMoreInfoManufacturer | URISyntax | X | |||
PrinterName | TextSyntax | X | |||
PrinterResolution | ResolutionSyntax | X | X | X | X |
PrinterState | PROCESSING, IDLE, STOPPED, UNKNOW | X | |||
PrinterStateReason | COVER_OPEN, FUSER_OVER_TEMP, MEDIA_JAM, ... | ||||
PrinterStateReasons | HashMap | ||||
PrinterURI | URISyntax | ||||
PrintQuality | DRAFT, NORMAL, HIGH | X | X | X | |
QueuedJobCount | IntegerSyntax | X | |||
ReferenceUriSchemesSupported | FILE, FTP, GOPHER, HTTP, HTTPS, NEWS, NMTP, WAIS | ||||
RequestingUserName | TextSyntax | X | |||
Severity | ERROR, REPORT, WARNING | ||||
SheetCollate | COLLATED, UNCOLLATED | X | X | X | |
Sides | ONE_SIDED, DUPLEX, TUMBLE | X | X | X |
Nous allons mettre en oeuvre ces attributs au travers de l'application que nous avons déjà mise en place lors de l'étude sur le service d'impression. Nous récupérons toujours une photo enregistrée dans un emplacement spécifique sur le disque dur, mais cette fois-ci, au lieu de prendre l'impression proposée par défaut, la photo sera imprimée en mode paysage avec une marge de 1 cm dans la partie la plus proche du bord de page.
package impression; import java.io.*; import javax.print.*; import javax.print.attribute.*; import javax.print.attribute.standard.*; import javax.swing.*; public class Imprimante { public static void main(String[] args) throws IOException, PrintException { InputStream fichierImage = new FileInputStream("C:/Photos/Papillon.jpg"); DocFlavor type = DocFlavor.INPUT_STREAM.JPEG; PrintRequestAttributeSet attributs = new HashPrintRequestAttributeSet(); attributs.add(OrientationRequested.LANDSCAPE); attributs.add(new MediaPrintableArea(10, 10, 190, 277, MediaPrintableArea.MM)); Doc document = new SimpleDoc(fichierImage, type, null); PrintService[ ] services = PrintServiceLookup.lookupPrintServices(type, null); PrintService service = (PrintService)JOptionPane.showInputDialog(null, "Choisissez votre imprimante ?", "Imprimer", JOptionPane.QUESTION_MESSAGE, null, services, null); DocPrintJob travail = service.createPrintJob(); travail.print(document, attributs); } }
package impression; import java.io.*; import javax.print.*; import javax.print.attribute.*; import javax.print.attribute.standard.*; import javax.swing.*; public class Imprimante { public static void main(String[] args) throws IOException, PrintException { InputStream fichierImage = new FileInputStream("C:/Photos/Papillon.jpg"); DocFlavor type = DocFlavor.INPUT_STREAM.JPEG; DocAttributeSet attributs = new HashDocAttributeSet(); attributs.add(OrientationRequested.LANDSCAPE); attributs.add(new MediaPrintableArea(10, 10, 190, 277, MediaPrintableArea.MM)); Doc document = new SimpleDoc(fichierImage, type, attributs); PrintService[ ] services = PrintServiceLookup.lookupPrintServices(type, null); PrintService service = (PrintService)JOptionPane.showInputDialog(null, "Choisissez votre imprimante ?", "Imprimer", JOptionPane.QUESTION_MESSAGE, null, services, null); DocPrintJob travail = service.createPrintJob(); travail.print(document, null); } }
Depuis la version 6 de Java, il est maintenant possible de demander à imprimer le contenu d'un composant utilisateur, comme un JTextArea, un JTextPane, un JTable, etc. C'est d'une extrême simplicité puisqu'il suffit de faire appel à la méthode print() du composant concerné. Lorsque nous lançons cette méthode, la boîte de dialogue d'impression apparaît. Vous pouvez alors choisir votre imprimante, régler les marges, l'orientation, etc. Ensuite, après confirmation, l'impression se lance automatiquement en respectant l'aspect de l'original.
A titre d'exemple, je vous propose de voir comment imprimer avec un composant JTextArea.
package impression; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.print.PrinterException; import javax.swing.*; public class Imprimante extends JFrame { private JTextArea éditeur = new JTextArea(); private JToolBar barre = new JToolBar(); public Imprimante() { super("Impression"); barre.add(new AbstractAction("Imprimer") { public void actionPerformed(ActionEvent e) { try { éditeur.print(); } catch (PrinterException ex) { setTitle("Problème d'impression"); } } }); add(barre, BorderLayout.NORTH); add(new JScrollPane(éditeur)); setSize(300, 250); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Imprimante(); } }