support general linear/radial backgrounds in table cells; version-->3.6.11
authorrobin
Sun, 12 Jun 2022 15:34:05 +0100
changeset 4731 aeb606795dc9
parent 4730 182384f0ce2b
child 4732 e856f2ea3e93
support general linear/radial backgrounds in table cells; version-->3.6.11
CHANGES.md
src/reportlab/__init__.py
src/reportlab/pdfgen/canvas.py
src/reportlab/platypus/tables.py
tests/test_table_layout.py
--- a/CHANGES.md	Thu Jun 09 13:34:44 2022 +0100
+++ b/CHANGES.md	Sun Jun 12 15:34:05 2022 +0100
@@ -14,6 +14,7 @@
 CHANGES  3.6.11  ??/06/2022
 ---------------------------
 	* support HORIZONTAL2 & VERTICAL2 table cell backgrounds; as suggested by Sina Khelil < sina at khelil dot com >
+	* support general LINEAR & RADIAL gradient table cell backgrounds
 
 CHANGES  3.6.10  31/05/2022
 ---------------------------
--- a/src/reportlab/__init__.py	Thu Jun 09 13:34:44 2022 +0100
+++ b/src/reportlab/__init__.py	Sun Jun 12 15:34:05 2022 +0100
@@ -3,7 +3,7 @@
 __doc__="""The Reportlab PDF generation library."""
 Version = "3.6.11"
 __version__=Version
-__date__='20220609'
+__date__='20220612'
 
 import sys, os
 
--- a/src/reportlab/pdfgen/canvas.py	Thu Jun 09 13:34:44 2022 +0100
+++ b/src/reportlab/pdfgen/canvas.py	Sun Jun 12 15:34:05 2022 +0100
@@ -202,6 +202,13 @@
         x._c = self._c
         return x
 
+def _gradientExtendStr(extend):
+    if isinstance(extend,(list,tuple)):
+        if len(extend)!=2:
+            raise ValueError('wrong length for extend argument' % extend)
+        return "[%s %s]" % ['true' if _ else 'false' for _ in extend]
+    return "[true true]" if extend else "[false false]"
+
 class Canvas(_PDFColorSetter):
     """This class is the programmer's interface to the PDF file format.  Methods
     are (or will be) provided here to do just about everything PDF can do.
@@ -1557,12 +1564,8 @@
         from reportlab.pdfbase.pdfdoc import PDFAxialShading
         colorSpace, ncolors = _normalizeColors(colors)
         fcn = _buildColorFunction(ncolors, positions)
-        if extend:
-            extendStr = "[true true]"
-        else:
-            extendStr = "[false false]"
         shading = PDFAxialShading(x0, y0, x1, y1, Function=fcn,
-                ColorSpace=colorSpace, Extend=extendStr)
+                ColorSpace=colorSpace, Extend=_gradientExtendStr(extend))
         self.shade(shading)
 
     def radialGradient(self, x, y, radius, colors, positions=None, extend=True):
@@ -1570,12 +1573,8 @@
         from reportlab.pdfbase.pdfdoc import PDFRadialShading
         colorSpace, ncolors = _normalizeColors(colors)
         fcn = _buildColorFunction(ncolors, positions)
-        if extend:
-            extendStr = "[true true]"
-        else:
-            extendStr = "[false false]"
         shading = PDFRadialShading(x, y, 0.0, x, y, radius, Function=fcn,
-                ColorSpace=colorSpace, Extend=extendStr)
+                ColorSpace=colorSpace, Extend=_gradientExtendStr(extend))
         self.shade(shading)
 
         ##################################################
--- a/src/reportlab/platypus/tables.py	Thu Jun 09 13:34:44 2022 +0100
+++ b/src/reportlab/platypus/tables.py	Sun Jun 12 15:34:05 2022 +0100
@@ -2259,37 +2259,86 @@
                     x0 = x0 +w
             else:   #cmd=='BACKGROUND'
                 if (arg and isinstance(arg,(list,tuple))
-                        and arg[0] in ('VERTICAL','HORIZONTAL', 'VERTICAL2', 'HORIZONTAL2')):
+                        and arg[0] in ('VERTICAL','HORIZONTAL', 'VERTICAL2', 'HORIZONTAL2',
+                                'LINEARGRADIENT', 'RADIALGRADIENT')):
+                    if ec==sc and er==sr and spanRects:
+                        xywh = spanRects.get((sc,sr))
+                        if xywh:
+                            #it's a single spanned cell
+                            x0, y0, w, h = xywh
+                    arg0, arg = arg[0], arg[1:]
                     #
-                    # Arg is a list, assume we are going for a gradient fill
+                    # arg is a list, assume we are going for a gradient fill
                     # where we expect a containing a direction for the gradient
                     # and the starting an final gradient colors. For example:
                     # ['HORIZONTAL', colors.white, colors.grey]   or
                     # ['VERTICAL', colors.red, colors.blue]
                     #
                     canv.saveState()
-
-                    if ec==sc and er==sr and spanRects:
-                        xywh = spanRects.get((sc,sr))
-                        if xywh:
-                            #it's a single cell
-                            x0, y0, w, h = xywh
                     p = canv.beginPath()
                     p.rect(x0, y0, w, h)
                     canv.clipPath(p, stroke=0)
-                    direction, arg = arg[0],arg[1:]
-                    if direction=="HORIZONTAL":
+                    if arg0=="HORIZONTAL":
                         canv.linearGradient(x0,y0,x0+w,y0,arg,extend=False)
-                    elif direction == "HORIZONTAL2":
+                    elif arg0 == "HORIZONTAL2":
                         xh = x0 + w/2.0
                         canv.linearGradient(x0, y0, xh, y0, arg, extend=False)
                         canv.linearGradient(xh, y0, x0 + w, y0, arg[::-1], extend=False)
-                    elif direction == "VERTICAL2":
+                        #canv.linearGradient(x0, y0, x0 + w, y0, arg+arg[1::-1], extend=False)
+                    elif arg0 == "VERTICAL2":
                         yh = y0 + h/2.0
                         canv.linearGradient(x0, y0, x0, yh, arg, extend=False)
                         canv.linearGradient(x0, yh, x0, y0 + h, arg[::-1], extend=False)
-                    else:  # Assuming "VERTICAL"
+                        #canv.linearGradient(x0, y0, x0, y0 + h, arg+arg[1::-1], extend=False)
+                    elif arg0=="VERTICAL":
                         canv.linearGradient(x0, y0, x0, y0 + h, arg, extend=False)
+                    elif arg0=='LINEARGRADIENT':
+                        # the remaining arguments define the axis, extend, colors, stops as
+                        # axis = (x0,y0, x1, y1)    given as fractions of width / height
+                        # extend = bool or [bool, bool]
+                        # colors a sequence/list of colors
+                        # stops an optional sequence of fractions 0 - 1
+                        if 4<=len(arg)<=5:
+                            (ax0, ay0), (ax1, ay1) = arg[:2]
+                            ax0 = x0 + ax0*w
+                            ax1 = x0 + ax1*w
+                            ay0 = y0 + ay0*h
+                            ay1 = y0 + ay1*h
+                            extend = arg[2]
+                            C = arg[3]
+                            P = arg[4] if len(arg)==4 else None
+                            canv.linearGradient(ax0, ay0, ax1, ay1, C, positions=P, extend=extend)
+                        else:
+                            raise ValueError('Wrong length for %s arguments %r' % (op, arg))
+                    elif arg0=='RADIALGRADIENT':
+                        # the remaining arguments define the centre, radius, extend, colors, stops as
+                        # center = (xc,yc) given as fractions of width / height
+                        # radius = (r,'ref') op in ('width','height','min','max')
+                        # extend = bool or [bool, bool]
+                        # colors a sequence/list of colors
+                        # stops an optional sequence of fractions 0 - 1
+                        if 4<=len(arg)<=5:
+                            xc, yc = arg[0]
+                            xc = x0 + xc*w
+                            yc = y0 + yc*h
+                            r, ref = arg[1]
+                            if ref=='width':
+                                ref = w
+                            elif ref=='height':
+                                ref = h
+                            elif ref=='min':
+                                ref = min (w,h)
+                            elif ref=='max':
+                                ref = max(w,h)
+                            else:
+                                raise ValueError('Bad radius, %r, for %s arguments %r' % (ascii(arg[1]),op, arg))
+                            r *= ref
+                            extend = arg[2]
+                            C = arg[3]
+                            P = arg[4] if len(arg)==4 else None
+                            canv.radialGradient(xc, yc, r, C, positions=P, extend=extend)
+                        else:
+                            raise ValueError('Wrong length for %s arguments %r' % (op, arg))
                     canv.restoreState()
                 else:
                     color = colors.toColorOrNone(arg)
--- a/tests/test_table_layout.py	Thu Jun 09 13:34:44 2022 +0100
+++ b/tests/test_table_layout.py	Sun Jun 12 15:34:05 2022 +0100
@@ -704,6 +704,16 @@
             ("BACKGROUND", (0,2), (1,2), ("VERTICAL", colors.red, colors.green)),
             ("BACKGROUND", (0,3), (1,3), ("VERTICAL2", colors.blue, colors.yellow)),
             ("BACKGROUND", (1,4), (1,4), ("HORIZONTAL2", colors.green, colors.yellow)),
+            ("BACKGROUND", (2,2), (2,2), ("LINEARGRADIENT", (0,0),(1,1), True, (colors.green, colors.yellow, colors.red), (0.25,0.5,0.75))),
+            ("BACKGROUND", (2,3), (2,3), ("LINEARGRADIENT", (0,1),(1,0), True, (colors.green, colors.yellow, colors.red), (0.25,0.5,0.75))),
+            ("BACKGROUND", (1,5), (1,5), ("LINEARGRADIENT", (0,0),(1,0), True, (colors.pink, colors.lightgreen, colors.lightblue), (0.25,0.5,0.75))),
+            ("BACKGROUND", (0,6), (1,6), ("LINEARGRADIENT", (0,0),(0,1), True, (colors.pink, colors.lightgreen, colors.lightblue), (0.25,0.5,0.75))),
+            ("BACKGROUND", (2,6), (2,6), ("LINEARGRADIENT", (1,0.2),(0,0.8), True, (colors.red, colors.yellow, colors.green, colors.lightblue), (0.2,0.4,0.6,0.8))),
+            ("BACKGROUND", (0,7), (0,7), ("RADIALGRADIENT", (0.5,0.5),(1,'width'), True, (colors.red, colors.yellow, colors.green, colors.lightblue), (0.2,0.4,0.6,0.8))),
+            ("BACKGROUND", (1,7), (1,7), ("RADIALGRADIENT", (0.5,0.5),(1,'height'), True, (colors.red, colors.yellow, colors.green, colors.lightblue), (0.2,0.4,0.6,0.8))),
+            ("BACKGROUND", (2,7), (2,7), ("RADIALGRADIENT", (0.6,0.4),(1,'max'), True, (colors.red, colors.yellow, colors.green, colors.lightblue), (0.2,0.4,0.6,0.8))),
+            ("BACKGROUND", (0,8), (1,8), ("LINEARGRADIENT", (0,1),(0,0), True, (colors.blue, colors.yellow, colors.blue), (0.25,0.5,0.75))),
+            ("BACKGROUND", (2,8), (2,8), ("LINEARGRADIENT", (0,1),(1,0), True, (colors.green, colors.yellow, colors.green), (0.25,0.5,0.75))),
             )
         datalg = [
             ["00","01","02"],
@@ -713,7 +723,13 @@
             ["30\nthis is the\nend\nmy friend","31\nthe bells of hell\ngo ting-aling-aling",
                 "32\ndespair all who\nenter here"],
             ["40","41 this is a long string","42"],
-            ["50","51","52"],
+            ["50","51 this is a long string","52"],
+            ["60\nthis is the\nend\nmy friend","61\nthe bells of hell\ngo ting-aling-aling",
+                "62\ndespair all who\nenter here"],
+            ["70\nthis is the\nend\nmy friend","71\nthe bells of hell\ngo ting-aling-aling",
+                "72\ndespair all who\nenter here"],
+            ["80\nthis is the\nend\nmy friend","81\nthe bells of hell\ngo ting-aling-aling",
+                "82\ndespair all who\nenter here"],
             ]
         lst.append(PageBreak())
         lst.append(Paragraph("Table with gradient backgrounds", styleSheet['Heading1']))