root / pkpgcounter / trunk / pkpgpdls / pclxl.py @ 3513

Revision 3513, 31.1 kB (checked in by jerome, 14 years ago)

Fixes #50. Such files use undocumented values in PCLXL class 3.0
streams. The 0x46 tag was already recognized, now the (0x31, 0x90) tag
is handled, although we don't have any way to know for sure if it's
fixed for good or not...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Id Rev
Line 
1# -*- coding: utf-8 -*-
2#
3# pkpgcounter : a generic Page Description Language parser
4#
5# (c) 2003-2009 Jerome Alet <alet@librelogiciel.com>
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19# $Id$
20#
21
22"""This modules implements a page counter for PCLXL (aka PCL6) documents."""
23
24import sys
25import os
26import mmap
27from struct import unpack
28
29import pdlparser
30import pjl
31
32class Parser(pdlparser.PDLParser) :
33    """A parser for PCLXL (aka PCL6) documents."""
34    totiffcommands = [ 'pcl6 -sDEVICE=pdfwrite -r"%(dpi)i" -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -sOutputFile=- "%(infname)s" | gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r"%(dpi)i" -sOutputFile="%(outfname)s" -',
35                       'pcl6 -sDEVICE=pswrite -r"%(dpi)i" -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -sOutputFile=- "%(infname)s" | gs -sDEVICE=tiff24nc -dPARANOIDSAFER -dNOPAUSE -dBATCH -dQUIET -r"%(dpi)i" -sOutputFile="%(outfname)s" -',
36                     ]
37    required = [ "pcl6", "gs" ]
38    format = "PCLXL (aka PCL6)"
39    mediasizes = {
40                    0 : "Letter",
41                    1 : "Legal",
42                    2 : "A4",
43                    3 : "Executive",
44                    4 : "Ledger",
45                    5 : "A3",
46                    6 : "COM10Envelope",
47                    7 : "MonarchEnvelope",
48                    8 : "C5Envelope",
49                    9 : "DLEnvelope",
50                    10 : "JB4",
51                    11 : "JB5",
52                    12 : "B5Envelope",
53                    12 : "B5",
54                    14 : "JPostcard",
55                    15 : "JDoublePostcard",
56                    16 : "A5",
57                    17 : "A6",
58                    18 : "JB6",
59                    19 : "JIS8K",
60                    20 : "JIS16K",
61                    21 : "JISExec",
62                    96 : "Default",
63                 }
64
65    mediasources = {
66                     0 : "Default",
67                     1 : "Auto",
68                     2 : "Manual",
69                     3 : "MultiPurpose",
70                     4 : "UpperCassette",
71                     5 : "LowerCassette",
72                     6 : "EnvelopeTray",
73                     7 : "ThirdCassette",
74                   }
75
76    orientations = {
77                     0 : "Portrait",
78                     1 : "Landscape",
79                     2 : "ReversePortrait",
80                     3 : "ReverseLandscape",
81                     4 : "Default",
82                   }
83
84    def isValid(self) :
85        """Returns True if data is HP PCLXL aka PCL6, or Brother's' XL2HB, else False."""
86        if (((self.firstblock[:128].find("\033%-12345X") != -1) and \
87             (self.firstblock.find(" HP-PCL XL;") != -1) and \
88             ((self.firstblock.find("LANGUAGE=PCLXL") != -1) or \
89              (self.firstblock.find("LANGUAGE = PCLXL") != -1)))) \
90             or ((self.firstblock.startswith(chr(0xcd)+chr(0xca)) and (self.firstblock.find(" HP-PCL XL;") != -1))) :
91            return True
92        elif (self.firstblock[:128].find("\033%-12345X") != -1) \
93            and (self.firstblock.find("BROTHER XL2HB;") != -1) :
94            self.format = "XL2HB"
95            return True
96        else :
97            return False
98
99    def beginPage(self, nextpos) :
100        """Indicates the beginning of a new page, and extracts media information."""
101        # self.logdebug("BeginPage at %x" % nextpos)
102        self.pagecount += 1
103
104        # Default values
105        mediatypelabel = "Plain"
106        mediasourcelabel = "Main"
107        mediasizelabel = "Default"
108        orientationlabel = "Portrait"
109        duplexmode = None
110
111        # Now go upstream to decode media type, size, source, and orientation
112        # this saves time because we don't need a complete parser !
113        minfile = self.minfile
114        pos = nextpos - 2
115        while pos > 0 : # safety check : don't go back to far !
116            val = ord(minfile[pos])
117            if val in (0x44, 0x48, 0x41) : # if previous endPage or openDataSource or beginSession (first page)
118                break
119            if val == 0x26 :
120                mediasource = ord(minfile[pos - 2])
121                mediasourcelabel = self.mediasources.get(mediasource, str(mediasource))
122                pos -= 4
123            elif val == 0x25 :
124                while (pos > 0) and (ord(minfile[pos]) != 0xc0) :
125                    # we search the preceding ubyte tag
126                    pos -= 1
127                if pos > 0 :
128                    if ord(minfile[pos-1]) == 0xc8 :
129                        # if we found an ubyte_array then the media
130                        # size is completely spelled
131                        arraylength = ord(minfile[pos+1])
132                        mediasizelabel = minfile[pos+2:pos+2+arraylength].title()
133                        pos -= 1
134                    else :
135                        # if we just found an ubyte, then the media
136                        # size is known by its index
137                        mediasize = ord(minfile[pos+1])
138                        mediasizelabel = self.mediasizes.get(mediasize, str(mediasize))
139                    pos -= 1
140                    # self.logdebug("Media size : %s" % mediasizelabel)
141            elif val == 0x28 :
142                orientation = ord(minfile[pos - 2])
143                orientationlabel = self.orientations.get(orientation, str(orientation))
144                pos -= 4
145            elif val == 0x27 :
146                savepos = pos
147                pos -= 1
148                startpos = size = None
149                while pos > 0 : # safety check : don't go back to far !
150                    val = ord(minfile[pos])
151                    pos -= 1
152                    if val == 0xc8 :
153                        length = self.tags[ord(minfile[pos+2])] # will probably always be a byte or uint16
154                        if length == 1 :
155                            startpos = pos + 4
156                            size = unpack("B", self.minfile[pos+3:startpos])[0]
157                        elif length == 2 :
158                            startpos = pos + 5
159                            size = unpack(self.unpackShort, self.minfile[pos+3:startpos])[0]
160                        elif length == 4 :
161                            startpos = pos + 7
162                            size = unpack(self.unpackLong, self.minfile[pos+3:startpos])[0]
163                        else :
164                            raise pdlparser.PDLParserError, "Error on size at %s : %s" % (pos+2, length)
165                        break
166                try :
167                    mediatypelabel = minfile[startpos:startpos+size]
168                except TypeError :
169                    self.logdebug("PCL/XL parser problem at %i" % savepos)
170                # self.logdebug("Media type : %s" % mediatypelabel)
171            elif val == 0x34 :
172                duplexmode = "Simplex"
173                pos -= 2
174            elif val in (0x35, 0x36) :
175                duplexmode = "Duplex"
176                pos -= 2
177            # else : TODO : CUSTOM MEDIA SIZE AND UNIT !
178            else :
179                pos -= 1  # ignored
180        self.pages[self.pagecount] = { "copies" : 1,
181                                       "orientation" : orientationlabel,
182                                       "mediatype" : mediatypelabel,
183                                       "mediasize" : mediasizelabel,
184                                       "mediasource" : mediasourcelabel,
185                                       "duplex" : duplexmode,
186                                     }
187        return 0
188
189    def endPage(self, nextpos) :
190        """Indicates the end of a page."""
191        # self.logdebug("EndPage at %x" % nextpos)
192        pos3 = nextpos - 3
193        minfile = self.minfile
194        if minfile[pos3:nextpos-1] == self.setNumberOfCopies :
195            # The EndPage operator may be preceded by a PageCopies attribute
196            # So set number of copies for current page.
197            # From what I read in PCLXL documentation, the number
198            # of copies is an unsigned 16 bits integer
199            try :
200                nbcopies = unpack(self.unpackShort, minfile[pos3-2:pos3])[0]
201                # self.logdebug("Number of copies : %i" % nbcopies)
202                self.pages[self.pagecount]["copies"] = nbcopies
203            except KeyError :
204                self.logdebug("It looks like this PCLXL file is corrupted.")
205        return 0
206
207    def setColorSpace(self, nextpos) :
208        """Changes the color space."""
209        if self.minfile[nextpos-4:nextpos-1] == self.RGBColorSpace : # TODO : doesn't seem to handle all cases !
210            self.iscolor = True
211        return 0
212
213    def array_Generic(self, nextpos, size) :
214        """Handles all arrays."""
215        pos = nextpos
216        datatype = ord(self.minfile[pos])
217        pos += 1
218        length = self.tags[datatype]
219        if callable(length) :
220            length = length(pos)
221        try :
222            return 1 + length + size * unpack(self.unpackType[length], self.minfile[pos:pos+length])[0]
223        except KeyError :
224            raise pdlparser.PDLParserError, "Error on array size at %x" % nextpos
225
226    def array_8(self, nextpos) :
227        """Handles byte arrays."""
228        return self.array_Generic(nextpos, 1)
229
230    def array_16(self, nextpos) :
231        """Handles 16 bits arrays."""
232        return self.array_Generic(nextpos, 2)
233
234    def array_32(self, nextpos) :
235        """Handles 32 bits arrays and Canon ImageRunner tags."""
236        minfile = self.minfile
237        irtag = minfile[nextpos-1:nextpos+3]
238        if irtag in (self.imagerunnermarker1, self.imagerunnermarker2) :
239            # This is the beginning of a Canon ImageRunner tag
240            # self.logdebug("Canon ImageRunner tag at %x" % (nextpos-1))
241            codop = minfile[nextpos+1:nextpos+3]
242            length = unpack(">H", minfile[nextpos+7:nextpos+9])[0]
243            # self.logdebug("Canon ImageRunner block length=%04x" % length)
244            toskip = 19
245            if irtag != self.imagerunnermarker2 :
246                toskip += length
247            # self.logdebug("Canon ImageRunner skip until %x" % (nextpos+toskip))
248            return toskip
249        else :
250            # This is a normal PCLXL array
251            return self.array_Generic(nextpos, 4)
252
253    def embeddedDataSmall(self, nextpos) :
254        """Handle small amounts of data."""
255        return 1 + ord(self.minfile[nextpos])
256
257    def embeddedData(self, nextpos) :
258        """Handle normal amounts of data."""
259        return 4 + unpack(self.unpackLong, self.minfile[nextpos:nextpos+4])[0]
260
261    def skipHPPCLXL(self, nextpos) :
262        """Skip the 'HP-PCL XL' statement if needed."""
263        minfile = self.minfile
264        if nextpos \
265           and ((minfile[nextpos:nextpos+11] == " HP-PCL XL;") \
266             or (minfile[nextpos:nextpos+14] == " BROTHER XLHB;")) :
267            pos = nextpos
268            while minfile[pos] != '\n' :
269                pos += 1
270            length = (pos - nextpos + 1)
271            # self.logdebug("Skip HP PCLXL statement until %x" % (nextpos + length))
272            return length
273        else :
274            return 0
275
276    def littleEndian(self, nextpos) :
277        """Toggles to little endianness."""
278        self.unpackType = { 1 : "B", 2 : "<H", 4 : "<I" }
279        self.unpackShort = self.unpackType[2]
280        self.unpackLong = self.unpackType[4]
281        # self.logdebug("LittleEndian at %x" % (nextpos - 1))
282        return self.skipHPPCLXL(nextpos)
283
284    def bigEndian(self, nextpos) :
285        """Toggles to big endianness."""
286        self.unpackType = { 1 : "B", 2 : ">H", 4 : ">I" }
287        self.unpackShort = self.unpackType[2]
288        self.unpackLong = self.unpackType[4]
289        # self.logdebug("BigEndian at %x" % (nextpos - 1))
290        return self.skipHPPCLXL(nextpos)
291
292    def reservedForFutureUse(self, nextpos) :
293        """Outputs something when a reserved byte is encountered."""
294        self.logdebug("Byte at %x is out of the PCLXL Protocol Class 2.0 Specification" % nextpos)
295        return 0
296
297    def x31_class3(self, nextpos) :
298        """Undocumented tag 0x13 in class 3.0 streams."""
299        #self.logdebug("x31 at 0x%08x" % (nextpos-1))
300        minfile = self.minfile
301        val = ord(minfile[nextpos])
302        if val == 0x90 : # Should we take care of this or not ? It's undocumented after all !
303            # BTW we don't know if it's the 0x31 or the 0x90 which counts, since 0x90 is reserved for future use
304            try :
305                return unpack(self.unpackType[4], self.minfile[nextpos+1:nextpos+5])[0] + 5
306            except KeyError :
307                raise pdlparser.PDLParserError, "Error on size '%s' at %x" % (length, nextpos+1)
308        return 0
309
310    def x46_class3(self, nextpos) :
311        """Undocumented tag 0x46 in class 3.0 streams."""
312        #self.logdebug("x46 at 0x%08x" % (nextpos-1))
313        pos = nextpos - 3
314        minfile = self.minfile
315        val = ord(minfile[pos])
316        while val == 0xf8 :
317            #self.logdebug("x46 continues at 0x%08x with 0x%02x" % (pos, val))
318            funcid = ord(minfile[pos+1])
319            try :
320                offset = self.x46_functions[funcid]
321            except KeyError :
322                self.logdebug("Unexpected subfunction 0x%02x for undocumented tag 0x46 at %x" % (funcid, nextpos))
323                break
324            else :
325                #self.logdebug("x46 funcid 0x%02x" % funcid)
326                pos -= offset
327                #self.logdebug("x46 new position 0x%08x" % pos)
328                length = self.tags[ord(self.minfile[pos])]
329                if callable(length) :
330                    length = length(pos+1)
331                #self.logdebug("x46 length %i" % length)
332                if funcid == 0x92 : # we want to skip these blocks
333                    try :
334                        return unpack(self.unpackType[length], self.minfile[pos+1:pos+length+1])[0]
335                    except KeyError :
336                        raise pdlparser.PDLParserError, "Error on size '%s' at %x" % (length, pos+1)
337            val = ord(minfile[pos])
338        return 0
339
340    def escape(self, nextpos) :
341        """Handles the ESC code."""
342        pos = endpos = nextpos
343        minfile = self.minfile
344        if minfile[pos : pos+8] == r"%-12345X" :
345            endpos = pos + 9
346            endmark = chr(0x0c) + chr(0x00) + chr(0x1b)
347            asciilimit = chr(0x80)
348            quotes = 0
349            while (minfile[endpos] not in endmark) and \
350                   ((minfile[endpos] < asciilimit) or (quotes % 2)) :
351                if minfile[endpos] == '"' :
352                    quotes += 1
353                endpos += 1
354
355            # Store this in a per page mapping.
356            # NB : First time will be at page 0 (i.e. **before** page 1) !
357            stuff = self.escapedStuff.setdefault(self.pagecount, [])
358            stuff.append(minfile[pos : endpos])
359            self.logdebug("Escaped datas : [%s]" % repr(minfile[pos : endpos]))
360        return endpos - pos
361
362    def skipKyoceraPrescribe(self, nextpos) :
363        """Skips Kyocera Prescribe commands."""
364        pos = nextpos - 1
365        minfile = self.minfile
366        if minfile[pos:pos+3] == "!R!" :
367            while (pos - nextpos) < 1024 :   # This is a realistic upper bound, to avoid infinite loops
368                if (minfile[pos] == ";") and (minfile[pos-4:pos] == "EXIT") :
369                    pos += 1
370                    prescribe = self.prescribeStuff.setdefault(self.pagecount, [])
371                    prescribe.append(minfile[nextpos-1:pos])
372                    self.logdebug("Prescribe commands : [%s]" % repr(minfile[nextpos-1:pos]))
373                    break
374                pos += 1
375            return (pos - nextpos)
376        else :
377            return 0
378
379    def getJobSize(self) :
380        """Counts pages in a PCLXL (PCL6) document.
381
382           Algorithm by Jerome Alet.
383
384           The documentation used for this was :
385
386           HP PCL XL Feature Reference
387           Protocol Class 2.0
388           http://www.hpdevelopersolutions.com/downloads/64/358/xl_ref20r22.pdf
389
390           Protocol Class 2.1 Supplement
391           xl_ref21.pdf
392
393           Protocol Class 3.0 Supplement
394           xl_refsup30r089.pdf
395        """
396
397        infileno = self.infile.fileno()
398        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
399
400        self.iscolor = False
401
402        found = False
403        while not found :
404            line = self.infile.readline()
405            if not line :
406                break
407            pos = line.find(" HP-PCL XL;")
408            if pos == -1 :
409                pos = line.find(" BROTHER XL2HB;")
410            if pos != -1 :
411                found = True
412                endian = ord(line[pos - 1])
413                if endian == 0x29 :
414                    self.littleEndian(0)
415                elif endian == 0x28 :
416                    self.bigEndian(0)
417                # elif endian == 0x27 : # TODO : This is the ASCII binding code : what does it do exactly ?
418                #
419                else :
420                    raise pdlparser.PDLParserError, "Unknown endianness marker 0x%02x at start !" % endian
421        if not found :
422            raise pdlparser.PDLParserError, "This file doesn't seem to be PCLXL (aka PCL6)"
423
424        # Initialize Media Sources
425        for i in range(8, 256) :
426            self.mediasources[i] = "ExternalTray%03i" % (i - 7)
427
428        # Initialize table of tags
429        self.tags = [ 0 ] * 256
430
431        self.tags[0x1b] = self.escape # The escape code
432
433        self.tags[0x21] = self.skipKyoceraPrescribe # 0x21 is not normally used
434
435        # GhostScript's sources tell us that HP printers
436        # only accept little endianness, but we can handle both.
437        self.tags[0x28] = self.bigEndian    # BigEndian
438        self.tags[0x29] = self.littleEndian # LittleEndian
439
440        self.tags[0x31] = self.x31_class3   # What's this ? Does it always follow 0x46 ?
441        self.tags[0x43] = self.beginPage    # BeginPage
442        self.tags[0x44] = self.endPage      # EndPage
443        self.tags[0x45] = self.reservedForFutureUse # reserved
444
445        self.tags[0x46] = self.x46_class3
446
447        self.tags[0x4a] = self.reservedForFutureUse # reserved
448        self.tags[0x4b] = self.reservedForFutureUse # reserved
449        self.tags[0x4c] = self.reservedForFutureUse # reserved
450        self.tags[0x4d] = self.reservedForFutureUse # reserved
451        self.tags[0x4e] = self.reservedForFutureUse # reserved
452
453        self.tags[0x56] = self.reservedForFutureUse # TODO : documentation not clear about reserved status
454
455        self.tags[0x57] = self.reservedForFutureUse # reserved
456
457        self.tags[0x59] = self.reservedForFutureUse # reserved
458        self.tags[0x5a] = self.reservedForFutureUse # reserved
459
460        self.tags[0x6a] = self.setColorSpace    # to detect color/b&w mode
461
462        self.tags[0x87] = self.reservedForFutureUse # reserved
463        self.tags[0x88] = self.reservedForFutureUse # reserved
464        self.tags[0x89] = self.reservedForFutureUse # reserved
465        self.tags[0x8a] = self.reservedForFutureUse # reserved
466
467        self.tags[0x8b] = self.reservedForFutureUse # reserved
468
469        self.tags[0x8c] = self.reservedForFutureUse # reserved
470        self.tags[0x8d] = self.reservedForFutureUse # reserved
471        self.tags[0x8e] = self.reservedForFutureUse # reserved
472        self.tags[0x8f] = self.reservedForFutureUse # reserved
473        self.tags[0x90] = self.reservedForFutureUse # reserved
474
475        self.tags[0x9a] = self.reservedForFutureUse # reserved
476        self.tags[0x9c] = self.reservedForFutureUse # reserved
477
478        self.tags[0xa4] = self.reservedForFutureUse # reserved
479        self.tags[0xa5] = self.reservedForFutureUse # reserved
480        self.tags[0xa6] = self.reservedForFutureUse # reserved
481        self.tags[0xa7] = self.reservedForFutureUse # reserved
482
483        self.tags[0xaa] = self.reservedForFutureUse # reserved
484        self.tags[0xab] = self.reservedForFutureUse # reserved
485        self.tags[0xac] = self.reservedForFutureUse # reserved
486        self.tags[0xad] = self.reservedForFutureUse # reserved
487        self.tags[0xae] = self.reservedForFutureUse # reserved
488        self.tags[0xaf] = self.reservedForFutureUse # reserved
489
490        self.tags[0xb7] = self.reservedForFutureUse # reserved
491
492        self.tags[0xba] = self.reservedForFutureUse # reserved
493        self.tags[0xbb] = self.reservedForFutureUse # reserved
494        self.tags[0xbc] = self.reservedForFutureUse # reserved
495        self.tags[0xbd] = self.reservedForFutureUse # reserved
496        self.tags[0xbe] = self.reservedForFutureUse # reserved
497
498        # self.tags[0xbf] = self.passThrough # PassThrough mode should already be taken care of automatically
499
500        self.tags[0xc0] = 1 # ubyte
501        self.tags[0xc1] = 2 # uint16
502        self.tags[0xc2] = 4 # uint32
503        self.tags[0xc3] = 2 # sint16
504        self.tags[0xc4] = 4 # sint32
505        self.tags[0xc5] = 4 # real32
506
507        self.tags[0xc6] = self.reservedForFutureUse # reserved
508        self.tags[0xc7] = self.reservedForFutureUse # reserved
509
510        self.tags[0xc8] = self.array_8  # ubyte_array
511        self.tags[0xc9] = self.array_16 # uint16_array
512        self.tags[0xca] = self.array_32 # uint32_array
513        self.tags[0xcb] = self.array_16 # sint16_array
514        self.tags[0xcc] = self.array_32 # sint32_array
515        self.tags[0xcd] = self.array_32 # real32_array and unfortunately Canon ImageRunner
516
517        self.tags[0xce] = self.reservedForFutureUse # reserved
518        self.tags[0xcf] = self.reservedForFutureUse # reserved
519
520        self.tags[0xd0] = 2 # ubyte_xy
521        self.tags[0xd1] = 4 # uint16_xy
522        self.tags[0xd2] = 8 # uint32_xy
523        self.tags[0xd3] = 4 # sint16_xy
524        self.tags[0xd4] = 8 # sint32_xy
525        self.tags[0xd5] = 8 # real32_xy
526        self.tags[0xd6] = self.reservedForFutureUse # reserved
527        self.tags[0xd7] = self.reservedForFutureUse # reserved
528        self.tags[0xd8] = self.reservedForFutureUse # reserved
529        self.tags[0xd9] = self.reservedForFutureUse # reserved
530        self.tags[0xda] = self.reservedForFutureUse # reserved
531        self.tags[0xdb] = self.reservedForFutureUse # reserved
532        self.tags[0xdc] = self.reservedForFutureUse # reserved
533        self.tags[0xdd] = self.reservedForFutureUse # reserved
534        self.tags[0xde] = self.reservedForFutureUse # reserved
535        self.tags[0xdf] = self.reservedForFutureUse # reserved
536
537        self.tags[0xe0] = 4  # ubyte_box
538        self.tags[0xe1] = 8  # uint16_box
539        self.tags[0xe2] = 16 # uint32_box
540        self.tags[0xe3] = 8  # sint16_box
541        self.tags[0xe4] = 16 # sint32_box
542        self.tags[0xe5] = 16 # real32_box
543        self.tags[0xe6] = self.reservedForFutureUse # reserved
544        self.tags[0xe7] = self.reservedForFutureUse # reserved
545        self.tags[0xe8] = self.reservedForFutureUse # reserved
546        self.tags[0xe9] = self.reservedForFutureUse # reserved
547        self.tags[0xea] = self.reservedForFutureUse # reserved
548        self.tags[0xeb] = self.reservedForFutureUse # reserved
549        self.tags[0xec] = self.reservedForFutureUse # reserved
550        self.tags[0xed] = self.reservedForFutureUse # reserved
551        self.tags[0xee] = self.reservedForFutureUse # reserved
552        self.tags[0xef] = self.reservedForFutureUse # reserved
553
554        self.tags[0xf0] = self.reservedForFutureUse # reserved
555        self.tags[0xf1] = self.reservedForFutureUse # reserved
556        self.tags[0xf2] = self.reservedForFutureUse # reserved
557        self.tags[0xf3] = self.reservedForFutureUse # reserved
558        self.tags[0xf4] = self.reservedForFutureUse # reserved
559        self.tags[0xf5] = self.reservedForFutureUse # reserved
560        self.tags[0xf6] = self.reservedForFutureUse # reserved
561        self.tags[0xf7] = self.reservedForFutureUse # reserved
562
563        self.tags[0xf8] = 1 # attr_ubyte
564        self.tags[0xf9] = 2 # attr_uint16
565
566        self.tags[0xfa] = self.embeddedData      # dataLength
567        self.tags[0xfb] = self.embeddedDataSmall # dataLengthByte
568
569        self.tags[0xfc] = self.reservedForFutureUse # reserved
570        self.tags[0xfd] = self.reservedForFutureUse # reserved
571        self.tags[0xfe] = self.reservedForFutureUse # reserved
572        self.tags[0xff] = self.reservedForFutureUse # reserved
573
574        # color spaces
575        self.BWColorSpace = "".join([chr(0x00), chr(0xf8), chr(0x03)])
576        self.GrayColorSpace = "".join([chr(0x01), chr(0xf8), chr(0x03)])
577        self.RGBColorSpace = "".join([chr(0x02), chr(0xf8), chr(0x03)])
578
579        # set number of copies
580        self.setNumberOfCopies = "".join([chr(0xf8), chr(0x31)])
581
582        # subcodes for undocumented tag 0x46 and the negative
583        # offset to grab the value from.
584        self.x46_functions = { 0x91 : 5,
585                               0x92 : 5,
586                               0x93 : 3,
587                               0x94 : 3,
588                               0x95 : 5,
589                               0x96 : 2,
590                               0x97 : 2,
591                               0x98 : 2,
592                             }
593
594        # Markers for Canon ImageRunner printers
595        self.imagerunnermarker1 = chr(0xcd) + chr(0xca) + chr(0x10) + chr(0x00)
596        self.imagerunnermarker2 = chr(0xcd) + chr(0xca) + chr(0x10) + chr(0x02)
597
598        self.pages = { 0 : { "copies" : 1,
599                             "orientation" : "Default",
600                             "mediatype" : "Plain",
601                             "mediasize" : "Default",
602                             "mediasource" : "Default",
603                             "duplex" : None,
604                           }
605                     }
606        tags = self.tags
607        self.pagecount = 0
608        self.escapedStuff = {}   # For escaped datas, mostly PJL commands
609        self.prescribeStuff = {} # For Kyocera Prescribe commands
610        pos = oldpos = 0
611        try :
612            try :
613                while 1 :
614                    try :
615                        tag = ord(minfile[pos])
616                    except OverflowError :
617                        pos = oldpos + 1
618                    #self.logdebug("0x%08x : 0x%02x" % (pos, tag))
619                    pos += 1
620                    length = tags[tag]
621                    if length :
622                        if callable(length) :
623                            length = length(pos)
624                        oldpos = pos
625                        pos += length
626            except IndexError : # EOF ?
627                pass
628        finally :
629            self.minfile.close()
630
631        # now handle number of copies for each page (may differ).
632        if self.iscolor :
633            colormode = "Color"
634        else :
635            colormode = "BW"
636
637        defaultduplexmode = "Simplex"
638        defaultpapersize = ""
639        defaultpjlcopies = 1
640        oldpjlcopies = -1
641        oldduplexmode = ""
642        oldpapersize = ""
643        for pnum in range(1, self.pagecount + 1) :
644            # if no number of copies defined, take 1, as explained
645            # in PCLXL documentation.
646            # NB : is number of copies is 0, the page won't be output
647            # but the formula below is still correct : we want
648            # to decrease the total number of pages in this case.
649            page = self.pages.get(pnum, self.pages.get(1, { "copies" : 1, "mediasize" : "Default", "duplex" : None }))
650            pjlstuff = self.escapedStuff.get(pnum, self.escapedStuff.get(0, []))
651            if pjlstuff :
652                pjlparser = pjl.PJLParser("".join(pjlstuff))
653                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
654                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
655                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
656                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
657                if nbdefaultcopies > -1 :
658                    defaultpjlcopies = nbdefaultcopies
659                if nbdefaultqty > -1 :
660                    defaultpjlcopies = nbdefaultqty
661                if nbcopies > -1 :
662                    pjlcopies = nbcopies
663                elif nbqty > -1 :
664                    pjlcopies = nbqty
665                else :
666                    if oldpjlcopies == -1 :
667                        pjlcopies = defaultpjlcopies
668                    else :
669                        pjlcopies = oldpjlcopies
670                if page["duplex"] :
671                    duplexmode = page["duplex"]
672                else :
673                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
674                    if defaultdm :
675                        if defaultdm.upper() == "ON" :
676                            defaultduplexmode = "Duplex"
677                        else :
678                            defaultduplexmode = "Simplex"
679                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
680                    if envdm :
681                        if envdm.upper() == "ON" :
682                            duplexmode = "Duplex"
683                        else :
684                            duplexmode = "Simplex"
685                    else :
686                        if not oldduplexmode :
687                            duplexmode = defaultduplexmode
688                        else :
689                            duplexmode = oldduplexmode
690                defaultps = pjlparser.default_variables.get("PAPER", "")
691                if defaultps :
692                    defaultpapersize = defaultps
693                envps = pjlparser.environment_variables.get("PAPER", "")
694                if envps :
695                    papersize = envps
696                else :
697                    if not oldpapersize :
698                        papersize = defaultpapersize
699                    else :
700                        papersize = oldpapersize
701            else :
702                if oldpjlcopies == -1 :
703                    pjlcopies = defaultpjlcopies
704                else :
705                    pjlcopies = oldpjlcopies
706                if not oldduplexmode :
707                    duplexmode = defaultduplexmode
708                else :
709                    duplexmode = oldduplexmode
710                if not oldpapersize :
711                    papersize = defaultpapersize
712                else :
713                    papersize = oldpapersize
714                duplexmode = oldduplexmode
715                papersize = oldpapersize or page["mediasize"]
716            if page["mediasize"] != "Default" :
717                papersize = page["mediasize"]
718            if not duplexmode :
719                duplexmode = oldduplexmode or defaultduplexmode
720            oldpjlcopies = pjlcopies
721            oldduplexmode = duplexmode
722            oldpapersize = papersize
723            copies = max(pjlcopies, page["copies"]) # Was : pjlcopies * page["copies"]
724            self.pagecount += (copies - 1)
725            self.logdebug("%s*%s*%s*%s*%s*%s*%s" % (copies,
726                                                 page["mediatype"],
727                                                 papersize,
728                                                 page["orientation"],
729                                                 page["mediasource"],
730                                                 duplexmode,
731                                                 colormode))
732        return self.pagecount
Note: See TracBrowser for help on using the browser.