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

Revision 307, 25.4 kB (checked in by jerome, 18 years ago)

Ensure that the input file is closed in all cases.
Skip faulty tags (due to unavailable PCLXL Class 3.0 specification) in PCLXL parser

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