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

Revision 491, 9.3 kB (checked in by jerome, 16 years ago)

Major code cleaning. Now clearer, although probably a bit slower since
a file can be opened several times.
Now universal line opening mode is only used when needed (PS, PDF and plain
text), and binary opening mode is used for the other formats.
This mean we will be able to remove mmap calls wherever possible, finally.

  • 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    openmode = "rU"
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        command = 'gs -sDEVICE=bbox -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET - 2>&1 | grep -c "%%HiResBoundingBox:" 2>/dev/null'
56        pagecount = 0
57        # we need to reopen the input file in binary mode again, just in case
58        # otherwise we might break the original file's contents.
59        infile = open(self.filename, "rb")
60        try :
61            child = popen2.Popen4(command)
62            try :
63                data = infile.read(pdlparser.MEGABYTE)   
64                while data :
65                    child.tochild.write(data)
66                    data = infile.read(pdlparser.MEGABYTE)
67                child.tochild.flush()
68                child.tochild.close()   
69            except (IOError, OSError), msg :   
70                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
71               
72            pagecount = 0
73            try :
74                pagecount = int(child.fromchild.readline().strip())
75            except (IOError, OSError, AttributeError, ValueError), msg :
76                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
77            child.fromchild.close()
78           
79            try :
80                child.wait()
81            except OSError, msg :   
82                raise pdlparser.PDLParserError, "Problem during analysis of Binary PostScript document : %s" % msg
83        finally :       
84            infile.close()
85        self.logdebug("GhostScript said : %s pages" % pagecount)   
86        return pagecount * self.copies
87       
88    def natively(self) :
89        """Count pages in a DSC compliant PostScript document."""
90        pagecount = 0
91        self.pages = { 0 : { "copies" : 1 } }
92        oldpagenum = None
93        previousline = ""
94        notrust = 0
95        prescribe = 0 # Kyocera's Prescribe commands
96        acrobatmarker = 0
97        pagescomment = None
98        for line in self.infile :
99            line = line.strip()
100            if (not prescribe) and line.startswith(r"%%BeginResource: procset pdf") \
101               and not acrobatmarker :
102                notrust = 1 # Let this stuff be managed by GhostScript, but we still extract number of copies
103            elif line.startswith(r"%ADOPrintSettings: L") :
104                acrobatmarker = 1
105            elif line.startswith("!R!") :
106                prescribe = 1
107            elif line.startswith(r"%%Pages: ") :
108                try :
109                    pagescomment = max(pagescomment or 0, int(line.split()[1]))
110                except ValueError :
111                    pass # strange, to say the least
112            elif line.startswith(r"%%Page: ") or line.startswith(r"(%%[Page: ") :
113                proceed = 1
114                try :
115                    # treats both "%%Page: x x" and "%%Page: (x-y) z" (probably N-up mode)
116                    newpagenum = int(line.split(']')[0].split()[-1])
117                except :   
118                    notinteger = 1 # It seems that sometimes it's not an integer but an EPS file name
119                else :   
120                    notinteger = 0
121                    if newpagenum == oldpagenum :
122                        proceed = 0
123                    else :
124                        oldpagenum = newpagenum
125                if proceed and not notinteger :       
126                    pagecount += 1
127                    self.pages[pagecount] = { "copies" : self.pages[pagecount-1]["copies"] }
128            elif line.startswith(r"%%Requirements: numcopies(") :   
129                try :
130                    number = int(line.split('(')[1].split(')')[0])
131                except :     
132                    pass
133                else :   
134                    if number > self.pages[pagecount]["copies"] :
135                        self.pages[pagecount]["copies"] = number
136            elif line.startswith(r"%%BeginNonPPDFeature: NumCopies ") :
137                # handle # of copies set by some Windows printer driver
138                try :
139                    number = int(line.split()[2])
140                except :     
141                    pass
142                else :   
143                    if number > self.pages[pagecount]["copies"] :
144                        self.pages[pagecount]["copies"] = number
145            elif line.startswith("1 dict dup /NumCopies ") :
146                # handle # of copies set by mozilla/kprinter
147                try :
148                    number = int(line.split()[4])
149                except :     
150                    pass
151                else :   
152                    if number > self.pages[pagecount]["copies"] :
153                        self.pages[pagecount]["copies"] = number
154            elif line.startswith("{ pop 1 dict dup /NumCopies ") :
155                # handle # of copies set by firefox/kprinter/cups (alternate syntax)
156                try :
157                    number = int(line.split()[6])
158                except :
159                    pass
160                else :
161                    if number > self.pages[pagecount]["copies"] :
162                        self.pages[pagecount]["copies"] = number
163            elif line.startswith("/languagelevel where{pop languagelevel}{1}ifelse 2 ge{1 dict dup/NumCopies") :
164                try :
165                    number = int(previousline[2:])
166                except :
167                    pass
168                else :
169                    if number > self.pages[pagecount]["copies"] :
170                        self.pages[pagecount]["copies"] = number
171            elif line.startswith("/#copies ") :
172                try :
173                    number = int(line.split()[1])
174                except :     
175                    pass
176                else :   
177                    if number > self.pages[pagecount]["copies"] :
178                        self.pages[pagecount]["copies"] = number
179            elif line.startswith(r"%RBINumCopies: ") :   
180                try :
181                    number = int(line.split()[1])
182                except :     
183                    pass
184                else :   
185                    if number > self.pages[pagecount]["copies"] :
186                        self.pages[pagecount]["copies"] = number
187            previousline = line
188           
189        # extract max number of copies to please the ghostscript parser, just   
190        # in case we will use it later
191        self.copies = max([ v["copies"] for (k, v) in self.pages.items() ])
192       
193        # now apply the number of copies to each page
194        if not pagecount and pagescomment :   
195            pagecount = pagescomment
196        for pnum in range(1, pagecount + 1) :
197            page = self.pages.get(pnum, self.pages.get(1, self.pages.get(0, { "copies" : 1 })))
198            copies = page["copies"]
199            pagecount += (copies - 1)
200            self.logdebug("%s * page #%s" % (copies, pnum))
201           
202        self.logdebug("Internal parser said : %s pages" % pagecount)
203        return (pagecount, notrust)
204       
205    def getJobSize(self) :   
206        """Count pages in PostScript document."""
207        self.copies = 1
208        (nbpages, notrust) = self.natively()
209        newnbpages = nbpages
210        if notrust or not nbpages :
211            try :
212                newnbpages = self.throughGhostScript()
213            except pdlparser.PDLParserError, msg :
214                self.logdebug(msg)
215        return max(nbpages, newnbpages)   
Note: See TracBrowser for help on using the browser.