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

Revision 463, 9.1 kB (checked in by jerome, 17 years ago)

Licensing terms changed to GNU GPL v3.0 or higher.
Removed old PCL3/4/5 parser which for a long time now wasn't used
anymore, and for which I was not the original copyright owner.
Version number bumped to 3.00alpha to reflect licensing changes.

  • 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, 2007 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"""This modules implements a page counter for PostScript documents."""
24
25import sys
26import os
27import tempfile
28import popen2
29
30import pdlparser
31import inkcoverage
32
33class Parser(pdlparser.PDLParser) :
34    """A parser for PostScript documents."""
35    totiffcommands = [ 'gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r%(dpi)i -sOutputFile="%(fname)s" -' ]
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            self.logdebug("DEBUG: Input file is in the PostScript format.")
47            return True
48        else :   
49            return False
50       
51    def throughGhostScript(self) :
52        """Get the count through GhostScript, useful for non-DSC compliant PS files."""
53        self.logdebug("Internal parser sucks, using GhostScript instead...")
54        self.infile.seek(0)
55        command = 'gs -sDEVICE=bbox -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET - 2>&1 | grep -c "%%HiResBoundingBox:" 2>/dev/null'
56        child = popen2.Popen4(command)
57        try :
58            data = self.infile.read(pdlparser.MEGABYTE)   
59            while data :
60                child.tochild.write(data)
61                data = self.infile.read(pdlparser.MEGABYTE)
62            child.tochild.flush()
63            child.tochild.close()   
64        except (IOError, OSError), msg :   
65            raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
66           
67        pagecount = 0
68        try :
69            pagecount = int(child.fromchild.readline().strip())
70        except (IOError, OSError, AttributeError, ValueError), msg :
71            raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
72        child.fromchild.close()
73       
74        try :
75            child.wait()
76        except OSError, msg :   
77            raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
78        self.logdebug("GhostScript said : %s pages" % pagecount)   
79        return pagecount * self.copies
80       
81    def natively(self) :
82        """Count pages in a DSC compliant PostScript document."""
83        self.infile.seek(0)
84        pagecount = 0
85        self.pages = { 0 : { "copies" : 1 } }
86        oldpagenum = None
87        previousline = ""
88        notrust = 0
89        prescribe = 0 # Kyocera's Prescribe commands
90        acrobatmarker = 0
91        pagescomment = None
92        for line in self.infile : 
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"%%Pages: ") :
101                try :
102                    pagescomment = max(pagescomment or 0, int(line.split()[1]))
103                except ValueError :
104                    pass # strange, to say the least
105            elif line.startswith(r"%%Page: ") or line.startswith(r"(%%[Page: ") :
106                proceed = 1
107                try :
108                    # treats both "%%Page: x x" and "%%Page: (x-y) z" (probably N-up mode)
109                    newpagenum = int(line.split(']')[0].split()[-1])
110                except :   
111                    notinteger = 1 # It seems that sometimes it's not an integer but an EPS file name
112                else :   
113                    notinteger = 0
114                    if newpagenum == oldpagenum :
115                        proceed = 0
116                    else :
117                        oldpagenum = newpagenum
118                if proceed and not notinteger :       
119                    pagecount += 1
120                    self.pages[pagecount] = { "copies" : self.pages[pagecount-1]["copies"] }
121            elif line.startswith(r"%%Requirements: numcopies(") :   
122                try :
123                    number = int(line.strip().split('(')[1].split(')')[0])
124                except :     
125                    pass
126                else :   
127                    if number > self.pages[pagecount]["copies"] :
128                        self.pages[pagecount]["copies"] = number
129            elif line.startswith(r"%%BeginNonPPDFeature: NumCopies ") :
130                # handle # of copies set by some Windows printer driver
131                try :
132                    number = int(line.strip().split()[2])
133                except :     
134                    pass
135                else :   
136                    if number > self.pages[pagecount]["copies"] :
137                        self.pages[pagecount]["copies"] = number
138            elif line.startswith("1 dict dup /NumCopies ") :
139                # handle # of copies set by mozilla/kprinter
140                try :
141                    number = int(line.strip().split()[4])
142                except :     
143                    pass
144                else :   
145                    if number > self.pages[pagecount]["copies"] :
146                        self.pages[pagecount]["copies"] = number
147            elif line.startswith("{ pop 1 dict dup /NumCopies ") :
148                # handle # of copies set by firefox/kprinter/cups (alternate syntax)
149                try :
150                    number = int(line.strip().split()[6])
151                except :
152                    pass
153                else :
154                    if number > self.pages[pagecount]["copies"] :
155                        self.pages[pagecount]["copies"] = number
156            elif line.startswith("/languagelevel where{pop languagelevel}{1}ifelse 2 ge{1 dict dup/NumCopies") :
157                try :
158                    number = int(previousline.strip()[2:])
159                except :
160                    pass
161                else :
162                    if number > self.pages[pagecount]["copies"] :
163                        self.pages[pagecount]["copies"] = number
164            elif line.startswith("/#copies ") :
165                try :
166                    number = int(line.strip().split()[1])
167                except :     
168                    pass
169                else :   
170                    if number > self.pages[pagecount]["copies"] :
171                        self.pages[pagecount]["copies"] = number
172            elif line.startswith(r"%RBINumCopies: ") :   
173                try :
174                    number = int(line.strip().split()[1])
175                except :     
176                    pass
177                else :   
178                    if number > self.pages[pagecount]["copies"] :
179                        self.pages[pagecount]["copies"] = number
180            previousline = line
181           
182        # extract max number of copies to please the ghostscript parser, just   
183        # in case we will use it later
184        self.copies = max([ v["copies"] for (k, v) in self.pages.items() ])
185       
186        # now apply the number of copies to each page
187        if not pagecount and pagescomment :   
188            pagecount = pagescomment
189        for pnum in range(1, pagecount + 1) :
190            page = self.pages.get(pnum, self.pages.get(1, self.pages.get(0, { "copies" : 1 })))
191            copies = page["copies"]
192            pagecount += (copies - 1)
193            self.logdebug("%s * page #%s" % (copies, pnum))
194           
195        self.logdebug("Internal parser said : %s pages" % pagecount)
196        return (pagecount, notrust)
197       
198    def getJobSize(self) :   
199        """Count pages in PostScript document."""
200        self.copies = 1
201        (nbpages, notrust) = self.natively()
202        newnbpages = nbpages
203        if notrust or not nbpages :
204            try :
205                newnbpages = self.throughGhostScript()
206            except pdlparser.PDLParserError, msg :
207                self.logdebug(msg)
208        return max(nbpages, newnbpages)   
209       
210if __name__ == "__main__" :   
211    pdlparser.test(Parser)
Note: See TracBrowser for help on using the browser.