root / pykota / trunk / bin / pkpgcounter @ 1461

Revision 1461, 8.8 kB (checked in by jalet, 20 years ago)

Skeleton for PCLXL aka PCL6
Added the "potential" fix for rastertoprinter's output

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# pkpgcounter, a smart software page counter
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22#
23# $Id$
24#
25# $Log$
26# Revision 1.7  2004/05/07 23:08:21  jalet
27# Skeleton for PCLXL aka PCL6
28# Added the "potential" fix for rastertoprinter's output
29#
30# Revision 1.6  2004/05/06 21:19:27  jalet
31# Doesn't exit anymore on the first nul byte
32#
33# Revision 1.5  2004/05/06 12:37:29  jalet
34# pkpgcounter : comments
35# pkprinters : when --add is used, existing printers are now skipped.
36#
37# Revision 1.4  2004/05/04 12:21:55  jalet
38# Now uses mmap in PCL mode
39#
40# Revision 1.3  2004/05/04 04:39:26  jalet
41# Better PCL support
42#
43# Revision 1.2  2004/05/04 03:14:26  jalet
44# fixed copy&paste problem in pkpgcounter
45#
46# Revision 1.1  2004/04/08 17:07:42  jalet
47# pkpgcounter added
48#
49#
50
51import sys
52import os
53import mmap
54import tempfile
55
56def ispostscript(data) :   
57    """Returns 1 if data is PostScript, else 0."""
58    if data.startswith("%!") or \
59       data.startswith("\004%!") or \
60       data.startswith("\033%-12345X%!PS") or \
61       ((data[:128].find("\033%-12345X") != -1) and \
62         ((data.find("LANGUAGE=POSTSCRIPT") != -1) or \
63          (data.find("LANGUAGE = POSTSCRIPT") != -1) or \
64          (data.find("LANGUAGE = Postscript") != -1))) :
65        return 1
66    else :   
67        return 0
68   
69def ispcl(data) :   
70    """Returns 1 if data is PCL, else 0."""
71    if data.startswith("\033E\033") or \
72       ((data[:128].find("\033%-12345X") != -1) and \
73         ((data.find("LANGUAGE=PCL") != -1) or \
74          (data.find("LANGUAGE = PCL") != -1) or \
75          (data.find("LANGUAGE = Pcl") != -1))) :
76        return 1
77    else :   
78        return 0
79   
80def ispclxl(data) :   
81    """Returns 1 if data is PCLXL aka PCL6, else 0."""
82    if ((data[:128].find("\033%-12345X") != -1) and \
83         (data.find(" HP-PCL XL;") != -1) and \
84         ((data.find("LANGUAGE=PCLXL") != -1) or \
85          (data.find("LANGUAGE = PCLXL") != -1))) :
86        return 1
87    else :   
88        return 0
89   
90def postscript(infile) :
91    """Count pages in a DSC compliant PostScript document."""
92    pagecount = 0
93    pagenum = None
94    while 1 :
95        line = infile.readline()
96        if not line :
97            break
98        if line.startswith("%%Page: ") :
99            pagecount += 1
100    return pagecount
101   
102def pcl(infile) :   
103    """Count pages in a PCL5 document."""
104    #
105    # Algorithm from pclcount
106    # (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
107    # published under the terms of the GNU General Public Licence v2.
108    #
109    # Backported from C to Python by Jerome Alet, then enhanced
110    # with more PCL tags detected. I think all the necessary PCL tags
111    # are recognized to correctly handle PCL5 files wrt their number
112    # of pages. The documentation used for this was :
113    #
114    # HP PCL/PJL Reference Set
115    # PCL5 Printer Language Technical Quick Reference Guide
116    # http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
117    #
118    infileno = infile.fileno()
119    infile = mmap.mmap(infileno, os.fstat(infileno).st_size, access=mmap.ACCESS_READ)
120    tagsends = { "&n" : "W", 
121                 "&b" : "W", 
122                 "*i" : "W", 
123                 "*l" : "W", 
124                 "*m" : "W", 
125                 "*v" : "W", 
126                 "*c" : "W", 
127                 "(f" : "W", 
128                 "*b" : "VW",
129                 "(s" : "W", 
130                 ")s" : "W", 
131                 "&p" : "X", 
132                 "&l" : "X" } 
133    copies = 1
134    pagecount = resets = 0
135    tag = None
136    position = 0
137    while 1 :
138        try :
139            char = infile[position]
140        except IndexError :     # EOF   
141            break
142        position += 1
143        if char == "\014" :   
144            pagecount += 1
145        elif char == "\033" :   
146            #
147            #     <ESC>*b###W -> Start of a raster data row/block
148            #     <ESC>*b###V -> Start of a raster data plane
149            #     <ESC>*c###W -> Start of a user defined pattern
150            #     <ESC>*i###W -> Start of a viewing illuminant block
151            #     <ESC>*l###W -> Start of a color lookup table
152            #     <ESC>*m###W -> Start of a download dither matrix block
153            #     <ESC>*v###W -> Start of a configure image data block
154            #     <ESC>(s###W -> Start of a characters description block
155            #     <ESC>)s###W -> Start of a fonts description block
156            #     <ESC>(f###W -> Start of a symbol set block
157            #     <ESC>&b###W -> Start of configuration data block
158            #     <ESC>&l###X -> Number of copies
159            #     <ESC>&n###W -> Starts an alphanumeric string ID block
160            #     <ESC>&p###X -> Start of a non printable characters block
161            #
162            tagstart = infile[position]
163            position += 1
164            if tagstart in "E9=YZ" : # one byte PCL tag
165                if tagstart == "E" :
166                    resets += 1
167                continue             # skip to next tag
168            tag = tagstart + infile[position]
169            position += 1
170            try :
171                tagend = tagsends[tag]
172            except KeyError :   
173                pass    # Unsupported PCL tag
174            else :   
175                # Now read the numeric argument
176                size = 0
177                while 1 :
178                    char = infile[position]
179                    position += 1
180                    if not char.isdigit() :
181                        break
182                    size = (size * 10) + int(char)   
183                if char in tagend :   
184                    if tag == "&l" :
185                        copies = size
186                    else :   
187                        # doing a read will prevent the seek
188                        # for unseekable streams.
189                        # we just ignore the block anyway.
190                        if tag == "&n" : 
191                            # we have to take care of the operation id byte
192                            # which is before the string itself
193                            size += 1
194                        position += size   
195                       
196    # if pagecount is still 0, we will return the number
197    # of resets instead of the number of form feed characters.
198    # but the number of resets is always at least 2 with a valid
199    # pcl file : one at the very start and one at the very end
200    # of the job's data. So we substract 2 from the number of
201    # resets. And since on our test data we needed to substract
202    # 1 more, we finally substract 3, and will test several
203    # PCL files with this. If resets < 2, then the file is
204    # probably not a valid PCL file, so we return 0
205    if not pagecount :
206        return copies * (resets - 3) * (resets > 2)
207    else :
208        return copies * pagecount
209
210def pclxl(infile) :     
211    """Count pages in a PCL6 aka PCLXL document."""
212    raise TypeError, "Sorry but PCL6, aka PCLXL, is not supported yet.\n"
213   
214def smartpagecounter(filename) :
215    """Autodetects file format and returns number of pages."""
216    if filename == "-" :
217        # we must read from stdin
218        # but since stdin is not seekable, we have to use a temporary
219        # file instead.
220        infile = tempfile.TemporaryFile()
221        while 1 :
222            data = sys.stdin.read(256 * 1024) 
223            if not data :
224                break
225            infile.write(data)
226        infile.flush()   
227        infile.seek(0)
228    else :   
229        # normal file
230        infile = open(filename, "rb")
231       
232    # Try to detect file type by reading first block of datas   
233    firstblock = infile.read(1024)
234    infile.seek(0)
235    if ispostscript(firstblock) :
236        size = postscript(infile)
237    elif ispclxl(firstblock) :   
238        size = pclxl(infile)
239    elif ispcl(firstblock) :   
240        size = pcl(infile)
241    else :   
242        sys.stderr.write("ERROR : Unknown file format for %s\n" % filename)
243        size = 0
244    infile.close()   
245    return size
246   
247if __name__ == "__main__" :   
248    if len(sys.argv) < 2 :
249        sys.argv.append("-")
250       
251    totalsize = 0   
252    for arg in sys.argv[1:] :
253        totalsize += smartpagecounter(arg)
254    print "%s" % totalsize
Note: See TracBrowser for help on using the browser.