root / pkpgcounter / trunk / pkpgpdls / postscript.py @ 3567

Revision 3567, 8.1 kB (checked in by jerome, 11 years ago)

This patch corrects the page count of postscripts files when printed
with multiple copies from the MS Office suite. Without this patch, every
time that a user try to print a job using multiple copies from MS office
it counts much more than the correct quantity of copies. Thanks to Jiri
Popelka.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Id Rev
RevLine 
[3410]1# -*- coding: utf-8 -*-
[191]2#
3# pkpgcounter : a generic Page Description Language parser
4#
[3474]5# (c) 2003-2009 Jerome Alet <alet@librelogiciel.com>
[463]6# This program is free software: you can redistribute it and/or modify
[191]7# it under the terms of the GNU General Public License as published by
[463]8# the Free Software Foundation, either version 3 of the License, or
[191]9# (at your option) any later version.
[3436]10#
[191]11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
[3436]15#
[191]16# You should have received a copy of the GNU General Public License
[463]17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[191]18#
19# $Id$
20#
[193]21
[357]22"""This modules implements a page counter for PostScript documents."""
23
[193]24import sys
[283]25import os
[193]26
[235]27import pdlparser
[283]28import inkcoverage
[193]29
[220]30class Parser(pdlparser.PDLParser) :
[193]31    """A parser for PostScript documents."""
[492]32    totiffcommands = [ 'gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r"%(dpi)i" -sOutputFile="%(outfname)s" "%(infname)s"' ]
[527]33    required = [ "gs" ]
[491]34    openmode = "rU"
[555]35    format = "PostScript"
[3436]36    def isValid(self) :
[387]37        """Returns True if data is PostScript, else False."""
[522]38        if self.firstblock.startswith("%!") or \
39           self.firstblock.startswith("\004%!") or \
40           self.firstblock.startswith("\033%-12345X%!PS") or \
41           ((self.firstblock[:128].find("\033%-12345X") != -1) and \
42             ((self.firstblock.find("LANGUAGE=POSTSCRIPT") != -1) or \
43              (self.firstblock.find("LANGUAGE = POSTSCRIPT") != -1) or \
44              (self.firstblock.find("LANGUAGE = Postscript") != -1))) or \
45              (self.firstblock.find("%!PS-Adobe") != -1) :
[387]46            return True
[3436]47        else :
[387]48            return False
[3436]49
[193]50    def throughGhostScript(self) :
51        """Get the count through GhostScript, useful for non-DSC compliant PS files."""
[252]52        self.logdebug("Internal parser sucks, using GhostScript instead...")
[527]53        if self.isMissing(self.required) :
54            raise pdlparser.PDLParserError, "The gs interpreter is nowhere to be found in your PATH (%s)" % os.environ.get("PATH", "")
[522]55        infname = self.filename
[521]56        command = 'gs -sDEVICE=bbox -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET "%(infname)s" 2>&1 | grep -c "%%HiResBoundingBox:" 2>/dev/null'
[491]57        pagecount = 0
[521]58        fromchild = os.popen(command % locals(), "r")
[193]59        try :
[491]60            try :
[521]61                pagecount = int(fromchild.readline().strip())
[491]62            except (IOError, OSError, AttributeError, ValueError), msg :
63                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
[3436]64        finally :
[521]65            if fromchild.close() is not None :
66                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document"
[3436]67        self.logdebug("GhostScript said : %s pages" % pagecount)
[193]68        return pagecount * self.copies
[3436]69
70    def setcopies(self, pagenum, txtvalue) :
[561]71        """Tries to extract a number of copies from a textual value and set the instance attributes accordingly."""
72        try :
73            number = int(txtvalue)
[3436]74        except (ValueError, TypeError) :
[561]75            pass
[3436]76        else :
[561]77            if number > self.pages[pagenum]["copies"] :
78                self.pages[pagenum]["copies"] = number
[3436]79
[193]80    def natively(self) :
81        """Count pages in a DSC compliant PostScript document."""
82        pagecount = 0
[273]83        self.pages = { 0 : { "copies" : 1 } }
[263]84        oldpagenum = None
[252]85        previousline = ""
[521]86        notrust = False
87        prescribe = False # Kyocera's Prescribe commands
88        acrobatmarker = False
[444]89        pagescomment = None
[491]90        for line in self.infile :
91            line = line.strip()
[561]92            parts = line.split()
93            nbparts = len(parts)
[562]94            if nbparts >= 1 :
95                part0 = parts[0]
[3436]96            else :
[562]97                part0 = ""
[561]98            if part0 == r"%ADOPrintSettings:" :
[521]99                acrobatmarker = True
[3436]100            elif part0 == "!R!" :
[521]101                prescribe = True
[561]102            elif part0 == r"%%Pages:" :
[444]103                try :
[561]104                    pagescomment = max(pagescomment or 0, int(parts[1]))
[444]105                except ValueError :
106                    pass # strange, to say the least
[561]107            elif (part0 == r"%%BeginNonPPDFeature:") \
108                  and (nbparts > 2) \
109                  and (parts[1] == "NumCopies") :
110                self.setcopies(pagecount, parts[2])
111            elif (part0 == r"%%Requirements:") \
112                  and (nbparts > 1) \
113                  and (parts[1] == "numcopies(") :
[263]114                try :
[561]115                    self.setcopies(pagecount, line.split('(')[1].split(')')[0])
116                except IndexError :
117                    pass
118            elif part0 == "/#copies" :
119                if nbparts > 1 :
120                    self.setcopies(pagecount, parts[1])
[3436]121            elif part0 == r"%RBINumCopies:" :
[561]122                if nbparts > 1 :
123                    self.setcopies(pagecount, parts[1])
124            elif (parts[:4] == ["1", "dict", "dup", "/NumCopies"]) \
125                  and (nbparts > 4) :
126                # handle # of copies set by mozilla/kprinter
127                self.setcopies(pagecount, parts[4])
128            elif (parts[:6] == ["{", "pop", "1", "dict", "dup", "/NumCopies"]) \
129                  and (nbparts > 6) :
130                # handle # of copies set by firefox/kprinter/cups (alternate syntax)
131                self.setcopies(pagecount, parts[6])
132            elif (part0 == r"%%Page:") or (part0 == r"(%%[Page:") :
133                proceed = True
134                try :
[437]135                    # treats both "%%Page: x x" and "%%Page: (x-y) z" (probably N-up mode)
136                    newpagenum = int(line.split(']')[0].split()[-1])
[3436]137                except :
[561]138                    notinteger = True # It seems that sometimes it's not an integer but an EPS file name
[3436]139                else :
[561]140                    notinteger = False
[3567]141                    if newpagenum <= oldpagenum :
142                        # Now correctly handles multiple copies when printed from MSOffice.
143                        # Thanks to Jiri Popelka for the fix.
[561]144                        proceed = False
[263]145                    else :
146                        oldpagenum = newpagenum
[3436]147                if proceed and not notinteger :
[263]148                    pagecount += 1
[273]149                    self.pages[pagecount] = { "copies" : self.pages[pagecount-1]["copies"] }
[561]150            elif (not prescribe) \
151               and (parts[:3] == [r"%%BeginResource:", "procset", "pdf"]) \
152               and not acrobatmarker :
153                notrust = True # Let this stuff be managed by GhostScript, but we still extract number of copies
[248]154            elif line.startswith("/languagelevel where{pop languagelevel}{1}ifelse 2 ge{1 dict dup/NumCopies") :
[561]155                self.setcopies(pagecount, previousline[2:])
156            elif (nbparts > 1) and (parts[1] == "@copies") :
157                self.setcopies(pagecount, part0)
[248]158            previousline = line
[3436]159
160        # extract max number of copies to please the ghostscript parser, just
[248]161        # in case we will use it later
[273]162        self.copies = max([ v["copies"] for (k, v) in self.pages.items() ])
[3436]163
[248]164        # now apply the number of copies to each page
[3436]165        if not pagecount and pagescomment :
[448]166            pagecount = pagescomment
[248]167        for pnum in range(1, pagecount + 1) :
[448]168            page = self.pages.get(pnum, self.pages.get(1, self.pages.get(0, { "copies" : 1 })))
[248]169            copies = page["copies"]
170            pagecount += (copies - 1)
[252]171            self.logdebug("%s * page #%s" % (copies, pnum))
[3436]172
[273]173        self.logdebug("Internal parser said : %s pages" % pagecount)
[384]174        return (pagecount, notrust)
[3436]175
176    def getJobSize(self) :
[193]177        """Count pages in PostScript document."""
[202]178        self.copies = 1
[384]179        (nbpages, notrust) = self.natively()
180        newnbpages = nbpages
[444]181        if notrust or not nbpages :
[384]182            try :
183                newnbpages = self.throughGhostScript()
184            except pdlparser.PDLParserError, msg :
185                self.logdebug(msg)
[3436]186        return max(nbpages, newnbpages)
Note: See TracBrowser for help on using the browser.