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

Revision 294, 23.1 kB (checked in by jerome, 18 years ago)

Fixed detection of duplex mode

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