root / pkpgcounter / trunk / pdlanalyzer / pcl345.py @ 211

Revision 211, 16.8 kB (checked in by jerome, 19 years ago)

License changes because of the new snail mail address of the Free
Software Foundation.
Added -h|--help and -v|--version command line options handling.
v1.50 is now out.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Id Rev
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 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 2 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, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
21# $Id$
22#
23
24import sys
25import os
26import mmap
27
28from pdlanalyzer import pdlparser
29
30class PCL345Parser(pdlparser.PDLParser) :
31    """A parser for PCL3, PCL4, PCL5 documents."""
32    mediasizes = {  # ESC&l####A
33                    0 : "Default",
34                    1 : "Executive",
35                    2 : "Letter",
36                    3 : "Legal",
37                    6 : "Ledger", 
38                    25 : "A5",
39                    26 : "A4",
40                    27 : "A3",
41                    45 : "JB5",
42                    46 : "JB4",
43                    71 : "HagakiPostcard",
44                    72 : "OufukuHagakiPostcard",
45                    80 : "MonarchEnvelope",
46                    81 : "COM10Envelope",
47                    90 : "DLEnvelope",
48                    91 : "C5Envelope",
49                    100 : "B5Envelope",
50                    101 : "Custom",
51                 }   
52                 
53    mediasources = { # ESC&l####H
54                     0 : "Default",
55                     1 : "Main",
56                     2 : "Manual",
57                     3 : "ManualEnvelope",
58                     4 : "Alternate",
59                     5 : "OptionalLarge",
60                     6 : "EnvelopeFeeder",
61                     7 : "Auto",
62                     8 : "Tray1",
63                   }
64                   
65    orientations = { # ESC&l####O
66                     0 : "Portrait",
67                     1 : "Landscape",
68                     2 : "ReversePortrait",
69                     3 : "ReverseLandscape",
70                   }
71                   
72    mediatypes = { # ESC&l####M
73                     0 : "Plain",
74                     1 : "Bond",
75                     2 : "Special",
76                     3 : "Glossy",
77                     4 : "Transparent",
78                   }
79       
80    def setPageDict(self, pages, number, attribute, value) :
81        """Initializes a page dictionnary."""
82        dict = pages.setdefault(number, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait"})
83        dict[attribute] = value
84       
85    def getJobSize(self) :     
86        """Count pages in a PCL5 document.
87         
88           Should also work for PCL3 and PCL4 documents.
89           
90           Algorithm from pclcount
91           (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
92           published under the terms of the GNU General Public Licence v2.
93         
94           Backported from C to Python by Jerome Alet, then enhanced
95           with more PCL tags detected. I think all the necessary PCL tags
96           are recognized to correctly handle PCL5 files wrt their number
97           of pages. The documentation used for this was :
98         
99           HP PCL/PJL Reference Set
100           PCL5 Printer Language Technical Quick Reference Guide
101           http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13205/bpl13205.pdf
102        """
103        infileno = self.infile.fileno()
104        minfile = mmap.mmap(infileno, os.fstat(infileno)[6], prot=mmap.PROT_READ, flags=mmap.MAP_SHARED)
105        tagsends = { "&n" : "W", 
106                     "&b" : "W", 
107                     "*i" : "W", 
108                     "*l" : "W", 
109                     "*m" : "W", 
110                     "*v" : "W", 
111                     "*c" : "W", 
112                     "(f" : "W", 
113                     "(s" : "W", 
114                     ")s" : "W", 
115                     "&p" : "X", 
116                     # "&l" : "XHAOM",  # treated specially
117                     "&a" : "G", # TODO : 0 means next side, 1 front side, 2 back side
118                     "*g" : "W",
119                     "*r" : "sbABC",
120                     "*t" : "R",
121                     # "*b" : "VW", # treated specially because it occurs very often
122                   } 
123        pagecount = resets = ejects = backsides = startgfx = endgfx = 0
124        starb = ampl = ispcl3 = escstart = 0
125        mediasourcecount = mediasizecount = orientationcount = mediatypecount = 0
126        tag = None
127        pages = {}
128        pos = 0
129        try :
130            while 1 :
131                char = minfile[pos] ; pos += 1
132                if char == "\014" :   
133                    pagecount += 1
134                elif char == "\033" :   
135                    starb = ampl = 0
136                    #
137                    #     <ESC>*b###y#m###v###w... -> PCL3 raster graphics
138                    #     <ESC>*b###W -> Start of a raster data row/block
139                    #     <ESC>*b###V -> Start of a raster data plane
140                    #     <ESC>*c###W -> Start of a user defined pattern
141                    #     <ESC>*i###W -> Start of a viewing illuminant block
142                    #     <ESC>*l###W -> Start of a color lookup table
143                    #     <ESC>*m###W -> Start of a download dither matrix block
144                    #     <ESC>*v###W -> Start of a configure image data block
145                    #     <ESC>*r1A -> Start Gfx
146                    #     <ESC>(s###W -> Start of a characters description block
147                    #     <ESC>)s###W -> Start of a fonts description block
148                    #     <ESC>(f###W -> Start of a symbol set block
149                    #     <ESC>&b###W -> Start of configuration data block
150                    #     <ESC>&l###X -> Number of copies for current page
151                    #     <ESC>&n###W -> Starts an alphanumeric string ID block
152                    #     <ESC>&p###X -> Start of a non printable characters block
153                    #     <ESC>&a2G -> Back side when duplex mode as generated by rastertohp
154                    #     <ESC>*g###W -> Needed for planes in PCL3 output
155                    #     <ESC>&l###H (or only 0 ?) -> Eject if NumPlanes > 1, as generated by rastertohp. Also defines mediasource
156                    #     <ESC>&l###A -> mediasize
157                    #     <ESC>&l###O -> orientation
158                    #     <ESC>&l###M -> mediatype
159                    #     <ESC>*t###R -> gfx resolution
160                    #
161                    tagstart = minfile[pos] ; pos += 1
162                    if tagstart in "E9=YZ" : # one byte PCL tag
163                        if tagstart == "E" :
164                            resets += 1
165                        continue             # skip to next tag
166                    tag = tagstart + minfile[pos] ; pos += 1
167                    if tag == "*b" : 
168                        starb = 1
169                        tagend = "VW"
170                    elif tag == "&l" :   
171                        ampl = 1
172                        tagend = "XHAOM"
173                    else :   
174                        try :
175                            tagend = tagsends[tag]
176                        except KeyError :   
177                            continue # Unsupported PCL tag
178                    # Now read the numeric argument
179                    size = 0
180                    while 1 :
181                        char = minfile[pos] ; pos += 1
182                        if not char.isdigit() :
183                            break
184                        size = (size * 10) + int(char)   
185                    if char in tagend :   
186                        if tag == "&l" :
187                            if char == "X" : 
188                                self.setPageDict(pages, pagecount, "copies", size)
189                            elif char == "H" :
190                                self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
191                                mediasourcecount += 1
192                                ejects += 1 
193                            elif char == "A" :
194                                self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
195                                mediasizecount += 1
196                            elif char == "O" :
197                                self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
198                                orientationcount += 1
199                            elif char == "M" :
200                                self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
201                                mediatypecount += 1
202                        elif tag == "*r" :
203                            # Special tests for PCL3
204                            if (char == "s") and size :
205                                while 1 :
206                                    char = minfile[pos] ; pos += 1
207                                    if char == "A" :
208                                        break
209                            elif (char == "b") and (minfile[pos] == "C") and not size :
210                                ispcl3 = 1 # Certainely a PCL3 file
211                            startgfx += (char == "A") and (minfile[pos - 2] in ("0", "1", "2", "3")) # Start Gfx
212                            endgfx += (not size) and (char in ("C", "B")) # End Gfx
213                        elif tag == "*t" :   
214                            escstart += 1
215                        elif (tag == "&a") and (size == 2) :
216                            backsides += 1      # Back side in duplex mode
217                        else :   
218                            # we just ignore the block.
219                            if tag == "&n" : 
220                                # we have to take care of the operation id byte
221                                # which is before the string itself
222                                size += 1
223                            pos += size   
224                else :                           
225                    if starb :
226                        # special handling of PCL3 in which
227                        # *b introduces combined ESCape sequences
228                        size = 0
229                        while 1 :
230                            char = minfile[pos] ; pos += 1
231                            if not char.isdigit() :
232                                break
233                            size = (size * 10) + int(char)   
234                        if char in ("w", "v") :   
235                            ispcl3 = 1  # certainely a PCL3 document
236                            pos += size - 1
237                        elif char in ("y", "m") :   
238                            ispcl3 = 1  # certainely a PCL3 document
239                            pos -= 1    # fix position : we were ahead
240                    elif ampl :       
241                        # special handling of PCL3 in which
242                        # &l introduces combined ESCape sequences
243                        size = 0
244                        while 1 :
245                            char = minfile[pos] ; pos += 1
246                            if not char.isdigit() :
247                                break
248                            size = (size * 10) + int(char)   
249                        if char in ("a", "o", "h", "m") :   
250                            ispcl3 = 1  # certainely a PCL3 document
251                            pos -= 1    # fix position : we were ahead
252                            if char == "h" :
253                                self.setPageDict(pages, pagecount, "mediasource", self.mediasources.get(size, str(size)))
254                                mediasourcecount += 1
255                            elif char == "a" :
256                                self.setPageDict(pages, pagecount, "mediasize", self.mediasizes.get(size, str(size)))
257                                mediasizecount += 1
258                            elif char == "o" :
259                                self.setPageDict(pages, pagecount, "orientation", self.orientations.get(size, str(size)))
260                                orientationcount += 1
261                            elif char == "m" :
262                                self.setPageDict(pages, pagecount, "mediatype", self.mediatypes.get(size, str(size)))
263                                mediatypecount += 1
264        except IndexError : # EOF ?
265            minfile.close() # reached EOF
266                           
267        # if pagecount is still 0, we will use the number
268        # of resets instead of the number of form feed characters.
269        # but the number of resets is always at least 2 with a valid
270        # pcl file : one at the very start and one at the very end
271        # of the job's data. So we substract 2 from the number of
272        # resets. And since on our test data we needed to substract
273        # 1 more, we finally substract 3, and will test several
274        # PCL files with this. If resets < 2, then the file is
275        # probably not a valid PCL file, so we use 0
276       
277        if self.debug :
278            sys.stderr.write("pagecount : %s\n" % pagecount)
279            sys.stderr.write("resets : %s\n" % resets)
280            sys.stderr.write("ejects : %s\n" % ejects)
281            sys.stderr.write("backsides : %s\n" % backsides)
282            sys.stderr.write("startgfx : %s\n" % startgfx)
283            sys.stderr.write("endgfx : %s\n" % endgfx)
284            sys.stderr.write("mediasourcecount : %s\n" % mediasourcecount)
285            sys.stderr.write("mediasizecount : %s\n" % mediasizecount)
286            sys.stderr.write("orientationcount : %s\n" % orientationcount)
287            sys.stderr.write("mediatypecount : %s\n" % mediatypecount)
288            sys.stderr.write("escstart : %s\n" % escstart)
289       
290#        if not pagecount :
291#            pagecount = (pagecount or ((resets - 3) * (resets > 2)))
292#        else :   
293#            # here we add counters for other ways new pages may have
294#            # been printed and ejected by the printer
295#            pagecount += ejects + backsides
296#       
297#        # now handle number of copies for each page (may differ).
298#        # in duplex mode, number of copies may be sent only once.
299#        for pnum in range(pagecount) :
300#            # if no number of copies defined, take the preceding one else the one set before any page else 1.
301#            page = pages.get(pnum, pages.get(pnum - 1, pages.get(0, { "copies" : 1 })))
302#            pagecount += (page["copies"] - 1)
303#           
304#        # in PCL3 files, there's one Start Gfx tag per page
305#        if ispcl3 :
306#            if endgfx == int(startgfx / 2) : # special case for cdj1600
307#                pagecount = endgfx
308#            elif startgfx :
309#                pagecount = startgfx
310#            elif endgfx :   
311#                pagecount = endgfx
312               
313           
314        if pagecount == mediasourcecount == escstart : 
315            pass        # should be OK.
316        elif (not startgfx) and (not endgfx) :
317            pagecount = ejects or pagecount
318        elif startgfx == endgfx :   
319            pagecount = startgfx
320        elif startgfx == (endgfx - 1) :   
321            pagecount = startgfx
322        elif (startgfx == 1) and not endgfx :   
323            pass
324        else :   
325            pagecount = abs(startgfx - endgfx)
326           
327        if self.debug :       
328            for pnum in range(pagecount) :
329                # if no number of copies defined, take the preceding one else the one set before any page else 1.
330                page = pages.get(pnum, pages.get(pnum - 1, pages.get(0, { "copies" : 1, "mediasource" : "Main", "mediasize" : "Default", "mediatype" : "Plain", "orientation" : "Portrait"})))
331                sys.stderr.write("%s*%s*%s*%s*%s\n" % (page["copies"], page["mediatype"], page["mediasize"], page["orientation"], page["mediasource"]))
332               
333        return pagecount
334       
335def test() :       
336    """Test function."""
337    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
338        sys.argv.append("-")
339    totalsize = 0   
340    for arg in sys.argv[1:] :
341        if arg == "-" :
342            infile = sys.stdin
343            mustclose = 0
344        else :   
345            infile = open(arg, "rb")
346            mustclose = 1
347        try :
348            parser = PCL345Parser(infile, debug=1)
349            totalsize += parser.getJobSize()
350        except pdlparser.PDLParserError, msg :   
351            sys.stderr.write("ERROR: %s\n" % msg)
352            sys.stderr.flush()
353        if mustclose :   
354            infile.close()
355    print "%s" % totalsize
356   
357if __name__ == "__main__" :   
358    test()
Note: See TracBrowser for help on using the browser.