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

Revision 491, 23.8 kB (checked in by jerome, 16 years ago)

Major code cleaning. Now clearer, although probably a bit slower since
a file can be opened several times.
Now universal line opening mode is only used when needed (PS, PDF and plain
text), and binary opening mode is used for the other formats.
This mean we will be able to remove mmap calls wherever possible, finally.

  • 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 3 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, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22
23"""This modules implements a page counter for PCL3/4/5 documents."""
24
25import sys
26import os
27import mmap
28from struct import unpack
29
30import pdlparser
31import pjl
32
33NUL = chr(0x00)
34LINEFEED = chr(0x0a)
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") != -1)) :
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, { "linescount" : 1,
108                                                      "copies" : 1, 
109                                                      "mediasource" : "Main", 
110                                                      "mediasize" : "Default", 
111                                                      "mediatype" : "Plain", 
112                                                      "orientation" : "Portrait", 
113                                                      "escaped" : "", 
114                                                      "duplex": 0 })
115        dic[attribute] = value
116       
117    def readByte(self) :   
118        """Reads a byte from the input stream."""
119        tag = ord(self.minfile[self.pos])
120        self.pos += 1
121        return tag
122       
123    def endPage(self) :   
124        """Handle the FF marker."""
125        #self.logdebug("FORMFEED %i at %08x" % (self.pagecount, self.pos-1))
126        if not self.hpgl2 :
127            # Increments page count only if we are not inside an HPGL2 block
128            self.pagecount += 1
129       
130    def escPercent(self) :   
131        """Handles the ESC% sequence."""
132        if self.minfile[self.pos : self.pos+7] == r"-12345X" :
133            #self.logdebug("Generic ESCAPE sequence at %08x" % self.pos)
134            self.pos += 7
135            buffer = []
136            quotes = 0
137            char = chr(self.readByte())
138            while ((char < ASCIILIMIT) or (quotes % 2)) and (char not in (FORMFEED, ESCAPE, NUL)) : 
139                buffer.append(char)
140                if char == '"' :
141                    quotes += 1
142                char = chr(self.readByte())
143            self.setPageDict("escaped", "".join(buffer))
144            #self.logdebug("ESCAPED : %s" % "".join(buffer))
145            self.pos -= 1   # Adjust position
146        else :   
147            while 1 :
148                (value, end) = self.getInteger()
149                if end == 'B' :
150                    self.enterHPGL2()
151                    while self.minfile[self.pos] != ESCAPE :
152                        self.pos += 1
153                    self.pos -= 1   
154                    return 
155                elif end == 'A' :   
156                    self.exitHPGL2()
157                    return
158                elif end is None :   
159                    return
160       
161    def enterHPGL2(self) :   
162        """Enters HPGL2 mode."""
163        #self.logdebug("ENTERHPGL2 %08x" % self.pos)
164        self.hpgl2 = True
165       
166    def exitHPGL2(self) :   
167        """Exits HPGL2 mode."""
168        #self.logdebug("EXITHPGL2 %08x" % self.pos)
169        self.hpgl2 = False
170       
171    def handleTag(self, tagtable) :   
172        """Handles tags."""
173        tagtable[self.readByte()]()
174       
175    def escape(self) :   
176        """Handles the ESC character."""
177        #self.logdebug("ESCAPE")
178        self.handleTag(self.esctags)
179       
180    def escAmp(self) :   
181        """Handles the ESC& sequence."""
182        #self.logdebug("AMP")
183        self.handleTag(self.escamptags)
184       
185    def escStar(self) :   
186        """Handles the ESC* sequence."""
187        #self.logdebug("STAR")
188        self.handleTag(self.escstartags)
189       
190    def escLeftPar(self) :   
191        """Handles the ESC( sequence."""
192        #self.logdebug("LEFTPAR")
193        self.handleTag(self.escleftpartags)
194       
195    def escRightPar(self) :   
196        """Handles the ESC( sequence."""
197        #self.logdebug("RIGHTPAR")
198        self.handleTag(self.escrightpartags)
199       
200    def escE(self) :   
201        """Handles the ESCE sequence."""
202        #self.logdebug("RESET")
203        self.resets += 1
204       
205    def escAmpl(self) :   
206        """Handles the ESC&l sequence."""
207        while 1 :
208            (value, end) = self.getInteger()
209            if value is None :
210                return
211            if end in ('h', 'H') :
212                mediasource = self.mediasources.get(value, str(value))
213                self.mediasourcesvalues.append(mediasource)
214                self.setPageDict("mediasource", mediasource)
215                #self.logdebug("MEDIASOURCE %s" % mediasource)
216            elif end in ('a', 'A') :
217                mediasize = self.mediasizes.get(value, str(value))
218                self.mediasizesvalues.append(mediasize)
219                self.setPageDict("mediasize", mediasize)
220                #self.logdebug("MEDIASIZE %s" % mediasize)
221            elif end in ('o', 'O') :
222                orientation = self.orientations.get(value, str(value))
223                self.orientationsvalues.append(orientation)
224                self.setPageDict("orientation", orientation)
225                #self.logdebug("ORIENTATION %s" % orientation)
226            elif end in ('m', 'M') :
227                mediatype = self.mediatypes.get(value, str(value))
228                self.mediatypesvalues.append(mediatype)
229                self.setPageDict("mediatype", mediatype)
230                #self.logdebug("MEDIATYPE %s" % mediatype)
231            elif end == 'X' :
232                self.copies.append(value)
233                self.setPageDict("copies", value)
234                #self.logdebug("COPIES %i" % value)
235            elif end == 'F' :   
236                self.linesperpagevalues.append(value)
237                self.linesperpage = value
238                #self.logdebug("LINES PER PAGE : %i" % self.linesperpage)
239            #else :
240            #    self.logdebug("Unexpected end <%s> and value <%s>" % (end, value))
241               
242    def escAmpa(self) :   
243        """Handles the ESC&a sequence."""
244        while 1 :
245            (value, end) = self.getInteger()
246            if value is None :
247                return
248            if end == 'G' :   
249                #self.logdebug("BACKSIDES %i" % value)
250                self.backsides.append(value)
251                self.setPageDict("duplex", value)
252               
253    def escAmpp(self) :   
254        """Handles the ESC&p sequence."""
255        while 1 :
256            (value, end) = self.getInteger()
257            if value is None :
258                return
259            if end == 'X' :   
260                self.pos += value
261                #self.logdebug("SKIPTO %08x" % self.pos)
262               
263    def escStarb(self) :   
264        """Handles the ESC*b sequence."""
265        while 1 :
266            (value, end) = self.getInteger()
267            if (end is None) and (value is None) :
268                return
269            if end in ('V', 'W', 'v', 'w') :   
270                self.pos += (value or 0)
271                #self.logdebug("SKIPTO %08x" % self.pos)
272               
273    def escStarr(self) :   
274        """Handles the ESC*r sequence."""
275        while 1 :
276            (value, end) = self.getInteger()
277            if value is None :
278                if end is None :
279                    return
280                elif end in ('B', 'C') :       
281                    #self.logdebug("EndGFX")
282                    if self.startgfx :
283                        self.endgfx.append(1)
284                    else :   
285                        #self.logdebug("EndGFX found before StartGFX, ignored.")
286                        pass
287            if end == 'A' and (0 <= value <= 3) :
288                #self.logdebug("StartGFX %i" % value)
289                self.startgfx.append(value)
290               
291    def escStaroptAmpu(self) :   
292        """Handles the ESC*o ESC*p ESC*t and ESC&u sequences."""
293        while 1 :
294            (value, end) = self.getInteger()
295            if value is None :
296                return
297       
298    def escSkipSomethingW(self) :   
299        """Handles the ESC???###W sequences."""
300        while 1 :
301            (value, end) = self.getInteger()
302            if value is None :
303                return
304            if end == 'W' :   
305                self.pos += value
306                #self.logdebug("SKIPTO %08x" % self.pos)
307               
308    def newLine(self) :           
309        """Handles new lines markers."""
310        if not self.hpgl2 :
311            dic = self.pages.get(self.pagecount, None)
312            if dic is None :
313                self.setPageDict("linescount", 1)                             
314                dic = self.pages.get(self.pagecount)
315            nblines = dic["linescount"]   
316            self.setPageDict("linescount", nblines + 1)                             
317            if (self.linesperpage is not None) \
318               and (dic["linescount"] > self.linesperpage) :
319                self.pagecount += 1
320       
321    def getInteger(self) :   
322        """Returns an integer value and the end character."""
323        sign = 1
324        value = None
325        while 1 :
326            char = chr(self.readByte())
327            if char in (NUL, ESCAPE, FORMFEED, ASCIILIMIT) :
328                self.pos -= 1 # Adjust position
329                return (None, None)
330            if char == '-' :
331                sign = -1
332            elif not char.isdigit() :
333                if value is not None :
334                    return (sign*value, char)
335                else :
336                    return (value, char)
337            else :   
338                value = ((value or 0) * 10) + int(char)   
339       
340    def skipByte(self) :   
341        """Skips a byte."""
342        #self.logdebug("SKIPBYTE %08x ===> %02x" % (self.pos, ord(self.minfile[self.pos])))
343        self.pos += 1
344       
345    def handleImageRunner(self) :   
346        """Handles Canon ImageRunner tags."""
347        tag = self.readByte()
348        if tag == ord(self.imagerunnermarker1[-1]) :
349            oldpos = self.pos-2
350            codop = self.minfile[self.pos:self.pos+2]
351            length = unpack(">H", self.minfile[self.pos+6:self.pos+8])[0]
352            self.pos += 18
353            if codop != self.imagerunnermarker2 :
354                self.pos += length
355            self.logdebug("IMAGERUNNERTAG SKIP %i AT %08x" % (self.pos-oldpos, self.pos))
356        else :
357            self.pos -= 1 # Adjust position
358               
359    def getJobSize(self) :     
360        """Count pages in a PCL5 document.
361         
362           Should also work for PCL3 and PCL4 documents.
363           
364           Algorithm from pclcount
365           (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
366           published under the terms of the GNU General Public Licence v2.
367         
368           Backported from C to Python by Jerome Alet, then enhanced
369           with more PCL tags detected. I think all the necessary PCL tags
370           are recognized to correctly handle PCL5 files wrt their number
371           of pages. The documentation used for this was :
372         
373           HP PCL/PJL Reference Set
374           PCL5 Printer Language Technical Quick Reference Guide
375           http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
376        """
377        infileno = self.infile.fileno()
378        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
379        self.pages = {}
380        self.pagecount = 0
381        self.resets = 0
382        self.backsides = []
383        self.copies = []
384        self.mediasourcesvalues = []
385        self.mediasizesvalues = []
386        self.orientationsvalues = []
387        self.mediatypesvalues = []
388        self.linesperpagevalues = []
389        self.linesperpage = None
390        self.startgfx = []
391        self.endgfx = []
392        self.hpgl2 = False
393        self.imagerunnermarker1 = chr(0xcd) + chr(0xca) # Markers for Canon ImageRunner printers
394        self.imagerunnermarker2 = chr(0x10) + chr(0x02)
395        self.isimagerunner = (minfile[:2] == self.imagerunnermarker1)
396       
397        tags = [ lambda : None] * 256
398        tags[ord(LINEFEED)] = self.newLine
399        tags[ord(FORMFEED)] = self.endPage
400        tags[ord(ESCAPE)] = self.escape
401        tags[ord(ASCIILIMIT)] = self.skipByte
402        tags[ord(self.imagerunnermarker1[0])] = self.handleImageRunner
403       
404        self.esctags = [ lambda : None ] * 256
405        self.esctags[ord('%')] = self.escPercent
406        self.esctags[ord('*')] = self.escStar
407        self.esctags[ord('&')] = self.escAmp
408        self.esctags[ord('(')] = self.escLeftPar
409        self.esctags[ord(')')] = self.escRightPar
410        self.esctags[ord('E')] = self.escE
411       
412        self.escamptags = [lambda : None ] * 256
413        self.escamptags[ord('a')] = self.escAmpa
414        self.escamptags[ord('l')] = self.escAmpl
415        self.escamptags[ord('p')] = self.escAmpp
416        self.escamptags[ord('b')] = self.escSkipSomethingW
417        self.escamptags[ord('n')] = self.escSkipSomethingW
418        self.escamptags[ord('u')] = self.escStaroptAmpu
419       
420        self.escstartags = [ lambda : None ] * 256
421        self.escstartags[ord('b')] = self.escStarb
422        self.escstartags[ord('r')] = self.escStarr
423        self.escstartags[ord('o')] = self.escStaroptAmpu
424        self.escstartags[ord('p')] = self.escStaroptAmpu
425        self.escstartags[ord('t')] = self.escStaroptAmpu
426        self.escstartags[ord('c')] = self.escSkipSomethingW
427        self.escstartags[ord('g')] = self.escSkipSomethingW
428        self.escstartags[ord('i')] = self.escSkipSomethingW
429        self.escstartags[ord('l')] = self.escSkipSomethingW
430        self.escstartags[ord('m')] = self.escSkipSomethingW
431        self.escstartags[ord('v')] = self.escSkipSomethingW
432       
433        self.escleftpartags = [ lambda : None ] * 256
434        self.escleftpartags[ord('s')] = self.escSkipSomethingW
435        self.escleftpartags[ord('f')] = self.escSkipSomethingW
436       
437        self.escrightpartags = [ lambda : None ] * 256
438        self.escrightpartags[ord('s')] = self.escSkipSomethingW
439       
440        self.pos = 0
441        try :
442            try :
443                while 1 :
444                    tags[self.readByte()]()
445            except IndexError : # EOF ?           
446                pass
447        finally :
448            self.minfile.close()
449       
450        self.logdebug("Pagecount : \t\t\t%i" % self.pagecount)
451        self.logdebug("Resets : \t\t\t%i" % self.resets)
452        self.logdebug("Copies : \t\t\t%s" % self.copies)
453        self.logdebug("NbCopiesMarks : \t\t%i" % len(self.copies))
454        self.logdebug("MediaTypes : \t\t\t%s" % self.mediatypesvalues)
455        self.logdebug("NbMediaTypes : \t\t\t%i" % len(self.mediatypesvalues))
456        self.logdebug("MediaSizes : \t\t\t%s" % self.mediasizesvalues)
457        self.logdebug("NbMediaSizes : \t\t\t%i" % len(self.mediasizesvalues))
458        self.logdebug("MediaSources : \t\t\t%s" % self.mediasourcesvalues)
459        nbmediasourcesdefault = len([m for m in self.mediasourcesvalues if m == 'Default'])
460        self.logdebug("MediaSourcesDefault : \t\t%i" % nbmediasourcesdefault)
461        self.logdebug("MediaSourcesNOTDefault : \t%i" % (len(self.mediasourcesvalues) - nbmediasourcesdefault))
462        self.logdebug("Orientations : \t\t\t%s" % self.orientationsvalues)
463        nborientations = len(self.orientationsvalues)
464        self.logdebug("NbOrientations : \t\t\t%i" % nborientations)
465        self.logdebug("StartGfx : \t\t\t%s" % len(self.startgfx))
466        self.logdebug("EndGfx : \t\t\t%s" % len(self.endgfx))
467        self.logdebug("BackSides : \t\t\t%s" % self.backsides)
468        self.logdebug("NbBackSides : \t\t\t%i" % len(self.backsides))
469        self.logdebug("IsImageRunner : \t\t\t%s" % self.isimagerunner)
470       
471        if self.isimagerunner :
472            self.pagecount += 1      # ImageRunner adjustment
473        elif self.linesperpage is not None :   
474            self.pagecount += 1      # Adjusts for incomplete last page
475        elif len(self.startgfx) == len(self.endgfx) == 0 :
476            if self.resets % 2 :
477                if nborientations == self.pagecount + 1 :
478                    self.logdebug("Adjusting PageCount : +1")
479                    self.pagecount += 1
480                elif nborientations == self.pagecount - 1 :
481                    self.logdebug("Adjusting PageCount : -1")
482                    self.pagecount -= 1
483                   
484        self.pagecount = self.pagecount or nbmediasourcesdefault
485       
486       
487        defaultpjlcopies = 1   
488        defaultduplexmode = "Simplex"
489        defaultpapersize = ""
490        oldpjlcopies = -1
491        oldduplexmode = ""
492        oldpapersize = ""
493        for pnum in range(self.pagecount) :
494            # if no number of copies defined, take the preceding one else the one set before any page else 1.
495            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})))
496            pjlstuff = page["escaped"]
497            if pjlstuff :
498                pjlparser = pjl.PJLParser(pjlstuff)
499                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
500                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
501                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
502                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
503                if nbdefaultcopies > -1 :
504                    defaultpjlcopies = nbdefaultcopies
505                if nbdefaultqty > -1 :
506                    defaultpjlcopies = nbdefaultqty
507                if nbcopies > -1 :
508                    pjlcopies = nbcopies
509                elif nbqty > -1 :
510                    pjlcopies = nbqty
511                else :
512                    if oldpjlcopies == -1 :   
513                        pjlcopies = defaultpjlcopies
514                    else :   
515                        pjlcopies = oldpjlcopies   
516                if page["duplex"] :       
517                    duplexmode = "Duplex"
518                else :   
519                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
520                    if defaultdm :
521                        if defaultdm.upper() == "ON" :
522                            defaultduplexmode = "Duplex"
523                        else :   
524                            defaultduplexmode = "Simplex"
525                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
526                    if envdm :
527                        if envdm.upper() == "ON" :
528                            duplexmode = "Duplex"
529                        else :   
530                            duplexmode = "Simplex"
531                    else :       
532                        duplexmode = oldduplexmode or defaultduplexmode
533                defaultps = pjlparser.default_variables.get("PAPER", "")
534                if defaultps :
535                    defaultpapersize = defaultps
536                envps = pjlparser.environment_variables.get("PAPER", "")
537                if envps :
538                    papersize = envps
539                else :   
540                    if not oldpapersize :
541                        papersize = defaultpapersize
542                    else :   
543                        papersize = oldpapersize
544            else :       
545                if oldpjlcopies == -1 :
546                    pjlcopies = defaultpjlcopies
547                else :   
548                    pjlcopies = oldpjlcopies
549               
550                duplexmode = (page["duplex"] and "Duplex") or oldduplexmode or defaultduplexmode
551                if not oldpapersize :   
552                    papersize = defaultpapersize
553                else :   
554                    papersize = oldpapersize
555                papersize = oldpapersize or page["mediasize"]
556            if page["mediasize"] != "Default" :
557                papersize = page["mediasize"]
558            if not duplexmode :   
559                duplexmode = oldduplexmode or defaultduplexmode
560            oldpjlcopies = pjlcopies   
561            oldduplexmode = duplexmode
562            oldpapersize = papersize
563            copies = max(pjlcopies, page["copies"]) # Was : pjlcopies * page["copies"]
564            self.pagecount += (copies - 1)
565            self.logdebug("%s*%s*%s*%s*%s*%s*BW" % (copies, \
566                                              page["mediatype"], \
567                                              papersize, \
568                                              page["orientation"], \
569                                              page["mediasource"], \
570                                              duplexmode))
571       
572        return self.pagecount
Note: See TracBrowser for help on using the browser.