Commit 8cd26224 authored by Daniel STAN's avatar Daniel STAN

impression_hp: first draft

parent dd369ca6
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
.. codeauthor:: Daniel STAN <dstan@crans.org>
.. codeauthor:: Antoine Durand-Gasselin <adg@crans.org>
Pour envoyer des jobs d'impression à l'imprimante canon via son interface web.
License: GPLv3
"""
# impression_hp.py
#
# Classe impression pour HP MFP 880
#
# Copyright (c) 2006, 2007, 2008, 2009 by Cr@ns (http://www.crans.org)
# #############################################################
import sys, os
import logging
import logging.handlers
if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts')
from base import FichierInvalide, SoldeInsuffisant, PrintError, SettingsError
from gestion.config import impression as config_impression
from subprocess import Popen, PIPE, check_output
import livret
##
logger = logging.getLogger('impression_hp')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
handler = logging.handlers.SysLogHandler(address = '/dev/log')
try:
handler.addFormatter(formatter)
except AttributeError:
handler.formatter = formatter
logger.addHandler(handler)
DEBUG = bool(os.getenv('DEBUG', False))
DECOUVERT_AUTHORISE = config_impression.decouvert
class Option(object):
# Valeur par défaut
value = None
def __str__(self):
return str(value)
name = "option"
pretty_name = u"option quelconque"
def parse(self, v):
self.value = v
class BooleanOption(Option):
value = False
def parse(self, v):
self.value = bool(v)
if str(v).lower() == 'false':
self.value = False
class Couleur(BooleanOption):
name = "couleur"
pretty_name = u"impression couleur"
class Livret(BooleanOption):
name = "livret"
pretty_name = u"livret piqûres à cheval"
def nb(self):
return 2*int(self.value)
class Duplex(BooleanOption):
name = "recto_verso"
pretty_name = u"Impression recto verso"
class Copies(Option):
name = "copies"
pretty_name = u"nombre d'exemplaires"
value = 1
def parse(self, v):
self.value = int(v)
class SelectOption(Option):
possibilities={
None: u'Aucun',
}
value = None
def parse(self, v):
if v == 'None':
v = None
if v not in self.possibilities:
return
self.value = v
class Agrafage(SelectOption):
name = "agrafage"
pretty_name = u"agrafage"
possibilities = {
None : "aucune agrafe",
"TopLeft" : u"agrafe en haut à gauche",
"TopRight" : u"agrafe en haut à droite",
#"BottomLeft" : u"agrafe en bas à gauche", # n'existe pas
#"BottomRight" : u"agrafe en bas à droite", # n' existe pas
"Left": u"deux agrafes sur le bord gauche",
"Right" : u"deux agrafes sur le bord droit",
#"Top" : u"deux agrafes sur le bord supérieur", #pas faisable
#"Bottom" : u"deux agrafes sur le bord inférieur", #n'existe pas
}
def nb(self):
if not self.value:
return 0
if 'Left' in self.value or 'Right' in self.value:
return 2 - int('Top' in self.value or 'Bottom' in self.value)
return 2
def HP_name(self):
if 'Top' in self.value:
return '1Staple%sAngled' % (self.value.replace('Top', ''))
else:
return '2Staples%s' % self.value
class Papier(SelectOption):
name = "papier"
pretty_name = u"format papier"
value = 'A4'
possibilities = {
'A4' : "Papier A4 ordinaire",
'A3' : "Papier A3 ordinaire",
}
class Perforation(SelectOption):
name = "perforation"
pretty_name = u"perforation"
possibilities = {
None: u"Aucune",
"2HolePunchLeft": u"2HolePunchLeft",
"2HolePunchRight": u"2HolePunchRight",
"2HolePunchTop": u"2HolePunchTop",
"2HolePunchBottom": u"2HolePunchBottom",
"3HolePunchLeft": u"3HolePunchLeft",
"3HolePunchRight": u"3HolePunchRight",
"3HolePunchTop": u"3HolePunchTop",
"4HolePunchLeft": u"4HolePunchLeft",
"4HolePunchRight": u"4HolePunchRight",
"4HolePunchTop": u"4HolePunchTop",
}
# ######################################################## #
# CLASSE IMPRESSION #
# ######################################################## #
#
#
class impression(object):
"""impression
Un objet impression correspond à un fichier pdf et un adhérent.
"""
# fichier (chemin)
_fichier = ""
# adhérent (instance)
_adh = None
# paramètres
_settings_list = [
Agrafage,
Papier,
Couleur,
Duplex,
Livret,
Copies,
Perforation,
]
_settings = {}
# le prix de l'impression
_prix = 0.0
_pages = 0
# le cout de base encre pour une impression en couleurs/n&b
# (prix pour papier A4)
_base_prix_nb = 0.0
_base_prix_couleurs = 0.0
# Format du pdf, tout droit sorti de pdfinfo
# (les dimensions sont donc en pts)
_format = '(A4)'
_width = 595.28
_height = 841.89
# Jid unique, à définir avant l'impression
_jid = 0
def __init__(self, path_to_pdf, adh = None):
"""impression(path_to_pdf [, adh])
Crée un nouvel objet impression à partir du fichier pdf pointé
par path_to_pdf. Si adh ext donné, il peut être soit une
instance d'un objet adhérent de crans_ldap soit le login de
l'adhérent. Lève l'exception FichierInvalide si le fichier
n'existe pas ou si ce n'est pas un pdf.
"""
# On génère la liste des options
self._settings = {cls.name: cls() for cls in self._settings_list}
self._fichier = path_to_pdf
self._adh = adh
# on verifie que le fichier existe
if not os.path.isfile(path_to_pdf):
raise FichierInvalide, ("Fichier introuvable", path_to_pdf)
if not open(path_to_pdf).read().startswith("%PDF"):
raise FichierInvalide, ("Le fichier ne semble pas etre un PDF", path_to_pdf)
# on compte les pages et on regarde le format
pdfinfo = Popen(["pdfinfo",self._fichier],stdout=PIPE,stderr=PIPE).communicate()
if pdfinfo[1] <> '':
raise FichierInvalide(u"pdfinfo n'arrive pas a lire le fichier (il est peut-etre corrompu ou protege par un mot de passe), https://wiki.crans.org/VieCrans/ImpressionReseau#Format_des_fichiers",path_to_pdf)
self._pages = -1
for line in pdfinfo[0].split('\n'):
if line.startswith('Pages'):
self._pages = int(line.split()[1])
elif line.startswith('Page size'):
size = line.split()
if len(size) <= 6:
self._format = "Unknown"
else:
self._format = size[6]
self._width = float(size[2])
self._height = float(size[4])
# Hack pour mieux reconnaître les formats
w = min(self._width,self._height)
h = max(self._width,self._height)
err = 100
if abs(w - 595) < err and abs(h - 842) < err:
self._format = "(A4)"
elif abs(w - 842) < err and abs(h - 1180) < err:
self._format = "(A3)"
if self._pages <= 0:
raise FichierInvalide(u"Impossible de lire le nombre de pages",path_to_pdf)
if not self._format in ['(A4)','(A3)']:
pass
#raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes"
# calcule le prix de l'encre tout de suite
self._calcule_prix()
def _uniq_jid(self):
""" Alloue un jid unique """
fname = '/var/impression/jid'
## Maybe need a lock ?
try:
f = file(fname,'r+')
cur = int(f.read())+1
f.seek(0)
except (IOError,ValueError):
cur = 0
f = file(fname,'w')
f.write(str(cur))
f.close()
return cur
def _pdfbook(self):
"""Génère un pdf livret équivalent et renvoie le chemin"""
newfile = '/tmp/' + self.fileName()[-4] + '-book.pdf'
newfile = '/tmp/book.pdf'
check_output(
['/usr/bin/pdfjam', self._fichier,
livret.pdfjam_order(self._pages),
'-o', newfile,
])
return newfile
def changeSettings(self, **kw):
"""changeSettings([keyword=value])
Change les parametres de l'impression, recalcule et renvoie le nouveau prix.
"""
for name in kw:
if name not in self._settings:
raise Exception('unknown %s option' % name)
self._settings[name].parse(kw[name])
if self._settings['livret'].value:
self._settings['recto_verso'].parse(True)
self._settings['agrafage'].parse(None)
# TODO
# Check si l'agrafage et cie sont compatibles avec le nb de pages
return self._calcule_prix()
def printSettings(self):
"""printSettings()
Affiche les paramètres courants sur la sortie standard
"""
for k in self._settings:
opt = self._settings[k]
label = u"%s: %s" % (opt.pretty_name.capitalize(), opt.value)
print label.encode('UTF-8')
def prix(self):
"""prix()
Renvoie le prix courrant de l'impression
"""
return self._prix
def fileName(self):
"""fileName()
renvoie le nom du fichier pdf (exemple : monPdf.pdf)
"""
return os.path.basename(self._fichier)
def filePath(self):
"""filePath()
renvoie le chemin d'accès au fichier pdf.
"""
return self._fichier
def pages(self):
"""pages()
renvoie le nombre de pages du document (page au sens nombre de
faces à imprimer et non le nombre de feuilles)
"""
return self._pages
def imprime(self):
"""imprime()
imprime le document pdf. débite l'adhérent si adhérent il y a.
(si il a été indiqué à l'initialisation de l'objet)
"""
self._jid = self._uniq_jid()
# debite l'adhérent si adherent il y a
if (self._adh != None):
adh = self._adh.split('@')
if len(adh) > 1:
adh = adh[1:]
adh = self._get_adh(adh[0])
self._calcule_prix() # Normalement inutile, mais évite les races
if (self._prix > (adh.solde() - DECOUVERT_AUTHORISE)):
raise SoldeInsuffisant
adh.solde(-self._prix, "impression(%d): %s par %s" % (self._jid,self._fichier,self._adh))
adh.save()
del adh
# imprime le document
self._exec_imprime()
def _calcule_prix(self):
faces = self._pages
if self._settings['livret'].value:
feuilles = int((faces+3)/4)
faces = 2 * feuilles
elif self._settings['recto_verso'].value:
feuilles = int(faces/2.+0.5)
else:
feuilles = faces
if (self._settings['papier'].value == "A3"):
c_papier = config_impression.c_a3
pages = 2*faces
else:
pages = faces
c_papier = config_impression.c_a4
if self._settings['couleur'].value:
c_impression = c_papier * feuilles + config_impression.c_face_couleur * pages
else:
c_impression = c_papier * feuilles + config_impression.c_face_nb * pages
# Cout des agrafes
nb_agrafes = self._settings['agrafage'].nb() + self._settings['livret'].nb()
c_agrafes = nb_agrafes * config_impression.c_agrafe
c_total = int(self._settings['copies'].value * ( c_impression +
c_agrafes ) + 0.5) # arrondi et facture
self._prix = float(c_total)/100
return self._prix
def _get_adh(self, adh):
if type(adh) == str:
#from ldap_crans_test import crans_ldap
from gestion.ldap_crans import CransLdap
adh = CransLdap().getProprio(adh, 'w')
return adh
## ################################# ##
## fonction qui imprime pour de vrai ##
## ################################# ##
##
def _exec_imprime(self):
""" Envoie l'impression a l'imprimante avec les parametres actuels """
if (self._adh != None):
logger.info('Impression(%d) [%s] : %s' % (self._jid, self._adh, self._fichier))
else:
logger.info("Impression(%d) : %s" % (self._jid, self._fichier))
# Création de la liste d'options pour lp
options = list()
# TODO que se passe-t-il si le fichier commence par -o ou -- ?
if self._settings['livret'].value:
options.append(self._pdfbook())
else:
options.append(self._fichier)
# Pour spécifier l'imprimante
options += ['-d', 'MFPM880']
# Pour spécifier un jobname de la forme jid:adh:nom_du_fichier
jobname = '%d:%s:%s' % (self._jid, self._adh, self._fichier.split('/')[-1].replace("\"","\\\""))
options += ['-t', jobname]
#Pour le nombre de copies et specifie non assemblee
options += ['-n', str(self._settings['copies'].value)]
#Et on ne les veux pas assemblées (sic)
options += ['-o', 'Collate=True']
# Pour donner le login de l'adherent (TODO: useful ?)
options += ['-U', str(self._adh)]
if self._settings['papier'].value == 'A4':
options += ['-o', 'PageSize=A4']
else:
options += ['-o', 'pdf-expand',
'-o', 'pdf-paper=841x1190',
'-o', 'PageSize=A3']
options.append('-o')
# Quelle alimentation papier utiliser ?
if self._settings['papier'].value == 'A3':
options.append('InputSlot=Tray1')
elif self._settings['livret'].value:
options.append('InputSlot=Tray2')
else:
options.append('InputSlot=Tray3')
if not self._settings['couleur'].value:
options += ['-o', 'HPColorAsGray=True']
duplex = self._settings['recto_verso'].value or\
self._settings['livret'].value
paysage = self._width > self._height
# mode paysage: on indique que c'est landscape (ou livret)
if paysage:
options += ['-o', 'landscape']
# Livret: 2 pages par côté, et on inverse le bord duplex
if self._settings['livret'].value:
options += ['-o', 'number-up=2']
paysage = not paysage
if duplex:
if paysage:
options += ['-o', 'sides=two-sided-short-edge']
else:
options += ['-o', 'sides=two-sided-long-edge']
if not duplex:
options += ['-o', 'sides=one-sided']
if self._settings['livret'].value:
options += ['-o', 'HPStaplerOptions=FoldStitch']
elif self._settings['agrafage'].value:
v = self._settings['agrafage'].HP_name()
options += ['-o', 'HPStaplerOptions=%s' % v]
if not DEBUG:
logger.info("lp " + " ".join(options))
check_output(['lp'] + options)
else:
logger.info("pretend printing (debug): " + " ".join(options))
#!/bin/bash /usr/scripts/python.sh
import sys
import math
import itertools
def booklet_order(n):
page = (lambda k: None if k > n else k)
recto = 2*int(math.ceil(n/4.))
verso = recto + 2
while recto > 1:
yield page(verso)
yield page(recto-1)
yield page(recto)
yield page(verso-1)
recto -= 2
verso += 2
def pdfjam_order(n):
return ",".join(itertools.imap(lambda k: str(k or '{}'), booklet_order(n)))
if __name__ == '__main__':
try:
n = int(sys.argv[1])
except:
print "Veuillez entrer un nombre"
exit(1)
print pdfjam_order(n)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment