rearange transformation math and text2Path functionality
authorrobin
Fri, 19 Feb 2021 10:50:39 +0000
changeset 4642 ba6b914af331
parent 4641 f206b15d2b1e
child 4643 7b8bbb5eeaa8
rearange transformation math and text2Path functionality
src/reportlab/graphics/charts/textlabels.py
src/reportlab/graphics/renderPM.py
src/reportlab/graphics/renderbase.py
src/reportlab/graphics/shapes.py
src/reportlab/graphics/transform.py
src/reportlab/graphics/utils.py
--- a/src/reportlab/graphics/charts/textlabels.py	Thu Feb 11 14:24:59 2021 +0000
+++ b/src/reportlab/graphics/charts/textlabels.py	Fri Feb 19 10:50:39 2021 +0000
@@ -5,106 +5,29 @@
 import string
 
 from reportlab.lib import colors
-from reportlab.lib.utils import simpleSplit, _simpleSplit, isBytes
+from reportlab.lib.utils import simpleSplit, _simpleSplit
 from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \
         isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString, isNoneOrCallable, \
         isSubclassOf
 from reportlab.lib.attrmap import *
-from reportlab.pdfbase.pdfmetrics import stringWidth, getAscentDescent, getFont, unicode2T1
+from reportlab.pdfbase.pdfmetrics import stringWidth, getAscentDescent, getFont
 from reportlab.graphics.shapes import Drawing, Group, Circle, Rect, String, STATE_DEFAULTS
-from reportlab.graphics.shapes import _PATH_OP_ARG_COUNT, _PATH_OP_NAMES, definePath
 from reportlab.graphics.widgetbase import Widget, PropHolder
 from reportlab.graphics.shapes import _baseGFontName, DirectDraw
 from reportlab.platypus import XPreformatted, Paragraph, Flowable
 from reportlab.lib.styles import ParagraphStyle, PropertySet
 from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
 _ta2al = dict(start=TA_LEFT,end=TA_RIGHT,middle=TA_CENTER)
+from ..utils import (text2Path as _text2Path,   #here for continuity
+                    pathNumTrunc as _pathNumTrunc,
+                    processGlyph as _processGlyph,
+                    text2PathDescription as _text2PathDescription)
 
 _A2BA=  {
         'x': {0:'n', 45:'ne', 90:'e', 135:'se', 180:'s', 225:'sw', 270:'w', 315: 'nw', -45: 'nw'},
         'y': {0:'e', 45:'se', 90:'s', 135:'sw', 180:'w', 225:'nw', 270:'n', 315: 'ne', -45: 'ne'},
         }
 
-def _pathNumTrunc(n):
-    if int(n)==n: return int(n)
-    return round(n,5)
-
-def _processGlyph(G, truncate=1, pathReverse=0):
-    O = []
-    P = []
-    R_append = [].append
-    if G and len(G)==1 and G[0][0]=='lineTo':
-        G = (('moveToClosed',)+G[0][1:],)+G #hack fix for some errors
-    for g in G+(('end',),):
-        op = g[0]
-        if O and op in ['moveTo', 'moveToClosed','end']:
-            if O[0]=='moveToClosed':
-                del O[0]
-                if pathReverse:
-                    P[1::2],P[0::2] = P[0::2],P[1::2]   #exchange x and y
-                    P.reverse()
-                    O.reverse()
-                O.insert(0,'moveTo')
-                O.append('closePath')
-            i = 0
-            if truncate: P = list(map(_pathNumTrunc,P))
-            for o in O:
-                j = i + _PATH_OP_ARG_COUNT[_PATH_OP_NAMES.index(o)]
-                if o=='closePath':
-                    R_append(o)
-                else:
-                    R_append((o,)+ tuple(P[i:j]))
-                i = j
-            O = []
-            P = []
-        O.append(op)
-        P.extend(g[1:])
-    return R_append.__self__
-
-def _text2PathDescription(text, x=0, y=0, fontName=_baseGFontName, fontSize=1000,
-                            anchor='start', truncate=1, pathReverse=0):
-    from reportlab.graphics import renderPM, _renderPM
-    font = getFont(fontName)
-    if font._multiByte and not font._dynamicFont:
-        raise ValueError("_text2PathDescription doesn't support multi byte fonts like %r" % fontName)
-    P_extend = [].extend
-    if not anchor=='start':
-        textLen = stringWidth(text, fontName, fontSize)
-        if anchor=='end':
-            x = x-textLen
-        elif anchor=='middle':
-            x = x - textLen/2.
-    _gs = _renderPM.gstate(1,1)
-    _setFont = renderPM._setFont
-    _setFont(_gs,fontName,fontSize)
-    if font._dynamicFont:
-        for g in _gs._stringPath(text,x,y):
-            P_extend(_processGlyph(g,truncate=truncate,pathReverse=pathReverse))
-    else:
-        if isBytes(text):
-            try:
-                text = text.decode('utf8')
-            except UnicodeDecodeError as e:
-                i,j = e.args[2:4]
-                raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[max(i-10,0):i],text[i:j],text[j:j+10]),)))
-        fc = font
-        FT = unicode2T1(text,[font]+font.substitutionFonts)
-        nm1 = len(FT)-1
-        for i, (f, t) in enumerate(FT):
-            if f!=fc:
-                _setFont(_gs,f.fontName,fontSize)
-                fc = f
-            for g in _gs._stringPath(t,x,y):
-                P_extend(_processGlyph(g,truncate=truncate,pathReverse=pathReverse))
-            if i!=nm1:
-                x += f.stringWidth(t.decode(f.encName), fontSize)
-    return P_extend.__self__
-
-def _text2Path(text, x=0, y=0, fontName=_baseGFontName, fontSize=1000,
-                anchor='start', truncate=1, pathReverse=0,**kwds):
-    return definePath(_text2PathDescription(text,x=x,y=y,fontName=fontName,
-                    fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse),**kwds)
-
 try:
     from rlextra.graphics.canvasadapter import DirectDrawFlowable
 except ImportError:
--- a/src/reportlab/graphics/renderPM.py	Thu Feb 11 14:24:59 2021 +0000
+++ b/src/reportlab/graphics/renderPM.py	Fri Feb 19 10:50:39 2021 +0000
@@ -18,9 +18,7 @@
 from math import sin, cos, pi, ceil
 from reportlab.lib.utils import getStringIO, getBytesIO, open_and_read, isUnicode
 from reportlab import rl_config, ascii
-
-class RenderPMError(Exception):
-    pass
+from .utils import setFont as _setFont, RenderPMError
 
 import os, sys
 
@@ -243,20 +241,6 @@
                 c.pathFill(fillMode)
             c.pathStroke()
 
-def _setFont(gs,fontName,fontSize):
-    try:
-        gs.setFont(fontName,fontSize)
-    except ValueError as e:
-        if not e.args[0].endswith("Can't find font!"): raise
-        #here's where we try to add a font to the canvas
-        try:
-            f = getFont(fontName)
-            _renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector,open_and_read)
-        except:
-            s1, s2 = list(map(str,sys.exc_info()[:2]))
-            raise RenderPMError("Can't setFont(%s) missing the T1 files?\nOriginally %s: %s" % (fontName,s1,s2))
-        gs.setFont(fontName,fontSize)
-
 def _convert2pilp(im):
     Image = _getImage()
     return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE)
--- a/src/reportlab/graphics/renderbase.py	Thu Feb 11 14:24:59 2021 +0000
+++ b/src/reportlab/graphics/renderbase.py	Fri Feb 19 10:50:39 2021 +0000
@@ -1,4 +1,4 @@
-#Copyright ReportLab Europe Ltd. 2000-2017
+#Copyright ReportLab Europe Ltd. 2000-2021
 #see license.txt for license details
 #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/renderbase.py
 
@@ -9,26 +9,7 @@
 from reportlab.lib.validators import DerivedValue
 from reportlab import rl_config
 
-def inverse(A):
-    "For A affine 2D represented as 6vec return 6vec version of A**(-1)"
-    # I checked this RGB
-    det = float(A[0]*A[3] - A[2]*A[1])
-    R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det]
-    return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]])
-
-def mmult(A, B):
-    "A postmultiplied by B"
-    # I checked this RGB
-    # [a0 a2 a4]    [b0 b2 b4]
-    # [a1 a3 a5] *  [b1 b3 b5]
-    # [      1 ]    [      1 ]
-    #
-    return (A[0]*B[0] + A[2]*B[1],
-            A[1]*B[0] + A[3]*B[1],
-            A[0]*B[2] + A[2]*B[3],
-            A[1]*B[2] + A[3]*B[3],
-            A[0]*B[4] + A[2]*B[5] + A[4],
-            A[1]*B[4] + A[3]*B[5] + A[5])
+from . transform import mmult, inverse
 
 def getStateDelta(shape):
     """Used to compute when we need to change the graphics state.
--- a/src/reportlab/graphics/shapes.py	Thu Feb 11 14:24:59 2021 +0000
+++ b/src/reportlab/graphics/shapes.py	Fri Feb 19 10:50:39 2021 +0000
@@ -6,7 +6,7 @@
 __doc__='''Core of the graphics library - defines Drawing and Shapes'''
 
 import os, sys
-from math import pi, cos, sin, tan, sqrt, radians, floor
+from math import pi, cos, sin, sqrt, radians, floor
 from pprint import pprint
 
 from reportlab.platypus import Flowable
@@ -65,68 +65,9 @@
     }
 
 ####################################################################
-# math utilities.  These could probably be moved into lib
-# somewhere.
+# math utilities.  These are now in reportlab.graphics.transform
 ####################################################################
-
-# constructors for matrices:
-def nullTransform():
-    return (1, 0, 0, 1, 0, 0)
-
-def translate(dx, dy):
-    return (1, 0, 0, 1, dx, dy)
-
-def scale(sx, sy):
-    return (sx, 0, 0, sy, 0, 0)
-
-def rotate(angle):
-    a = angle * pi/180
-    return (cos(a), sin(a), -sin(a), cos(a), 0, 0)
-
-def skewX(angle):
-    a = angle * pi/180
-    return (1, 0, tan(a), 1, 0, 0)
-
-def skewY(angle):
-    a = angle * pi/180
-    return (1, tan(a), 0, 1, 0, 0)
-
-def mmult(A, B):
-    "A postmultiplied by B"
-    # I checked this RGB
-    # [a0 a2 a4]    [b0 b2 b4]
-    # [a1 a3 a5] *  [b1 b3 b5]
-    # [      1 ]    [      1 ]
-    #
-    return (A[0]*B[0] + A[2]*B[1],
-            A[1]*B[0] + A[3]*B[1],
-            A[0]*B[2] + A[2]*B[3],
-            A[1]*B[2] + A[3]*B[3],
-            A[0]*B[4] + A[2]*B[5] + A[4],
-            A[1]*B[4] + A[3]*B[5] + A[5])
-
-def inverse(A):
-    "For A affine 2D represented as 6vec return 6vec version of A**(-1)"
-    # I checked this RGB
-    det = float(A[0]*A[3] - A[2]*A[1])
-    R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det]
-    return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]])
-
-def zTransformPoint(A,v):
-    "Apply the homogenous part of atransformation a to vector v --> A*v"
-    return (A[0]*v[0]+A[2]*v[1],A[1]*v[0]+A[3]*v[1])
-
-def transformPoint(A,v):
-    "Apply transformation a to vector v --> A*v"
-    return (A[0]*v[0]+A[2]*v[1]+A[4],A[1]*v[0]+A[3]*v[1]+A[5])
-
-def transformPoints(matrix, V):
-    r = [transformPoint(matrix,v) for v in V]
-    if isinstance(V,tuple): r = tuple(r)
-    return r
-
-def zTransformPoints(matrix, V):
-    return list(map(lambda x,matrix=matrix: zTransformPoint(matrix,x), V))
+from . transform import *
 
 def _textBoxLimits(text, font, fontSize, leading, textAnchor, boxAnchor):
     w = 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/reportlab/graphics/transform.py	Fri Feb 19 10:50:39 2021 +0000
@@ -0,0 +1,75 @@
+'''functions for 2D affine transformations'''
+__all__ = (
+    'nullTransform',
+    'translate',
+    'scale',
+    'rotate',
+    'skewX',
+    'skewY',
+    'mmult',
+    'inverse',
+    'zTransformPoint',
+    'transformPoint',
+    'transformPoints',
+    'zTransformPoints',
+    )
+from math import pi, cos, sin, tan
+
+# constructors for matrices:
+def nullTransform():
+    return (1, 0, 0, 1, 0, 0)
+
+def translate(dx, dy):
+    return (1, 0, 0, 1, dx, dy)
+
+def scale(sx, sy):
+    return (sx, 0, 0, sy, 0, 0)
+
+def rotate(angle):
+    a = angle * pi/180
+    return (cos(a), sin(a), -sin(a), cos(a), 0, 0)
+
+def skewX(angle):
+    a = angle * pi/180
+    return (1, 0, tan(a), 1, 0, 0)
+
+def skewY(angle):
+    a = angle * pi/180
+    return (1, tan(a), 0, 1, 0, 0)
+
+def mmult(A, B):
+    "A postmultiplied by B"
+    # I checked this RGB
+    # [a0 a2 a4]    [b0 b2 b4]
+    # [a1 a3 a5] *  [b1 b3 b5]
+    # [      1 ]    [      1 ]
+    #
+    return (A[0]*B[0] + A[2]*B[1],
+            A[1]*B[0] + A[3]*B[1],
+            A[0]*B[2] + A[2]*B[3],
+            A[1]*B[2] + A[3]*B[3],
+            A[0]*B[4] + A[2]*B[5] + A[4],
+            A[1]*B[4] + A[3]*B[5] + A[5])
+
+def inverse(A):
+    "For A affine 2D represented as 6vec return 6vec version of A**(-1)"
+    # I checked this RGB
+    det = float(A[0]*A[3] - A[2]*A[1])
+    R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det]
+    return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]])
+
+def zTransformPoint(A,v):
+    "Apply the homogenous part of atransformation a to vector v --> A*v"
+    return (A[0]*v[0]+A[2]*v[1],A[1]*v[0]+A[3]*v[1])
+
+def transformPoint(A,v):
+    "Apply transformation a to vector v --> A*v"
+    return (A[0]*v[0]+A[2]*v[1]+A[4],A[1]*v[0]+A[3]*v[1]+A[5])
+
+def transformPoints(matrix, V):
+    r = [transformPoint(matrix,v) for v in V]
+    if isinstance(V,tuple): r = tuple(r)
+    return r
+
+def zTransformPoints(matrix, V):
+    return list(map(lambda x,matrix=matrix: zTransformPoint(matrix,x), V))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/reportlab/graphics/utils.py	Fri Feb 19 10:50:39 2021 +0000
@@ -0,0 +1,110 @@
+__all__ = (
+        'setFont',
+        'pathNumTrunc',
+        'processGlyph',
+        'text2PathDescription',
+        'text2Path',
+        'RenderPMError',
+        )
+from . _renderPM import makeT1Font
+from reportlab.pdfbase.pdfmetrics import getFont, unicode2T1
+from reportlab.lib.utils import open_and_read, isBytes
+from .shapes import _baseGFontName, _PATH_OP_ARG_COUNT, _PATH_OP_NAMES, definePath
+from sys import exc_info
+
+class RenderPMError(Exception):
+    pass
+
+def setFont(gs,fontName,fontSize):
+    try:
+        gs.setFont(fontName,fontSize)
+    except ValueError as e:
+        if not e.args[0].endswith("Can't find font!"): raise
+        #here's where we try to add a font to the canvas
+        try:
+            f = getFont(fontName)
+            makeT1Font(fontName,f.face.findT1File(),f.encoding.vector,open_and_read)
+        except:
+            s1, s2 = list(map(str,exc_info()[:2]))
+            raise RenderPMError("Can't setFont(%s) missing the T1 files?\nOriginally %s: %s" % (fontName,s1,s2))
+        gs.setFont(fontName,fontSize)
+
+def pathNumTrunc(n):
+    if int(n)==n: return int(n)
+    return round(n,5)
+
+def processGlyph(G, truncate=1, pathReverse=0):
+    O = []
+    P = []
+    R_append = [].append
+    if G and len(G)==1 and G[0][0]=='lineTo':
+        G = (('moveToClosed',)+G[0][1:],)+G #hack fix for some errors
+    for g in G+(('end',),):
+        op = g[0]
+        if O and op in ['moveTo', 'moveToClosed','end']:
+            if O[0]=='moveToClosed':
+                del O[0]
+                if pathReverse:
+                    P[1::2],P[0::2] = P[0::2],P[1::2]   #exchange x and y
+                    P.reverse()
+                    O.reverse()
+                O.insert(0,'moveTo')
+                O.append('closePath')
+            i = 0
+            if truncate: P = list(map(pathNumTrunc,P))
+            for o in O:
+                j = i + _PATH_OP_ARG_COUNT[_PATH_OP_NAMES.index(o)]
+                if o=='closePath':
+                    R_append(o)
+                else:
+                    R_append((o,)+ tuple(P[i:j]))
+                i = j
+            O = []
+            P = []
+        O.append(op)
+        P.extend(g[1:])
+    return R_append.__self__
+
+def text2PathDescription(text, x=0, y=0, fontName=_baseGFontName, fontSize=1000,
+                            anchor='start', truncate=1, pathReverse=0, gs=None):
+    font = getFont(fontName)
+    if font._multiByte and not font._dynamicFont:
+        raise ValueError("text2PathDescription doesn't support multi byte fonts like %r" % fontName)
+    P_extend = [].extend
+    if not anchor=='start':
+        textLen = stringWidth(text, fontName, fontSize)
+        if anchor=='end':
+            x = x-textLen
+        elif anchor=='middle':
+            x = x - textLen/2.
+    if gs is None:
+        from ._renderPM import gstate
+        gs = gstate(1,1)
+    setFont(gs,fontName,fontSize)
+    if font._dynamicFont:
+        for g in gs._stringPath(text,x,y):
+            P_extend(processGlyph(g,truncate=truncate,pathReverse=pathReverse))
+    else:
+        if isBytes(text):
+            try:
+                text = text.decode('utf8')
+            except UnicodeDecodeError as e:
+                i,j = e.args[2:4]
+                raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[max(i-10,0):i],text[i:j],text[j:j+10]),)))
+        fc = font
+        FT = unicode2T1(text,[font]+font.substitutionFonts)
+        nm1 = len(FT)-1
+        for i, (f, t) in enumerate(FT):
+            if f!=fc:
+                setFont(gs,f.fontName,fontSize)
+                fc = f
+            for g in gs._stringPath(t,x,y):
+                P_extend(processGlyph(g,truncate=truncate,pathReverse=pathReverse))
+            if i!=nm1:
+                x += f.stringWidth(t.decode(f.encName), fontSize)
+    return P_extend.__self__
+
+def text2Path(text, x=0, y=0, fontName=_baseGFontName, fontSize=1000,
+                anchor='start', truncate=1, pathReverse=0, gs=None, **kwds):
+    return definePath(text2PathDescription(text,x=x,y=y,fontName=fontName,
+                    fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse, gs=gs),**kwds)