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

Revision 397, 18.8 kB (checked in by jerome, 18 years ago)

Fixed a problem with the ESC% sequence.

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