~openerp/openobject-server/web-dashboard

« back to all changes in this revision

Viewing changes to bin/reportlab/pdfbase/pdfutils.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#Copyright ReportLab Europe Ltd. 2000-2004
 
2
#see license.txt for license details
 
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/pdfutils.py
 
4
__version__=''' $Id$ '''
 
5
__doc__=''
 
6
# pdfutils.py - everything to do with images, streams,
 
7
# compression, and some constants
 
8
 
 
9
import os
 
10
from reportlab import rl_config
 
11
from string import join, replace, strip, split
 
12
from reportlab.lib.utils import getStringIO, ImageReader
 
13
 
 
14
LINEEND = '\015\012'
 
15
 
 
16
def _chunker(src,dst=[],chunkSize=60):
 
17
    for i in xrange(0,len(src),chunkSize):
 
18
        dst.append(src[i:i+chunkSize])
 
19
    return dst
 
20
 
 
21
##########################################################
 
22
#
 
23
#  Image compression helpers.  Preprocessing a directory
 
24
#  of images will offer a vast speedup.
 
25
#
 
26
##########################################################
 
27
 
 
28
def cacheImageFile(filename, returnInMemory=0, IMG=None):
 
29
    "Processes image as if for encoding, saves to a file with .a85 extension."
 
30
 
 
31
    from reportlab.lib.utils import open_for_read
 
32
    import zlib
 
33
 
 
34
    cachedname = os.path.splitext(filename)[0] + '.a85'
 
35
    if filename==cachedname:
 
36
        if cachedImageExists(filename):
 
37
            if returnInMemory: return split(open_for_read(cachedname).read(),LINEEND)[:-1]
 
38
        else:
 
39
            raise IOError, 'No such cached image %s' % filename
 
40
    else:
 
41
        img = ImageReader(filename)
 
42
        if IMG is not None: IMG.append(img)
 
43
 
 
44
        imgwidth, imgheight = img.getSize()
 
45
        raw = img.getRGBData()
 
46
 
 
47
        code = []
 
48
        # this describes what is in the image itself
 
49
        code.append('BI')
 
50
        code.append('/W %s /H %s /BPC 8 /CS /RGB /F [/A85 /Fl]' % (imgwidth, imgheight))
 
51
        code.append('ID')
 
52
        #use a flate filter and Ascii Base 85
 
53
        assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image")
 
54
        compressed = zlib.compress(raw)   #this bit is very fast...
 
55
        encoded = _AsciiBase85Encode(compressed) #...sadly this may not be
 
56
 
 
57
        #append in blocks of 60 characters
 
58
        _chunker(encoded,code)
 
59
 
 
60
        code.append('EI')
 
61
        if returnInMemory: return code
 
62
 
 
63
        #save it to a file
 
64
        f = open(cachedname,'wb')
 
65
        f.write(join(code, LINEEND)+LINEEND)
 
66
        f.close()
 
67
        if rl_config.verbose:
 
68
            print 'cached image as %s' % cachedname
 
69
 
 
70
 
 
71
def preProcessImages(spec):
 
72
    """Preprocesses one or more image files.
 
73
 
 
74
    Accepts either a filespec ('C:\mydir\*.jpg') or a list
 
75
    of image filenames, crunches them all to save time.  Run this
 
76
    to save huge amounts of time when repeatedly building image
 
77
    documents."""
 
78
 
 
79
    import types, glob
 
80
 
 
81
    if type(spec) is types.StringType:
 
82
        filelist = glob.glob(spec)
 
83
    else:  #list or tuple OK
 
84
        filelist = spec
 
85
 
 
86
    for filename in filelist:
 
87
        if cachedImageExists(filename):
 
88
            if rl_config.verbose:
 
89
                print 'cached version of %s already exists' % filename
 
90
        else:
 
91
            cacheImageFile(filename)
 
92
 
 
93
 
 
94
def cachedImageExists(filename):
 
95
    """Determines if a cached image already exists for a given file.
 
96
 
 
97
    Determines if a cached image exists which has the same name
 
98
    and equal or newer date to the given file."""
 
99
    cachedname = os.path.splitext(filename)[0] + '.a85'
 
100
    if os.path.isfile(cachedname):
 
101
        #see if it is newer
 
102
        original_date = os.stat(filename)[8]
 
103
        cached_date = os.stat(cachedname)[8]
 
104
        if original_date > cached_date:
 
105
            return 0
 
106
        else:
 
107
            return 1
 
108
    else:
 
109
        return 0
 
110
 
 
111
 
 
112
##############################################################
 
113
#
 
114
#            PDF Helper functions
 
115
#
 
116
##############################################################
 
117
 
 
118
try:
 
119
    from _rl_accel import escapePDF, _instanceEscapePDF
 
120
    _escape = escapePDF
 
121
except ImportError:
 
122
    try:
 
123
        from reportlab.lib._rl_accel import escapePDF, _instanceEscapePDF
 
124
        _escape = escapePDF
 
125
    except ImportError:
 
126
        _instanceEscapePDF=None
 
127
        if rl_config.sys_version>='2.1':
 
128
            _ESCAPEDICT={}
 
129
            for c in range(0,256):
 
130
                if c<32 or c>=127:
 
131
                    _ESCAPEDICT[chr(c)]= '\\%03o' % c
 
132
                elif c in (ord('\\'),ord('('),ord(')')):
 
133
                    _ESCAPEDICT[chr(c)] = '\\'+chr(c)
 
134
                else:
 
135
                    _ESCAPEDICT[chr(c)] = chr(c)
 
136
            del c
 
137
            #Michael Hudson donated this
 
138
            def _escape(s):
 
139
                return join(map(lambda c, d=_ESCAPEDICT: d[c],s),'')
 
140
        else:
 
141
            def _escape(s):
 
142
                """Escapes some PDF symbols (in fact, parenthesis).
 
143
                PDF escapes are almost like Python ones, but brackets
 
144
                need slashes before them too. Uses Python's repr function
 
145
                and chops off the quotes first."""
 
146
                s = repr(s)[1:-1]
 
147
                s = replace(s, '(','\(')
 
148
                s = replace(s, ')','\)')
 
149
                return s
 
150
 
 
151
def _normalizeLineEnds(text,desired=LINEEND):
 
152
    """Normalizes different line end character(s).
 
153
 
 
154
    Ensures all instances of CR, LF and CRLF end up as
 
155
    the specified one."""
 
156
    unlikely = '\000\001\002\003'
 
157
    text = replace(text, '\015\012', unlikely)
 
158
    text = replace(text, '\015', unlikely)
 
159
    text = replace(text, '\012', unlikely)
 
160
    text = replace(text, unlikely, desired)
 
161
    return text
 
162
 
 
163
 
 
164
def _AsciiHexEncode(input):
 
165
    """Encodes input using ASCII-Hex coding.
 
166
 
 
167
    This is a verbose encoding used for binary data within
 
168
    a PDF file.  One byte binary becomes two bytes of ASCII.
 
169
    Helper function used by images."""
 
170
    output = getStringIO()
 
171
    for char in input:
 
172
        output.write('%02x' % ord(char))
 
173
    output.write('>')
 
174
    return output.getvalue()
 
175
 
 
176
 
 
177
def _AsciiHexDecode(input):
 
178
    """Decodes input using ASCII-Hex coding.
 
179
 
 
180
    Not used except to provide a test of the inverse function."""
 
181
 
 
182
    #strip out all whitespace
 
183
    stripped = join(split(input),'')
 
184
    assert stripped[-1] == '>', 'Invalid terminator for Ascii Hex Stream'
 
185
    stripped = stripped[:-1]  #chop off terminator
 
186
    assert len(stripped) % 2 == 0, 'Ascii Hex stream has odd number of bytes'
 
187
 
 
188
    i = 0
 
189
    output = getStringIO()
 
190
    while i < len(stripped):
 
191
        twobytes = stripped[i:i+2]
 
192
        output.write(chr(eval('0x'+twobytes)))
 
193
        i = i + 2
 
194
    return output.getvalue()
 
195
 
 
196
 
 
197
if 1: # for testing always define this
 
198
    def _AsciiBase85EncodePYTHON(input):
 
199
        """Encodes input using ASCII-Base85 coding.
 
200
 
 
201
        This is a compact encoding used for binary data within
 
202
        a PDF file.  Four bytes of binary data become five bytes of
 
203
        ASCII.  This is the default method used for encoding images."""
 
204
        outstream = getStringIO()
 
205
        # special rules apply if not a multiple of four bytes.
 
206
        whole_word_count, remainder_size = divmod(len(input), 4)
 
207
        cut = 4 * whole_word_count
 
208
        body, lastbit = input[0:cut], input[cut:]
 
209
 
 
210
        for i in range(whole_word_count):
 
211
            offset = i*4
 
212
            b1 = ord(body[offset])
 
213
            b2 = ord(body[offset+1])
 
214
            b3 = ord(body[offset+2])
 
215
            b4 = ord(body[offset+3])
 
216
 
 
217
            if b1<128:
 
218
                num = (((((b1<<8)|b2)<<8)|b3)<<8)|b4
 
219
            else:
 
220
                num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4
 
221
 
 
222
            if num == 0:
 
223
                #special case
 
224
                outstream.write('z')
 
225
            else:
 
226
                #solve for five base-85 numbers
 
227
                temp, c5 = divmod(num, 85)
 
228
                temp, c4 = divmod(temp, 85)
 
229
                temp, c3 = divmod(temp, 85)
 
230
                c1, c2 = divmod(temp, 85)
 
231
                assert ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5 == num, 'dodgy code!'
 
232
                outstream.write(chr(c1+33))
 
233
                outstream.write(chr(c2+33))
 
234
                outstream.write(chr(c3+33))
 
235
                outstream.write(chr(c4+33))
 
236
                outstream.write(chr(c5+33))
 
237
 
 
238
        # now we do the final bit at the end.  I repeated this separately as
 
239
        # the loop above is the time-critical part of a script, whereas this
 
240
        # happens only once at the end.
 
241
 
 
242
        #encode however many bytes we have as usual
 
243
        if remainder_size > 0:
 
244
            while len(lastbit) < 4:
 
245
                lastbit = lastbit + '\000'
 
246
            b1 = ord(lastbit[0])
 
247
            b2 = ord(lastbit[1])
 
248
            b3 = ord(lastbit[2])
 
249
            b4 = ord(lastbit[3])
 
250
 
 
251
            num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4
 
252
 
 
253
            #solve for c1..c5
 
254
            temp, c5 = divmod(num, 85)
 
255
            temp, c4 = divmod(temp, 85)
 
256
            temp, c3 = divmod(temp, 85)
 
257
            c1, c2 = divmod(temp, 85)
 
258
 
 
259
            #print 'encoding: %d %d %d %d -> %d -> %d %d %d %d %d' % (
 
260
            #    b1,b2,b3,b4,num,c1,c2,c3,c4,c5)
 
261
            lastword = chr(c1+33) + chr(c2+33) + chr(c3+33) + chr(c4+33) + chr(c5+33)
 
262
            #write out most of the bytes.
 
263
            outstream.write(lastword[0:remainder_size + 1])
 
264
 
 
265
        #terminator code for ascii 85
 
266
        outstream.write('~>')
 
267
        return outstream.getvalue()
 
268
 
 
269
    def _AsciiBase85DecodePYTHON(input):
 
270
        """Decodes input using ASCII-Base85 coding.
 
271
 
 
272
        This is not used - Acrobat Reader decodes for you
 
273
        - but a round trip is essential for testing."""
 
274
        outstream = getStringIO()
 
275
        #strip all whitespace
 
276
        stripped = join(split(input),'')
 
277
        #check end
 
278
        assert stripped[-2:] == '~>', 'Invalid terminator for Ascii Base 85 Stream'
 
279
        stripped = stripped[:-2]  #chop off terminator
 
280
 
 
281
        #may have 'z' in it which complicates matters - expand them
 
282
        stripped = replace(stripped,'z','!!!!!')
 
283
        # special rules apply if not a multiple of five bytes.
 
284
        whole_word_count, remainder_size = divmod(len(stripped), 5)
 
285
        #print '%d words, %d leftover' % (whole_word_count, remainder_size)
 
286
        #assert remainder_size <> 1, 'invalid Ascii 85 stream!'
 
287
        cut = 5 * whole_word_count
 
288
        body, lastbit = stripped[0:cut], stripped[cut:]
 
289
 
 
290
        for i in range(whole_word_count):
 
291
            offset = i*5
 
292
            c1 = ord(body[offset]) - 33
 
293
            c2 = ord(body[offset+1]) - 33
 
294
            c3 = ord(body[offset+2]) - 33
 
295
            c4 = ord(body[offset+3]) - 33
 
296
            c5 = ord(body[offset+4]) - 33
 
297
 
 
298
            num = ((85L**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5
 
299
 
 
300
            temp, b4 = divmod(num,256)
 
301
            temp, b3 = divmod(temp,256)
 
302
            b1, b2 = divmod(temp, 256)
 
303
 
 
304
            assert  num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
 
305
            outstream.write(chr(b1))
 
306
            outstream.write(chr(b2))
 
307
            outstream.write(chr(b3))
 
308
            outstream.write(chr(b4))
 
309
 
 
310
        #decode however many bytes we have as usual
 
311
        if remainder_size > 0:
 
312
            while len(lastbit) < 5:
 
313
                lastbit = lastbit + '!'
 
314
            c1 = ord(lastbit[0]) - 33
 
315
            c2 = ord(lastbit[1]) - 33
 
316
            c3 = ord(lastbit[2]) - 33
 
317
            c4 = ord(lastbit[3]) - 33
 
318
            c5 = ord(lastbit[4]) - 33
 
319
            num = (((85*c1+c2)*85+c3)*85+c4)*85L + (c5
 
320
                     +(0,0,0xFFFFFF,0xFFFF,0xFF)[remainder_size])
 
321
            temp, b4 = divmod(num,256)
 
322
            temp, b3 = divmod(temp,256)
 
323
            b1, b2 = divmod(temp, 256)
 
324
            assert  num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
 
325
            #print 'decoding: %d %d %d %d %d -> %d -> %d %d %d %d' % (
 
326
            #    c1,c2,c3,c4,c5,num,b1,b2,b3,b4)
 
327
 
 
328
            #the last character needs 1 adding; the encoding loses
 
329
            #data by rounding the number to x bytes, and when
 
330
            #divided repeatedly we get one less
 
331
            if remainder_size == 2:
 
332
                lastword = chr(b1)
 
333
            elif remainder_size == 3:
 
334
                lastword = chr(b1) + chr(b2)
 
335
            elif remainder_size == 4:
 
336
                lastword = chr(b1) + chr(b2) + chr(b3)
 
337
            else:
 
338
                lastword = ''
 
339
            outstream.write(lastword)
 
340
 
 
341
        #terminator code for ascii 85
 
342
        return outstream.getvalue()
 
343
 
 
344
try:
 
345
    from _rl_accel import _AsciiBase85Encode                    # builtin or on the path
 
346
except ImportError:
 
347
    try:
 
348
        from reportlab.lib._rl_accel import _AsciiBase85Encode  # where we think it should be
 
349
    except ImportError:
 
350
        _AsciiBase85Encode = _AsciiBase85EncodePYTHON
 
351
 
 
352
try:
 
353
    from _rl_accel import _AsciiBase85Decode                    # builtin or on the path
 
354
except ImportError:
 
355
    try:
 
356
        from reportlab.lib._rl_accel import _AsciiBase85Decode  # where we think it should be
 
357
    except ImportError:
 
358
        _AsciiBase85Decode = _AsciiBase85DecodePYTHON
 
359
 
 
360
def _wrap(input, columns=60):
 
361
    "Wraps input at a given column size by inserting LINEEND characters."
 
362
 
 
363
    output = []
 
364
    length = len(input)
 
365
    i = 0
 
366
    pos = columns * i
 
367
    while pos < length:
 
368
        output.append(input[pos:pos+columns])
 
369
        i = i + 1
 
370
        pos = columns * i
 
371
 
 
372
    return join(output, LINEEND)
 
373
 
 
374
 
 
375
#########################################################################
 
376
#
 
377
#  JPEG processing code - contributed by Eric Johnson
 
378
#
 
379
#########################################################################
 
380
 
 
381
# Read data from the JPEG file. We should probably be using PIL to
 
382
# get this information for us -- but this way is more fun!
 
383
# Returns (width, height, color components) as a triple
 
384
# This is based on Thomas Merz's code from GhostScript (viewjpeg.ps)
 
385
def readJPEGInfo(image):
 
386
    "Read width, height and number of components from open JPEG file."
 
387
 
 
388
    import struct
 
389
 
 
390
    #Acceptable JPEG Markers:
 
391
    #  SROF0=baseline, SOF1=extended sequential or SOF2=progressive
 
392
    validMarkers = [0xC0, 0xC1, 0xC2]
 
393
 
 
394
    #JPEG markers without additional parameters
 
395
    noParamMarkers = \
 
396
        [ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01 ]
 
397
 
 
398
    #Unsupported JPEG Markers
 
399
    unsupportedMarkers = \
 
400
        [ 0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF ]
 
401
 
 
402
    #read JPEG marker segments until we find SOFn marker or EOF
 
403
    done = 0
 
404
    while not done:
 
405
        x = struct.unpack('B', image.read(1))
 
406
        if x[0] == 0xFF:                    #found marker
 
407
            x = struct.unpack('B', image.read(1))
 
408
            #print "Marker: ", '%0.2x' % x[0]
 
409
            #check marker type is acceptable and process it
 
410
            if x[0] in validMarkers:
 
411
                image.seek(2, 1)            #skip segment length
 
412
                x = struct.unpack('B', image.read(1)) #data precision
 
413
                if x[0] != 8:
 
414
                    raise 'PDFError', ' JPEG must have 8 bits per component'
 
415
                y = struct.unpack('BB', image.read(2))
 
416
                height = (y[0] << 8) + y[1]
 
417
                y = struct.unpack('BB', image.read(2))
 
418
                width =  (y[0] << 8) + y[1]
 
419
                y = struct.unpack('B', image.read(1))
 
420
                color =  y[0]
 
421
                return width, height, color
 
422
                done = 1
 
423
            elif x[0] in unsupportedMarkers:
 
424
                raise 'PDFError', ' Unsupported JPEG marker: %0.2x' % x[0]
 
425
            elif x[0] not in noParamMarkers:
 
426
                #skip segments with parameters
 
427
                #read length and skip the data
 
428
                x = struct.unpack('BB', image.read(2))
 
429
                image.seek( (x[0] << 8) + x[1] - 2, 1)
 
430
 
 
431
class _fusc:
 
432
    def __init__(self,k, n):
 
433
        assert k, 'Argument k should be a non empty string'
 
434
        self._k = k
 
435
        self._klen = len(k)
 
436
        self._n = int(n) or 7
 
437
 
 
438
    def encrypt(self,s):
 
439
        return self.__rotate(_AsciiBase85Encode(''.join(map(chr,self.__fusc(map(ord,s))))),self._n)
 
440
 
 
441
    def decrypt(self,s):
 
442
        return ''.join(map(chr,self.__fusc(map(ord,_AsciiBase85Decode(self.__rotate(s,-self._n))))))
 
443
 
 
444
    def __rotate(self,s,n):
 
445
        l = len(s)
 
446
        if n<0: n = l+n
 
447
        n %= l
 
448
        if not n: return s
 
449
        return s[-n:]+s[:l-n]
 
450
 
 
451
    def __fusc(self,s):
 
452
        slen = len(s)
 
453
        return map(lambda x,y: x ^ y,s,map(ord,((int(slen/self._klen)+1)*self._k)[:slen]))