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

Revision 305, 24.7 kB (checked in by jerome, 18 years ago)

Added preliminary support for Canon ImageRunner?'s "extension" to PCL.

  • 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            while 1 :
151                if hasirmarker and (minfile[pos:pos+2] == irmarker) :
152                    codop = minfile[pos+2:pos+4]
153                    # self.logdebug("Marker at 0x%08x     (%s)" % (pos, wasirmarker))
154                    length = unpack(">H", minfile[pos+8:pos+10])[0]
155                    pos += 20
156                    if codop != irmarker2 :
157                        pos += length
158                    wasirmarker = 1   
159                else :       
160                    wasirmarker = 0
161                    char = minfile[pos] ; pos += 1
162                    if char == "\014" :   
163                        pagecount += 1
164                    elif char == "\033" :   
165                        starb = ampl = 0
166                        if minfile[pos : pos+8] == r"%-12345X" :
167                            endpos = pos + 9
168                            quotes = 0
169                            while (minfile[endpos] not in endmark) and \
170                                  ((minfile[endpos] < asciilimit) or (quotes % 2)) :
171                                if minfile[endpos] == '"' :
172                                    quotes += 1
173                                endpos += 1
174                            self.setPageDict(pages, pagecount, "escaped", minfile[pos : endpos])
175                            pos += (endpos - pos)
176                        else :
177                            #
178                            #     <ESC>*b###y#m###v###w... -> PCL3 raster graphics
179                            #     <ESC>*b###W -> Start of a raster data row/block
180                            #     <ESC>*b###V -> Start of a raster data plane
181                            #     <ESC>*c###W -> Start of a user defined pattern
182                            #     <ESC>*i###W -> Start of a viewing illuminant block
183                            #     <ESC>*l###W -> Start of a color lookup table
184                            #     <ESC>*m###W -> Start of a download dither matrix block
185                            #     <ESC>*v###W -> Start of a configure image data block
186                            #     <ESC>*r1A -> Start Gfx
187                            #     <ESC>(s###W -> Start of a characters description block
188                            #     <ESC>)s###W -> Start of a fonts description block
189                            #     <ESC>(f###W -> Start of a symbol set block
190                            #     <ESC>&b###W -> Start of configuration data block
191                            #     <ESC>&l###X -> Number of copies for current page
192                            #     <ESC>&n###W -> Starts an alphanumeric string ID block
193                            #     <ESC>&p###X -> Start of a non printable characters block
194                            #     <ESC>&a2G -> Back side when duplex mode as generated by rastertohp
195                            #     <ESC>*g###W -> Needed for planes in PCL3 output
196                            #     <ESC>&l###H (or only 0 ?) -> Eject if NumPlanes > 1, as generated by rastertohp. Also defines mediasource
197                            #     <ESC>&l###A -> mediasize
198                            #     <ESC>&l###O -> orientation
199                            #     <ESC>&l###M -> mediatype
200                            #     <ESC>*t###R -> gfx resolution
201                            #
202                            tagstart = minfile[pos] ; pos += 1
203                            if tagstart in "E9=YZ" : # one byte PCL tag
204                                if tagstart == "E" :
205                                    resets += 1
206                                continue             # skip to next tag
207                            tag = tagstart + minfile[pos] ; pos += 1
208                            if tag == "*b" : 
209                                starb = 1
210                                tagend = "VW"
211                            elif tag == "&l" :   
212                                ampl = 1
213                                tagend = "XHAOM"
214                            else :   
215                                try :
216                                    tagend = tagsends[tag]
217                                except KeyError :   
218                                    continue # Unsupported PCL tag
219                            # Now read the numeric argument
220                            size = 0
221                            while 1 :
222                                char = minfile[pos] ; pos += 1
223                                if not char.isdigit() :
224                                    break
225                                size = (size * 10) + int(char)   
226                            if char in tagend :   
227                                if tag == "&l" :
228                                    if char == "X" : 
229                                        self.setPageDict(pages, pagecount, "copies", size)
230                                    elif char == "H" :
231                                        self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
232                                        mediasourcecount += 1
233                                        ejects += 1 
234                                    elif char == "A" :
235                                        self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
236                                        mediasizecount += 1
237                                    elif char == "O" :
238                                        self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
239                                        orientationcount += 1
240                                    elif char == "M" :
241                                        self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
242                                        mediatypecount += 1
243                                elif tag == "*r" :
244                                    # Special tests for PCL3
245                                    if (char == "s") and size :
246                                        while 1 :
247                                            char = minfile[pos] ; pos += 1
248                                            if char == "A" :
249                                                break
250                                    elif (char == "b") and (minfile[pos] == "C") and not size :
251                                        ispcl3 = 1 # Certainely a PCL3 file
252                                    startgfx += (char == "A") and (minfile[pos - 2] in ("0", "1", "2", "3")) # Start Gfx
253                                    endgfx += (not size) and (char in ("C", "B")) # End Gfx
254                                elif tag == "*t" :   
255                                    escstart += 1
256                                elif (tag == "&a") and (size == 2) :
257                                    # We are on the backside, so mark current page as duplex
258                                    self.setPageDict(pages, pagecount, "duplex", 1)
259                                    backsides += 1      # Back side in duplex mode
260                                else :   
261                                    # we just ignore the block.
262                                    if tag == "&n" : 
263                                        # we have to take care of the operation id byte
264                                        # which is before the string itself
265                                        size += 1
266                                    pos += size   
267                    else :                           
268                        if starb :
269                            # special handling of PCL3 in which
270                            # *b introduces combined ESCape sequences
271                            size = 0
272                            while 1 :
273                                char = minfile[pos] ; pos += 1
274                                if not char.isdigit() :
275                                    break
276                                size = (size * 10) + int(char)   
277                            if char in ("w", "v") :   
278                                ispcl3 = 1  # certainely a PCL3 document
279                                pos += size - 1
280                            elif char in ("y", "m") :   
281                                ispcl3 = 1  # certainely a PCL3 document
282                                pos -= 1    # fix position : we were ahead
283                        elif ampl :       
284                            # special handling of PCL3 in which
285                            # &l introduces combined ESCape sequences
286                            size = 0
287                            while 1 :
288                                char = minfile[pos] ; pos += 1
289                                if not char.isdigit() :
290                                    break
291                                size = (size * 10) + int(char)   
292                            if char in ("a", "o", "h", "m") :   
293                                ispcl3 = 1  # certainely a PCL3 document
294                                pos -= 1    # fix position : we were ahead
295                                if char == "h" :
296                                    self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
297                                    mediasourcecount += 1
298                                elif char == "a" :
299                                    self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
300                                    mediasizecount += 1
301                                elif char == "o" :
302                                    self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
303                                    orientationcount += 1
304                                elif char == "m" :
305                                    self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
306                                    mediatypecount += 1
307        except IndexError : # EOF ?
308            minfile.close() # reached EOF
309                           
310        # if pagecount is still 0, we will use the number
311        # of resets instead of the number of form feed characters.
312        # but the number of resets is always at least 2 with a valid
313        # pcl file : one at the very start and one at the very end
314        # of the job's data. So we substract 2 from the number of
315        # resets. And since on our test data we needed to substract
316        # 1 more, we finally substract 3, and will test several
317        # PCL files with this. If resets < 2, then the file is
318        # probably not a valid PCL file, so we use 0
319       
320        if self.debug :
321            sys.stderr.write("pagecount : %s\n" % pagecount)
322            sys.stderr.write("resets : %s\n" % resets)
323            sys.stderr.write("ejects : %s\n" % ejects)
324            sys.stderr.write("backsides : %s\n" % backsides)
325            sys.stderr.write("startgfx : %s\n" % startgfx)
326            sys.stderr.write("endgfx : %s\n" % endgfx)
327            sys.stderr.write("mediasourcecount : %s\n" % mediasourcecount)
328            sys.stderr.write("mediasizecount : %s\n" % mediasizecount)
329            sys.stderr.write("orientationcount : %s\n" % orientationcount)
330            sys.stderr.write("mediatypecount : %s\n" % mediatypecount)
331            sys.stderr.write("escstart : %s\n" % escstart)
332            sys.stderr.write("hasirmarker : %s\n" % hasirmarker)
333       
334        if hasirmarker :
335            self.logdebug("Rule #20 (probably a Canon ImageRunner)")
336            pagecount += 1
337        elif (orientationcount == (pagecount - 1)) and (resets == 1) :
338            if resets == ejects == startgfx == mediasourcecount == escstart == 1 :
339                self.logdebug("Rule #19")
340            else :   
341                self.logdebug("Rule #1")
342                pagecount -= 1
343        elif pagecount and (pagecount == orientationcount) :
344            self.logdebug("Rule #2")
345        elif resets == ejects == mediasourcecount == mediasizecount == escstart == 1 :
346            #if ((startgfx and endgfx) and (startgfx != endgfx)) or (startgfx == endgfx == 0) :
347            if (startgfx and endgfx) or (startgfx == endgfx == 0) :
348                self.logdebug("Rule #3")
349                pagecount = orientationcount
350            elif (endgfx and not startgfx) and (pagecount > orientationcount) :   
351                self.logdebug("Rule #4")
352                pagecount = orientationcount
353            else :     
354                self.logdebug("Rule #5")
355                pagecount += 1
356        elif (ejects == mediasourcecount == orientationcount) and (startgfx == endgfx) :     
357            if (resets == 2) and (orientationcount == (pagecount - 1)) and (orientationcount > 1) :
358                self.logdebug("Rule #6")
359                pagecount = orientationcount
360        elif pagecount == mediasourcecount == escstart : 
361            self.logdebug("Rule #7")
362        elif resets == startgfx == endgfx == mediasizecount == orientationcount == escstart == 1 :     
363            self.logdebug("Rule #8")
364        elif resets == startgfx == endgfx == (pagecount - 1) :   
365            self.logdebug("Rule #9")
366        elif (not startgfx) and (not endgfx) :
367            self.logdebug("Rule #10")
368        elif (resets == 2) and (startgfx == endgfx) and (mediasourcecount == 1) :
369            if orientationcount == (pagecount - 1) :
370                self.logdebug("Rule #11")
371                pagecount = orientationcount
372            elif not pagecount :   
373                self.logdebug("Rule #17")
374                pagecount = ejects
375        elif (resets == 1) and (startgfx == endgfx) and (mediasourcecount == 0) :
376            if (startgfx > 1) and (startgfx != (pagecount - 1)) :
377                self.logdebug("Rule #12")
378                pagecount -= 1
379            else :   
380                self.logdebug("Rule #18")
381        elif startgfx == endgfx :   
382            self.logdebug("Rule #13")
383            pagecount = startgfx
384        elif startgfx == (endgfx - 1) :   
385            self.logdebug("Rule #14")
386            pagecount = startgfx
387        elif (startgfx == 1) and not endgfx :   
388            self.logdebug("Rule #15")
389            pass
390        else :   
391            self.logdebug("Rule #16")
392            pagecount = abs(startgfx - endgfx)
393           
394        defaultpjlcopies = 1   
395        defaultduplexmode = "Simplex"
396        defaultpapersize = ""
397        oldpjlcopies = -1
398        oldduplexmode = ""
399        oldpapersize = ""
400        for pnum in range(pagecount) :
401            # if no number of copies defined, take the preceding one else the one set before any page else 1.
402            page = pages.get(pnum, pages.get(pnum - 1, pages.get(0, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : "", "duplex": 0})))
403            pjlstuff = page["escaped"]
404            if pjlstuff :
405                pjlparser = pjl.PJLParser(pjlstuff)
406                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
407                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
408                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
409                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
410                if nbdefaultcopies > -1 :
411                    defaultpjlcopies = nbdefaultcopies
412                if nbdefaultqty > -1 :
413                    defaultpjlcopies = nbdefaultqty
414                if nbcopies > -1 :
415                    pjlcopies = nbcopies
416                elif nbqty > -1 :
417                    pjlcopies = nbqty
418                else :
419                    if oldpjlcopies == -1 :   
420                        pjlcopies = defaultpjlcopies
421                    else :   
422                        pjlcopies = oldpjlcopies   
423                if page["duplex"] :       
424                    duplexmode = "Duplex"
425                else :   
426                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
427                    if defaultdm :
428                        if defaultdm.upper() == "ON" :
429                            defaultduplexmode = "Duplex"
430                        else :   
431                            defaultduplexmode = "Simplex"
432                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
433                    if envdm :
434                        if envdm.upper() == "ON" :
435                            duplexmode = "Duplex"
436                        else :   
437                            duplexmode = "Simplex"
438                    else :       
439                        duplexmode = oldduplexmode or defaultduplexmode
440                defaultps = pjlparser.default_variables.get("PAPER", "")
441                if defaultps :
442                    defaultpapersize = defaultps
443                envps = pjlparser.environment_variables.get("PAPER", "")
444                if envps :
445                    papersize = envps
446                else :   
447                    if not oldpapersize :
448                        papersize = defaultpapersize
449                    else :   
450                        papersize = oldpapersize
451            else :       
452                if oldpjlcopies == -1 :
453                    pjlcopies = defaultpjlcopies
454                else :   
455                    pjlcopies = oldpjlcopies
456               
457                duplexmode = (page["duplex"] and "Duplex") or oldduplexmode or defaultduplexmode
458                if not oldpapersize :   
459                    papersize = defaultpapersize
460                else :   
461                    papersize = oldpapersize
462                papersize = oldpapersize or page["mediasize"]
463            if page["mediasize"] != "Default" :
464                papersize = page["mediasize"]
465            if not duplexmode :   
466                duplexmode = oldduplexmode or defaultduplexmode
467            oldpjlcopies = pjlcopies   
468            oldduplexmode = duplexmode
469            oldpapersize = papersize
470            copies = pjlcopies * page["copies"]       
471            pagecount += (copies - 1)
472            self.logdebug("%s*%s*%s*%s*%s*%s*BW" % (copies, \
473                                              page["mediatype"], \
474                                              papersize, \
475                                              page["orientation"], \
476                                              page["mediasource"], \
477                                              duplexmode))
478               
479        return pagecount
480       
481def test() :       
482    """Test function."""
483    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
484        sys.argv.append("-")
485    totalsize = 0   
486    for arg in sys.argv[1:] :
487        if arg == "-" :
488            infile = sys.stdin
489            mustclose = 0
490        else :   
491            infile = open(arg, "rb")
492            mustclose = 1
493        try :
494            parser = Parser(infile, debug=1)
495            totalsize += parser.getJobSize()
496        except pdlparser.PDLParserError, msg :   
497            sys.stderr.write("ERROR: %s\n" % msg)
498            sys.stderr.flush()
499        if mustclose :   
500            infile.close()
501    print "%s" % totalsize
502   
503if __name__ == "__main__" :   
504    test()
Note: See TracBrowser for help on using the browser.