Faire bonne impression avec Python

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 4

Par Frédéric Mazué

Faire bonne impression avec Python


Découvrons
I
l existe de nombreuses possibilités pour effectuer des travaux
d'impression avec Python. Ces possibilités sont assez dispa-
ensemble l'éventail rates, et pas d'une efficacité constante selon les plates-formes.
Nous allons examiner tout cela dans le détail, pour UNIX en gé-
néral et Linux en particulier, pour Windows, ou, lorsque c'est pos-
des possibilités sible, pour les deux à la fois.

d'impression de Si vous êtes pressés


PRATIQUE
Python. Au départ, lorsque Guido Van Rossum a créé Python, il a voulu
faire un langage de script, proposant une alternative au Shell.
Guido est issu d'une culture UNIX et ne s'est pas particulièrement
intéressé à l'impression, ou plutôt, a considéré que les choses
allaient de soi. En effet sous UNIX, on imprime un document en
l'injectant dans la file d'impression, tout simplement. Si la file
d'impression est bien configurée, bien équipée en filtres, on impri-
me à peu près ce que l'on veut, même depuis la console, par
exemple comme ceci :
cat document | lpr

Python permet de lancer une commande système, donc imprimer


en Python de cette façon est simple. Voici un programme Python
'printlinux.py' qui s'imprime lui même :

#! /usr/bin/env python

import os

modele_commande = 'cat %s | lpr'

commande = modele_commande %('printlinux.py')


os.popen(commande)

Il est possible d'appliquer cette méthode expéditive sous Win-


dows, en changeant une ligne de code :

modele_commande = 'type %s > prn'

Malheureusement, sous Windows, les résultats ne sont pas garan-


tis sur facture. Cette méthode fait appel au DOS et il peut arriver
que votre imprimante ne soit pas toujours docile. En outre, vous
pouvez obtenir un horrible effet d'escalier, si vous tentez d'impri-
mer un fichier texte écrit sous Linux ou même sous Emacs - Win-
dows. En effet, dans ce cas, les retours à la ligne ne sont indiqués
que par le caractère \x0A, alors que Windows attend la séquence
\x0A\x0D pour assurer le retour du chariot, d'où l'effet d'escalier.
Cette méthode n'est donc finalement à retenir que sous Linux.

Sous Windows
Python dispose de riches extensions pour cette plate-forme. Les

Python
NIVEAU : DÉBUTANT
extensions Windows ne font pas partie du package Python de
base et sont donc à télécharger séparément à
http://www.python.org. Parmi ces extensions vient Pythonwin, qui
est une librairie enveloppe autour de la librairie C++ MFC de Micro-
60 soft. MFC est un ensemble de classes plus ou moins bien conçues,

Programmez N°49 • JANVIER 2003


mais accélérant toutefois la programmation sous Windows. quez de rencontrer des problèmes, en raison d'un bug des MFC
Sous Windows, on emploie la même méthode, pour tracer à eux-mêmes, qui ne gèrent pas bien les bitmaps indépendantes du
l'écran et pour imprimer. Cette méthode consiste en l'obtention périphérique. Or les pilotes d'imprimantes ne garantissent en
d'un contexte de périphérique (dit DC pour Device Context) et en général que l'impression de telles bitmaps et non l'impression des
l'appel des fonctions graphiques du système avec ce DC en argu- bitmaps dépendantes du périphérique. (Pour plus d'informations
ment. Lorsque le fameux DC est encapsulé dans une classe, vous pouvez vous reporter à un article sur le sujet dans Program-
comme c'est le cas avec MFC, les méthodes graphiques sont invo- mez! N°19 et éventuellement à l'article Q195830 de la MSDN de
quées depuis l'objet. Il n'est pas ici question d'entrer dans le Microsoft).
détail des MFC. Voici simplement un exemple de code qui imprime La question est maintenant de savoir comment aller un peu plus
quelque chose : loin. Les classes Pythonwin sont un reflet assez exact des classes
import win32ui MFC. Il existe une documentation u peu rudimentaire au sein de
l'application Pythonwin (figure 1). Comprendre : le vocable
dc = win32ui.CreateDC() Pythonwin recouvre deux choses. Une librairie, et une application
dc.CreatePrinterDC() qui est en fait un utilitaire de programmation. Le code ci-dessous
dc.StartDoc('Impression avec Python')
montre comment créer une fonte :
dc.StartPage()
dc.TextOut(100, 100, 'Bonjour :-)')
import win32ui
dc.EndPage()
dc.EndDoc
dc = win32ui.CreateDC()
dc.CreatePrinterDC()
del dc
dc.StartDoc('Impression avec Python')
Quelques explications. On commence par créer un dc 'générique'
font_properties = {
puis on spécialise celui-ci pour l'imprimante, en invoquant sa
'name': 'Times'
méthode CreatePrinterDC. Remarquez la dernière ligne qui détruit }
cet objet. Cette ligne n'est pas vraiment utile ici, puisque tous les
objets seront quoi qu'il en soit, détruits après exécution du script. font = win32ui.CreateFont(font_properties)
Mais si vous insérez ce code dans un autre, alors la destruction du
dc.StartPage()
DC est très importante. Faute de quoi, vous vous heurteriez très vite
dc.SelectObject(font)
à un épuisement des ressources de votre système. Les travaux d'im- dc.TextOut(100, 100, 'Bonjour :-)')
pression (ici un simple appel à TextOut) doivent impérativement dc.EndPage()
être encadrés dans un double sandwich. La première couche crée dc.EndDoc
un document du point de vue du pilote de l'imprimante. La seconde
del dc
couche encadre le travail spécifique à une page. Celle-ci est impri-
Pour les paresseux
Sous Windows, une excellente façon de procéder paresseusement,
est de demander à une application d'imprimer le document pour
nous. En effet, les applications bureautiques de Microsoft sont
avant tout des serveurs Automation. Ceci signifie qu'ils exposent
toutes leurs fonctionnalités sous la forme de méthodes pouvant
être invoquées par un programme extérieur. Supposons que vous
ayez un gros fichier texte à imprimer. Si vous chargez ce fichier
dans Word, ce dernier se chargera de toute la mise en page pour
vous. Vous pouvez procéder de même avec des feuilles de calcul
Excel ou avec des pages HTML, via FrontPage.
Les extensions Python-Windows proposent un outil pour générer
automatiquement des classes enveloppes Python autour de ces
serveurs. Supposons que nous voulions travailler avec Word.
> Figure 1: L'application Pythonwin Démarrer d'abord la première fois Pythonwin, puis dans le menu,
sélectionnez 'COM makepy Utility'. Puis dans la liste, recherchez
mée et éjectée dès l'appel à EndPage. Plusieurs pages seront donc quelque chose comme: 'Microsoft Word 9.0 Object Library' (figure
chacune, encadrées par un sandwich StartPage - EndPage. 2). Le numéro peut différer selon votre version de Word. Puis cli-
Cette méthode est pleinement satisfaisante, tant que l'on imprime quez sur Ok. Pythonwin va alors générer ces fameuses enve-
du texte ou que l'on trace des lignes. Mais si vous souhaitez impri- loppes. Ceci fait, vous pouvez fermer Pythonwin si vous voulez.
mer des images, c'est à dire des bitmaps sous Windows, vous ris- Vous n'en avez plus besoin car les classes enveloppes sont totale- 61

Programmez N°49 • JANVIER 2003


ment indépendantes. Voici maintenant un exemple de code qui Les classes de wxWindows et donc de wxPython ne sont pas sans
demande à Word de charger un fichier texte puis de l'imprimer : rappeler les classes MFC, par leurs dénominations et leur organi-
sation. Toutefois, les deux n'ont rien à voir. Ici, le jeu de classes
from win32com.client import Dispatch, constants est complet, les classes plus élaborées et surtout, sans bugs gros-
siers. Ainsi le problème d'impression des bitmaps, signalé plus
word = Dispatch('Word.Application.9')
document = word.Documents.Open('c:\\texte.txt')
haut, n'existe pas avec wxPython. Mieux : il est possible d'impri-
# éventuellement pour admirer le travail mer des images tiff, png, jpg, etc. Le revers de la médaille est qu'il
#word.Visible = 1 faut ici écrire beaucoup de code, car les fonctionnalités d'impres-
document.PrintOut() sion doivent faire partie d'une application complète.
# éventuellement, une fois l'impression terminée
Ceci posé, au sein d'une application, il y a deux façons d'imprimer.
#word.Quit()
L'encadré 1 montre la marche à suivre pour la première. Le listing
Cette façon de procéder est formidable, car il est possible de est partiel puisque ce n'est que le code du gestionnaire d'événe-
générer ainsi un document de A à Z, avec constitution de para- ment d'un bouton poussoir. La démarche consiste à instancier une
graphes, insertion d'images, sélection de polices, etc. Le tout est classe wxPrintDialog. Il s'agit de la boîte de dialogue d'impression
classique. Ceci fait, on récupère un DC, puis on imprime 'dans un
sandwich' à la Windows, comme vu plus haut, y compris sous
Linux. Pour instancier la classe wxPrintDialog, il faut d'abord ins-
tancier deux autres classes: wxPrintData puis wxPrintDialogData.
Attention! Ne craignez pas d'appeler toutes les méthodes définis-
sant le comportement de ces classes. Une carence d'initialisation
peut provoquer un échec de l'impression, selon l'humeur de Win-
dows ou de votre pilote d'imprimante. Sous Linux, vous devez
rajouter une ligne de code comme ceci :

pd.SetPrinterCommand('lpr')

Le résultat s'observe figure 4 Nous constatons que la file d'im-


pression ne perd pas ses droits. Sous Linux, wxPython mémorise
> Figure 2: Génération automatique de classes autour des serveurs vos tracés dans un fichier PostScript, puis injecte celui-ci dans la
Automation de Microsoft. file, à l'issue de l'appel à EndDoc. Un script Python peut tester sur
quelle plate-forme il tourne, comme ceci:
de comprendre Automation et ses rapports avec Python. Je vous
invite à vous reporter à un article de Programmez! 35 sur ce sujet.
Il est possible de connaître les interfaces exposées par les applica-
tions Automation, en consultant la MSDN. Par exemple, pour Word
2000, chercher à 'Word 2000 Object Model' et pour Office XP, cher-
cher à 'Word Object Model'. (figure 3). Quand on a bien compris le
principe et qu'on a le pied à l'étrier, piloter les applications Office
est franchement ludique et extrêmement puissant. N'omettez pas
d'y jeter un coup d'œil.

wxPython, pour du code portable


A ce stade, nous avons vu des méthodes présentant toutes leur
intérêt propre. Facilité, ou puissance, etc. Ce qui nous manque, est
le moyen d'écrire du code portable. Après tout, Python est le lan-
gage portable par excellence. Il est vrai que le cas de l'impression
PRATIQUE

est difficile et un peu à part, mais nous avons toutefois une bonne
solution avec wxPython. wxPython est une librairie de classes
> Figure 3: L'organisation interne de Word 2000 selon la MSDN.
Python, enveloppant les classes C++ de l'excellente librairie
wxWindows, que j'ai déjà eu l'occasion de vous présenter. wxPy-
import os
thon est sous licence GPL. Vous pouvez le télécharger à
http://www.wxpython.org. wxPython fonctionne à merveille sous if os.name == "posix":
Windows, UNIX et Linux. # c'est UNIX Linux
if os.name in ['nt', 'dos', 'ce']:
# c'est Windows

Python
NIVEAU : DÉBUTANT
Deux choses à noter : En mode MM_POINT, les sens des ordon-
nées diffère de MFC à wxPython : négatif vers le bas avec MFC,
positif vers le bas avec wxPython. Enfin, notez la destruction du
62 DC par del dc, comme précédemment.

Programmez N°49 • JANVIER 2003


encadré1 encadré2
# listing partiel wxpythonprint.py #! /usr/bin/env python

def OnPrintBouton(self, event): from wxPython.wx import *


pd = wxPrintData()
pd.SetPrinterName('') class MonPrintout(wxPrintout):
pd.SetOrientation(wxPORTRAIT) def __init__(self, titre):
pd.SetPaperId(wxPAPER_A4) wxPrintout.__init__(self, titre)
pd.SetQuality(wxPRINT_QUALITY_DRAFT)
pd.SetColour(false) # impression noir et blanc def HasPage(self,page):
pd.SetNoCopies(1)
if(page <= 1):
pd.SetCollate(true)
return true
else:
pdd = wxPrintDialogData()
return false
pdd.SetPrintData(pd)
pdd.SetSetupDialog(false)
def GetPageInfo(self):
pdd.SetMinPage(1)
return (1, 1, 1, 1)
pdd.SetMaxPage(1)
pdd.SetFromPage(1)
def OnPrintPage(self, page):
pdd.SetToPage(1)
dc = self.GetDC()
pdd.SetPrintToFile(false)
dc.SetMapMode(wxMM_POINTS)
dc.DrawText("Bonjour :-)", 72, 72)
printdialog = wxPrintDialog(self, pdd)
return true
ok = printdialog.ShowModal()
if not ok:
return
dc = printdialog.GetPrintDC() class MainWindow(wxFrame):
dc.StartDoc("Mon document") def __init__(self, parent, id, title):
dc.StartPage() wxFrame.__init__(self, parent, -1, title, size = (500, 500),
dc.SetMapMode(wxMM_POINTS) style=wxDEFAULT_FRAME_STYLE|wxNO_FULL
dc.DrawText("Bonjour :-)", 72, 72) _REPAINT_ON_RESIZE)
dc.EndPage() id_bouton = wxNewId()
dc.EndDoc() wxButton(self, id_bouton, 'Imprimer', wxPoint(200, 200))
del dc EVT_BUTTON(self, id_bouton, self.OnPrintBouton)

La deuxième méthode, plus complexe, permet aussi une meilleure def OnPrintBouton(self, event):
gestion des pages. L'encadré 2 en présente le listing complet. En pd = wxPrintData()
pd.SetPrinterName('')
dehors de l'initialisation de wxPrintData et wxPrintDialogData, qui
pd.SetOrientation(wxPORTRAIT)
fait partie des meubles, on utilise cette fois un objet wxPrinter, pd.SetPaperId(wxPAPER_A4)
dont la méthode Print s'attend à recevoir un objet dérivant de pd.SetQuality(wxPRINT_QUALITY_DRAFT)
wxPrintout. Cette classe dérivée DOIT surcharger quatre méthodes : pd.SetColour(false) # impression noir et blanc
pd.SetNoCopies(1)
• le constructeur dans lequel on appelle le constructeur de base.
pd.SetCollate(true)
• HasPage. Tant que cette méthode renvoie true, la fonction call-
back OnPrintPage est automatiquement appelée.
pdd = wxPrintDialogData()
• GetPageInfo, qui retourne un tuple contenant les premières, der- pdd.SetPrintData(pd)
nières pages et le début et la fin de l'impression. pdd.SetSetupDialog(false)
pdd.SetMinPage(1)
pdd.SetMaxPage(1)
pdd.SetFromPage(1)
pdd.SetToPage(1)
pdd.SetPrintToFile(false)

printer = wxPrinter(pdd)
monprintout = MonPrintout("mon objet d'impression")
printer.Print(self, monprintout, true)

def OnCloseWindow(self, event):


self.Destroy()

> Figure 4: Sous Linux, tout finit par aboutir dans la file d'impression.
class App(wxApp):
def OnInit(self):
• OnPrintPage, dans laquelle sont effectués les travaux d'impres- frame = MainWindow(None, -1, "Demo d'impression avec wxPython")
sion proprement dits. Ici le DC obtenu est préfabriqué par la
self.SetTopWindow(frame)
classe et NE DOIT PAS être libéré par le programmeur. Cette
frame.Show(true)
méthode doit retourner 'true'. Si elle retourne false, le job d'im- return true
pression est annulé. ■
Frédéric Mazué app = App(0)
fmazue@programmez.com app.MainLoop() 63

Programmez N°49 • JANVIER 2003

Vous aimerez peut-être aussi