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

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

Changed copyright years.

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