all _rl_accel exports come through rl_accel.py py33
authorrobin
Tue, 15 Oct 2013 14:44:54 +0100
branchpy33
changeset 3779 47bb4a41365d
parent 3778 1c9196ca9795
child 3780 b809eb915503
all _rl_accel exports come through rl_accel.py
src/reportlab/lib/rl_accel.py
--- a/src/reportlab/lib/rl_accel.py	Tue Oct 15 14:43:34 2013 +0100
+++ b/src/reportlab/lib/rl_accel.py	Tue Oct 15 14:44:54 2013 +0100
@@ -1,65 +1,328 @@
-from reportlab.lib.utils import isUnicode, isSeq
-from math import log
-_log_10 = lambda x,log=log,_log_e_10=log(10.0): log(x)/_log_e_10
-_fp_fmts = "%.0f", "%.1f", "%.2f", "%.3f", "%.4f", "%.5f", "%.6f"
-def fp_str(*a):
-    '''convert separate arguments (or single sequence arg) into space separated numeric strings'''
-    if len(a)==1 and isSeq(a[0]): a = a[0]
-    s = []
-    A = s.append
-    for i in a:
-        sa =abs(i)
-        if sa<=1e-7: A('0')
-        else:
-            l = sa<=1 and 6 or min(max(0,(6-int(_log_10(sa)))),6)
-            n = _fp_fmts[l]%i
-            if l:
-                j = len(n)
-                while j:
-                    j -= 1
-                    if n[j]!='0':
-                        if n[j]!='.': j += 1
-                        break
-                n = n[:j]
-            A((n[0]!='0' or len(n)==1) and n or n[1:])
-    return ' '.join(s)
+#this is the interface module that imports all from the C extension _rl_accel
+_c_funcs = {}
+_py_funcs = {}
+### NOTE!  FP_STR SHOULD PROBABLY ALWAYS DO A PYTHON STR() CONVERSION ON ARGS
+### IN CASE THEY ARE "LAZY OBJECTS".  ACCELLERATOR DOESN'T DO THIS (YET)
+__all__ = list(filter(None,'''
+        fp_str
+        unicode2T1
+        instanceStringWidthT1
+        instanceStringWidthTTF
+        asciiBase85Encode
+        asciiBase85Decode
+        escapePDF
+        sameFrag
+        calcChecksum
+        add32
+        hex32
+        '''.split()))
+import __main__
+testing = getattr(__main__,'_rl_testing',False)
+del __main__
 
-#hack test for comma users
-if ',' in fp_str(0.25):
-    _FP_STR = fp_str
+for fn in __all__:
+    try:
+        exec('from reportlab.lib._rl_accel import %s as f' % fn)
+        _c_funcs[fn] = f
+    except ImportError:
+        _py_funcs[fn] = None
+
+if _py_funcs:
+    from reportlab.lib.utils import isUnicode, isSeq
+    from math import log
+
+if 'fp_str' in _py_funcs:
+    _log_10 = lambda x,log=log,_log_e_10=log(10.0): log(x)/_log_e_10
+    _fp_fmts = "%.0f", "%.1f", "%.2f", "%.3f", "%.4f", "%.5f", "%.6f"
     def fp_str(*a):
-        return _FP_STR(*a).replace(',','.')
+        '''convert separate arguments (or single sequence arg) into space separated numeric strings'''
+        if len(a)==1 and isSeq(a[0]): a = a[0]
+        s = []
+        A = s.append
+        for i in a:
+            sa =abs(i)
+            if sa<=1e-7: A('0')
+            else:
+                l = sa<=1 and 6 or min(max(0,(6-int(_log_10(sa)))),6)
+                n = _fp_fmts[l]%i
+                if l:
+                    j = len(n)
+                    while j:
+                        j -= 1
+                        if n[j]!='0':
+                            if n[j]!='.': j += 1
+                            break
+                    n = n[:j]
+                A((n[0]!='0' or len(n)==1) and n or n[1:])
+        return ' '.join(s)
+
+    #hack test for comma users
+    if ',' in fp_str(0.25):
+        _FP_STR = _fp_str
+        def _fp_str(*a):
+            return _FP_STR(*a).replace(',','.')
+    _py_funcs['fp_str'] = fp_str
+
+if 'unicode2T1' in _py_funcs:
+    def unicode2T1(utext,fonts):
+        '''return a list of (font,string) pairs representing the unicode text'''
+        R = []
+        font, fonts = fonts[0], fonts[1:]
+        enc = font.encName
+        if 'UCS-2' in enc:
+            enc = 'UTF16'
+        while utext:
+            try:
+                if isUnicode(utext):
+                    s = utext.encode(enc)
+                else:
+                    s = utext
+                R.append((font,s))
+                break
+            except UnicodeEncodeError as e:
+                i0, il = e.args[2:4]
+                if i0:
+                    R.append((font,utext[:i0].encode(enc)))
+                if fonts:
+                    R.extend(unicode2T1(utext[i0:il],fonts))
+                else:
+                    R.append((font._notdefFont,font._notdefChar*(il-i0)))
+                utext = utext[il:]
+        return R
+    _py_funcs['unicode2T1'] = unicode2T1
+
+if 'instanceStringWidthT1' in _py_funcs:
+    def _instanceStringWidthT1(self, text, size, encoding='utf8'):
+        """This is the "purist" approach to width"""
+        if not isUnicode(text): text = text.decode(encoding)
+        return sum([sum(map(f.widths.__getitem__,list(map(ord,t)))) for f, t in unicode2T1(text,[self]+self.substitutionFonts)])*0.001*size
+    _py_funcs['instanceStringWidthT1'] = instanceStringWidthT1
 
-def unicode2T1(utext,fonts):
-    '''return a list of (font,string) pairs representing the unicode text'''
-    R = []
-    font, fonts = fonts[0], fonts[1:]
-    enc = font.encName
-    if 'UCS-2' in enc:
-        enc = 'UTF16'
-    while utext:
-        try:
-            if isUnicode(utext):
-                s = utext.encode(enc)
+if 'instanceStringWidthTTF' in _py_funcs:
+    def instanceStringWidthTTF(self, text, size, encoding='utf-8'):
+        "Calculate text width"
+        if not isinstance(text,str):
+            text = str(text, encoding or 'utf-8')   # encoding defaults to utf-8
+        g = self.face.charWidths.get
+        dw = self.face.defaultWidth
+        return 0.001*size*sum([g(ord(u),dw) for u in text])
+    _py_funcs['instanceStringWidthTTF'] = instanceStringWidthTTF
+
+if 'hex32' in _py_funcs:
+    def hex32(i):
+        return '0X%8.8X' % (int(i)&0xFFFFFFFF)
+    _py_funcs['hex32'] = hex32
+
+if 'add32' in _py_funcs:
+    def add32(x, y):
+        "Calculate (x + y) modulo 2**32"
+        return (x+y) & 0xFFFFFFFF
+    _py_funcs['add32'] = add32
+
+if 'calcChecksum' in _py_funcs:
+    def calcChecksum(data):
+        """Calculates TTF-style checksums"""
+        if len(data)&3: data = data + (4-(len(data)&3))*"\0"
+        return sum(unpack(">%dl" % (len(data)>>2), data)) & 0xFFFFFFFF
+    _py_funcs['calcChecksum'] = calcChecksum
+
+if 'escapePDF' in _py_funcs:
+    _ESCAPEDICT={}
+    for c in range(256):
+        if c<32 or c>=127:
+            _ESCAPEDICT[c]= '\\%03o' % c
+        elif c in (ord('\\'),ord('('),ord(')')):
+            _ESCAPEDICT[c] = '\\'+chr(c)
+        else:
+            _ESCAPEDICT[c] = chr(c)
+    del c
+    #Michael Hudson donated this
+    def escapePDF(s):
+        r = []
+        for c in s:
+            if not type(c) is int:
+                c = ord(c)
+            r.append(_ESCAPEDICT[c])
+        return ''.join(r)
+    _py_funcs['escapePDF'] = escapePDF
+
+if 'asciiBase85Encode' in _py_funcs:
+    def asciiBase85Encode(input):
+        """Encodes input using ASCII-Base85 coding.
+
+        This is a compact encoding used for binary data within
+        a PDF file.  Four bytes of binary data become five bytes of
+        ASCII.  This is the default method used for encoding images."""
+        # special rules apply if not a multiple of four bytes.
+        whole_word_count, remainder_size = divmod(len(input), 4)
+        cut = 4 * whole_word_count
+        body, lastbit = input[0:cut], input[cut:]
+        if isPy3 and isStr(lastbit):
+            lastbit = lastbit.encode('utf-8')
+
+        out = [].append
+        for i in range(whole_word_count):
+            offset = i*4
+            b1 = body[offset]
+            b2 = body[offset+1]
+            b3 = body[offset+2]
+            b4 = body[offset+3]
+            if isStr(b1): b1 = ord(b1)
+            if isStr(b2): b2 = ord(b2)
+            if isStr(b3): b3 = ord(b3)
+            if isStr(b4): b4 = ord(b4)
+
+            if b1<128:
+                num = (((((b1<<8)|b2)<<8)|b3)<<8)|b4
             else:
-                s = utext
-            R.append((font,s))
-            break
-        except UnicodeEncodeError as e:
-            i0, il = e.args[2:4]
-            if i0:
-                R.append((font,utext[:i0].encode(enc)))
-            if fonts:
-                R.extend(unicode2T1(utext[i0:il],fonts))
+                num = 16777216 * b1 + 65536 * b2 + 256 * b3 + b4
+
+            if num == 0:
+                #special case
+                out('z')
             else:
-                R.append((font._notdefFont,font._notdefChar*(il-i0)))
-            utext = utext[il:]
-    return R
+                #solve for five base-85 numbers
+                temp, c5 = divmod(num, 85)
+                temp, c4 = divmod(temp, 85)
+                temp, c3 = divmod(temp, 85)
+                c1, c2 = divmod(temp, 85)
+                assert ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5 == num, 'dodgy code!'
+                out(chr(c1+33))
+                out(chr(c2+33))
+                out(chr(c3+33))
+                out(chr(c4+33))
+                out(chr(c5+33))
+
+        # now we do the final bit at the end.  I repeated this separately as
+        # the loop above is the time-critical part of a script, whereas this
+        # happens only once at the end.
+
+        #encode however many bytes we have as usual
+        if remainder_size > 0:
+            while len(lastbit) < 4:
+                lastbit = lastbit + b'\000'
+            b1 = lastbit[0]
+            b2 = lastbit[1]
+            b3 = lastbit[2]
+            b4 = lastbit[3]
+            if isStr(b1): b1 = ord(b1)
+            if isStr(b2): b2 = ord(b2)
+            if isStr(b3): b3 = ord(b3)
+            if isStr(b4): b4 = ord(b4)
+
+            num = 16777216 * b1 + 65536 * b2 + 256 * b3 + b4
+
+            #solve for c1..c5
+            temp, c5 = divmod(num, 85)
+            temp, c4 = divmod(temp, 85)
+            temp, c3 = divmod(temp, 85)
+            c1, c2 = divmod(temp, 85)
+
+            #print 'encoding: %d %d %d %d -> %d -> %d %d %d %d %d' % (
+            #    b1,b2,b3,b4,num,c1,c2,c3,c4,c5)
+            lastword = chr(c1+33) + chr(c2+33) + chr(c3+33) + chr(c4+33) + chr(c5+33)
+            #write out most of the bytes.
+            out(lastword[0:remainder_size + 1])
+
+        #terminator code for ascii 85
+        out('~>')
+        return ''.join(out.__self__)
+    _py_funcs['asciiBase85Encode'] = asciiBase85Encode
+
+if 'asciiBase85Decode' in _py_funcs:
+    def asciiBase85Decode(input):
+        """Decodes input using ASCII-Base85 coding.
+
+        This is not used - Acrobat Reader decodes for you
+        - but a round trip is essential for testing."""
+        #strip all whitespace
+        stripped = ''.join(input.split())
+        #check end
+        assert stripped[-2:] == '~>', 'Invalid terminator for Ascii Base 85 Stream'
+        stripped = stripped[:-2]  #chop off terminator
+
+        #may have 'z' in it which complicates matters - expand them
+        stripped = stripped.replace('z','!!!!!')
+        # special rules apply if not a multiple of five bytes.
+        whole_word_count, remainder_size = divmod(len(stripped), 5)
+        #print '%d words, %d leftover' % (whole_word_count, remainder_size)
+        #assert remainder_size != 1, 'invalid Ascii 85 stream!'
+        cut = 5 * whole_word_count
+        body, lastbit = stripped[0:cut], stripped[cut:]
 
-def _instanceStringWidthU(self, text, size, encoding='utf8'):
-    """This is the "purist" approach to width"""
-    if not isUnicode(text): text = text.decode(encoding)
-    return sum([sum(map(f.widths.__getitem__,list(map(ord,t)))) for f, t in unicode2T1(text,[self]+self.substitutionFonts)])*0.001*size
+        out = [].append
+        for i in range(whole_word_count):
+            offset = i*5
+            c1 = ord(body[offset]) - 33
+            c2 = ord(body[offset+1]) - 33
+            c3 = ord(body[offset+2]) - 33
+            c4 = ord(body[offset+3]) - 33
+            c5 = ord(body[offset+4]) - 33
+
+            num = ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5
+
+            temp, b4 = divmod(num,256)
+            temp, b3 = divmod(temp,256)
+            b1, b2 = divmod(temp, 256)
+
+            assert  num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
+            out(chr(b1))
+            out(chr(b2))
+            out(chr(b3))
+            out(chr(b4))
+
+        #decode however many bytes we have as usual
+        if remainder_size > 0:
+            while len(lastbit) < 5:
+                lastbit = lastbit + '!'
+            c1 = ord(lastbit[0]) - 33
+            c2 = ord(lastbit[1]) - 33
+            c3 = ord(lastbit[2]) - 33
+            c4 = ord(lastbit[3]) - 33
+            c5 = ord(lastbit[4]) - 33
+            num = (((85*c1+c2)*85+c3)*85+c4)*85 + (c5
+                     +(0,0,0xFFFFFF,0xFFFF,0xFF)[remainder_size])
+            temp, b4 = divmod(num,256)
+            temp, b3 = divmod(temp,256)
+            b1, b2 = divmod(temp, 256)
+            assert  num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
+            #print 'decoding: %d %d %d %d %d -> %d -> %d %d %d %d' % (
+            #    c1,c2,c3,c4,c5,num,b1,b2,b3,b4)
+
+            #the last character needs 1 adding; the encoding loses
+            #data by rounding the number to x bytes, and when
+            #divided repeatedly we get one less
+            if remainder_size == 2:
+                lastword = chr(b1)
+            elif remainder_size == 3:
+                lastword = chr(b1) + chr(b2)
+            elif remainder_size == 4:
+                lastword = chr(b1) + chr(b2) + chr(b3)
+            else:
+                lastword = ''
+            out(lastword)
+
+        #terminator code for ascii 85
+        return ''.join(out.__self__)
+    _py_funcs['asciiBase85Decode'] = asciiBase85Decode
+
+if 'sameFrag' in _py_funcs:
+    def sameFrag(f,g):
+        'returns 1 if two ParaFrags map out the same'
+        if (hasattr(f,'cbDefn') or hasattr(g,'cbDefn')
+                or hasattr(f,'lineBreak') or hasattr(g,'lineBreak')): return 0
+        for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'strike', 'link', "backColor"):
+            if getattr(f,a,None)!=getattr(g,a,None): return 0
+        return 1
+    _py_funcs['sameFrag'] = sameFrag
+
+G=globals()
+for fn in __all__:
+    f = _c_funcs[fn] or _py_funcs[fn]
+    if not f:
+        raise RuntimeError('function %s is not properly defined' % fn)
+    G[fn] = f
+del fn, f, G
 
 if __name__=='__main__':
     import sys, os