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

Revision 387, 25.7 kB (checked in by jerome, 18 years ago)

Code cleanups.

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