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

Revision 357, 25.5 kB (checked in by jerome, 18 years ago)

Added missing docstrings, thanks to pylint.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Id Rev
RevLine 
[193]1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
[191]3#
4# pkpgcounter : a generic Page Description Language parser
5#
[303]6# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
[191]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
[211]19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
[191]20#
21# $Id$
22#
[193]23
[357]24"""This modules implements a page counter for PCL3/4/5 documents."""
25
[193]26import sys
[202]27import os
[193]28import mmap
[305]29from struct import unpack
[193]30
[235]31import pdlparser
[253]32import pjl
[193]33
[220]34class Parser(pdlparser.PDLParser) :
[193]35    """A parser for PCL3, PCL4, PCL5 documents."""
36    mediasizes = {  # ESC&l####A
37                    0 : "Default",
38                    1 : "Executive",
39                    2 : "Letter",
40                    3 : "Legal",
41                    6 : "Ledger", 
42                    25 : "A5",
43                    26 : "A4",
44                    27 : "A3",
45                    45 : "JB5",
46                    46 : "JB4",
47                    71 : "HagakiPostcard",
48                    72 : "OufukuHagakiPostcard",
49                    80 : "MonarchEnvelope",
50                    81 : "COM10Envelope",
51                    90 : "DLEnvelope",
52                    91 : "C5Envelope",
53                    100 : "B5Envelope",
54                    101 : "Custom",
55                 }   
56                 
57    mediasources = { # ESC&l####H
58                     0 : "Default",
59                     1 : "Main",
60                     2 : "Manual",
61                     3 : "ManualEnvelope",
62                     4 : "Alternate",
63                     5 : "OptionalLarge",
64                     6 : "EnvelopeFeeder",
65                     7 : "Auto",
66                     8 : "Tray1",
67                   }
68                   
69    orientations = { # ESC&l####O
70                     0 : "Portrait",
71                     1 : "Landscape",
72                     2 : "ReversePortrait",
73                     3 : "ReverseLandscape",
74                   }
75                   
76    mediatypes = { # ESC&l####M
77                     0 : "Plain",
78                     1 : "Bond",
79                     2 : "Special",
80                     3 : "Glossy",
81                     4 : "Transparent",
82                   }
83       
[220]84    def isValid(self) :   
85        """Returns 1 if data is PCL, else 0."""
86        if self.firstblock.startswith("\033E\033") or \
87           (self.firstblock.startswith("\033*rbC") and (not self.lastblock[-3:] == "\f\033@")) or \
88           self.firstblock.startswith("\033%8\033") or \
[305]89           (self.firstblock.find("\033%-12345X") != -1) or \
[343]90           (self.firstblock.find("@PJL ENTER LANGUAGE=PCL\012\015\033") != -1) or \
[305]91           (self.firstblock.startswith(chr(0xcd)+chr(0xca)) and self.firstblock.find("\033E\033")) :
[252]92            self.logdebug("DEBUG: Input file is in the PCL3/4/5 format.")
[220]93            return 1
94        else :   
95            return 0
96       
[193]97    def setPageDict(self, pages, number, attribute, value) :
98        """Initializes a page dictionnary."""
[294]99        dic = pages.setdefault(number, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : "", "duplex": 0})
[252]100        dic[attribute] = value
[193]101       
102    def getJobSize(self) :     
103        """Count pages in a PCL5 document.
104         
105           Should also work for PCL3 and PCL4 documents.
106           
107           Algorithm from pclcount
108           (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
109           published under the terms of the GNU General Public Licence v2.
110         
111           Backported from C to Python by Jerome Alet, then enhanced
112           with more PCL tags detected. I think all the necessary PCL tags
113           are recognized to correctly handle PCL5 files wrt their number
114           of pages. The documentation used for this was :
115         
116           HP PCL/PJL Reference Set
117           PCL5 Printer Language Technical Quick Reference Guide
118           http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
119        """
120        infileno = self.infile.fileno()
121        minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
122        tagsends = { "&n" : "W", 
123                     "&b" : "W", 
124                     "*i" : "W", 
125                     "*l" : "W", 
126                     "*m" : "W", 
127                     "*v" : "W", 
128                     "*c" : "W", 
129                     "(f" : "W", 
130                     "(s" : "W", 
131                     ")s" : "W", 
132                     "&p" : "X", 
133                     # "&l" : "XHAOM",  # treated specially
134                     "&a" : "G", # TODO : 0 means next side, 1 front side, 2 back side
135                     "*g" : "W",
136                     "*r" : "sbABC",
137                     "*t" : "R",
138                     # "*b" : "VW", # treated specially because it occurs very often
139                   } 
[305]140        irmarker = chr(0xcd) + chr(0xca) # Marker for Canon ImageRunner printers
141        irmarker2 = chr(0x10) + chr(0x02)
142        wasirmarker = 0
143        hasirmarker = (minfile[:2] == (irmarker))
[193]144        pagecount = resets = ejects = backsides = startgfx = endgfx = 0
145        starb = ampl = ispcl3 = escstart = 0
146        mediasourcecount = mediasizecount = orientationcount = mediatypecount = 0
147        tag = None
[252]148        endmark = chr(0x1b) + chr(0x0c) + chr(0x00) 
149        asciilimit = chr(0x80)
[193]150        pages = {}
151        pos = 0
152        try :
[307]153            try :
154                while 1 :
155                    if hasirmarker and (minfile[pos:pos+2] == irmarker) :
156                        codop = minfile[pos+2:pos+4]
157                        # self.logdebug("Marker at 0x%08x     (%s)" % (pos, wasirmarker))
158                        length = unpack(">H", minfile[pos+8:pos+10])[0]
159                        pos += 20
160                        if codop != irmarker2 :
161                            pos += length
162                        wasirmarker = 1   
163                    else :       
164                        wasirmarker = 0
165                        char = minfile[pos] ; pos += 1
166                        if char == "\014" :   
167                            pagecount += 1
168                        elif char == "\033" :   
169                            starb = ampl = 0
170                            if minfile[pos : pos+8] == r"%-12345X" :
171                                endpos = pos + 9
172                                quotes = 0
173                                while (minfile[endpos] not in endmark) and \
174                                      ((minfile[endpos] < asciilimit) or (quotes % 2)) :
175                                    if minfile[endpos] == '"' :
176                                        quotes += 1
177                                    endpos += 1
178                                self.setPageDict(pages, pagecount, "escaped", minfile[pos : endpos])
179                                pos += (endpos - pos)
180                            else :
181                                #
182                                #     <ESC>*b###y#m###v###w... -> PCL3 raster graphics
183                                #     <ESC>*b###W -> Start of a raster data row/block
184                                #     <ESC>*b###V -> Start of a raster data plane
185                                #     <ESC>*c###W -> Start of a user defined pattern
186                                #     <ESC>*i###W -> Start of a viewing illuminant block
187                                #     <ESC>*l###W -> Start of a color lookup table
188                                #     <ESC>*m###W -> Start of a download dither matrix block
189                                #     <ESC>*v###W -> Start of a configure image data block
190                                #     <ESC>*r1A -> Start Gfx
191                                #     <ESC>(s###W -> Start of a characters description block
192                                #     <ESC>)s###W -> Start of a fonts description block
193                                #     <ESC>(f###W -> Start of a symbol set block
194                                #     <ESC>&b###W -> Start of configuration data block
195                                #     <ESC>&l###X -> Number of copies for current page
196                                #     <ESC>&n###W -> Starts an alphanumeric string ID block
197                                #     <ESC>&p###X -> Start of a non printable characters block
198                                #     <ESC>&a2G -> Back side when duplex mode as generated by rastertohp
199                                #     <ESC>*g###W -> Needed for planes in PCL3 output
200                                #     <ESC>&l###H (or only 0 ?) -> Eject if NumPlanes > 1, as generated by rastertohp. Also defines mediasource
201                                #     <ESC>&l###A -> mediasize
202                                #     <ESC>&l###O -> orientation
203                                #     <ESC>&l###M -> mediatype
204                                #     <ESC>*t###R -> gfx resolution
205                                #
206                                tagstart = minfile[pos] ; pos += 1
207                                if tagstart in "E9=YZ" : # one byte PCL tag
208                                    if tagstart == "E" :
209                                        resets += 1
210                                    continue             # skip to next tag
211                                tag = tagstart + minfile[pos] ; pos += 1
212                                if tag == "*b" : 
213                                    starb = 1
214                                    tagend = "VW"
215                                elif tag == "&l" :   
216                                    ampl = 1
217                                    tagend = "XHAOM"
218                                else :   
219                                    try :
220                                        tagend = tagsends[tag]
221                                    except KeyError :   
222                                        continue # Unsupported PCL tag
223                                # Now read the numeric argument
224                                size = 0
225                                while 1 :
226                                    char = minfile[pos] ; pos += 1
227                                    if not char.isdigit() :
228                                        break
229                                    size = (size * 10) + int(char)   
230                                if char in tagend :   
231                                    if tag == "&l" :
232                                        if char == "X" : 
233                                            self.setPageDict(pages, pagecount, "copies", size)
234                                        elif char == "H" :
235                                            self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
236                                            mediasourcecount += 1
237                                            ejects += 1 
238                                        elif char == "A" :
239                                            self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
240                                            mediasizecount += 1
241                                        elif char == "O" :
242                                            self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
243                                            orientationcount += 1
244                                        elif char == "M" :
245                                            self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
246                                            mediatypecount += 1
247                                    elif tag == "*r" :
248                                        # Special tests for PCL3
249                                        if (char == "s") and size :
250                                            while 1 :
251                                                char = minfile[pos] ; pos += 1
252                                                if char == "A" :
253                                                    break
254                                        elif (char == "b") and (minfile[pos] == "C") and not size :
255                                            ispcl3 = 1 # Certainely a PCL3 file
256                                        startgfx += (char == "A") and (minfile[pos - 2] in ("0", "1", "2", "3")) # Start Gfx
257                                        endgfx += (not size) and (char in ("C", "B")) # End Gfx
258                                    elif tag == "*t" :   
259                                        escstart += 1
260                                    elif (tag == "&a") and (size == 2) :
261                                        # We are on the backside, so mark current page as duplex
262                                        self.setPageDict(pages, pagecount, "duplex", 1)
263                                        backsides += 1      # Back side in duplex mode
264                                    else :   
265                                        # we just ignore the block.
266                                        if tag == "&n" : 
267                                            # we have to take care of the operation id byte
268                                            # which is before the string itself
269                                            size += 1
270                                        pos += size   
271                        else :                           
272                            if starb :
273                                # special handling of PCL3 in which
274                                # *b introduces combined ESCape sequences
275                                size = 0
276                                while 1 :
277                                    char = minfile[pos] ; pos += 1
278                                    if not char.isdigit() :
279                                        break
280                                    size = (size * 10) + int(char)   
281                                if char in ("w", "v") :   
282                                    ispcl3 = 1  # certainely a PCL3 document
283                                    pos += size - 1
284                                elif char in ("y", "m") :   
285                                    ispcl3 = 1  # certainely a PCL3 document
286                                    pos -= 1    # fix position : we were ahead
287                            elif ampl :       
288                                # special handling of PCL3 in which
289                                # &l introduces combined ESCape sequences
290                                size = 0
291                                while 1 :
292                                    char = minfile[pos] ; pos += 1
293                                    if not char.isdigit() :
294                                        break
295                                    size = (size * 10) + int(char)   
296                                if char in ("a", "o", "h", "m") :   
297                                    ispcl3 = 1  # certainely a PCL3 document
298                                    pos -= 1    # fix position : we were ahead
299                                    if char == "h" :
[305]300                                        self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
301                                        mediasourcecount += 1
[307]302                                    elif char == "a" :
[305]303                                        self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
304                                        mediasizecount += 1
[307]305                                    elif char == "o" :
[305]306                                        self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
307                                        orientationcount += 1
[307]308                                    elif char == "m" :
[305]309                                        self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
310                                        mediatypecount += 1
[307]311            except IndexError : # EOF ?
312                pass
313        finally :
314            minfile.close()
[193]315                           
316        # if pagecount is still 0, we will use the number
317        # of resets instead of the number of form feed characters.
318        # but the number of resets is always at least 2 with a valid
319        # pcl file : one at the very start and one at the very end
320        # of the job's data. So we substract 2 from the number of
321        # resets. And since on our test data we needed to substract
322        # 1 more, we finally substract 3, and will test several
323        # PCL files with this. If resets < 2, then the file is
324        # probably not a valid PCL file, so we use 0
325       
326        if self.debug :
327            sys.stderr.write("pagecount : %s\n" % pagecount)
328            sys.stderr.write("resets : %s\n" % resets)
329            sys.stderr.write("ejects : %s\n" % ejects)
330            sys.stderr.write("backsides : %s\n" % backsides)
331            sys.stderr.write("startgfx : %s\n" % startgfx)
332            sys.stderr.write("endgfx : %s\n" % endgfx)
333            sys.stderr.write("mediasourcecount : %s\n" % mediasourcecount)
334            sys.stderr.write("mediasizecount : %s\n" % mediasizecount)
335            sys.stderr.write("orientationcount : %s\n" % orientationcount)
336            sys.stderr.write("mediatypecount : %s\n" % mediatypecount)
337            sys.stderr.write("escstart : %s\n" % escstart)
[305]338            sys.stderr.write("hasirmarker : %s\n" % hasirmarker)
[193]339       
[305]340        if hasirmarker :
341            self.logdebug("Rule #20 (probably a Canon ImageRunner)")
342            pagecount += 1
343        elif (orientationcount == (pagecount - 1)) and (resets == 1) :
[285]344            if resets == ejects == startgfx == mediasourcecount == escstart == 1 :
345                self.logdebug("Rule #19")
346            else :   
347                self.logdebug("Rule #1")
348                pagecount -= 1
[265]349        elif pagecount and (pagecount == orientationcount) :
[267]350            self.logdebug("Rule #2")
[262]351        elif resets == ejects == mediasourcecount == mediasizecount == escstart == 1 :
[267]352            #if ((startgfx and endgfx) and (startgfx != endgfx)) or (startgfx == endgfx == 0) :
353            if (startgfx and endgfx) or (startgfx == endgfx == 0) :
354                self.logdebug("Rule #3")
[262]355                pagecount = orientationcount
[265]356            elif (endgfx and not startgfx) and (pagecount > orientationcount) :   
[267]357                self.logdebug("Rule #4")
[265]358                pagecount = orientationcount
[262]359            else :     
[267]360                self.logdebug("Rule #5")
[262]361                pagecount += 1
362        elif (ejects == mediasourcecount == orientationcount) and (startgfx == endgfx) :     
[267]363            if (resets == 2) and (orientationcount == (pagecount - 1)) and (orientationcount > 1) :
364                self.logdebug("Rule #6")
365                pagecount = orientationcount
[249]366        elif pagecount == mediasourcecount == escstart : 
[267]367            self.logdebug("Rule #7")
[255]368        elif resets == startgfx == endgfx == mediasizecount == orientationcount == escstart == 1 :     
[267]369            self.logdebug("Rule #8")
[255]370        elif resets == startgfx == endgfx == (pagecount - 1) :   
[267]371            self.logdebug("Rule #9")
[193]372        elif (not startgfx) and (not endgfx) :
[267]373            self.logdebug("Rule #10")
[256]374        elif (resets == 2) and (startgfx == endgfx) and (mediasourcecount == 1) :
[267]375            if orientationcount == (pagecount - 1) :
376                self.logdebug("Rule #11")
377                pagecount = orientationcount
[285]378            elif not pagecount :   
379                self.logdebug("Rule #17")
380                pagecount = ejects
[256]381        elif (resets == 1) and (startgfx == endgfx) and (mediasourcecount == 0) :
[265]382            if (startgfx > 1) and (startgfx != (pagecount - 1)) :
[267]383                self.logdebug("Rule #12")
[265]384                pagecount -= 1
[285]385            else :   
386                self.logdebug("Rule #18")
[193]387        elif startgfx == endgfx :   
[267]388            self.logdebug("Rule #13")
[193]389            pagecount = startgfx
390        elif startgfx == (endgfx - 1) :   
[267]391            self.logdebug("Rule #14")
[193]392            pagecount = startgfx
[210]393        elif (startgfx == 1) and not endgfx :   
[267]394            self.logdebug("Rule #15")
[210]395            pass
[193]396        else :   
[267]397            self.logdebug("Rule #16")
[193]398            pagecount = abs(startgfx - endgfx)
399           
[253]400        defaultpjlcopies = 1   
401        defaultduplexmode = "Simplex"
402        defaultpapersize = ""
403        oldpjlcopies = -1
404        oldduplexmode = ""
405        oldpapersize = ""
[252]406        for pnum in range(pagecount) :
407            # if no number of copies defined, take the preceding one else the one set before any page else 1.
[294]408            page = pages.get(pnum, pages.get(pnum - 1, pages.get(0, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : "", "duplex": 0})))
[252]409            pjlstuff = page["escaped"]
[253]410            if pjlstuff :
411                pjlparser = pjl.PJLParser(pjlstuff)
412                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
413                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
414                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
415                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
416                if nbdefaultcopies > -1 :
417                    defaultpjlcopies = nbdefaultcopies
418                if nbdefaultqty > -1 :
419                    defaultpjlcopies = nbdefaultqty
420                if nbcopies > -1 :
421                    pjlcopies = nbcopies
422                elif nbqty > -1 :
423                    pjlcopies = nbqty
[252]424                else :
[253]425                    if oldpjlcopies == -1 :   
426                        pjlcopies = defaultpjlcopies
427                    else :   
428                        pjlcopies = oldpjlcopies   
[294]429                if page["duplex"] :       
430                    duplexmode = "Duplex"
431                else :   
432                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
433                    if defaultdm :
434                        if defaultdm.upper() == "ON" :
435                            defaultduplexmode = "Duplex"
436                        else :   
437                            defaultduplexmode = "Simplex"
438                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
439                    if envdm :
440                        if envdm.upper() == "ON" :
441                            duplexmode = "Duplex"
442                        else :   
443                            duplexmode = "Simplex"
444                    else :       
445                        duplexmode = oldduplexmode or defaultduplexmode
[253]446                defaultps = pjlparser.default_variables.get("PAPER", "")
447                if defaultps :
448                    defaultpapersize = defaultps
449                envps = pjlparser.environment_variables.get("PAPER", "")
450                if envps :
451                    papersize = envps
452                else :   
453                    if not oldpapersize :
454                        papersize = defaultpapersize
455                    else :   
456                        papersize = oldpapersize
457            else :       
458                if oldpjlcopies == -1 :
459                    pjlcopies = defaultpjlcopies
460                else :   
461                    pjlcopies = oldpjlcopies
[294]462               
463                duplexmode = (page["duplex"] and "Duplex") or oldduplexmode or defaultduplexmode
[253]464                if not oldpapersize :   
465                    papersize = defaultpapersize
466                else :   
467                    papersize = oldpapersize
468                papersize = oldpapersize or page["mediasize"]
469            if page["mediasize"] != "Default" :
470                papersize = page["mediasize"]
471            if not duplexmode :   
472                duplexmode = oldduplexmode or defaultduplexmode
473            oldpjlcopies = pjlcopies   
474            oldduplexmode = duplexmode
475            oldpapersize = papersize
[252]476            copies = pjlcopies * page["copies"]       
477            pagecount += (copies - 1)
[253]478            self.logdebug("%s*%s*%s*%s*%s*%s*BW" % (copies, \
[252]479                                              page["mediatype"], \
[253]480                                              papersize, \
[252]481                                              page["orientation"], \
[253]482                                              page["mediasource"], \
483                                              duplexmode))
[193]484               
485        return pagecount
486       
487def test() :       
488    """Test function."""
[196]489    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
490        sys.argv.append("-")
491    totalsize = 0   
492    for arg in sys.argv[1:] :
493        if arg == "-" :
494            infile = sys.stdin
495            mustclose = 0
496        else :   
497            infile = open(arg, "rb")
498            mustclose = 1
499        try :
[220]500            parser = Parser(infile, debug=1)
[196]501            totalsize += parser.getJobSize()
[200]502        except pdlparser.PDLParserError, msg :   
[196]503            sys.stderr.write("ERROR: %s\n" % msg)
504            sys.stderr.flush()
505        if mustclose :   
506            infile.close()
507    print "%s" % totalsize
[193]508   
509if __name__ == "__main__" :   
510    test()
Note: See TracBrowser for help on using the browser.