Index: /pkpgcounter/trunk/pkpgpdls/qpdl.py
===================================================================
--- /pkpgcounter/trunk/pkpgpdls/qpdl.py (revision 386)
+++ /pkpgcounter/trunk/pkpgpdls/qpdl.py (revision 386)
@@ -0,0 +1,319 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+#
+# pkpgcounter : a generic Page Description Language parser
+#
+# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+#
+
+"""This modules implements a page counter for QPDL (aka SPL2) documents."""
+
+import sys
+import os
+import mmap
+from struct import unpack
+
+import pdlparser
+import pjl
+
+class Parser(pdlparser.PDLParser) :
+    """A parser for QPDL (aka SPL2) documents."""
+    mediasizes = { 
+                    # The first values are identical to that of PCLXL
+                    0 : "Letter",
+                    1 : "Legal",
+                    2 : "A4",
+                    3 : "Executive",
+                    4 : "Ledger",
+                    5 : "A3",
+                    6 : "COM10Envelope",
+                    7 : "MonarchEnvelope",
+                    8 : "C5Envelope",
+                    9 : "DLEnvelope",
+                    10 : "JB4",
+                    11 : "JB5",
+                    12 : "B5Envelope",
+                    12 : "B5",
+                    14 : "JPostcard",
+                    15 : "JDoublePostcard",
+                    16 : "A5",
+                    17 : "A6",
+                    18 : "JB6",
+                    21 : "Custom",
+                    23 : "C6",
+                    24 : "Folio",
+                 }   
+                 
+    mediasources = {             
+                     # Again, values are identical to that of PCLXL
+                     0 : "Default",
+                     1 : "Auto",
+                     2 : "Manual",
+                     3 : "MultiPurpose",
+                     4 : "UpperCassette",
+                     5 : "LowerCassette",
+                     6 : "EnvelopeTray",
+                     7 : "ThirdCassette",
+                   }
+            
+    def isValid(self) :    
+        """Returns 1 if data is PCLXL aka PCL6, else 0."""
+        if ((self.firstblock[:128].find("\033%-12345X") != -1) and \
+             ((self.firstblock.find("LANGUAGE=QPDL") != -1) or \
+              (self.firstblock.find("LANGUAGE = QPDL") != -1))) :
+            self.logdebug("DEBUG: Input file is in the QPDL (aka SPL2) format.")
+            return 1
+        else :    
+            return 0
+            
+    def beginPage(self, nextpos) :
+        """Indicates the beginning of a new page, and extracts media information."""
+        self.pagecount += 1
+        
+        copies = unpack(self.unpackShort, self.minfile[nextpos+1:nextpos+3])[0]
+        mediasize = ord(self.minfile[nextpos+3])
+        mediasource = ord(self.minfile[nextpos+8])
+        duplexmode = unpack(self.unpackShort, self.minfile[nextpos+10:nextpos+12])[0]
+        
+        self.pages[self.pagecount] = { "copies" : copies, 
+                                       "mediasize" : self.mediasizes.get(mediasize, str(mediasize)),
+                                       "mediasource" : self.mediasources.get(mediasource, str(mediasource)),
+                                       "duplex" : duplexmode,
+                                     } 
+        return 16       # Length of a page header
+        
+    def endPage(self, nextpos) :    
+        """Indicates the end of a page."""
+        epcopies = unpack(self.unpackShort, self.minfile[nextpos:nextpos+2])[0]
+        bpcopies = self.pages[self.pagecount]["copies"]
+        if epcopies != bpcopies :
+            self.logdebug("ERROR: discrepancy between beginPage (%i) and endPage (%i) copies" % (bpcopies, epcopies))
+        return 2        # Length of a page footer
+        
+    def beginBand(self, nextpos) :
+        """Indicates the beginning of a new band."""
+        bandlength = unpack(self.unpackLong, self.minfile[nextpos+6:nextpos+10])[0]
+        return bandlength + 10 # Length of a band header - length of checksum
+        
+    def littleEndian(self) :
+        """Toggles to little endianness."""
+        self.unpackType = { 1 : "B", 2 : "<H", 4 : "<I" }
+        self.unpackShort = self.unpackType[2]
+        self.unpackLong = self.unpackType[4]
+        return 0
+        
+    def bigEndian(self) :
+        """Toggles to big endianness."""
+        self.unpackType = { 1 : "B", 2 : ">H", 4 : ">I" }
+        self.unpackShort = self.unpackType[2]
+        self.unpackLong = self.unpackType[4]
+        return 0
+    
+    def escape(self, nextpos) :    
+        """Handles the ESC code."""
+        pos = endpos = nextpos
+        minfile = self.minfile
+        if minfile[pos : pos+8] == r"%-12345X" :
+            endpos = pos + 9
+            endmark = chr(0x0c) + chr(0x00) + chr(0x1b)
+            asciilimit = chr(0x80)
+            quotes = 0
+            while (minfile[endpos] not in endmark) and \
+                   ((minfile[endpos] < asciilimit) or (quotes % 2)) :
+                if minfile[endpos] == '"' :
+                    quotes += 1
+                endpos += 1
+                
+            # Store this in a per page mapping.    
+            # NB : First time will be at page 0 (i.e. **before** page 1) !
+            stuff = self.escapedStuff.setdefault(self.pagecount, [])
+            stuff.append(minfile[pos : endpos])
+            self.logdebug("Escaped datas : [%s]" % repr(minfile[pos : endpos]))
+        return endpos - pos
+        
+    def maybeEOF(self, nextpos) :    
+        """Tries to detect the EOF marker."""
+        if self.minfile[nextpos:nextpos+9] == self.eofmarker :
+            return 9
+        else :    
+            return 0
+        
+    def getJobSize(self) :
+        """Counts pages in a QPDL (SPL2) document.
+        
+           Algorithm by Jerome Alet.
+           
+           The documentation used for this was :
+         
+           Sp�fication Technique (documentation non officielle)
+           Le Language SPL2
+           par Aur�en Croc
+           http://splix.ap2c.org
+        """
+        # Initialize table of tags
+        self.tags = [ lambda pos : 0 ] * 256    
+        self.tags[0x00] = self.beginPage
+        self.tags[0x01] = self.endPage
+        self.tags[0x09] = self.maybeEOF
+        self.tags[0x0c] = self.beginBand
+        self.tags[0x1b] = self.escape # The escape code
+        
+        self.eofmarker = "\033%-12345X"
+        
+        infileno = self.infile.fileno()
+        self.pages = { 0 : { "copies" : 1, 
+                             "orientation" : "Default", 
+                             "mediatype" : "Plain", 
+                             "mediasize" : "Default", 
+                             "mediasource" : "Default", 
+                             "duplex" : None,
+                           } 
+                     }      
+        self.minfile = minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
+        self.pagecount = 0
+        self.escapedStuff = {}   # For escaped datas, mostly PJL commands
+        self.bigEndian()
+        pos = 0
+        tags = self.tags
+        try :
+            try :
+                while 1 :
+                    tag = ord(minfile[pos])
+                    pos += 1
+                    pos += tags[tag](pos)
+            except IndexError : # EOF ?            
+                pass
+        finally :
+            self.minfile.close()
+            
+        defaultduplexmode = "Simplex"
+        defaultpapersize = ""
+        defaultpjlcopies = 1    
+        oldpjlcopies = -1
+        oldduplexmode = ""
+        oldpapersize = ""
+        for pnum in range(1, self.pagecount + 1) :
+            # NB : is number of copies is 0, the page won't be output
+            # but the formula below is still correct : we want 
+            # to decrease the total number of pages in this case.
+            page = self.pages.get(pnum, self.pages.get(1, { "copies" : 1, "mediasize" : "Default", "duplex" : None }))
+            pjlstuff = self.escapedStuff.get(pnum, self.escapedStuff.get(0, []))
+            if pjlstuff :
+                pjlparser = pjl.PJLParser("".join(pjlstuff))
+                nbdefaultcopies = int(pjlparser.default_variables.get("COPIES", -1))
+                nbcopies = int(pjlparser.environment_variables.get("COPIES", -1))
+                nbdefaultqty = int(pjlparser.default_variables.get("QTY", -1))
+                nbqty = int(pjlparser.environment_variables.get("QTY", -1))
+                if nbdefaultcopies > -1 :
+                    defaultpjlcopies = nbdefaultcopies
+                if nbdefaultqty > -1 :
+                    defaultpjlcopies = nbdefaultqty
+                if nbcopies > -1 :
+                    pjlcopies = nbcopies
+                elif nbqty > -1 :
+                    pjlcopies = nbqty
+                else :
+                    if oldpjlcopies == -1 :    
+                        pjlcopies = defaultpjlcopies
+                    else :    
+                        pjlcopies = oldpjlcopies    
+                if page["duplex"] :        
+                    duplexmode = page["duplex"]
+                else :    
+                    defaultdm = pjlparser.default_variables.get("DUPLEX", "")
+                    if defaultdm :
+                        if defaultdm.upper() == "ON" :
+                            defaultduplexmode = "Duplex"
+                        else :    
+                            defaultduplexmode = "Simplex"
+                    envdm = pjlparser.environment_variables.get("DUPLEX", "")
+                    if envdm :
+                        if envdm.upper() == "ON" :
+                            duplexmode = "Duplex"
+                        else :    
+                            duplexmode = "Simplex"
+                    else :        
+                        if not oldduplexmode :
+                            duplexmode = defaultduplexmode
+                        else :    
+                            duplexmode = oldduplexmode
+                defaultps = pjlparser.default_variables.get("PAPER", "")
+                if defaultps :
+                    defaultpapersize = defaultps
+                envps = pjlparser.environment_variables.get("PAPER", "")
+                if envps :
+                    papersize = envps
+                else :    
+                    if not oldpapersize :
+                        papersize = defaultpapersize
+                    else :    
+                        papersize = oldpapersize
+            else :        
+                if oldpjlcopies == -1 :
+                    pjlcopies = defaultpjlcopies
+                else :    
+                    pjlcopies = oldpjlcopies
+                if not oldduplexmode :
+                    duplexmode = defaultduplexmode
+                else :    
+                    duplexmode = oldduplexmode
+                if not oldpapersize :    
+                    papersize = defaultpapersize
+                else :    
+                    papersize = oldpapersize
+                duplexmode = oldduplexmode
+                papersize = oldpapersize or page["mediasize"]
+            if page["mediasize"] != "Default" :
+                papersize = page["mediasize"]
+            if not duplexmode :    
+                duplexmode = oldduplexmode or defaultduplexmode
+            oldpjlcopies = pjlcopies    
+            oldduplexmode = duplexmode
+            oldpapersize = papersize
+            copies = pjlcopies * page["copies"]
+            self.pagecount += (copies - 1)
+            self.logdebug("%s*%s*%s*%s" % (copies, 
+                                           papersize, 
+                                           page["mediasource"], 
+                                           duplexmode))
+        return self.pagecount
+        
+def test() :        
+    """Test function."""
+    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
+        sys.argv.append("-")
+    totalsize = 0    
+    for arg in sys.argv[1:] :
+        if arg == "-" :
+            infile = sys.stdin
+            mustclose = 0
+        else :    
+            infile = open(arg, "rb")
+            mustclose = 1
+        try :
+            parser = Parser(infile, debug=1)
+            totalsize += parser.getJobSize()
+        except pdlparser.PDLParserError, msg :    
+            sys.stderr.write("ERROR: %s\n" % msg)
+            sys.stderr.flush()
+        if mustclose :    
+            infile.close()
+    print "%s" % totalsize
+    
+if __name__ == "__main__" :    
+    test()
Index: /pkpgcounter/trunk/pkpgpdls/analyzer.py
===================================================================
--- /pkpgcounter/trunk/pkpgpdls/analyzer.py (revision 383)
+++ /pkpgcounter/trunk/pkpgpdls/analyzer.py (revision 386)
@@ -30,5 +30,5 @@
 
 import version, pdlparser, postscript, pdf, pcl345, pclxl, \
-       escp2, dvi, tiff, ooo, zjstream
+       escp2, dvi, tiff, ooo, zjstream, qpdl
 import inkcoverage
 
@@ -167,7 +167,9 @@
             raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
         else :    
+            # IMPORTANT : the order is important below. FIXME.
             for module in (postscript, \
                            pclxl, \
                            pdf, \
+                           qpdl, \
                            pcl345, \
                            escp2, \
Index: /pkpgcounter/trunk/bin/pkpgcounter
===================================================================
--- /pkpgcounter/trunk/bin/pkpgcounter (revision 375)
+++ /pkpgcounter/trunk/bin/pkpgcounter (revision 386)
@@ -49,6 +49,7 @@
     * OpenDocument (ISO/IEC DIS 26300)
     * Zenographics ZjStream
+    * Samsung QPDL (aka SPL2)
 
-The three latter ones, as well as some TIFF documents, are currently 
+The four latter ones, as well as some TIFF documents, are currently 
 only supported in page counting mode. 
 
Index: /pkpgcounter/trunk/man/pkpgcounter.1
===================================================================
--- /pkpgcounter/trunk/man/pkpgcounter.1 (revision 378)
+++ /pkpgcounter/trunk/man/pkpgcounter.1 (revision 386)
@@ -1,8 +1,8 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.36.
-.TH PKPGCOUNTER "1" "August 2006" "C@LL - Conseil Internet & Logiciels Libres" "User Commands"
+.TH PKPGCOUNTER "1" "September 2006" "C@LL - Conseil Internet & Logiciels Libres" "User Commands"
 .SH NAME
-pkpgcounter \- manual page for pkpgcounter 1.84
+pkpgcounter \- manual page for pkpgcounter 1.85alpha
 .SH DESCRIPTION
-pkpgcounter v1.84 (c) 2003, 2004, 2005, 2006 Jerome Alet
+pkpgcounter v1.85alpha (c) 2003, 2004, 2005, 2006 Jerome Alet
 .PP
 pkpgcounter is a generic Page Description Language parser.
@@ -25,6 +25,7 @@
 * OpenDocument (ISO/IEC DIS 26300)
 * Zenographics ZjStream
+* Samsung QPDL (aka SPL2)
 .PP
-The three latter ones, as well as some TIFF documents, are currently
+The four latter ones, as well as some TIFF documents, are currently
 only supported in page counting mode.
 .PP
Index: /pkpgcounter/trunk/CREDITS
===================================================================
--- /pkpgcounter/trunk/CREDITS (revision 368)
+++ /pkpgcounter/trunk/CREDITS (revision 386)
@@ -29,4 +29,7 @@
   - Daniel Franklin : pkpgcounter's algorithm for the computation 
     of ink coverage comes from Daniel's PrintBill project.
+    
+  - Aur�en Croc : reverse engineered Samsung QPDL and published  
+                    a language specification.
 
 
Index: /pkpgcounter/trunk/README
===================================================================
--- /pkpgcounter/trunk/README (revision 375)
+++ /pkpgcounter/trunk/README (revision 386)
@@ -44,5 +44,7 @@
         - Zenographics ZjStream
         
-The three latter ones, as well as some TIFF documents, are currently 
+        - Samsung QPDL (aka SPL2)
+        
+The four latter ones, as well as some TIFF documents, are currently 
 only supported in page counting mode. 
 
