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

Revision 428, 22.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 Id Revision
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
34NUL = chr(0x00)
35FORMFEED = chr(0x0c)
36ESCAPE = chr(0x1b)
37ASCIILIMIT = chr(0x80)
38
39class Parser(pdlparser.PDLParser) :
40    """A parser for PCL3, PCL4, PCL5 documents."""
41    totiffcommands = [ 'pcl6 -sDEVICE=pdfwrite -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -sOutputFile=- - | gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r%(dpi)i -sOutputFile="%(fname)s" -', 
42                       'pcl6 -sDEVICE=pswrite -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -sOutputFile=- - | gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r%(dpi)i -sOutputFile="%(fname)s" -',
43                     ]
44    mediasizes = {  # ESC&l####A
45                    0 : "Default",
46                    1 : "Executive",
47                    2 : "Letter",
48                    3 : "Legal",
49                    6 : "Ledger", 
50                    25 : "A5",
51                    26 : "A4",
52                    27 : "A3",
53                    45 : "JB5",
54                    46 : "JB4",
55                    71 : "HagakiPostcard",
56                    72 : "OufukuHagakiPostcard",
57                    80 : "MonarchEnvelope",
58                    81 : "COM10Envelope",
59                    90 : "DLEnvelope",
60                    91 : "C5Envelope",
61                    100 : "B5Envelope",
62                    101 : "Custom",
63                 }   
64                 
65    mediasources = { # ESC&l####H
66                     0 : "Default",
67                     1 : "Main",
68                     2 : "Manual",
69                     3 : "ManualEnvelope",
70                     4 : "Alternate",
71                     5 : "OptionalLarge",
72                     6 : "EnvelopeFeeder",
73                     7 : "Auto",
74                     8 : "Tray1",
75                   }
76                   
77    orientations = { # ESC&l####O
78                     0 : "Portrait",
79                     1 : "Landscape",
80                     2 : "ReversePortrait",
81                     3 : "ReverseLandscape",
82                   }
83                   
84    mediatypes = { # ESC&l####M
85                     0 : "Plain",
86                     1 : "Bond",
87                     2 : "Special",
88                     3 : "Glossy",
89                     4 : "Transparent",
90                   }
91       
92    def isValid(self) :   
93        """Returns True if data is PCL3/4/5, else False."""
94        if self.firstblock.startswith("\033E\033") or \
95           (self.firstblock.startswith("\033*rbC") and (not self.lastblock[-3:] == "\f\033@")) or \
96           self.firstblock.startswith("\033%8\033") or \
97           (self.firstblock.find("\033%-12345X") != -1) or \
98           (self.firstblock.find("@PJL ENTER LANGUAGE=PCL\012\015\033") != -1) or \
99           (self.firstblock.startswith(chr(0xcd)+chr(0xca)) and self.firstblock.find("\033E\033")) :
100            self.logdebug("DEBUG: Input file is in the PCL3/4/5 format.")
101            return True
102        else :   
103            return False
104       
105    def setPageDict(self, attribute, value) :
106        """Initializes a page dictionnary."""
107        dic = self.pages.setdefault(self.pagecount, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : "", "duplex": 0})
108        dic[attribute] = value
109       
110    def readByte(self) :   
111        """Reads a byte from the input stream."""
112        tag = ord(self.minfile[self.pos])
113        self.pos += 1
114        return tag
115       
116    def endPage(self) :   
117        """Handle the FF marker."""
118        #self.logdebug("FORMFEED %i at %08x" % (self.pagecount, self.pos-1))
119        if not self.hpgl2 :
120            # Increments page count only if we are not inside an HPGL2 block
121            self.pagecount += 1
122       
123    def escPercent(self) :   
124        """Handles the ESC% sequence."""
125        if self.minfile[self.pos : self.pos+7] == r"-12345X" :
126            #self.logdebug("Generic ESCAPE sequence at %08x" % self.pos)
127            self.pos += 7
128            buffer = []
129            quotes = 0
130            char = chr(self.readByte())
131            while ((char < ASCIILIMIT) or (quotes % 2)) and (char not in (FORMFEED, ESCAPE, NUL)) : 
132                buffer.append(char)
133                if char == '"' :
134                    quotes += 1
135                char = chr(self.readByte())
136            self.setPageDict("escaped", "".join(buffer))
137            #self.logdebug("ESCAPED : %s" % "".join(buffer))
138            self.pos -= 1   # Adjust position
139        else :   
140            while 1 :
141                (value, end) = self.getInteger()
142                if end == 'B' :
143                    self.enterHPGL2()
144                    while self.minfile[self.pos] != ESCAPE :
145                        self.pos += 1
146                    self.pos -= 1   
147                    return 
148                elif end == 'A' :   
149                    self.exitHPGL2()
150                    return
151                elif end is None :   
152                    return
153       
154    def enterHPGL2(self) :   
155        """Enters HPGL2 mode."""
156        #self.logdebug("ENTERHPGL2 %08x" % self.pos)
157        self.hpgl2 = True
158       
159    def exitHPGL2(self) :   
160        """Exits HPGL2 mode."""
161        #self.logdebug("EXITHPGL2 %08x" % self.pos)
162        self.hpgl2 = False
163       
164    def handleTag(self, tagtable) :   
165        """Handles tags."""
166        tagtable[self.readByte()]()
167       
168    def escape(self) :   
169        """Handles the ESC character."""
170        #self.logdebug("ESCAPE")
171        self.handleTag(self.esctags)
172       
173    def escAmp(self) :   
174        """Handles the ESC& sequence."""
175        #self.logdebug("AMP")
176        self.handleTag(self.escamptags)
177       
178    def escStar(self) :   
179        """Handles the ESC* sequence."""
180        #self.logdebug("STAR")
181        self.handleTag(self.escstartags)
182       
183    def escLeftPar(self) :   
184        """Handles the ESC( sequence."""
185        #self.logdebug("LEFTPAR")
186        self.handleTag(self.escleftpartags)
187       
188    def escRightPar(self) :   
189        """Handles the ESC( sequence."""
190        #self.logdebug("RIGHTPAR")
191        self.handleTag(self.escrightpartags)
192       
193    def escE(self) :   
194        """Handles the ESCE sequence."""
195        #self.logdebug("RESET")
196        self.resets += 1
197       
198    def escAmpl(self) :   
199        """Handles the ESC&l sequence."""
200        while 1 :
201            (value, end) = self.getInteger()
202            if value is None :
203                return
204            if end in ('h', 'H') :
205                mediasource = self.mediasources.get(value, str(value))
206                self.mediasourcesvalues.append(mediasource)
207                self.setPageDict("mediasource", mediasource)
208                #self.logdebug("MEDIASOURCE %s" % mediasource)
209            elif end in ('a', 'A') :
210                mediasize = self.mediasizes.get(value, str(value))
211                self.mediasizesvalues.append(mediasize)
212                self.setPageDict("mediasize", mediasize)
213                #self.logdebug("MEDIASIZE %s" % mediasize)
214            elif end in ('o', 'O') :
215                orientation = self.orientations.get(value, str(value))
216                self.orientationsvalues.append(orientation)
217                self.setPageDict("orientation", orientation)
218                #self.logdebug("ORIENTATION %s" % orientation)
219            elif end in ('m', 'M') :
220                mediatype = self.mediatypes.get(value, str(value))
221                self.mediatypesvalues.append(mediatype)
222                self.setPageDict("mediatype", mediatype)
223                #self.logdebug("MEDIATYPE %s" % mediatype)
224            elif end == 'X' :
225                self.copies.append(value)
226                self.setPageDict("copies", value)
227                #self.logdebug("COPIES %i" % value)
228               
229    def escAmpa(self) :   
230        """Handles the ESC&a sequence."""
231        while 1 :
232            (value, end) = self.getInteger()
233            if value is None :
234                return
235            if end == 'G' :   
236                #self.logdebug("BACKSIDES %i" % value)
237                self.backsides.append(value)
238                self.setPageDict("duplex", value)
239               
240    def escAmpp(self) :   
241        """Handles the ESC&p sequence."""
242        while 1 :
243            (value, end) = self.getInteger()
244            if value is None :
245                return
246            if end == 'X' :   
247                self.pos += value
248                #self.logdebug("SKIPTO %08x" % self.pos)
249               
250    def escStarb(self) :   
251        """Handles the ESC*b sequence."""
252        while 1 :
253            (value, end) = self.getInteger()
254            if (end is None) and (value is None) :
255                return
256            if end in ('V', 'W', 'v', 'w') :   
257                self.pos += (value or 0)
258                #self.logdebug("SKIPTO %08x" % self.pos)
259               
260    def escStarr(self) :   
261        """Handles the ESC*r sequence."""
262        while 1 :
263            (value, end) = self.getInteger()
264            if value is None :
265                if end is None :
266                    return
267                elif end in ('B', 'C') :       
268                    #self.logdebug("EndGFX")
269                    if self.startgfx :
270                        self.endgfx.append(1)
271                    else :   
272                        #self.logdebug("EndGFX found before StartGFX, ignored.")
273                        pass
274            if end == 'A' and (0 <= value <= 3) :
275                #self.logdebug("StartGFX %i" % value)
276                self.startgfx.append(value)
277               
278    def escStaroptAmpu(self) :   
279        """Handles the ESC*o ESC*p ESC*t and ESC&u sequences."""
280        while 1 :
281            (value, end) = self.getInteger()
282            if value is None :
283                return
284       
285    def escSkipSomethingW(self) :   
286        """Handles the ESC???###W sequences."""
287        while 1 :
288            (value, end) = self.getInteger()
289            if value is None :
290                return
291            if end == 'W' :   
292                self.pos += value
293                #self.logdebug("SKIPTO %08x" % self.pos)
294               
295    def getInteger(self) :   
296        """Returns an integer value and the end character."""
297        sign = 1
298        value = None
299        while 1 :
300            char = chr(self.readByte())
301            if char in (NUL, ESCAPE, FORMFEED, ASCIILIMIT) :
302                self.pos -= 1 # Adjust position
303                return (None, None)
304            if char == '-' :
305                sign = -1
306            elif not char.isdigit() :
307                if value is not None :
308                    return (sign*value, char)
309                else :
310                    return (value, char)
311            else :   
312                value = ((value or 0) * 10) + int(char)   
313       
314    def skipByte(self) :   
315        """Skips a byte."""
316        #self.logdebug("SKIPBYTE %08x ===> %02x" % (self.pos, ord(self.minfile[self.pos])))
317        self.pos += 1
318       
319    def handleImageRunner(self) :   
320        """Handles Canon ImageRunner tags."""
321        tag = self.readByte()
322        if tag == ord(self.imagerunnermarker1[-1]) :
323            oldpos = self.pos-2
324            codop = self.minfile[self.pos:self.pos+2]
325            length = unpack(">H", minfile[pos+6:pos+8])[0]
326            self.pos += 18
327            if codop != self.imagerunnermarker2 :
328                self.pos += length
329            self.logdebug("IMAGERUNNERTAG SKIP %i AT %08x" % (self.pos-oldpos, self.pos))
330        else :
331            self.pos -= 1 # Adjust position
332               
333    def getJobSize(self) :     
334        """Count pages in a PCL5 document.
335         
336           Should also work for PCL3 and PCL4 documents.
337           
338           Algorithm from pclcount
339           (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
340           published under the terms of the GNU General Public Licence v2.
341         
342           Backported from C to Python by Jerome Alet, then enhanced
343           with more PCL tags detected. I think all the necessary PCL tags
344           are recognized to correctly handle PCL5 files wrt their number
345           of pages. The documentation used for this was :
346         
347           HP PCL/PJL Reference Set
348           PCL5 Printer Language Technical Quick Reference Guide
349           http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
350        """
351        infileno = self.infile.fileno()
352        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
353        self.pages = {}
354        self.pagecount = 0
355        self.resets = 0
356        self.backsides = []
357        self.copies = []
358        self.mediasourcesvalues = []
359        self.mediasizesvalues = []
360        self.orientationsvalues = []
361        self.mediatypesvalues = []
362        self.startgfx = []
363        self.endgfx = []
364        self.hpgl2 = False
365        self.imagerunnermarker1 = chr(0xcd) + chr(0xca) # Markers for Canon ImageRunner printers
366        self.imagerunnermarker2 = chr(0x10) + chr(0x02)
367        self.isimagerunner = (minfile[:2] == self.imagerunnermarker1)
368       
369        tags = [ lambda : None] * 256
370        tags[ord(FORMFEED)] = self.endPage
371        tags[ord(ESCAPE)] = self.escape
372        tags[ord(ASCIILIMIT)] = self.skipByte
373        tags[ord(self.imagerunnermarker1[0])] = self.handleImageRunner
374       
375        self.esctags = [ lambda : None ] * 256
376        self.esctags[ord('%')] = self.escPercent
377        self.esctags[ord('*')] = self.escStar
378        self.esctags[ord('&')] = self.escAmp
379        self.esctags[ord('(')] = self.escLeftPar
380        self.esctags[ord(')')] = self.escRightPar
381        self.esctags[ord('E')] = self.escE
382       
383        self.escamptags = [lambda : None ] * 256
384        self.escamptags[ord('a')] = self.escAmpa
385        self.escamptags[ord('l')] = self.escAmpl
386        self.escamptags[ord('p')] = self.escAmpp
387        self.escamptags[ord('b')] = self.escSkipSomethingW
388        self.escamptags[ord('n')] = self.escSkipSomethingW
389        self.escamptags[ord('u')] = self.escStaroptAmpu
390       
391        self.escstartags = [ lambda : None ] * 256
392        self.escstartags[ord('b')] = self.escStarb
393        self.escstartags[ord('r')] = self.escStarr
394        self.escstartags[ord('o')] = self.escStaroptAmpu
395        self.escstartags[ord('p')] = self.escStaroptAmpu
396        self.escstartags[ord('t')] = self.escStaroptAmpu
397        self.escstartags[ord('c')] = self.escSkipSomethingW
398        self.escstartags[ord('g')] = self.escSkipSomethingW
399        self.escstartags[ord('i')] = self.escSkipSomethingW
400        self.escstartags[ord('l')] = self.escSkipSomethingW
401        self.escstartags[ord('m')] = self.escSkipSomethingW
402        self.escstartags[ord('v')] = self.escSkipSomethingW
403       
404        self.escleftpartags = [ lambda : None ] * 256
405        self.escleftpartags[ord('s')] = self.escSkipSomethingW
406        self.escleftpartags[ord('f')] = self.escSkipSomethingW
407       
408        self.escrightpartags = [ lambda : None ] * 256
409        self.escrightpartags[ord('s')] = self.escSkipSomethingW
410       
411        self.pos = 0
412        try :
413            try :
414                while 1 :
415                    tags[self.readByte()]()
416            except IndexError : # EOF ?           
417                pass
418        finally :
419            self.minfile.close()
420       
421        self.logdebug("Pagecount : \t\t\t%i" % self.pagecount)
422        self.logdebug("Resets : \t\t\t%i" % self.resets)
423        self.logdebug("Copies : \t\t\t%s" % self.copies)
424        self.logdebug("NbCopiesMarks : \t\t%i" % len(self.copies))
425        self.logdebug("MediaTypes : \t\t\t%s" % self.mediatypesvalues)
426        self.logdebug("NbMediaTypes : \t\t\t%i" % len(self.mediatypesvalues))
427        self.logdebug("MediaSizes : \t\t\t%s" % self.mediasizesvalues)
428        self.logdebug("NbMediaSizes : \t\t\t%i" % len(self.mediasizesvalues))
429        self.logdebug("MediaSources : \t\t\t%s" % self.mediasourcesvalues)
430        nbmediasourcesdefault = len([m for m in self.mediasourcesvalues if m == 'Default'])
431        self.logdebug("MediaSourcesDefault : \t\t%i" % nbmediasourcesdefault)
432        self.logdebug("MediaSourcesNOTDefault : \t%i" % (len(self.mediasourcesvalues) - nbmediasourcesdefault))
433        self.logdebug("Orientations : \t\t\t%s" % self.orientationsvalues)
434        nborientations = len(self.orientationsvalues)
435        self.logdebug("NbOrientations : \t\t\t%i" % nborientations)
436        self.logdebug("StartGfx : \t\t\t%s" % len(self.startgfx))
437        self.logdebug("EndGfx : \t\t\t%s" % len(self.endgfx))
438        self.logdebug("BackSides : \t\t\t%s" % self.backsides)
439        self.logdebug("NbBackSides : \t\t\t%i" % len(self.backsides))
440        self.logdebug("IsImageRunner : \t\t\t%s" % self.isimagerunner)
441       
442        if self.isimagerunner :
443            self.pagecount += 1      # ImageRunner adjustment
444        elif len(self.startgfx) == len(self.endgfx) == 0 :
445            if self.resets % 2 :
446                if nborientations == self.pagecount + 1 :
447                    self.logdebug("Adjusting PageCount : +1")
448                    self.pagecount += 1
449                elif nborientations == self.pagecount - 1 :
450                    self.logdebug("Adjusting PageCount : -1")
451                    self.pagecount -= 1
452                   
453        self.pagecount = self.pagecount or nbmediasourcesdefault
454       
455       
456        defaultpjlcopies = 1   
457        defaultduplexmode = "Simplex"
458        defaultpapersize = ""
459        oldpjlcopies = -1
460        oldduplexmode = ""
461        oldpapersize = ""
462        for pnum in range(self.pagecount) :
463            # if no number of copies defined, take the preceding one else the one set before any page else 1.
464            page = self.pages.get(pnum, self.pages.get(pnum - 1, self.pages.get(0, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait", "escaped" : "", "duplex": 0})))
465            pjlstuff = page["escaped"]
466            if pjlstuff :
467                pjlparser = pjl.PJLParser(pjlstuff)
468                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
469                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
470                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
471                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
472                if nbdefaultcopies > -1 :
473                    defaultpjlcopies = nbdefaultcopies
474                if nbdefaultqty > -1 :
475                    defaultpjlcopies = nbdefaultqty
476                if nbcopies > -1 :
477                    pjlcopies = nbcopies
478                elif nbqty > -1 :
479                    pjlcopies = nbqty
480                else :
481                    if oldpjlcopies == -1 :   
482                        pjlcopies = defaultpjlcopies
483                    else :   
484                        pjlcopies = oldpjlcopies   
485                if page["duplex"] :       
486                    duplexmode = "Duplex"
487                else :   
488                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
489                    if defaultdm :
490                        if defaultdm.upper() == "ON" :
491                            defaultduplexmode = "Duplex"
492                        else :   
493                            defaultduplexmode = "Simplex"
494                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
495                    if envdm :
496                        if envdm.upper() == "ON" :
497                            duplexmode = "Duplex"
498                        else :   
499                            duplexmode = "Simplex"
500                    else :       
501                        duplexmode = oldduplexmode or defaultduplexmode
502                defaultps = pjlparser.default_variables.get("PAPER", "")
503                if defaultps :
504                    defaultpapersize = defaultps
505                envps = pjlparser.environment_variables.get("PAPER", "")
506                if envps :
507                    papersize = envps
508                else :   
509                    if not oldpapersize :
510                        papersize = defaultpapersize
511                    else :   
512                        papersize = oldpapersize
513            else :       
514                if oldpjlcopies == -1 :
515                    pjlcopies = defaultpjlcopies
516                else :   
517                    pjlcopies = oldpjlcopies
518               
519                duplexmode = (page["duplex"] and "Duplex") or oldduplexmode or defaultduplexmode
520                if not oldpapersize :   
521                    papersize = defaultpapersize
522                else :   
523                    papersize = oldpapersize
524                papersize = oldpapersize or page["mediasize"]
525            if page["mediasize"] != "Default" :
526                papersize = page["mediasize"]
527            if not duplexmode :   
528                duplexmode = oldduplexmode or defaultduplexmode
529            oldpjlcopies = pjlcopies   
530            oldduplexmode = duplexmode
531            oldpapersize = papersize
532            copies = pjlcopies * page["copies"]       
533            self.pagecount += (copies - 1)
534            self.logdebug("%s*%s*%s*%s*%s*%s*BW" % (copies, \
535                                              page["mediatype"], \
536                                              papersize, \
537                                              page["orientation"], \
538                                              page["mediasource"], \
539                                              duplexmode))
540       
541        return self.pagecount
542       
543if __name__ == "__main__" :   
544    pdlparser.test(Parser)
Note: See TracBrowser for help on using the browser.