root / pkpgcounter / trunk / pkpgpdls / newpcl345.py @ 395

Revision 395, 16.6 kB (checked in by jerome, 18 years ago)

Improved the new pcl345 parser. It correctly parses the testsuite, but doesn't
work with some other documents someone sent to me...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
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 PCL3/4/5 documents."""
25
26import sys
27import os
28import mmap
29from struct import unpack
30
31import pdlparser
32import pjl
33
34FORMFEED = chr(12)
35ESCAPE = chr(27)
36
37class Parser(pdlparser.PDLParser) :
38    """A parser for PCL3, PCL4, PCL5 documents."""
39    totiffcommand = 'pcl6 -sDEVICE=pdfwrite -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -sOutputFile=- - | gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r%(dpi)i -sOutputFile="%(fname)s" -'
40    mediasizes = {  # ESC&l####A
41                    0 : "Default",
42                    1 : "Executive",
43                    2 : "Letter",
44                    3 : "Legal",
45                    6 : "Ledger", 
46                    25 : "A5",
47                    26 : "A4",
48                    27 : "A3",
49                    45 : "JB5",
50                    46 : "JB4",
51                    71 : "HagakiPostcard",
52                    72 : "OufukuHagakiPostcard",
53                    80 : "MonarchEnvelope",
54                    81 : "COM10Envelope",
55                    90 : "DLEnvelope",
56                    91 : "C5Envelope",
57                    100 : "B5Envelope",
58                    101 : "Custom",
59                 }   
60                 
61    mediasources = { # ESC&l####H
62                     0 : "Default",
63                     1 : "Main",
64                     2 : "Manual",
65                     3 : "ManualEnvelope",
66                     4 : "Alternate",
67                     5 : "OptionalLarge",
68                     6 : "EnvelopeFeeder",
69                     7 : "Auto",
70                     8 : "Tray1",
71                   }
72                   
73    orientations = { # ESC&l####O
74                     0 : "Portrait",
75                     1 : "Landscape",
76                     2 : "ReversePortrait",
77                     3 : "ReverseLandscape",
78                   }
79                   
80    mediatypes = { # ESC&l####M
81                     0 : "Plain",
82                     1 : "Bond",
83                     2 : "Special",
84                     3 : "Glossy",
85                     4 : "Transparent",
86                   }
87       
88    def isValid(self) :   
89        """Returns True if data is PCL3/4/5, else False."""
90        if self.firstblock.startswith("\033E\033") or \
91           (self.firstblock.startswith("\033*rbC") and (not self.lastblock[-3:] == "\f\033@")) or \
92           self.firstblock.startswith("\033%8\033") or \
93           (self.firstblock.find("\033%-12345X") != -1) or \
94           (self.firstblock.find("@PJL ENTER LANGUAGE=PCL\012\015\033") != -1) or \
95           (self.firstblock.startswith(chr(0xcd)+chr(0xca)) and self.firstblock.find("\033E\033")) :
96            self.logdebug("DEBUG: Input file is in the PCL3/4/5 format.")
97            return True
98        else :   
99            return False
100       
101    def setPageDict(self, pages, number, attribute, value) :
102        """Initializes a page dictionnary."""
103        dic = pages.setdefault(number, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : "", "duplex": 0})
104        dic[attribute] = value
105       
106    def readByte(self) :   
107        """Reads a byte from the input stream."""
108        tag = ord(self.minfile[self.pos])
109        #self.logdebug("%08x  ===>  %02x" % (self.pos, tag))
110        self.pos += 1
111        return tag
112       
113    def endPage(self) :   
114        """Handle the FF marker."""
115        self.logdebug("FORMFEED %i" % self.pagecount)
116        self.pagecount += 1
117       
118    def escPercent(self) :   
119        """Handles the ESC% sequence."""
120        if self.minfile[self.pos : self.pos+7] == r"-12345X" :
121            self.logdebug("Generic ESCAPE sequence at %08x" % self.pos)
122            self.pos += 7
123       
124    def handleTag(self, tagtable) :   
125        """Handles tags."""
126        tagtable[self.readByte()]()
127       
128    def escape(self) :   
129        """Handles the ESC character."""
130        #self.logdebug("ESCAPE")
131        self.handleTag(self.esctags)
132       
133    def escAmp(self) :   
134        """Handles the ESC& sequence."""
135        #self.logdebug("AMP")
136        self.handleTag(self.escamptags)
137       
138    def escStar(self) :   
139        """Handles the ESC* sequence."""
140        #self.logdebug("STAR")
141        self.handleTag(self.escstartags)
142       
143    def escLeftPar(self) :   
144        """Handles the ESC( sequence."""
145        #self.logdebug("LEFTPAR")
146        self.handleTag(self.escleftpartags)
147       
148    def escRightPar(self) :   
149        """Handles the ESC( sequence."""
150        #self.logdebug("RIGHTPAR")
151        self.handleTag(self.escrightpartags)
152       
153    def escE(self) :   
154        """Handles the ESCE sequence."""
155        #self.logdebug("RESET")
156        self.resets += 1
157       
158    def escAmpl(self) :   
159        """Handles the ESC&l sequence."""
160        while 1 :
161            (value, end) = self.getInteger()
162            if value is None :
163                return
164            if end in ('h', 'H') :
165                mediasource = self.mediasources.get(value, str(value))
166                self.mediasourcesvalues.append(mediasource)
167                self.logdebug("MEDIASOURCE %s" % mediasource)
168            elif end in ('a', 'A') :
169                mediasize = self.mediasizes.get(value, str(value))
170                self.mediasizesvalues.append(mediasize)
171                self.logdebug("MEDIASIZE %s" % mediasize)
172            elif end in ('o', 'O') :
173                orientation = self.orientations.get(value, str(value))
174                self.orientationsvalues.append(orientation)
175                self.logdebug("ORIENTATION %s" % orientation)
176            elif end in ('m', 'M') :
177                mediatype = self.mediatypes.get(value, str(value))
178                self.mediatypesvalues.append(mediatype)
179                self.logdebug("MEDIATYPE %s" % mediatype)
180            elif end == 'X' :
181                self.copies.append(value)
182                self.logdebug("COPIES %i" % value)
183               
184    def escAmpa(self) :   
185        """Handles the ESC&a sequence."""
186        while 1 :
187            (value, end) = self.getInteger()
188            if value is None :
189                return
190            if end == 'G' :   
191                self.logdebug("BACKSIDES %i" % value)
192                self.backsides.append(value)
193               
194    def escAmpb(self) :   
195        """Handles the ESC&b sequence."""
196        while 1 :
197            (value, end) = self.getInteger()
198            if value is None :
199                return
200            if end == 'W' :   
201                self.pos += value
202                #self.logdebug("SKIPTO %08x" % self.pos)
203               
204    def escAmpn(self) :   
205        """Handles the ESC&n sequence."""
206        while 1 :
207            (value, end) = self.getInteger()
208            if value is None :
209                return
210            if end == 'W' :   
211                self.pos += value
212                #self.logdebug("SKIPTO %08x" % self.pos)
213               
214    def escAmpp(self) :   
215        """Handles the ESC&p sequence."""
216        while 1 :
217            (value, end) = self.getInteger()
218            if value is None :
219                return
220            if end == 'X' :   
221                self.pos += value
222                #self.logdebug("SKIPTO %08x" % self.pos)
223               
224    def escAmpu(self) :   
225        """Handles the ESC&u sequence."""
226        while 1 :
227            (value, end) = self.getInteger()
228            if value is None :
229                return
230               
231    def escStarb(self) :   
232        """Handles the ESC*b sequence."""
233        while 1 :
234            (value, end) = self.getInteger()
235            if (end is None) and (value is None) :
236                return
237            if end in ('V', 'W', 'v', 'w') :   
238                self.pos += (value or 0)
239                #self.logdebug("SKIPTO %08x" % self.pos)
240               
241    def escStarcgilmv(self) :   
242        """Handles the ESC*c, ESC*g, ESC*i, ESC*l, ESC*m, ESC*v sequences."""
243        while 1 :
244            (value, end) = self.getInteger()
245            if value is None :
246                return
247            if end == 'W' :   
248                self.pos += value
249                #self.logdebug("SKIPTO %08x" % self.pos)
250               
251    def escStaro(self) :   
252        """Handles the ESC*o sequence."""
253        while 1 :
254            (value, end) = self.getInteger()
255            if value is None :
256                return
257               
258    def escStarp(self) :   
259        """Handles the ESC*p sequence."""
260        while 1 :
261            (value, end) = self.getInteger()
262            if value is None :
263                return
264               
265    def escStarr(self) :   
266        """Handles the ESC*r sequence."""
267        while 1 :
268            (value, end) = self.getInteger()
269            if value is None :
270                if end is None :
271                    return
272                elif end in ('B', 'C') :       
273                    #self.logdebug("EndGFX")
274                    if not self.startgfx :
275                        self.logdebug("EndGFX found before StartGFX, ignored.")
276                    else :   
277                        self.endgfx.append(1)
278            if end == 'A' and (0 <= value <= 3) :
279                #self.logdebug("StartGFX %i" % value)
280                self.startgfx.append(value)
281               
282    def escStart(self) :   
283        """Handles the ESC*t sequence."""
284        while 1 :
285            (value, end) = self.getInteger()
286            if value is None :
287                return
288       
289    def escRightorLeftParsf(self) :   
290        """Handles the ESC(s, ESC)s, ESC(f sequences."""
291        while 1 :
292            (value, end) = self.getInteger()
293            if value is None :
294                return
295            if end == 'W' :   
296                self.pos += value
297                #self.logdebug("SKIPTO %08x" % self.pos)
298               
299    def getInteger(self) :   
300        """Returns an integer value and the end character."""
301        sign = 1
302        value = None
303        while 1 :
304            char = chr(self.readByte())
305            if char in (ESCAPE, FORMFEED) :
306                self.pos -= 1 # Adjust position
307                return (None, None)
308            if char == '-' :
309                sign = -1
310            elif not char.isdigit() :
311                if value is not None :
312                    return (sign*value, char)
313                else :
314                    return (value, char)
315            else :   
316                value = ((value or 0) * 10) + int(char)   
317       
318    def getJobSize(self) :     
319        """Count pages in a PCL5 document.
320         
321           Should also work for PCL3 and PCL4 documents.
322           
323           Algorithm from pclcount
324           (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
325           published under the terms of the GNU General Public Licence v2.
326         
327           Backported from C to Python by Jerome Alet, then enhanced
328           with more PCL tags detected. I think all the necessary PCL tags
329           are recognized to correctly handle PCL5 files wrt their number
330           of pages. The documentation used for this was :
331         
332           HP PCL/PJL Reference Set
333           PCL5 Printer Language Technical Quick Reference Guide
334           http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
335        """
336        infileno = self.infile.fileno()
337        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
338        self.pagecount = 0
339        self.resets = 0
340        self.backsides = []
341        self.copies = []
342        self.mediasourcesvalues = []
343        self.mediasizesvalues = []
344        self.orientationsvalues = []
345        self.mediatypesvalues = []
346        self.startgfx = []
347        self.endgfx = []
348       
349        tags = [ lambda : None] * 256
350        tags[ord(FORMFEED)] = self.endPage
351        tags[ord(ESCAPE)] = self.escape
352       
353        self.esctags = [ lambda : None ] * 256
354        self.esctags[ord('%')] = self.escPercent
355        self.esctags[ord('*')] = self.escStar
356        self.esctags[ord('&')] = self.escAmp
357        self.esctags[ord('(')] = self.escLeftPar
358        self.esctags[ord(')')] = self.escRightPar
359        self.esctags[ord('E')] = self.escE
360       
361        self.escamptags = [lambda : None ] * 256
362        self.escamptags[ord('a')] = self.escAmpa
363        self.escamptags[ord('b')] = self.escAmpb
364        self.escamptags[ord('l')] = self.escAmpl
365        self.escamptags[ord('n')] = self.escAmpn
366        self.escamptags[ord('p')] = self.escAmpp
367        self.escamptags[ord('u')] = self.escAmpu
368       
369        self.escstartags = [ lambda : None ] * 256
370        self.escstartags[ord('b')] = self.escStarb
371        self.escstartags[ord('o')] = self.escStaro
372        self.escstartags[ord('p')] = self.escStarp
373        self.escstartags[ord('r')] = self.escStarr
374        self.escstartags[ord('t')] = self.escStart
375        self.escstartags[ord('c')] = self.escStarcgilmv
376        self.escstartags[ord('g')] = self.escStarcgilmv
377        self.escstartags[ord('i')] = self.escStarcgilmv
378        self.escstartags[ord('l')] = self.escStarcgilmv
379        self.escstartags[ord('m')] = self.escStarcgilmv
380        self.escstartags[ord('v')] = self.escStarcgilmv
381       
382        self.escleftpartags = [ lambda : None ] * 256
383        self.escleftpartags[ord('s')] = self.escRightorLeftParsf
384        self.escleftpartags[ord('f')] = self.escRightorLeftParsf
385       
386        self.escrightpartags = [ lambda : None ] * 256
387        self.escrightpartags[ord('s')] = self.escRightorLeftParsf
388       
389        self.pos = 0
390        try :
391            try :
392                while 1 :
393                    tags[self.readByte()]()
394            except IndexError : # EOF ?           
395                pass
396        finally :
397            self.minfile.close()
398       
399        self.logdebug("Pagecount : \t\t\t%i" % self.pagecount)
400        self.logdebug("Resets : \t\t\t%i" % self.resets)
401        self.logdebug("Copies : \t\t\t%s" % self.copies)
402        self.logdebug("NbCopiesMarks : \t\t%i" % len(self.copies))
403        self.logdebug("MediaTypes : \t\t\t%s" % self.mediatypesvalues)
404        self.logdebug("NbMediaTypes : \t\t\t%i" % len(self.mediatypesvalues))
405        self.logdebug("MediaSizes : \t\t\t%s" % self.mediasizesvalues)
406        self.logdebug("NbMediaSizes : \t\t\t%i" % len(self.mediasizesvalues))
407        self.logdebug("MediaSources : \t\t\t%s" % self.mediasourcesvalues)
408        nbmediasourcesdefault = len([m for m in self.mediasourcesvalues if m == 'Default'])
409        self.logdebug("MediaSourcesDefault : \t\t%i" % nbmediasourcesdefault)
410        self.logdebug("MediaSourcesNOTDefault : \t%i" % (len(self.mediasourcesvalues) - nbmediasourcesdefault))
411        self.logdebug("Orientations : \t\t\t%s" % self.orientationsvalues)
412        self.logdebug("NbOrientations : \t\t\t%i" % len(self.orientationsvalues))
413        self.logdebug("StartGfx : \t\t\t%s" % len(self.startgfx))
414        self.logdebug("EndGfx : \t\t\t%s" % len(self.endgfx))
415        self.logdebug("BackSides : \t\t\t%s" % self.backsides)
416        self.logdebug("NbBackSides : \t\t\t%i" % len(self.backsides))
417       
418        return self.pagecount or nbmediasourcesdefault
419       
420def test() :       
421    """Test function."""
422    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
423        sys.argv.append("-")
424    totalsize = 0   
425    for arg in sys.argv[1:] :
426        if arg == "-" :
427            infile = sys.stdin
428            mustclose = 0
429        else :   
430            infile = open(arg, "rb")
431            mustclose = 1
432        try :
433            parser = Parser(infile, debug=1)
434            totalsize += parser.getJobSize()
435        except pdlparser.PDLParserError, msg :   
436            sys.stderr.write("ERROR: %s\n" % msg)
437            sys.stderr.flush()
438        if mustclose :   
439            infile.close()
440    print "%s" % totalsize
441   
442if __name__ == "__main__" :   
443    test()
Note: See TracBrowser for help on using the browser.