root / pykota / trunk / bin / pkbanner @ 3303

Revision 3303, 13.5 kB (checked in by jerome, 16 years ago)

PDF generation routines now use unicode everywhere.
Ensure translations are used for PDF document's titles and subjects.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: UTF-8 -*-
3#
4# PyKota : Print Quotas for CUPS
5#
6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22#
23
24import sys
25import os
26import time
27import cStringIO
28import popen2
29
30try :
31    from reportlab.pdfgen import canvas
32    from reportlab.lib import pagesizes
33    from reportlab.lib.units import cm
34except ImportError :   
35    hasRL = False
36else :   
37    hasRL = True
38   
39try :
40    import PIL.Image 
41except ImportError :   
42    hasPIL = False
43else :   
44    hasPIL = True
45   
46import pykota.appinit
47from pykota.utils import *
48
49from pykota.errors import PyKotaToolError, PyKotaCommandLineError
50from pykota.tool import Tool
51from pykota import version
52
53__doc__ = N_("""pkbanner v%(__version__)s (c) %(__years__)s %(__author__)s
54
55Generates banners.
56
57command line usage :
58
59  pkbanner  [options]  [more info]
60
61options :
62
63  -v | --version       Prints pkbanner's version number then exits.
64  -h | --help          Prints this message then exits.
65 
66  -l | --logo img      Use the image as the banner's logo. The logo will
67                       be drawn at the center top of the page. The default
68                       logo is /usr/share/pykota/logos/pykota.jpeg
69                       
70  -p | --pagesize sz   Sets sz as the page size. Most well known
71                       page sizes are recognized, like 'A4' or 'Letter'
72                       to name a few. The default size is A4.
73 
74  -s | --savetoner s   Sets the text luminosity factor to s%%. This can be
75                       used to save toner. The default value is 0, which
76                       means that no toner saving will be done.
77 
78  -u | --url u         Uses u as an url to be written at the bottom of
79                       the banner page. The default url is :
80                       http://www.pykota.com/
81 
82examples :                             
83
84  Using pkbanner directly from the command line is not recommended,
85  excepted for testing purposes. You should use pkbanner in the
86  'startingbanner' or 'endingbanner' directives in pykota.conf
87 
88    startingbanner: /usr/bin/pkbanner --logo="" --savetoner=75
89 
90      With such a setting in pykota.conf, all print jobs will be
91      prefixed with an A4 banner with no logo, and text luminosity will
92      be increased by 75%%. The PostScript output will be directly sent
93      to your printer.
94     
95  You'll find more examples in the sample configuration file included   
96  in PyKota.
97""")
98       
99class PyKotaBanner(Tool) :       
100    """A class for pkbanner."""
101    def getPageSize(self, pgsize) :
102        """Returns the correct page size or None if not found."""
103        try :
104            return getattr(pagesizes, pgsize.upper())
105        except AttributeError :   
106            try :
107                return getattr(pagesizes, pgsize.lower())
108            except AttributeError :
109                pass
110               
111    def getVar(self, varname) :           
112        """Extracts a variable from the environment and returns its value or 'Unknown' in the current locale."""
113        return os.environ.get(varname) or _("Unknown")
114       
115    def printVar(self, canvas, x, y, label, value, size, savetoner) :
116        """Outputs a variable onto the PDF canvas.
117       
118           Returns the number of points to substract to current Y coordinate.
119        """   
120        canvas.saveState()
121        canvas.setFont("Helvetica-Bold", size)
122        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 0) ] # Black * savetoner
123        canvas.setFillColorRGB(r, g, b)
124        message = "%s :" % _(label)
125        canvas.drawRightString(x, y, message)
126        canvas.setFont("Courier-Bold", size)
127        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (1, 0, 0) ] # Red * savetoner
128        canvas.setFillColorRGB(r, g, b)
129        canvas.drawString(x + 0.5*cm, y, value)
130        canvas.restoreState()
131        return (size + 4)
132   
133    def genPDF(self, pagesize, logo, url, text, savetoner) :
134        """Generates the banner in PDF format, return the PDF document as a string."""
135        document = cStringIO.StringIO()
136        c = canvas.Canvas(document, pagesize=pagesize, pageCompression=1)
137       
138        c.setAuthor(self.effectiveUserName)
139        c.setTitle(_("PyKota generated Banner"))
140        c.setSubject(_("This is a print banner generated with PyKota"))
141       
142        xcenter = pagesize[0] / 2.0
143        ycenter = pagesize[1] / 2.0
144                   
145        ypos = pagesize[1] - (2 * cm)           
146       
147        if logo :
148            try :   
149                imglogo = PIL.Image.open(logo)
150            except :   
151                self.printInfo("Unable to open image %s" % logo, "warn")
152            else :
153                (width, height) = imglogo.size
154                multi = float(width) / (8 * cm) 
155                width = float(width) / multi
156                height = float(height) / multi
157                xpos = xcenter - (width / 2.0)
158                ypos -= height
159                c.drawImage(logo, xpos, ypos, width, height)
160       
161        # New top
162        xpos = pagesize[0] / 5.0
163        ypos -= (1 * cm) + 20
164       
165        printername = self.getVar("PYKOTAPRINTERNAME")
166        username = self.getVar("PYKOTAUSERNAME")
167        accountbanner = self.config.getAccountBanner(printername)
168       
169        # Outputs the username
170        ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 
171       
172        # Text   
173        if text :
174            ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 
175       
176        # Printer and Job Id
177        job = "%s - %s" % (printername, self.getVar("PYKOTAJOBID"))
178        ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 
179       
180        # Current date (TODO : at the time the banner was printed ! Change this to job's submission date)
181        datetime = time.strftime("%c", time.localtime())
182        ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 
183       
184        # Result of the print job
185        action = self.getVar("PYKOTAACTION")
186        if action == "ALLOW" :
187            action = _("Allowed")
188        elif action == "DENY" :   
189            action = _("Denied")
190        elif action == "WARN" :   
191            action = _("Allowed with Warning")
192        elif action == "PROBLEM" :   
193            # should never occur
194            action = _("Problem")
195        elif action == "CANCEL" :   
196            # should never occur
197            action = _("Cancelled")
198        ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 
199       
200        # skip some space
201        ypos -= 20
202       
203        # Outputs title and filename
204        # We put them at x=0.25*pagewidth so that the line is long enough to hold them
205        title = self.getVar("PYKOTATITLE")
206        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 
207       
208        filename = self.getVar("PYKOTAFILENAME")
209        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 
210       
211        # skip some space
212        ypos -= 20
213       
214        # Now outputs the user's account balance or page counter
215        ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 
216        limitby = self.getVar("PYKOTALIMITBY")
217        if limitby == "balance" : 
218            ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 
219        elif limitby == "quota" :
220            ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner) 
221            ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner) 
222            ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner) 
223        else :
224            if limitby == "noquota" :
225                msg = _("No Limit")
226            elif limitby == "nochange" :   
227                msg = _("No Accounting")
228            elif limitby == "noprint" :   
229                msg = _("Forbidden")
230            else :   
231                msg = _("Unknown")
232            ypos -= self.printVar(c, xcenter, ypos, _("Printing Mode"), msg, 14, savetoner)
233           
234        # URL
235        if url :
236            c.saveState()
237            c.setFont("Courier-Bold", 16)
238            (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 1) ] # Blue * savetoner
239            c.setFillColorRGB(r, g, b)
240            c.drawCentredString(xcenter, 2 * cm, url)
241            c.restoreState()
242       
243        c.showPage()
244        c.save()
245        return document.getvalue()
246       
247    def main(self, arguments, options) :
248        """Generates a banner."""
249        if not hasRL :
250            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
251        if not hasPIL :
252            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
253           
254        try :
255            savetoner = int(options["savetoner"])
256            if (savetoner < 0) or (savetoner > 99) :
257                raise ValueError, _("Allowed range is (0..99)")
258            savetoner /= 100.0   
259        except (TypeError, ValueError), msg :
260            self.printInfo(_("Invalid 'savetoner' option %s : %s") % (options["savetoner"], msg), "warn")
261            savetoner = 0.0
262           
263        pagesize = self.getPageSize(options["pagesize"])
264        if pagesize is None :
265            pagesize = self.getPageSize("a4")
266            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
267           
268        self.logdebug("Generating the banner in PDF format...")   
269        doc = self.genPDF(pagesize, 
270                          options["logo"].strip(), 
271                          options["url"].strip(), 
272                          " ".join(arguments).strip(), 
273                          savetoner)
274       
275        self.logdebug("Converting the banner to PostScript...")   
276        os.environ["PATH"] = "%s:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin" % os.environ.get("PATH", "")
277        child = popen2.Popen3("gs -q -dNOPAUSE -dBATCH -dPARANOIDSAFER -sDEVICE=pswrite -sOutputFile=- -")
278        try :
279            child.tochild.write(doc)
280            child.tochild.close()
281            sys.stdout.write(child.fromchild.read())
282            sys.stdout.flush()
283            child.fromchild.close()
284        except IOError, msg :   
285            self.printInfo("I/O Error %s" % msg, "error")
286        status = child.wait()
287        if os.WIFEXITED(status) :
288            status = os.WEXITSTATUS(status)
289        self.logdebug("PDF to PostScript converter exit code is %s" % str(status))
290        self.logdebug("Banner completed.")
291        return status
292
293if __name__ == "__main__" :
294    # TODO : --papertray : to print banners on a different paper (colored for example)
295    retcode = 0
296    try :
297        defaults = { \
298                     "savetoner" : "0", \
299                     "pagesize" : "a4", \
300                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
301                     "url" : "http://www.pykota.com/",
302                   }
303        short_options = "vhs:l:p:u:"
304        long_options = ["help", "version", "savetoner=", "pagesize=", "logo=", "url="]
305       
306        # Initializes the command line tool
307        banner = PyKotaBanner(doc=__doc__)
308        banner.deferredInit()
309       
310        # parse and checks the command line
311        (options, args) = banner.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=1)
312       
313        # sets long options
314        options["help"] = options["h"] or options["help"]
315        options["version"] = options["v"] or options["version"]
316        options["savetoner"] = options["s"] or options["savetoner"] or defaults["savetoner"]
317        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
318        options["url"] = options["u"] or options["url"] or defaults["url"]
319       
320        options["logo"] = options["l"] or options["logo"]
321        if options["logo"] is None : # Allows --logo="" to disable the logo entirely
322            options["logo"] = defaults["logo"] 
323       
324        if options["help"] :
325            banner.display_usage_and_quit()
326        elif options["version"] :
327            banner.display_version_and_quit()
328        else :
329            retcode = banner.main(args, options)
330    except KeyboardInterrupt :       
331        logerr("\nInterrupted with Ctrl+C !\n")
332        retcode = -3
333    except PyKotaCommandLineError, msg :   
334        logerr("%s : %s\n" % (sys.argv[0], msg))
335        retcode = -2
336    except SystemExit :       
337        pass
338    except :
339        try :
340            banner.crashed("pkbanner failed")
341        except :   
342            crashed("pkbanner failed")
343        retcode = -1
344       
345    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.