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
Line 
1# -*- coding: utf-8 -*-
2#
3# pkpgcounter : a generic Page Description Language parser
4#
5# (c) 2003-2009 Jerome Alet <alet@librelogiciel.com>
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
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.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19# $Id$
20#
21
22"""This modules implements a page counter for PostScript documents."""
23
24import sys
25import os
26
27import pdlparser
28import inkcoverage
29
30class Parser(pdlparser.PDLParser) :
31    """A parser for PostScript documents."""
32    totiffcommands = [ 'gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r"%(dpi)i" -sOutputFile="%(outfname)s" "%(infname)s"' ]
33    required = [ "gs" ]
34    openmode = "rU"
35    format = "PostScript"
36    def isValid(self) :
37        """Returns True if data is PostScript, else False."""
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) :
46            return True
47        else :
48            return False
49
50    def throughGhostScript(self) :
51        """Get the count through GhostScript, useful for non-DSC compliant PS files."""
52        self.logdebug("Internal parser sucks, using GhostScript instead...")
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", "")
55        infname = self.filename
56        command = 'gs -sDEVICE=bbox -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET "%(infname)s" 2>&1 | grep -c "%%HiResBoundingBox:" 2>/dev/null'
57        pagecount = 0
58        fromchild = os.popen(command % locals(), "r")
59        try :
60            try :
61                pagecount = int(fromchild.readline().strip())
62            except (IOError, OSError, AttributeError, ValueError), msg :
63                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
64        finally :
65            if fromchild.close() is not None :
66                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document"
67        self.logdebug("GhostScript said : %s pages" % pagecount)
68        return pagecount * self.copies
69
70    def setcopies(self, pagenum, txtvalue) :
71        """Tries to extract a number of copies from a textual value and set the instance attributes accordingly."""
72        try :
73            number = int(txtvalue)
74        except (ValueError, TypeError) :
75            pass
76        else :
77            if number > self.pages[pagenum]["copies"] :
78                self.pages[pagenum]["copies"] = number
79
80    def natively(self) :
81        """Count pages in a DSC compliant PostScript document."""
82        pagecount = 0
83        self.pages = { 0 : { "copies" : 1 } }
84        oldpagenum = None
85        previousline = ""
86        notrust = False
87        prescribe = False # Kyocera's Prescribe commands
88        acrobatmarker = False
89        pagescomment = None
90        for line in self.infile :
91            line = line.strip()
92            parts = line.split()
93            nbparts = len(parts)
94            if nbparts >= 1 :
95                part0 = parts[0]
96            else :
97                part0 = ""
98            if part0 == r"%ADOPrintSettings:" :
99                acrobatmarker = True
100            elif part0 == "!R!" :
101                prescribe = True
102            elif part0 == r"%%Pages:" :
103                try :
104                    pagescomment = max(pagescomment or 0, int(parts[1]))
105                except ValueError :
106                    pass # strange, to say the least
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(") :
114                try :
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])
121            elif part0 == r"%RBINumCopies:" :
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 :
135                    # treats both "%%Page: x x" and "%%Page: (x-y) z" (probably N-up mode)
136                    newpagenum = int(line.split(']')[0].split()[-1])
137                except :
138                    notinteger = True # It seems that sometimes it's not an integer but an EPS file name
139                else :
140                    notinteger = False
141                    if newpagenum <= oldpagenum :
142                        # Now correctly handles multiple copies when printed from MSOffice.
143                        # Thanks to Jiri Popelka for the fix.
144                        proceed = False
145                    else :
146                        oldpagenum = newpagenum
147                if proceed and not notinteger :
148                    pagecount += 1
149                    self.pages[pagecount] = { "copies" : self.pages[pagecount-1]["copies"] }
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
154            elif line.startswith("/languagelevel where{pop languagelevel}{1}ifelse 2 ge{1 dict dup/NumCopies") :
155                self.setcopies(pagecount, previousline[2:])
156            elif (nbparts > 1) and (parts[1] == "@copies") :
157                self.setcopies(pagecount, part0)
158            previousline = line
159
160        # extract max number of copies to please the ghostscript parser, just
161        # in case we will use it later
162        self.copies = max([ v["copies"] for (k, v) in self.pages.items() ])
163
164        # now apply the number of copies to each page
165        if not pagecount and pagescomment :
166            pagecount = pagescomment
167        for pnum in range(1, pagecount + 1) :
168            page = self.pages.get(pnum, self.pages.get(1, self.pages.get(0, { "copies" : 1 })))
169            copies = page["copies"]
170            pagecount += (copies - 1)
171            self.logdebug("%s * page #%s" % (copies, pnum))
172
173        self.logdebug("Internal parser said : %s pages" % pagecount)
174        return (pagecount, notrust)
175
176    def getJobSize(self) :
177        """Count pages in PostScript document."""
178        self.copies = 1
179        (nbpages, notrust) = self.natively()
180        newnbpages = nbpages
181        if notrust or not nbpages :
182            try :
183                newnbpages = self.throughGhostScript()
184            except pdlparser.PDLParserError, msg :
185                self.logdebug(msg)
186        return max(nbpages, newnbpages)
Note: See TracBrowser for help on using the browser.