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
RevLine 
[1908]1#! /usr/bin/env python
[3260]2# -*- coding: UTF-8 -*-
[1908]3#
[3260]4# PyKota : Print Quotas for CUPS
[1908]5#
[3275]6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
[3260]7# This program is free software: you can redistribute it and/or modify
[1908]8# it under the terms of the GNU General Public License as published by
[3260]9# the Free Software Foundation, either version 3 of the License, or
[1908]10# (at your option) any later version.
[3260]11#
[1908]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
[3260]18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[1908]19#
20# $Id$
21#
[2028]22#
[1908]23
24import sys
25import os
[1923]26import time
[1911]27import cStringIO
[1918]28import popen2
[1908]29
[1911]30try :
31    from reportlab.pdfgen import canvas
32    from reportlab.lib import pagesizes
33    from reportlab.lib.units import cm
34except ImportError :   
[3288]35    hasRL = False
[1911]36else :   
[3288]37    hasRL = True
[1911]38   
39try :
40    import PIL.Image 
41except ImportError :   
[3288]42    hasPIL = False
[1911]43else :   
[3288]44    hasPIL = True
[1911]45   
[3294]46import pykota.appinit
47from pykota.utils import *
48
[3288]49from pykota.errors import PyKotaToolError, PyKotaCommandLineError
[3295]50from pykota.tool import Tool
[3303]51from pykota import version
[1911]52
[2344]53__doc__ = N_("""pkbanner v%(__version__)s (c) %(__years__)s %(__author__)s
[1911]54
55Generates banners.
56
57command line usage :
58
[2193]59  pkbanner  [options]  [more info]
[1911]60
61options :
62
63  -v | --version       Prints pkbanner's version number then exits.
64  -h | --help          Prints this message then exits.
65 
[1923]66  -l | --logo img      Use the image as the banner's logo. The logo will
[1938]67                       be drawn at the center top of the page. The default
[1923]68                       logo is /usr/share/pykota/logos/pykota.jpeg
69                       
[1934]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 
[1938]74  -s | --savetoner s   Sets the text luminosity factor to s%%. This can be
[1934]75                       used to save toner. The default value is 0, which
76                       means that no toner saving will be done.
77 
[1923]78  -u | --url u         Uses u as an url to be written at the bottom of
79                       the banner page. The default url is :
[2909]80                       http://www.pykota.com/
[1923]81 
[1911]82examples :                             
83
[1923]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 
[1936]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.
[2344]97""")
[1911]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               
[1923]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       
[1934]115    def printVar(self, canvas, x, y, label, value, size, savetoner) :
[1923]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)
[1934]122        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 0) ] # Black * savetoner
[1923]123        canvas.setFillColorRGB(r, g, b)
[1925]124        message = "%s :" % _(label)
[3303]125        canvas.drawRightString(x, y, message)
[1923]126        canvas.setFont("Courier-Bold", size)
[1934]127        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (1, 0, 0) ] # Red * savetoner
[1923]128        canvas.setFillColorRGB(r, g, b)
[3303]129        canvas.drawString(x + 0.5*cm, y, value)
[1923]130        canvas.restoreState()
131        return (size + 4)
132   
[2193]133    def genPDF(self, pagesize, logo, url, text, savetoner) :
[1911]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       
[3303]138        c.setAuthor(self.effectiveUserName)
139        c.setTitle(_("PyKota generated Banner"))
140        c.setSubject(_("This is a print banner generated with PyKota"))
[1911]141       
142        xcenter = pagesize[0] / 2.0
143        ycenter = pagesize[1] / 2.0
144                   
145        ypos = pagesize[1] - (2 * cm)           
146       
[1923]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       
[1911]161        # New top
162        xpos = pagesize[0] / 5.0
163        ypos -= (1 * cm) + 20
164       
[1923]165        printername = self.getVar("PYKOTAPRINTERNAME")
166        username = self.getVar("PYKOTAUSERNAME")
167        accountbanner = self.config.getAccountBanner(printername)
168       
169        # Outputs the username
[1934]170        ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 
[1923]171       
[2193]172        # Text   
173        if text :
174            ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 
175       
[1923]176        # Printer and Job Id
177        job = "%s - %s" % (printername, self.getVar("PYKOTAJOBID"))
[1934]178        ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 
[1923]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())
[1934]182        ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 
[1923]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")
[2631]192        elif action == "PROBLEM" :   
193            # should never occur
194            action = _("Problem")
195        elif action == "CANCEL" :   
196            # should never occur
197            action = _("Cancelled")
[1934]198        ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 
[1923]199       
200        # skip some space
201        ypos -= 20
202       
203        # Outputs title and filename
[1926]204        # We put them at x=0.25*pagewidth so that the line is long enough to hold them
[1923]205        title = self.getVar("PYKOTATITLE")
[1934]206        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 
[1923]207       
208        filename = self.getVar("PYKOTAFILENAME")
[1934]209        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 
[1923]210       
211        # skip some space
212        ypos -= 20
213       
214        # Now outputs the user's account balance or page counter
[1934]215        ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 
[1923]216        limitby = self.getVar("PYKOTALIMITBY")
217        if limitby == "balance" : 
[1934]218            ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 
[2627]219        elif limitby == "quota" :
[1934]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) 
[2627]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)
[1923]233           
234        # URL
235        if url :
[1911]236            c.saveState()
[1923]237            c.setFont("Courier-Bold", 16)
[1934]238            (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 1) ] # Blue * savetoner
[1923]239            c.setFillColorRGB(r, g, b)
240            c.drawCentredString(xcenter, 2 * cm, url)
[1911]241            c.restoreState()
242       
243        c.showPage()
244        c.save()
245        return document.getvalue()
[1923]246       
[2193]247    def main(self, arguments, options) :
[1911]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           
[1923]254        try :
[1934]255            savetoner = int(options["savetoner"])
256            if (savetoner < 0) or (savetoner > 99) :
257                raise ValueError, _("Allowed range is (0..99)")
258            savetoner /= 100.0   
[1923]259        except (TypeError, ValueError), msg :
[1934]260            self.printInfo(_("Invalid 'savetoner' option %s : %s") % (options["savetoner"], msg), "warn")
261            savetoner = 0.0
[1923]262           
[1911]263        pagesize = self.getPageSize(options["pagesize"])
264        if pagesize is None :
265            pagesize = self.getPageSize("a4")
[1934]266            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
[1911]267           
[1918]268        self.logdebug("Generating the banner in PDF format...")   
[2193]269        doc = self.genPDF(pagesize, 
270                          options["logo"].strip(), 
271                          options["url"].strip(), 
272                          " ".join(arguments).strip(), 
273                          savetoner)
[1918]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", "")
[2193]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")
[1918]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
[1911]292
[1908]293if __name__ == "__main__" :
[1909]294    # TODO : --papertray : to print banners on a different paper (colored for example)
[1911]295    retcode = 0
296    try :
297        defaults = { \
[1971]298                     "savetoner" : "0", \
[1911]299                     "pagesize" : "a4", \
300                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
[2909]301                     "url" : "http://www.pykota.com/",
[1911]302                   }
[1934]303        short_options = "vhs:l:p:u:"
304        long_options = ["help", "version", "savetoner=", "pagesize=", "logo=", "url="]
[1911]305       
306        # Initializes the command line tool
307        banner = PyKotaBanner(doc=__doc__)
[2210]308        banner.deferredInit()
[1911]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"]
[1934]316        options["savetoner"] = options["s"] or options["savetoner"] or defaults["savetoner"]
[1911]317        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
318        options["url"] = options["u"] or options["url"] or defaults["url"]
319       
[1934]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       
[1911]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)
[2216]330    except KeyboardInterrupt :       
[3294]331        logerr("\nInterrupted with Ctrl+C !\n")
[2609]332        retcode = -3
[2512]333    except PyKotaCommandLineError, msg :   
[3294]334        logerr("%s : %s\n" % (sys.argv[0], msg))
[2609]335        retcode = -2
[1911]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.