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

Revision 463, 11.9 kB (checked in by jerome, 17 years ago)

Licensing terms changed to GNU GPL v3.0 or higher.
Removed old PCL3/4/5 parser which for a long time now wasn't used
anymore, and for which I was not the original copyright owner.
Version number bumped to 3.00alpha to reflect licensing changes.

  • 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
294       
295if __name__ == "__main__" :   
296    pdlparser.test(Parser)
Note: See TracBrowser for help on using the browser.