root / pkpgcounter / trunk / pkpgpdls / pcl345.py @ 252

Revision 252, 19.6 kB (checked in by jerome, 19 years ago)

Added a PJL parsing module to extract SET and DEFAULT statements.
Improved general readability.
Fixed some minor problems thanks to pychecker.

  • 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 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
24import sys
25import os
26import mmap
27
28import pdlparser
29
30class Parser(pdlparser.PDLParser) :
31    """A parser for PCL3, PCL4, PCL5 documents."""
32    mediasizes = {  # ESC&l####A
33                    0 : "Default",
34                    1 : "Executive",
35                    2 : "Letter",
36                    3 : "Legal",
37                    6 : "Ledger", 
38                    25 : "A5",
39                    26 : "A4",
40                    27 : "A3",
41                    45 : "JB5",
42                    46 : "JB4",
43                    71 : "HagakiPostcard",
44                    72 : "OufukuHagakiPostcard",
45                    80 : "MonarchEnvelope",
46                    81 : "COM10Envelope",
47                    90 : "DLEnvelope",
48                    91 : "C5Envelope",
49                    100 : "B5Envelope",
50                    101 : "Custom",
51                 }   
52                 
53    mediasources = { # ESC&l####H
54                     0 : "Default",
55                     1 : "Main",
56                     2 : "Manual",
57                     3 : "ManualEnvelope",
58                     4 : "Alternate",
59                     5 : "OptionalLarge",
60                     6 : "EnvelopeFeeder",
61                     7 : "Auto",
62                     8 : "Tray1",
63                   }
64                   
65    orientations = { # ESC&l####O
66                     0 : "Portrait",
67                     1 : "Landscape",
68                     2 : "ReversePortrait",
69                     3 : "ReverseLandscape",
70                   }
71                   
72    mediatypes = { # ESC&l####M
73                     0 : "Plain",
74                     1 : "Bond",
75                     2 : "Special",
76                     3 : "Glossy",
77                     4 : "Transparent",
78                   }
79       
80    def isValid(self) :   
81        """Returns 1 if data is PCL, else 0."""
82        if self.firstblock.startswith("\033E\033") or \
83           (self.firstblock.startswith("\033*rbC") and (not self.lastblock[-3:] == "\f\033@")) or \
84           self.firstblock.startswith("\033%8\033") or \
85           (self.firstblock.find("\033%-12345X") != -1) :
86            self.logdebug("DEBUG: Input file is in the PCL3/4/5 format.")
87            return 1
88        else :   
89            return 0
90       
91    def setPageDict(self, pages, number, attribute, value) :
92        """Initializes a page dictionnary."""
93        dic = pages.setdefault(number, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : []})
94        dic[attribute] = value
95       
96    def getJobSize(self) :     
97        """Count pages in a PCL5 document.
98         
99           Should also work for PCL3 and PCL4 documents.
100           
101           Algorithm from pclcount
102           (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
103           published under the terms of the GNU General Public Licence v2.
104         
105           Backported from C to Python by Jerome Alet, then enhanced
106           with more PCL tags detected. I think all the necessary PCL tags
107           are recognized to correctly handle PCL5 files wrt their number
108           of pages. The documentation used for this was :
109         
110           HP PCL/PJL Reference Set
111           PCL5 Printer Language Technical Quick Reference Guide
112           http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
113        """
114        infileno = self.infile.fileno()
115        minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
116        tagsends = { "&n" : "W", 
117                     "&b" : "W", 
118                     "*i" : "W", 
119                     "*l" : "W", 
120                     "*m" : "W", 
121                     "*v" : "W", 
122                     "*c" : "W", 
123                     "(f" : "W", 
124                     "(s" : "W", 
125                     ")s" : "W", 
126                     "&p" : "X", 
127                     # "&l" : "XHAOM",  # treated specially
128                     "&a" : "G", # TODO : 0 means next side, 1 front side, 2 back side
129                     "*g" : "W",
130                     "*r" : "sbABC",
131                     "*t" : "R",
132                     # "*b" : "VW", # treated specially because it occurs very often
133                   } 
134        pagecount = resets = ejects = backsides = startgfx = endgfx = 0
135        starb = ampl = ispcl3 = escstart = 0
136        mediasourcecount = mediasizecount = orientationcount = mediatypecount = 0
137        tag = None
138        endmark = chr(0x1b) + chr(0x0c) + chr(0x00) 
139        asciilimit = chr(0x80)
140        pages = {}
141        pos = 0
142        try :
143            while 1 :
144                char = minfile[pos] ; pos += 1
145                if char == "\014" :   
146                    pagecount += 1
147                elif char == "\033" :   
148                    if minfile[pos : pos+8] == r"%-12345X" :
149                        endpos = pos + 9
150                        while (minfile[endpos] not in endmark) and (minfile[endpos] < asciilimit) :
151                            endpos += 1
152                        self.setPageDict(pages, pagecount, "escaped", minfile[pos : endpos].replace('\r\n', '\n').split('\n'))
153                        self.logdebug("Escaped datas : [%s]" % repr(minfile[pos : endpos]))
154                        pos += (endpos - pos)
155                    else :
156                        starb = ampl = 0
157                        #
158                        #     <ESC>*b###y#m###v###w... -> PCL3 raster graphics
159                        #     <ESC>*b###W -> Start of a raster data row/block
160                        #     <ESC>*b###V -> Start of a raster data plane
161                        #     <ESC>*c###W -> Start of a user defined pattern
162                        #     <ESC>*i###W -> Start of a viewing illuminant block
163                        #     <ESC>*l###W -> Start of a color lookup table
164                        #     <ESC>*m###W -> Start of a download dither matrix block
165                        #     <ESC>*v###W -> Start of a configure image data block
166                        #     <ESC>*r1A -> Start Gfx
167                        #     <ESC>(s###W -> Start of a characters description block
168                        #     <ESC>)s###W -> Start of a fonts description block
169                        #     <ESC>(f###W -> Start of a symbol set block
170                        #     <ESC>&b###W -> Start of configuration data block
171                        #     <ESC>&l###X -> Number of copies for current page
172                        #     <ESC>&n###W -> Starts an alphanumeric string ID block
173                        #     <ESC>&p###X -> Start of a non printable characters block
174                        #     <ESC>&a2G -> Back side when duplex mode as generated by rastertohp
175                        #     <ESC>*g###W -> Needed for planes in PCL3 output
176                        #     <ESC>&l###H (or only 0 ?) -> Eject if NumPlanes > 1, as generated by rastertohp. Also defines mediasource
177                        #     <ESC>&l###A -> mediasize
178                        #     <ESC>&l###O -> orientation
179                        #     <ESC>&l###M -> mediatype
180                        #     <ESC>*t###R -> gfx resolution
181                        #
182                        tagstart = minfile[pos] ; pos += 1
183                        if tagstart in "E9=YZ" : # one byte PCL tag
184                            if tagstart == "E" :
185                                resets += 1
186                            continue             # skip to next tag
187                        tag = tagstart + minfile[pos] ; pos += 1
188                        if tag == "*b" : 
189                            starb = 1
190                            tagend = "VW"
191                        elif tag == "&l" :   
192                            ampl = 1
193                            tagend = "XHAOM"
194                        else :   
195                            try :
196                                tagend = tagsends[tag]
197                            except KeyError :   
198                                continue # Unsupported PCL tag
199                        # Now read the numeric argument
200                        size = 0
201                        while 1 :
202                            char = minfile[pos] ; pos += 1
203                            if not char.isdigit() :
204                                break
205                            size = (size * 10) + int(char)   
206                        if char in tagend :   
207                            if tag == "&l" :
208                                if char == "X" : 
209                                    self.setPageDict(pages, pagecount, "copies", size)
210                                elif char == "H" :
211                                    self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
212                                    mediasourcecount += 1
213                                    ejects += 1 
214                                elif char == "A" :
215                                    self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
216                                    mediasizecount += 1
217                                elif char == "O" :
218                                    self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
219                                    orientationcount += 1
220                                elif char == "M" :
221                                    self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
222                                    mediatypecount += 1
223                            elif tag == "*r" :
224                                # Special tests for PCL3
225                                if (char == "s") and size :
226                                    while 1 :
227                                        char = minfile[pos] ; pos += 1
228                                        if char == "A" :
229                                            break
230                                elif (char == "b") and (minfile[pos] == "C") and not size :
231                                    ispcl3 = 1 # Certainely a PCL3 file
232                                startgfx += (char == "A") and (minfile[pos - 2] in ("0", "1", "2", "3")) # Start Gfx
233                                endgfx += (not size) and (char in ("C", "B")) # End Gfx
234                            elif tag == "*t" :   
235                                escstart += 1
236                            elif (tag == "&a") and (size == 2) :
237                                backsides += 1      # Back side in duplex mode
238                            else :   
239                                # we just ignore the block.
240                                if tag == "&n" : 
241                                    # we have to take care of the operation id byte
242                                    # which is before the string itself
243                                    size += 1
244                                pos += size   
245                else :                           
246                    if starb :
247                        # special handling of PCL3 in which
248                        # *b introduces combined ESCape sequences
249                        size = 0
250                        while 1 :
251                            char = minfile[pos] ; pos += 1
252                            if not char.isdigit() :
253                                break
254                            size = (size * 10) + int(char)   
255                        if char in ("w", "v") :   
256                            ispcl3 = 1  # certainely a PCL3 document
257                            pos += size - 1
258                        elif char in ("y", "m") :   
259                            ispcl3 = 1  # certainely a PCL3 document
260                            pos -= 1    # fix position : we were ahead
261                    elif ampl :       
262                        # special handling of PCL3 in which
263                        # &l introduces combined ESCape sequences
264                        size = 0
265                        while 1 :
266                            char = minfile[pos] ; pos += 1
267                            if not char.isdigit() :
268                                break
269                            size = (size * 10) + int(char)   
270                        if char in ("a", "o", "h", "m") :   
271                            ispcl3 = 1  # certainely a PCL3 document
272                            pos -= 1    # fix position : we were ahead
273                            if char == "h" :
274                                self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
275                                mediasourcecount += 1
276                            elif char == "a" :
277                                self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
278                                mediasizecount += 1
279                            elif char == "o" :
280                                self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
281                                orientationcount += 1
282                            elif char == "m" :
283                                self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
284                                mediatypecount += 1
285        except IndexError : # EOF ?
286            minfile.close() # reached EOF
287                           
288        # if pagecount is still 0, we will use the number
289        # of resets instead of the number of form feed characters.
290        # but the number of resets is always at least 2 with a valid
291        # pcl file : one at the very start and one at the very end
292        # of the job's data. So we substract 2 from the number of
293        # resets. And since on our test data we needed to substract
294        # 1 more, we finally substract 3, and will test several
295        # PCL files with this. If resets < 2, then the file is
296        # probably not a valid PCL file, so we use 0
297       
298        if self.debug :
299            sys.stderr.write("pagecount : %s\n" % pagecount)
300            sys.stderr.write("resets : %s\n" % resets)
301            sys.stderr.write("ejects : %s\n" % ejects)
302            sys.stderr.write("backsides : %s\n" % backsides)
303            sys.stderr.write("startgfx : %s\n" % startgfx)
304            sys.stderr.write("endgfx : %s\n" % endgfx)
305            sys.stderr.write("mediasourcecount : %s\n" % mediasourcecount)
306            sys.stderr.write("mediasizecount : %s\n" % mediasizecount)
307            sys.stderr.write("orientationcount : %s\n" % orientationcount)
308            sys.stderr.write("mediatypecount : %s\n" % mediatypecount)
309            sys.stderr.write("escstart : %s\n" % escstart)
310       
311#        if not pagecount :
312#            pagecount = (pagecount or ((resets - 3) * (resets > 2)))
313#        else :   
314#            # here we add counters for other ways new pages may have
315#            # been printed and ejected by the printer
316#            pagecount += ejects + backsides
317#       
318#        # now handle number of copies for each page (may differ).
319#        # in duplex mode, number of copies may be sent only once.
320#        for pnum in range(pagecount) :
321#            # if no number of copies defined, take the preceding one else the one set before any page else 1.
322#            page = pages.get(pnum, pages.get(pnum - 1, pages.get(0, { "copies" : 1 })))
323#            pagecount += (page["copies"] - 1)
324#           
325#        # in PCL3 files, there's one Start Gfx tag per page
326#        if ispcl3 :
327#            if endgfx == int(startgfx / 2) : # special case for cdj1600
328#                pagecount = endgfx
329#            elif startgfx :
330#                pagecount = startgfx
331#            elif endgfx :   
332#                pagecount = endgfx
333               
334           
335        if resets == ejects == mediasourcecount == mediasizecount == escstart == 1 :
336            pagecount = orientationcount
337        elif pagecount == mediasourcecount == escstart : 
338            pass        # should be OK.
339        elif (not startgfx) and (not endgfx) :
340            if ejects :
341                pagecount = ejects
342        elif startgfx == endgfx :   
343            pagecount = startgfx
344        elif startgfx == (endgfx - 1) :   
345            pagecount = startgfx
346        elif (startgfx == 1) and not endgfx :   
347            pass
348        else :   
349            pagecount = abs(startgfx - endgfx)
350           
351        pjlstatements = []
352        for pnum in range(pagecount) :
353            # if no number of copies defined, take the preceding one else the one set before any page else 1.
354            page = pages.get(pnum, pages.get(pnum - 1, pages.get(0, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : []})))
355            pjlstuff = page["escaped"]
356            if not pjlstuff :
357                pjlcopies = 1
358            else :   
359                pjlstatements.extend(pjlstuff)
360                newpjl = "\n".join(pjlstatements)
361                copiesstatement = newpjl.rfind("@PJL SET COPIES=")
362                qtystatement = newpjl.rfind("@PJL SET QTY=")
363                if copiesstatement > qtystatement :
364                    # we use the COPIES= statement
365                    try :
366                        pjlcopies = int(newpjl[copiesstatement+16:].split()[0].strip())
367                    except :   
368                        pjlcopies = 1
369                elif qtystatement > copiesstatement :   
370                    # we use the QTY= statement
371                    try :
372                        pjlcopies = int(newpjl[qtystatement+13:].split()[0].strip())
373                    except :   
374                        pjlcopies = 1
375                else :
376                    # both can't be equal unless they both equal -1 (not found)
377                    pjlcopies = 1
378            copies = pjlcopies * page["copies"]       
379            pagecount += (copies - 1)
380            self.logdebug("%s*%s*%s*%s*%s" % (copies, \
381                                              page["mediatype"], \
382                                              page["mediasize"], \
383                                              page["orientation"], \
384                                              page["mediasource"]))
385               
386        return pagecount
387       
388def test() :       
389    """Test function."""
390    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
391        sys.argv.append("-")
392    totalsize = 0   
393    for arg in sys.argv[1:] :
394        if arg == "-" :
395            infile = sys.stdin
396            mustclose = 0
397        else :   
398            infile = open(arg, "rb")
399            mustclose = 1
400        try :
401            parser = Parser(infile, debug=1)
402            totalsize += parser.getJobSize()
403        except pdlparser.PDLParserError, msg :   
404            sys.stderr.write("ERROR: %s\n" % msg)
405            sys.stderr.flush()
406        if mustclose :   
407            infile.close()
408    print "%s" % totalsize
409   
410if __name__ == "__main__" :   
411    test()
Note: See TracBrowser for help on using the browser.