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
RevLine 
[3409]1# -*- coding: utf-8 -*-
[191]2#
3# pkpgcounter : a generic Page Description Language parser
4#
[3474]5# (c) 2003-2009 Jerome Alet <alet@librelogiciel.com>
[463]6# This program is free software: you can redistribute it and/or modify
[191]7# it under the terms of the GNU General Public License as published by
[463]8# the Free Software Foundation, either version 3 of the License, or
[191]9# (at your option) any later version.
[3436]10#
[191]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.
[3436]15#
[191]16# You should have received a copy of the GNU General Public License
[463]17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[191]18#
19# $Id$
20#
[193]21
[357]22"""This modules implements a page counter for PCLXL (aka PCL6) documents."""
23
[193]24import sys
[202]25import os
[193]26import mmap
27from struct import unpack
28
[235]29import pdlparser
[252]30import pjl
[193]31
[220]32class Parser(pdlparser.PDLParser) :
[193]33    """A parser for PCLXL (aka PCL6) documents."""
[3436]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" -',
[492]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" -',
[428]36                     ]
[527]37    required = [ "pcl6", "gs" ]
[555]38    format = "PCLXL (aka PCL6)"
[3436]39    mediasizes = {
[193]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",
[246]53                    12 : "B5",
[193]54                    14 : "JPostcard",
55                    15 : "JDoublePostcard",
56                    16 : "A5",
57                    17 : "A6",
58                    18 : "JB6",
[246]59                    19 : "JIS8K",
60                    20 : "JIS16K",
61                    21 : "JISExec",
62                    96 : "Default",
[3436]63                 }
64
65    mediasources = {
[193]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                   }
[3436]75
76    orientations = {
[193]77                     0 : "Portrait",
78                     1 : "Landscape",
79                     2 : "ReversePortrait",
80                     3 : "ReverseLandscape",
[246]81                     4 : "Default",
[193]82                   }
[3436]83
84    def isValid(self) :
[3409]85        """Returns True if data is HP PCLXL aka PCL6, or Brother's' XL2HB, else False."""
[522]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))) :
[387]91            return True
[3409]92        elif (self.firstblock[:128].find("\033%-12345X") != -1) \
93            and (self.firstblock.find("BROTHER XL2HB;") != -1) :
94            self.format = "XL2HB"
95            return True
[3436]96        else :
[387]97            return False
[3436]98
[318]99    def beginPage(self, nextpos) :
[193]100        """Indicates the beginning of a new page, and extracts media information."""
[479]101        # self.logdebug("BeginPage at %x" % nextpos)
[193]102        self.pagecount += 1
[3436]103
[193]104        # Default values
105        mediatypelabel = "Plain"
106        mediasourcelabel = "Main"
107        mediasizelabel = "Default"
108        orientationlabel = "Portrait"
[299]109        duplexmode = None
[3436]110
[193]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
[318]114        pos = nextpos - 2
[193]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
[3436]119            if val == 0x26 :
[193]120                mediasource = ord(minfile[pos - 2])
121                mediasourcelabel = self.mediasources.get(mediasource, str(mediasource))
[302]122                pos -= 4
[193]123            elif val == 0x25 :
[302]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
[3436]134                    else :
[302]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))
[3436]139                    pos -= 1
[310]140                    # self.logdebug("Media size : %s" % mediasizelabel)
[3436]141            elif val == 0x28 :
[193]142                orientation = ord(minfile[pos - 2])
[252]143                orientationlabel = self.orientations.get(orientation, str(orientation))
[302]144                pos -= 4
[3436]145            elif val == 0x27 :
[193]146                savepos = pos
[302]147                pos -= 1
[3436]148                startpos = size = None
[193]149                while pos > 0 : # safety check : don't go back to far !
150                    val = ord(minfile[pos])
[3436]151                    pos -= 1
[193]152                    if val == 0xc8 :
[293]153                        length = self.tags[ord(minfile[pos+2])] # will probably always be a byte or uint16
[3436]154                        if length == 1 :
[293]155                            startpos = pos + 4
156                            size = unpack("B", self.minfile[pos+3:startpos])[0]
[3436]157                        elif length == 2 :
[293]158                            startpos = pos + 5
[316]159                            size = unpack(self.unpackShort, self.minfile[pos+3:startpos])[0]
[3436]160                        elif length == 4 :
[293]161                            startpos = pos + 7
[316]162                            size = unpack(self.unpackLong, self.minfile[pos+3:startpos])[0]
[3436]163                        else :
[301]164                            raise pdlparser.PDLParserError, "Error on size at %s : %s" % (pos+2, length)
[193]165                        break
[3491]166                try :
167                    mediatypelabel = minfile[startpos:startpos+size]
168                except TypeError :
169                    self.logdebug("PCL/XL parser problem at %i" % savepos)
[310]170                # self.logdebug("Media type : %s" % mediatypelabel)
[3436]171            elif val == 0x34 :
[299]172                duplexmode = "Simplex"
[302]173                pos -= 2
[3436]174            elif val in (0x35, 0x36) :
[299]175                duplexmode = "Duplex"
[302]176                pos -= 2
[3436]177            # else : TODO : CUSTOM MEDIA SIZE AND UNIT !
178            else :
[302]179                pos -= 1  # ignored
[3436]180        self.pages[self.pagecount] = { "copies" : 1,
181                                       "orientation" : orientationlabel,
182                                       "mediatype" : mediatypelabel,
[193]183                                       "mediasize" : mediasizelabel,
184                                       "mediasource" : mediasourcelabel,
[299]185                                       "duplex" : duplexmode,
[3436]186                                     }
[193]187        return 0
[3436]188
189    def endPage(self, nextpos) :
[193]190        """Indicates the end of a page."""
[479]191        # self.logdebug("EndPage at %x" % nextpos)
[318]192        pos3 = nextpos - 3
[193]193        minfile = self.minfile
[318]194        if minfile[pos3:nextpos-1] == self.setNumberOfCopies :
[193]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
[258]199            try :
[454]200                nbcopies = unpack(self.unpackShort, minfile[pos3-2:pos3])[0]
[456]201                # self.logdebug("Number of copies : %i" % nbcopies)
[454]202                self.pages[self.pagecount]["copies"] = nbcopies
[3436]203            except KeyError :
[258]204                self.logdebug("It looks like this PCLXL file is corrupted.")
[193]205        return 0
[3436]206
207    def setColorSpace(self, nextpos) :
[205]208        """Changes the color space."""
[318]209        if self.minfile[nextpos-4:nextpos-1] == self.RGBColorSpace : # TODO : doesn't seem to handle all cases !
[479]210            self.iscolor = True
[205]211        return 0
[3436]212
[318]213    def array_Generic(self, nextpos, size) :
[317]214        """Handles all arrays."""
[318]215        pos = nextpos
[317]216        datatype = ord(self.minfile[pos])
[193]217        pos += 1
[317]218        length = self.tags[datatype]
[193]219        if callable(length) :
[316]220            length = length(pos)
[318]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
[3436]225
226    def array_8(self, nextpos) :
[317]227        """Handles byte arrays."""
[318]228        return self.array_Generic(nextpos, 1)
[3436]229
[318]230    def array_16(self, nextpos) :
[424]231        """Handles 16 bits arrays."""
[318]232        return self.array_Generic(nextpos, 2)
[3436]233
[318]234    def array_32(self, nextpos) :
[479]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))
[3436]248            return toskip
[479]249        else :
250            # This is a normal PCLXL array
251            return self.array_Generic(nextpos, 4)
[3436]252
[318]253    def embeddedDataSmall(self, nextpos) :
[193]254        """Handle small amounts of data."""
[318]255        return 1 + ord(self.minfile[nextpos])
[3436]256
[318]257    def embeddedData(self, nextpos) :
[193]258        """Handle normal amounts of data."""
[318]259        return 4 + unpack(self.unpackLong, self.minfile[nextpos:nextpos+4])[0]
[3436]260
261    def skipHPPCLXL(self, nextpos) :
[479]262        """Skip the 'HP-PCL XL' statement if needed."""
263        minfile = self.minfile
[3409]264        if nextpos \
265           and ((minfile[nextpos:nextpos+11] == " HP-PCL XL;") \
266             or (minfile[nextpos:nextpos+14] == " BROTHER XLHB;")) :
[479]267            pos = nextpos
268            while minfile[pos] != '\n' :
269                pos += 1
[3436]270            length = (pos - nextpos + 1)
271            # self.logdebug("Skip HP PCLXL statement until %x" % (nextpos + length))
[479]272            return length
[3436]273        else :
[479]274            return 0
[3436]275
[318]276    def littleEndian(self, nextpos) :
[193]277        """Toggles to little endianness."""
[318]278        self.unpackType = { 1 : "B", 2 : "<H", 4 : "<I" }
279        self.unpackShort = self.unpackType[2]
280        self.unpackLong = self.unpackType[4]
[479]281        # self.logdebug("LittleEndian at %x" % (nextpos - 1))
282        return self.skipHPPCLXL(nextpos)
[3436]283
[318]284    def bigEndian(self, nextpos) :
[193]285        """Toggles to big endianness."""
[318]286        self.unpackType = { 1 : "B", 2 : ">H", 4 : ">I" }
287        self.unpackShort = self.unpackType[2]
288        self.unpackLong = self.unpackType[4]
[479]289        # self.logdebug("BigEndian at %x" % (nextpos - 1))
290        return self.skipHPPCLXL(nextpos)
[3436]291
[318]292    def reservedForFutureUse(self, nextpos) :
[245]293        """Outputs something when a reserved byte is encountered."""
[318]294        self.logdebug("Byte at %x is out of the PCLXL Protocol Class 2.0 Specification" % nextpos)
[3436]295        return 0
296
[3513]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
[318]310    def x46_class3(self, nextpos) :
[310]311        """Undocumented tag 0x46 in class 3.0 streams."""
[3513]312        #self.logdebug("x46 at 0x%08x" % (nextpos-1))
[318]313        pos = nextpos - 3
[309]314        minfile = self.minfile
[318]315        val = ord(minfile[pos])
316        while val == 0xf8 :
[3513]317            #self.logdebug("x46 continues at 0x%08x with 0x%02x" % (pos, val))
[318]318            funcid = ord(minfile[pos+1])
319            try :
320                offset = self.x46_functions[funcid]
[3436]321            except KeyError :
[318]322                self.logdebug("Unexpected subfunction 0x%02x for undocumented tag 0x46 at %x" % (funcid, nextpos))
323                break
[3436]324            else :
[3513]325                #self.logdebug("x46 funcid 0x%02x" % funcid)
[320]326                pos -= offset
[3513]327                #self.logdebug("x46 new position 0x%08x" % pos)
[320]328                length = self.tags[ord(self.minfile[pos])]
[318]329                if callable(length) :
[320]330                    length = length(pos+1)
[3513]331                #self.logdebug("x46 length %i" % length)
[320]332                if funcid == 0x92 : # we want to skip these blocks
[318]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)
[309]337            val = ord(minfile[pos])
[3436]338        return 0
339
340    def escape(self, nextpos) :
[245]341        """Handles the ESC code."""
[318]342        pos = endpos = nextpos
[314]343        minfile = self.minfile
344        if minfile[pos : pos+8] == r"%-12345X" :
[245]345            endpos = pos + 9
[252]346            endmark = chr(0x0c) + chr(0x00) + chr(0x1b)
[246]347            asciilimit = chr(0x80)
[269]348            quotes = 0
[314]349            while (minfile[endpos] not in endmark) and \
350                   ((minfile[endpos] < asciilimit) or (quotes % 2)) :
351                if minfile[endpos] == '"' :
[269]352                    quotes += 1
[245]353                endpos += 1
[3436]354
355            # Store this in a per page mapping.
[245]356            # NB : First time will be at page 0 (i.e. **before** page 1) !
357            stuff = self.escapedStuff.setdefault(self.pagecount, [])
[314]358            stuff.append(minfile[pos : endpos])
359            self.logdebug("Escaped datas : [%s]" % repr(minfile[pos : endpos]))
[245]360        return endpos - pos
[3436]361
[318]362    def skipKyoceraPrescribe(self, nextpos) :
[312]363        """Skips Kyocera Prescribe commands."""
[318]364        pos = nextpos - 1
[312]365        minfile = self.minfile
366        if minfile[pos:pos+3] == "!R!" :
[318]367            while (pos - nextpos) < 1024 :   # This is a realistic upper bound, to avoid infinite loops
[312]368                if (minfile[pos] == ";") and (minfile[pos-4:pos] == "EXIT") :
[314]369                    pos += 1
370                    prescribe = self.prescribeStuff.setdefault(self.pagecount, [])
[318]371                    prescribe.append(minfile[nextpos-1:pos])
372                    self.logdebug("Prescribe commands : [%s]" % repr(minfile[nextpos-1:pos]))
[417]373                    break
[3436]374                pos += 1
[417]375            return (pos - nextpos)
[312]376        else :
377            return 0
[3436]378
[193]379    def getJobSize(self) :
380        """Counts pages in a PCLXL (PCL6) document.
[3436]381
[193]382           Algorithm by Jerome Alet.
[3436]383
[193]384           The documentation used for this was :
[3436]385
[193]386           HP PCL XL Feature Reference
387           Protocol Class 2.0
[3436]388           http://www.hpdevelopersolutions.com/downloads/64/358/xl_ref20r22.pdf
389
[246]390           Protocol Class 2.1 Supplement
391           xl_ref21.pdf
[3436]392
[246]393           Protocol Class 3.0 Supplement
394           xl_refsup30r089.pdf
[193]395        """
[3436]396
[479]397        infileno = self.infile.fileno()
398        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
[3436]399
[479]400        self.iscolor = False
[3436]401
[449]402        found = False
[193]403        while not found :
404            line = self.infile.readline()
405            if not line :
406                break
[3409]407            pos = line.find(" HP-PCL XL;")
408            if pos == -1 :
409                pos = line.find(" BROTHER XL2HB;")
[293]410            if pos != -1 :
[449]411                found = True
[293]412                endian = ord(line[pos - 1])
[193]413                if endian == 0x29 :
[316]414                    self.littleEndian(0)
[3436]415                elif endian == 0x28 :
[316]416                    self.bigEndian(0)
[479]417                # elif endian == 0x27 : # TODO : This is the ASCII binding code : what does it do exactly ?
[3436]418                #
419                else :
[200]420                    raise pdlparser.PDLParserError, "Unknown endianness marker 0x%02x at start !" % endian
[193]421        if not found :
[200]422            raise pdlparser.PDLParserError, "This file doesn't seem to be PCLXL (aka PCL6)"
[3436]423
[246]424        # Initialize Media Sources
425        for i in range(8, 256) :
426            self.mediasources[i] = "ExternalTray%03i" % (i - 7)
[3436]427
[193]428        # Initialize table of tags
[3436]429        self.tags = [ 0 ] * 256
430
[245]431        self.tags[0x1b] = self.escape # The escape code
[3436]432
[352]433        self.tags[0x21] = self.skipKyoceraPrescribe # 0x21 is not normally used
[3436]434
[193]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
[3436]439
[3513]440        self.tags[0x31] = self.x31_class3   # What's this ? Does it always follow 0x46 ?
[193]441        self.tags[0x43] = self.beginPage    # BeginPage
442        self.tags[0x44] = self.endPage      # EndPage
[245]443        self.tags[0x45] = self.reservedForFutureUse # reserved
[3436]444
445        self.tags[0x46] = self.x46_class3
446
[245]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
[3436]452
[245]453        self.tags[0x56] = self.reservedForFutureUse # TODO : documentation not clear about reserved status
[3436]454
[245]455        self.tags[0x57] = self.reservedForFutureUse # reserved
[3436]456
[245]457        self.tags[0x59] = self.reservedForFutureUse # reserved
458        self.tags[0x5a] = self.reservedForFutureUse # reserved
[3436]459
[206]460        self.tags[0x6a] = self.setColorSpace    # to detect color/b&w mode
[3436]461
[245]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
[3436]466
[245]467        self.tags[0x8b] = self.reservedForFutureUse # reserved
[3436]468
[245]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
[3436]474
[245]475        self.tags[0x9a] = self.reservedForFutureUse # reserved
476        self.tags[0x9c] = self.reservedForFutureUse # reserved
[3436]477
[245]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
[3436]482
[245]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
[3436]489
[245]490        self.tags[0xb7] = self.reservedForFutureUse # reserved
[3436]491
[245]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
[3436]497
[316]498        # self.tags[0xbf] = self.passThrough # PassThrough mode should already be taken care of automatically
[3436]499
[193]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
[3436]506
[245]507        self.tags[0xc6] = self.reservedForFutureUse # reserved
508        self.tags[0xc7] = self.reservedForFutureUse # reserved
[3436]509
[193]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
[479]515        self.tags[0xcd] = self.array_32 # real32_array and unfortunately Canon ImageRunner
[3436]516
[245]517        self.tags[0xce] = self.reservedForFutureUse # reserved
518        self.tags[0xcf] = self.reservedForFutureUse # reserved
[3436]519
[193]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
[245]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
[3436]536
[193]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
[245]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
[3436]553
[245]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
[3436]562
[193]563        self.tags[0xf8] = 1 # attr_ubyte
564        self.tags[0xf9] = 2 # attr_uint16
[3436]565
[193]566        self.tags[0xfa] = self.embeddedData      # dataLength
567        self.tags[0xfb] = self.embeddedDataSmall # dataLengthByte
[3436]568
[245]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
[3436]573
574        # color spaces
[206]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)])
[3436]578
[206]579        # set number of copies
[3436]580        self.setNumberOfCopies = "".join([chr(0xf8), chr(0x31)])
581
[318]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                             }
[3436]593
[479]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)
[3436]597
598        self.pages = { 0 : { "copies" : 1,
599                             "orientation" : "Default",
600                             "mediatype" : "Plain",
601                             "mediasize" : "Default",
602                             "mediasource" : "Default",
[299]603                             "duplex" : None,
[3436]604                           }
605                     }
[193]606        tags = self.tags
607        self.pagecount = 0
[314]608        self.escapedStuff = {}   # For escaped datas, mostly PJL commands
609        self.prescribeStuff = {} # For Kyocera Prescribe commands
[316]610        pos = oldpos = 0
[193]611        try :
[307]612            try :
613                while 1 :
614                    try :
[316]615                        tag = ord(minfile[pos])
[3436]616                    except OverflowError :
[307]617                        pos = oldpos + 1
[3513]618                    #self.logdebug("0x%08x : 0x%02x" % (pos, tag))
[307]619                    pos += 1
[316]620                    length = tags[tag]
[307]621                    if length :
[3436]622                        if callable(length) :
[316]623                            length = length(pos)
[3436]624                        oldpos = pos
625                        pos += length
626            except IndexError : # EOF ?
[307]627                pass
628        finally :
629            self.minfile.close()
[3436]630
[193]631        # now handle number of copies for each page (may differ).
[206]632        if self.iscolor :
633            colormode = "Color"
[3436]634        else :
[253]635            colormode = "BW"
[3436]636
[253]637        defaultduplexmode = "Simplex"
638        defaultpapersize = ""
[3436]639        defaultpjlcopies = 1
[252]640        oldpjlcopies = -1
[253]641        oldduplexmode = ""
642        oldpapersize = ""
[193]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
[3436]647            # but the formula below is still correct : we want
[193]648            # to decrease the total number of pages in this case.
[299]649            page = self.pages.get(pnum, self.pages.get(1, { "copies" : 1, "mediasize" : "Default", "duplex" : None }))
[266]650            pjlstuff = self.escapedStuff.get(pnum, self.escapedStuff.get(0, []))
[252]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 :
[253]662                    pjlcopies = nbcopies
[252]663                elif nbqty > -1 :
[253]664                    pjlcopies = nbqty
[252]665                else :
[3436]666                    if oldpjlcopies == -1 :
[253]667                        pjlcopies = defaultpjlcopies
[3436]668                    else :
669                        pjlcopies = oldpjlcopies
670                if page["duplex"] :
[299]671                    duplexmode = page["duplex"]
[3436]672                else :
[299]673                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
674                    if defaultdm :
675                        if defaultdm.upper() == "ON" :
676                            defaultduplexmode = "Duplex"
[3436]677                        else :
[299]678                            defaultduplexmode = "Simplex"
679                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
680                    if envdm :
681                        if envdm.upper() == "ON" :
682                            duplexmode = "Duplex"
[3436]683                        else :
[299]684                            duplexmode = "Simplex"
[3436]685                    else :
[299]686                        if not oldduplexmode :
687                            duplexmode = defaultduplexmode
[3436]688                        else :
[299]689                            duplexmode = oldduplexmode
[253]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
[3436]696                else :
[253]697                    if not oldpapersize :
698                        papersize = defaultpapersize
[3436]699                    else :
[253]700                        papersize = oldpapersize
[3436]701            else :
[252]702                if oldpjlcopies == -1 :
703                    pjlcopies = defaultpjlcopies
[3436]704                else :
[252]705                    pjlcopies = oldpjlcopies
[253]706                if not oldduplexmode :
707                    duplexmode = defaultduplexmode
[3436]708                else :
[253]709                    duplexmode = oldduplexmode
[3436]710                if not oldpapersize :
[253]711                    papersize = defaultpapersize
[3436]712                else :
[253]713                    papersize = oldpapersize
714                duplexmode = oldduplexmode
715                papersize = oldpapersize or page["mediasize"]
716            if page["mediasize"] != "Default" :
717                papersize = page["mediasize"]
[3436]718            if not duplexmode :
[253]719                duplexmode = oldduplexmode or defaultduplexmode
[3436]720            oldpjlcopies = pjlcopies
[253]721            oldduplexmode = duplexmode
722            oldpapersize = papersize
[456]723            copies = max(pjlcopies, page["copies"]) # Was : pjlcopies * page["copies"]
[193]724            self.pagecount += (copies - 1)
[3436]725            self.logdebug("%s*%s*%s*%s*%s*%s*%s" % (copies,
726                                                 page["mediatype"],
727                                                 papersize,
728                                                 page["orientation"],
729                                                 page["mediasource"],
730                                                 duplexmode,
[252]731                                                 colormode))
[193]732        return self.pagecount
Note: See TracBrowser for help on using the browser.