root / pkpgcounter / trunk / pkpgpdls / oldpcl345.py @ 443

Revision 443, 25.3 kB (checked in by jerome, 16 years ago)

Changed copyright years.

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