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

Revision 389, 9.0 kB (checked in by jerome, 16 years ago)

Added the detection of number of copies in code generated by some software.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Id Rev
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3#
4# pkpgcounter : a generic Page Description Language parser
5#
6# (c) 2003, 2004, 2005, 2006 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 2 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, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23
24"""This modules implements a page counter for PostScript documents."""
25
26import sys
27import os
28import tempfile
29import popen2
30
31import pdlparser
32import inkcoverage
33
34class Parser(pdlparser.PDLParser) :
35    """A parser for PostScript documents."""
36    totiffcommand = 'gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r%(dpi)i -sOutputFile="%(fname)s" -'
37    def isValid(self) :   
38        """Returns True if data is PostScript, else False."""
39        if self.firstblock.startswith("%!") or \
40           self.firstblock.startswith("\004%!") or \
41           self.firstblock.startswith("\033%-12345X%!PS") or \
42           ((self.firstblock[:128].find("\033%-12345X") != -1) and \
43             ((self.firstblock.find("LANGUAGE=POSTSCRIPT") != -1) or \
44              (self.firstblock.find("LANGUAGE = POSTSCRIPT") != -1) or \
45              (self.firstblock.find("LANGUAGE = Postscript") != -1))) or \
46              (self.firstblock.find("%!PS-Adobe") != -1) :
47            self.logdebug("DEBUG: Input file is in the PostScript format.")
48            return True
49        else :   
50            return False
51       
52    def throughGhostScript(self) :
53        """Get the count through GhostScript, useful for non-DSC compliant PS files."""
54        self.logdebug("Internal parser sucks, using GhostScript instead...")
55        self.infile.seek(0)
56        command = 'gs -sDEVICE=bbox -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET - 2>&1 | grep -c "%%HiResBoundingBox:" 2>/dev/null'
57        child = popen2.Popen4(command)
58        try :
59            data = self.infile.read(pdlparser.MEGABYTE)   
60            while data :
61                child.tochild.write(data)
62                data = self.infile.read(pdlparser.MEGABYTE)
63            child.tochild.flush()
64            child.tochild.close()   
65        except (IOError, OSError), msg :   
66            raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
67           
68        pagecount = 0
69        try :
70            pagecount = int(child.fromchild.readline().strip())
71        except (IOError, OSError, AttributeError, ValueError), msg :
72            raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
73        child.fromchild.close()
74       
75        try :
76            child.wait()
77        except OSError, msg :   
78            raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
79        self.logdebug("GhostScript said : %s pages" % pagecount)   
80        return pagecount * self.copies
81       
82    def natively(self) :
83        """Count pages in a DSC compliant PostScript document."""
84        self.infile.seek(0)
85        pagecount = 0
86        self.pages = { 0 : { "copies" : 1 } }
87        oldpagenum = None
88        previousline = ""
89        notrust = 0
90        prescribe = 0 # Kyocera's Prescribe commands
91        acrobatmarker = 0
92        for line in self.infile.xreadlines() : 
93            if (not prescribe) and line.startswith(r"%%BeginResource: procset pdf") \
94               and not acrobatmarker :
95                notrust = 1 # Let this stuff be managed by GhostScript, but we still extract number of copies
96            elif line.startswith(r"%ADOPrintSettings: L") :
97                acrobatmarker = 1
98            elif line.startswith("!R!") :
99                prescribe = 1
100            elif line.startswith(r"%%Page: ") or line.startswith(r"(%%[Page: ") :
101                proceed = 1
102                try :
103                    newpagenum = int(line.split(']')[0].split()[1])
104                except :   
105                    notinteger = 1 # It seems that sometimes it's not an integer but an EPS file name
106                else :   
107                    notinteger = 0
108                    if newpagenum == oldpagenum :
109                        proceed = 0
110                    else :
111                        oldpagenum = newpagenum
112                if proceed and not notinteger :       
113                    pagecount += 1
114                    self.pages[pagecount] = { "copies" : self.pages[pagecount-1]["copies"] }
115            elif line.startswith(r"%%Requirements: numcopies(") :   
116                try :
117                    number = int(line.strip().split('(')[1].split(')')[0])
118                except :     
119                    pass
120                else :   
121                    if number > self.pages[pagecount]["copies"] :
122                        self.pages[pagecount]["copies"] = number
123            elif line.startswith(r"%%BeginNonPPDFeature: NumCopies ") :
124                # handle # of copies set by some Windows printer driver
125                try :
126                    number = int(line.strip().split()[2])
127                except :     
128                    pass
129                else :   
130                    if number > self.pages[pagecount]["copies"] :
131                        self.pages[pagecount]["copies"] = number
132            elif line.startswith("1 dict dup /NumCopies ") :
133                # handle # of copies set by mozilla/kprinter
134                try :
135                    number = int(line.strip().split()[4])
136                except :     
137                    pass
138                else :   
139                    if number > self.pages[pagecount]["copies"] :
140                        self.pages[pagecount]["copies"] = number
141            elif line.startswith("{ pop 1 dict dup /NumCopies ") :
142                # handle # of copies set by firefox/kprinter/cups (alternate syntax)
143                try :
144                    number = int(line.strip().split()[6])
145                except :
146                    pass
147                else :
148                    if number > self.pages[pagecount]["copies"] :
149                        self.pages[pagecount]["copies"] = number
150            elif line.startswith("/languagelevel where{pop languagelevel}{1}ifelse 2 ge{1 dict dup/NumCopies") :
151                try :
152                    number = int(previousline.strip()[2:])
153                except :
154                    pass
155                else :
156                    if number > self.pages[pagecount]["copies"] :
157                        self.pages[pagecount]["copies"] = number
158            elif line.startswith("/#copies ") :
159                try :
160                    number = int(line.strip().split()[1])
161                except :     
162                    pass
163                else :   
164                    if number > self.pages[pagecount]["copies"] :
165                        self.pages[pagecount]["copies"] = number
166            previousline = line
167           
168        # extract max number of copies to please the ghostscript parser, just   
169        # in case we will use it later
170        self.copies = max([ v["copies"] for (k, v) in self.pages.items() ])
171       
172        # now apply the number of copies to each page
173        for pnum in range(1, pagecount + 1) :
174            page = self.pages.get(pnum, self.pages.get(1, { "copies" : 1 }))
175            copies = page["copies"]
176            pagecount += (copies - 1)
177            self.logdebug("%s * page #%s" % (copies, pnum))
178        self.logdebug("Internal parser said : %s pages" % pagecount)
179        return (pagecount, notrust)
180       
181    def getJobSize(self) :   
182        """Count pages in PostScript document."""
183        self.copies = 1
184        (nbpages, notrust) = self.natively()
185        newnbpages = nbpages
186        if notrust :
187            try :
188                newnbpages = self.throughGhostScript()
189            except pdlparser.PDLParserError, msg :
190                self.logdebug(msg)
191        return max(nbpages, newnbpages)   
192       
193def test() :       
194    """Test function."""
195    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
196        sys.argv.append("-")
197    totalsize = 0   
198    for arg in sys.argv[1:] :
199        if arg == "-" :
200            infile = sys.stdin
201            mustclose = 0
202        else :   
203            infile = open(arg, "rb")
204            mustclose = 1
205        try :
206            parser = Parser(infile, debug=1)
207            totalsize += parser.getJobSize()
208        except pdlparser.PDLParserError, msg :   
209            sys.stderr.write("ERROR: %s\n" % msg)
210            sys.stderr.flush()
211        if mustclose :   
212            infile.close()
213    print "%s" % totalsize
214   
215if __name__ == "__main__" :   
216    test()
Note: See TracBrowser for help on using the browser.