root / pkpgcounter / trunk / pkpgpdls / qpdl.py @ 493

Revision 491, 11.9 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:keywords set to Author Date 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 QPDL (aka SPL2) documents."""
24
25import sys
26import os
27import mmap
28from struct import unpack
29
30import pdlparser
31import pjl
32
33class Parser(pdlparser.PDLParser) :
34    """A parser for QPDL (aka SPL2) documents."""
35    mediasizes = { 
36                    # The first values are identical to that of PCLXL
37                    0 : "Letter",
38                    1 : "Legal",
39                    2 : "A4",
40                    3 : "Executive",
41                    4 : "Ledger",
42                    5 : "A3",
43                    6 : "COM10Envelope",
44                    7 : "MonarchEnvelope",
45                    8 : "C5Envelope",
46                    9 : "DLEnvelope",
47                    10 : "JB4",
48                    11 : "JB5",
49                    12 : "B5Envelope",
50                    12 : "B5",
51                    14 : "JPostcard",
52                    15 : "JDoublePostcard",
53                    16 : "A5",
54                    17 : "A6",
55                    18 : "JB6",
56                    21 : "Custom",
57                    23 : "C6",
58                    24 : "Folio",
59                 }   
60                 
61    mediasources = {             
62                     # Again, values are identical to that of PCLXL
63                     0 : "Default",
64                     1 : "Auto",
65                     2 : "Manual",
66                     3 : "MultiPurpose",
67                     4 : "UpperCassette",
68                     5 : "LowerCassette",
69                     6 : "EnvelopeTray",
70                     7 : "ThirdCassette",
71                   }
72           
73    def isValid(self) :   
74        """Returns True if data is QPDL aka SPL2, else False."""
75        if ((self.firstblock[:128].find("\033%-12345X") != -1) and \
76             ((self.firstblock.find("LANGUAGE=QPDL") != -1) or \
77              (self.firstblock.find("LANGUAGE = QPDL") != -1))) :
78            self.logdebug("DEBUG: Input file is in the QPDL (aka SPL2) format.")
79            return True
80        else :   
81            return False
82           
83    def beginPage(self, nextpos) :
84        """Indicates the beginning of a new page, and extracts media information."""
85        self.pagecount += 1
86       
87        copies = unpack(self.unpackShort, self.minfile[nextpos+1:nextpos+3])[0]
88        mediasize = ord(self.minfile[nextpos+3])
89        mediasource = ord(self.minfile[nextpos+8])
90        duplexmode = unpack(self.unpackShort, self.minfile[nextpos+10:nextpos+12])[0]
91       
92        self.pages[self.pagecount] = { "copies" : copies, 
93                                       "mediasize" : self.mediasizes.get(mediasize, str(mediasize)),
94                                       "mediasource" : self.mediasources.get(mediasource, str(mediasource)),
95                                       "duplex" : duplexmode,
96                                     } 
97        return 16       # Length of a page header
98       
99    def endPage(self, nextpos) :   
100        """Indicates the end of a page."""
101        epcopies = unpack(self.unpackShort, self.minfile[nextpos:nextpos+2])[0]
102        bpcopies = self.pages[self.pagecount]["copies"]
103        if epcopies != bpcopies :
104            self.logdebug("ERROR: discrepancy between beginPage (%i) and endPage (%i) copies" % (bpcopies, epcopies))
105        return 2        # Length of a page footer
106       
107    def beginBand(self, nextpos) :
108        """Indicates the beginning of a new band."""
109        bandlength = unpack(self.unpackLong, self.minfile[nextpos+6:nextpos+10])[0]
110        return bandlength + 10 # Length of a band header - length of checksum
111       
112    def littleEndian(self) :
113        """Toggles to little endianness."""
114        self.unpackType = { 1 : "B", 2 : "<H", 4 : "<I" }
115        self.unpackShort = self.unpackType[2]
116        self.unpackLong = self.unpackType[4]
117        return 0
118       
119    def bigEndian(self) :
120        """Toggles to big endianness."""
121        self.unpackType = { 1 : "B", 2 : ">H", 4 : ">I" }
122        self.unpackShort = self.unpackType[2]
123        self.unpackLong = self.unpackType[4]
124        return 0
125   
126    def escape(self, nextpos) :   
127        """Handles the ESC code."""
128        pos = endpos = nextpos
129        minfile = self.minfile
130        if minfile[pos : pos+8] == r"%-12345X" :
131            endpos = pos + 9
132            endmark = chr(0x0c) + chr(0x00) + chr(0x1b)
133            asciilimit = chr(0x80)
134            quotes = 0
135            while (minfile[endpos] not in endmark) and \
136                   ((minfile[endpos] < asciilimit) or (quotes % 2)) :
137                if minfile[endpos] == '"' :
138                    quotes += 1
139                endpos += 1
140               
141            # Store this in a per page mapping.   
142            # NB : First time will be at page 0 (i.e. **before** page 1) !
143            stuff = self.escapedStuff.setdefault(self.pagecount, [])
144            stuff.append(minfile[pos : endpos])
145            self.logdebug("Escaped datas : [%s]" % repr(minfile[pos : endpos]))
146        return endpos - pos
147       
148    def maybeEOF(self, nextpos) :   
149        """Tries to detect the EOF marker."""
150        if self.minfile[nextpos:nextpos+9] == self.eofmarker :
151            return 9
152        else :   
153            return 0
154       
155    def getJobSize(self) :
156        """Counts pages in a QPDL (SPL2) document.
157       
158           Algorithm by Jerome Alet.
159           
160           The documentation used for this was :
161         
162           Sp�fication Technique (documentation non officielle)
163           Le Language SPL2
164           par Aur�en Croc
165           http://splix.ap2c.org
166        """
167        # Initialize table of tags
168        self.tags = [ lambda pos : 0 ] * 256   
169        self.tags[0x00] = self.beginPage
170        self.tags[0x01] = self.endPage
171        self.tags[0x09] = self.maybeEOF
172        self.tags[0x0c] = self.beginBand
173        self.tags[0x1b] = self.escape # The escape code
174       
175        self.eofmarker = "\033%-12345X"
176       
177        infileno = self.infile.fileno()
178        self.pages = { 0 : { "copies" : 1, 
179                             "orientation" : "Default", 
180                             "mediatype" : "Plain", 
181                             "mediasize" : "Default", 
182                             "mediasource" : "Default", 
183                             "duplex" : None,
184                           } 
185                     }     
186        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
187        self.pagecount = 0
188        self.escapedStuff = {}   # For escaped datas, mostly PJL commands
189        self.bigEndian()
190        pos = 0
191        tags = self.tags
192        try :
193            try :
194                while 1 :
195                    tag = ord(minfile[pos])
196                    pos += 1
197                    pos += tags[tag](pos)
198            except IndexError : # EOF ?           
199                pass
200        finally :
201            self.minfile.close()
202           
203        defaultduplexmode = "Simplex"
204        defaultpapersize = ""
205        defaultpjlcopies = 1   
206        oldpjlcopies = -1
207        oldduplexmode = ""
208        oldpapersize = ""
209        for pnum in range(1, self.pagecount + 1) :
210            # NB : is number of copies is 0, the page won't be output
211            # but the formula below is still correct : we want
212            # to decrease the total number of pages in this case.
213            page = self.pages.get(pnum, self.pages.get(1, { "copies" : 1, "mediasize" : "Default", "duplex" : None }))
214            pjlstuff = self.escapedStuff.get(pnum, self.escapedStuff.get(0, []))
215            if pjlstuff :
216                pjlparser = pjl.PJLParser("".join(pjlstuff))
217                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
218                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
219                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
220                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
221                if nbdefaultcopies > -1 :
222                    defaultpjlcopies = nbdefaultcopies
223                if nbdefaultqty > -1 :
224                    defaultpjlcopies = nbdefaultqty
225                if nbcopies > -1 :
226                    pjlcopies = nbcopies
227                elif nbqty > -1 :
228                    pjlcopies = nbqty
229                else :
230                    if oldpjlcopies == -1 :   
231                        pjlcopies = defaultpjlcopies
232                    else :   
233                        pjlcopies = oldpjlcopies   
234                if page["duplex"] :       
235                    duplexmode = page["duplex"]
236                else :   
237                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
238                    if defaultdm :
239                        if defaultdm.upper() == "ON" :
240                            defaultduplexmode = "Duplex"
241                        else :   
242                            defaultduplexmode = "Simplex"
243                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
244                    if envdm :
245                        if envdm.upper() == "ON" :
246                            duplexmode = "Duplex"
247                        else :   
248                            duplexmode = "Simplex"
249                    else :       
250                        if not oldduplexmode :
251                            duplexmode = defaultduplexmode
252                        else :   
253                            duplexmode = oldduplexmode
254                defaultps = pjlparser.default_variables.get("PAPER", "")
255                if defaultps :
256                    defaultpapersize = defaultps
257                envps = pjlparser.environment_variables.get("PAPER", "")
258                if envps :
259                    papersize = envps
260                else :   
261                    if not oldpapersize :
262                        papersize = defaultpapersize
263                    else :   
264                        papersize = oldpapersize
265            else :       
266                if oldpjlcopies == -1 :
267                    pjlcopies = defaultpjlcopies
268                else :   
269                    pjlcopies = oldpjlcopies
270                if not oldduplexmode :
271                    duplexmode = defaultduplexmode
272                else :   
273                    duplexmode = oldduplexmode
274                if not oldpapersize :   
275                    papersize = defaultpapersize
276                else :   
277                    papersize = oldpapersize
278                duplexmode = oldduplexmode
279                papersize = oldpapersize or page["mediasize"]
280            if page["mediasize"] != "Default" :
281                papersize = page["mediasize"]
282            if not duplexmode :   
283                duplexmode = oldduplexmode or defaultduplexmode
284            oldpjlcopies = pjlcopies   
285            oldduplexmode = duplexmode
286            oldpapersize = papersize
287            copies = max(pjlcopies, page["copies"]) # Was : pjlcopies * page["copies"]
288            self.pagecount += (copies - 1)
289            self.logdebug("%s*%s*%s*%s" % (copies, 
290                                           papersize, 
291                                           page["mediasource"], 
292                                           duplexmode))
293        return self.pagecount
Note: See TracBrowser for help on using the browser.