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

Revision 428, 25.3 kB (checked in by jerome, 18 years ago)

Improved ink accounting by allowing several commands to be launch to convert to TIFF in case one of them fails.

  • 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    totiffcommands = [ 'pcl6 -sDEVICE=pdfwrite -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -sOutputFile=- - | gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r%(dpi)i -sOutputFile="%(fname)s" -', 
37                       'pcl6 -sDEVICE=pswrite -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.