Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-reportlab for
openSUSE:Factory checked in at 2026-06-15 19:43:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-reportlab (Old)
and /work/SRC/openSUSE:Factory/.python-reportlab.new.1981 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-reportlab"
Mon Jun 15 19:43:58 2026 rev:44 rq:1359295 version:4.5.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-reportlab/python-reportlab.changes
2026-01-28 15:15:04.881811068 +0100
+++
/work/SRC/openSUSE:Factory/.python-reportlab.new.1981/python-reportlab.changes
2026-06-15 19:47:12.284870450 +0200
@@ -1,0 +2,19 @@
+Sun Jun 14 18:54:27 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.5.1:
+ * fix tests/runAll.py to avoid issues with sys.args
+ * fix no such attribute in lib.utils.rl_warn
+ * SVGCanvas new keyword argument fontSizer defaulting to 'px'
+ * add rl_warn to lib/utils.py and use in tables (via extra
+ allowTableBoundsErrors bits).
+ * rl_config.register_reset can have callback and now handles
+ methods
+ * shapes.py fix Group.asDrawing
+ * fix __rl_get_module__ for Python 3.15
+ * moved extformat.py elsewhere
+ * added combineTransforms
+ * improve colors.cssParse
+ * change None to mean no-draw in acroform.py
+ * extend allowTableBoundsErrors scope
+
+-------------------------------------------------------------------
Old:
----
reportlab-4.4.9.tar.gz
New:
----
reportlab-4.5.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-reportlab.spec ++++++
--- /var/tmp/diff_new_pack.Po6OYr/_old 2026-06-15 19:47:13.308913365 +0200
+++ /var/tmp/diff_new_pack.Po6OYr/_new 2026-06-15 19:47:13.312913533 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-reportlab
-Version: 4.4.9
+Version: 4.5.1
Release: 0
Summary: The Reportlab Toolkit
License: BSD-3-Clause
++++++ reportlab-4.4.9.tar.gz -> reportlab-4.5.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/CHANGES.md
new/reportlab-4.5.1/CHANGES.md
--- old/reportlab-4.4.9/CHANGES.md 2026-01-15 10:17:03.000000000 +0100
+++ new/reportlab-4.5.1/CHANGES.md 2026-05-12 11:14:40.000000000 +0200
@@ -11,6 +11,37 @@
The contributors lists are in no order and apologies to those accidentally not
mentioned. If we missed you, please let us know!
+
+CHANGES 4.5.1 12/05/2026
+---------------------------
+ * fix tests/runAll.py to avoid issues with sys.args
+ * release 4.5.1
+
+CHANGES 4.5.1a2 08/05/2026
+---------------------------
+ * fix no such attribute in lib.utils.rl_warn
+
+CHANGES 4.5.1a1 05/05/2026
+---------------------------
+ * SVGCanvas new keyword argument fontSizer defaulting to 'px'
+ * add rl_warn to lib/utils.py and use in tables (via extra
allowTableBoundsErrors bits).
+ * rl_config.register_reset can have callback and now handles methods
+
+CHANGES 4.5.0 27/04/2026
+---------------------------
+ * shapes.py fix Group.asDrawing
+ * fix __rl_get_module__ for Python 3.15
+ * moved extformat.py elsewhere
+ * added combineTransforms
+ * improve colors.cssParse suggested by moritz dot schreiber at tu-ilmenau
dot de
+ * change None to mean no-draw in acroform.py: Cozmin Velciu cozmin dot
velciu at gmail dot com
+ * extend allowTableBoundsErrors scope: James Beith james dot beith at
kraken dot tec
+
+CHANGES 4.4.10 12/02/2026
+---------------------------
+ * fix security issue found by Ethan Kim lt ethan 4t cremit d0t io gt
+ * add a User-Agent header in rlUrlRead to overcome some anti bot actions
+
CHANGES 4.4.9 15/01/2026
---------------------------
* remove unwanted debug
@@ -18,6 +49,7 @@
CHANGES 4.4.8 15/01/2026
---------------------------
* fix callback security hole reported by Pedro "gankd" lt pedrovgcruz at
icloud dot com gt
+ * https://www.linkedin.com/in/pedro-cruz-967b2524b/
CHANGES 4.4.7 21/12/2025
---------------------------
@@ -730,6 +762,7 @@
* Axel P. Kielhorn
* ben @ readingtype.org.uk
* Chris Jerdonek cjerdonek @ bitbucket
+ * Cozmin Velciu cozmin dot velciu at gmail dot com
* Dan Palmer danpalmer @ bitbucket
* Garry Williams gary_williams @ bit_bucket
* Greg Svitak
@@ -742,6 +775,8 @@
* Martin J. Laubach bitbucket issue #140
* Moritz Pfeiffer moritzpfeiffer @ bitbucket
* Raji Sundar
+ * Schreiber Moritz TU Ilmenau
+ * James Beith james dot beith at kraken dot tec
* Silas Sewell silassewell @ bitbucket
* simonkagwe @ bitbucket
* Tom Alexander @ bitbucket
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/PKG-INFO new/reportlab-4.5.1/PKG-INFO
--- old/reportlab-4.4.9/PKG-INFO 2026-01-15 10:23:12.014963200 +0100
+++ new/reportlab-4.5.1/PKG-INFO 2026-05-12 11:20:50.736108300 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: reportlab
-Version: 4.4.9
+Version: 4.5.1
Summary: The Reportlab Toolkit
Home-page: https://www.reportlab.com/
Author: Andy Robinson, Robin Becker, the ReportLab team and the community
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/__init__.py
new/reportlab-4.5.1/src/reportlab/__init__.py
--- old/reportlab-4.4.9/src/reportlab/__init__.py 2026-01-15
10:17:13.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/__init__.py 2026-05-12
11:14:11.000000000 +0200
@@ -1,9 +1,9 @@
-#Copyright ReportLab Europe Ltd. 2000-2023
+#Copyright ReportLab Europe Ltd. 2000-2026
#see license.txt for license details
__doc__="""The Reportlab PDF generation library."""
-Version = "4.4.9"
+Version = "4.5.1"
__version__=Version
-__date__='20260115'
+__date__='20260512'
import sys, os
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/reportlab-4.4.9/src/reportlab/graphics/charts/utils.py
new/reportlab-4.5.1/src/reportlab/graphics/charts/utils.py
--- old/reportlab-4.4.9/src/reportlab/graphics/charts/utils.py 2025-03-24
13:48:38.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/graphics/charts/utils.py 2026-01-21
12:29:52.000000000 +0100
@@ -1,8 +1,30 @@
-#Copyright ReportLab Europe Ltd. 2000-2017
+#Copyright ReportLab Europe Ltd. 2000-2026
#see license.txt for license details
#history
https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/charts/utils.py
+__all__ = (
+ 'angle2corner',
+ 'angle2dir',
+ 'boxCornerCoords',
+ 'CustomDrawChanger',
+ 'DrawTimeCollector',
+ 'FillPairedData',
+ 'find_good_grid',
+ 'find_interval',
+ 'findNones',
+ 'lineSegmentIntersect',
+ 'makeCircularString',
+ 'maverage',
+ 'mkTimeTuple',
+ 'nextRoundNumber',
+ 'pairFixNones',
+ 'pairMaverage',
+ 'seconds2str',
+ 'str2seconds',
+ 'ticks',
+ 'xyDist',
+ )
-__version__='3.3.0'
+__version__='3.4.8'
__doc__="Utilities used here and there."
from time import mktime, gmtime, strftime
from math import log10, pi, floor, sin, cos, hypot
@@ -393,3 +415,69 @@
def __init__(self,v,other=0):
list.__init__(self,v)
self.other = other
+
+_arange2dirs = [
+ (-1,22.5,'e'),
+ (22.5,67.5,'ne'),
+ (67.5,112.5,'n'),
+ (112.5,157.5,'nw'),
+ (157.5,202.5,'w'),
+ (202.5,247.5,'sw'),
+ (247.5,292.5,'s'),
+ (292.5,337.5,'se'),
+ (337.5,361,'e'),
+ ]
+def angle2dir(angle):
+ '''converts mathematical angle to a compass point from a math angle where
+ 0 degrees lies along the x axis ie east==0 degrees
+
+ >>> [angle2dir(_) for _ in [0,360]+[__[0] for __ in _arange2dirs]+[__[1]
for __ in _arange2dirs]]
+ ['e', 'e', 'e', 'e', 'ne', 'n', 'nw', 'w', 'sw', 's', 'se', 'e', 'ne',
'n', 'nw', 'w', 'sw', 's', 'se', 'e']
+ '''
+ a = angle % 360
+ for lo, hi, d in _arange2dirs:
+ if lo<a<=hi: return d
+ return 'c'
+ #I tested the bisect version below; it works, but is fractionally slower
+ #import bisect
+ #_elemk = lambda _: _[1]
+ #return _arange2dirs[bisect.bisect_left(_arange2dirs,angle %
360,key=_elemk)][2]
+
+_cornerNames=dict(e='w',ne='sw',n='s',nw='se',w='e',sw='ne',s='n',se='nw',c='c')
+def angle2corner(angle):
+ '''converts a direction angle to a box corner name effectively the reverse
direction
+ >>> [angle2corner(_) for _ in [0,360]+[__[0] for __ in
_arange2dirs]+[__[1] for __ in _arange2dirs]]
+ ['w', 'w', 'w', 'w', 'sw', 's', 'se', 'e', 'ne', 'n', 'nw', 'w', 'sw',
's', 'se', 'e', 'ne', 'n', 'nw', 'w']
+ '''
+ return _cornerNames[angle2dir(angle)]
+
+def boxCornerCoords(bb, cn):
+ '''return (x,y) for bounding box and corner name
+ >>> bb=(1,0,0,1);[boxCornerCoords(bb,_) for _ in 'c n ne e se s sw w
nw'.split()]
+ [(0.5, 0.5), (0.5, 1), (1, 1), (1, 0.5), (1, 0), (0.5, 0), (0, 0), (0,
0.5), (0, 1)]
+ >>> boxCornerCoords(bb,'z')
+ Traceback (most recent call last):
+ ...
+ ValueError: invalid box corner name 'z'
+ '''
+ if bb[0]>bb[2] or bb[1]>bb[3]:
+ bb =
(min(bb[0],bb[2]),min(bb[1],bb[3]),max(bb[0],bb[2]),max(bb[1],bb[3]))
+ if cn not in ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'c'):
+ raise ValueError(f'invalid box corner name {cn!r}')
+ if cn in ('c','s','n'):
+ x = (bb[0]+bb[2])/2
+ elif cn in ('ne','e','se'):
+ x = bb[2]
+ else:
+ x = bb[0]
+ if cn in ('e','c','w'):
+ y = (bb[1]+bb[3])/2
+ elif cn in ('nw','n','ne'):
+ y = bb[3]
+ else:
+ y = bb[1]
+ return x, y
+
+if __name__=='__main__':
+ import doctest
+ doctest.testmod()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/graphics/renderSVG.py
new/reportlab-4.5.1/src/reportlab/graphics/renderSVG.py
--- old/reportlab-4.4.9/src/reportlab/graphics/renderSVG.py 2024-12-13
09:46:59.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/graphics/renderSVG.py 2026-05-11
16:12:06.000000000 +0200
@@ -157,12 +157,20 @@
extraXmlDecl = '' use to add extra xml declarations
scaleGroupId = '' id of an extra group to add around the drawing to
allow easy scaling
svgAttrs = {} dictionary of attributes to be applied to the svg
tag itself
+ fontSizer = 'px' a string unit or acallable that returns a string
fontSize value
'''
self.verbose = verbose
self.encoding = codecs.lookup(encoding).name
self.bom = bom
useClip = kwds.pop('useClip',False)
self.fontHacks = kwds.pop('fontHacks',{})
+ fz = kwds.pop('fontSizer','px')
+ if isinstance(fz,str):
+ self.fontSizer = lambda v: f'%s{fz}' % v
+ elif callable(fz):
+ self.fontSizer = fz
+ else:
+ raise ValueError(f'{fontSizer=} should be a str unit eg px/pt or a
callable that returns a string')
self.extraXmlDecl = kwds.pop('extraXmlDecl','')
scaleGroupId = kwds.pop('scaleGroupId','')
self._fillMode = FILL_EVEN_ODD
@@ -414,7 +422,7 @@
style[a] = v
if 'font-family' not in style:
style['font-family'] = font
- style['font-size'] = '%spx' % fontSize
+ style['font-size'] = self.fontSizer(fontSize)
def _add_link(self, dom_object, link_info) :
assert isinstance(link_info, dict)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/graphics/shapes.py
new/reportlab-4.5.1/src/reportlab/graphics/shapes.py
--- old/reportlab-4.4.9/src/reportlab/graphics/shapes.py 2025-02-13
14:38:28.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/graphics/shapes.py 2026-03-13
11:13:42.000000000 +0100
@@ -455,7 +455,7 @@
After calling this the instance will be a drawing!
"""
self.__class__ = Drawing
- self._attrMap.update(self._xtraAttrMap)
+ self._attrMap.update(getattr(self,'_xtraAttrMap',{}))
self.width = width
self.height = height
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/graphics/transform.py
new/reportlab-4.5.1/src/reportlab/graphics/transform.py
--- old/reportlab-4.4.9/src/reportlab/graphics/transform.py 2025-08-21
11:46:51.000000000 +0200
+++ new/reportlab-4.5.1/src/reportlab/graphics/transform.py 2026-03-12
09:20:47.000000000 +0100
@@ -7,6 +7,7 @@
'skewX',
'skewY',
'mmult',
+ 'combineTransforms',
'inverse',
'zTransformPoint',
'transformPoint',
@@ -57,6 +58,23 @@
A[0]*B[4] + A[2]*B[5] + A[4],
A[1]*B[4] + A[3]*B[5] + A[5])
+def combineTransforms(*T):
+ '''
+ given transform matrices in the order they should be applied generate
+ a combined transform.
+
+ combineTransforms(T0,T1,T2) == mmult(T2,mmult(T1,T0))
+ == T2*T1*T0
+ so that T0 is applied first, then T1 and finally T2.
+ '''
+ nT = len(T)
+ return (
+ mmult(T[1],T[0]) if nT==2
+ else mmult(combineTransforms(*T[2:]), mmult(T[1],T[0])) if nT>2
+ else T[0] if nT==1
+ else nullTransform()
+ )
+
def inverse(A):
"For A affine 2D represented as 6vec return 6vec version of A**(-1)"
# I checked this RGB
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/lib/colors.py
new/reportlab-4.5.1/src/reportlab/lib/colors.py
--- old/reportlab-4.4.9/src/reportlab/lib/colors.py 2024-12-13
09:46:59.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/lib/colors.py 2026-05-01
12:15:44.000000000 +0200
@@ -769,15 +769,37 @@
return hue2rgb(m1, m2, h+1./3),hue2rgb(m1, m2, h),hue2rgb(m1, m2, h-1./3)
import re
-_re_css = re.compile(r'^\s*(pcmyk|cmyk|rgb|hsl)(a|)\s*\(\s*([^)]*)\)\s*$')
+_re_css_func = re.compile(r'^\s*(pcmyk|cmyk|rgb|hsl)(a|)\s*(.*)\s*$')
+_re_css_args =
re.compile(r'^\(\s*([^)/]*)(?:\s*/\s*(\d{0,3}(?:\.\d*|)%?)|)\)$')
class cssParse:
- def pcVal(self,v):
+ '''
+ best effort convert css like rgb/rgba colours into reportlab Color
+ we support functions rgb/a, cmyk/a, pcmyk/a & hsl/a
+
+ rgb & rgba have special treatment if the r g b arguments have a
+ decimal point and no % signs. In that case the r g b values are
+ treated as simple floats and must lie in [0,1]. Otherwise we assume
+ the r g b values will be treated as 255 fractions ie 1 --> 1/255.
+
+ if arguments have a percentage sign appended then the values are first
+ divided by 100.
+
+ The alpha values can be specified using the <func>a form and adding
+ an extra argument or using the simple form and adding /<alpha> before
+ the closing parenthesis. The alpha values can have decimal points and
+ percent signs as desired. It's not clear if we should force rgb alpha
+ values into 8 bit form.
+
+ Arguments can be separated by comma or space.
+ '''
+ def pcVal(self,v,n='argument'):
v = v.strip()
try:
- c=float(v[:-1])
- c=min(100,max(0,c))/100.
+ c=float(v.rstrip('%'))
+ if c<0 or c>100: raise ValueError
+ return c/100.
except:
- raise ValueError('bad percentage argument value %r in css color
%r' % (v,self.s))
+ raise ValueError(f'bad {n} percentage value {v!r} in css color
{self.s!r}')
return c
def rgbPcVal(self,v):
@@ -787,10 +809,19 @@
v = v.strip()
try:
c=float(v)
- if 0<c<=1: c *= 255
- return int(min(255,max(0,c)))/255.
+ #if 0<c<=1: c *= 255
+ if c<0 or c>255: raise ValueError
+ return int(c)/255.
except:
- raise ValueError('bad argument value %r in css color %r' %
(v,self.s))
+ raise ValueError(f'bad argument value {v!r} in css color
{self.s!r}')
+
+ def floatVal(self,v):
+ try:
+ c=float(v)
+ if c<0 or c>1: raise ValueError
+ return c
+ except:
+ raise ValueError(f'bad argument value {v!r} in css color
{self.s!r}')
def hueVal(self,v):
v = v.strip()
@@ -798,31 +829,46 @@
c=float(v)
return ((c%360+360)%360)/360.
except:
- raise ValueError('bad hue argument value %r in css color %r' %
(v,self.s))
+ raise ValueError(f'bad hue argument value {v!r} in css color
{self.s!r}')
def alphaVal(self,v,c=1,n='alpha'):
try:
a = float(v)
- return min(c,max(0,a))
+ if a>c or a<0: raise VaueError
+ return a
except:
- raise ValueError('bad %s argument value %r in css color %r' %
(n,v,self.s))
+ raise ValueError(f'bad {n} argument value {v!r} in css color
{self.s!r}')
_n_c =
dict(pcmyk=(4,100,True,False),cmyk=(4,1,True,False),hsl=(3,1,False,True),rgb=(3,1,False,False))
def __call__(self,s):
- n = _re_css.match(s)
- if not n: return
+ f = _re_css_func.match(s)
+ if not f: return #we didn't match the start of a css func
self.s = s
- b,c,cmyk,hsl = self._n_c[n.group(1)]
- ha = n.group(2)
- n = n.group(3).split(',') #strip parens and split on comma
+ b,c,cmyk,hsl = self._n_c[f.group(1)]
+ n = _re_css_args.match(f.group(3))
+ if not n: raise ValueError(f'css color {s!r} has bad argument list
{f.group(3)!r}')
+ ha = f.group(2)
+ ha1 = n.group(2)
+ if ha and ha1:
+ raise ValueError(f'css color {s!r} has both inline alpha and
/alpha%')
+ n = n.group(1)
+ n = n.split(',') if ',' in n else n.strip().split() #split on comma or
spaces
if len(n)!=(b+(ha and 1 or 0)):
- raise ValueError('css color %r has wrong number of components' % s)
+ raise ValueError(f'css color {s!r} has wrong number of components')
if ha:
- n,a = n[:b],self.alphaVal(n[b],c)
+ a = n[b]
+ n = n[:b]
+ a = self.pcVal(a,'alpha') if '%' in a else self.alphaVal(a,c)
+ elif ha1:
+ a = self.pcVal(ha1,f'/{ha1}') if '%' in ha1 else
self.alphaVal(ha1,c)
else:
a = c
+ pc = ''.join(n)
+ dp = '.' in pc
+ pc = '%' in pc
+
if cmyk:
C = self.alphaVal(n[0],c,'cyan')
M = self.alphaVal(n[1],c,'magenta')
@@ -832,8 +878,11 @@
else:
if hsl:
R,G,B=
hsl2rgb(self.hueVal(n[0]),self.pcVal(n[1]),self.pcVal(n[2]))
+ elif dp and not pc:
+ R,G,B = map(self.floatVal,n)
else:
- R,G,B = list(map('%' in n[0] and self.rgbPcVal or
self.rgbVal,n))
+ R,G,B = list(map(pc and self.rgbPcVal or self.rgbVal,n))
+ #a = int(a*255+0.5)/255.
return Color(R,G,B,a)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/lib/extformat.py
new/reportlab-4.5.1/src/reportlab/lib/extformat.py
--- old/reportlab-4.4.9/src/reportlab/lib/extformat.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/lib/extformat.py 1970-01-01
01:00:00.000000000 +0100
@@ -1,91 +0,0 @@
-#Copyright ReportLab Europe Ltd. 2000-2017
-#see license.txt for license details
-__version__='3.3.0'
-__doc__='''Apparently not used anywhere, purpose unknown!'''
-try:
- from tokenize import tokenprog
-except ImportError:
- from tokenize import Token
- import re
- tokenprog = re.compile(Token)
- del Token, re
-import sys
-
-def _matchorfail(text, pos):
- match = tokenprog.match(text, pos)
- if match is None: raise ValueError(text, pos)
- return match, match.end()
-
-'''
- Extended dictionary formatting
- We allow expressions in the parentheses instead of
- just a simple variable.
-'''
-def dictformat(_format, L={}, G={}):
- format = _format
-
- S = {}
- chunks = []
- pos = 0
- n = 0
-
- while 1:
- pc = format.find("%", pos)
- if pc < 0: break
- nextchar = format[pc+1]
-
- if nextchar == "(":
- chunks.append(format[pos:pc])
- pos, level = pc+2, 1
- while level:
- match, pos = _matchorfail(format, pos)
- tstart, tend = match.regs[3]
- token = format[tstart:tend]
- if token == "(": level = level+1
- elif token == ")": level = level-1
- vname = '__superformat_%d' % n
- n += 1
- S[vname] = eval(format[pc+2:pos-1],G,L)
- chunks.append('%%(%s)' % vname)
- else:
- nc = pc+1+(nextchar=="%")
- chunks.append(format[pos:nc])
- pos = nc
-
- if pos < len(format): chunks.append(format[pos:])
- return (''.join(chunks)) % S
-
-def magicformat(format):
- """Evaluate and substitute the appropriate parts of the string."""
- frame = sys._getframe(1)
- return dictformat(format,frame.f_locals, frame.f_globals)
-
-if __name__=='__main__':
- from reportlab.lib.formatters import DecimalFormatter
- _DF={}
- def df(n,dp=2,ds='.',ts=','):
- try:
- _df = _DF[dp,ds]
- except KeyError:
- _df = _DF[dp,ds] =
DecimalFormatter(places=dp,decimalSep=ds,thousandSep=ts)
- return _df(n)
-
- from reportlab.lib.extformat import magicformat
-
- Z={'abc': ('ab','c')}
- x = 300000.23
- percent=79.2
- class dingo:
- a=3
- print((magicformat('''
-$%%(df(x,dp=3))s --> $%(df(x,dp=3))s
-$%%(df(x,dp=2,ds=',',ts='.'))s --> $%(df(x,dp=2,ds=',',ts='.'))s
-%%(percent).2f%%%% --> %(percent).2f%%
-%%(dingo.a)s --> %(dingo.a)s
-%%(Z['abc'][0])s --> %(Z['abc'][0])s
-''')))
- def func0(aa=1):
- def func1(bb=2):
- print((magicformat('bb=%(bb)s Z=%(Z)r')))
- func1('BB')
- func0('AA')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/lib/rl_safe_eval.py
new/reportlab-4.5.1/src/reportlab/lib/rl_safe_eval.py
--- old/reportlab-4.4.9/src/reportlab/lib/rl_safe_eval.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/lib/rl_safe_eval.py 2026-02-12
10:24:11.000000000 +0100
@@ -666,16 +666,12 @@
"""Allow function definitions (`def`) with some restrictions."""
self.isAllowedName(node, node.name)
self.check_function_argument_names(node)
-
- return node
+ return self.visit_children(node)
def visit_Lambda(self, node):
"""Allow lambda with some restrictions."""
self.check_function_argument_names(node)
-
- node = self.visit_children(node)
-
- return node
+ return self.visit_children(node)
def visit_ClassDef(self, node):
"""Check the name of a class definition."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/lib/utils.py
new/reportlab-4.5.1/src/reportlab/lib/utils.py
--- old/reportlab-4.4.9/src/reportlab/lib/utils.py 2026-01-14
12:49:17.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/lib/utils.py 2026-05-08
11:06:21.000000000 +0200
@@ -471,9 +471,11 @@
return BytesIO(s)
from urllib.parse import unquote, urlparse
-from urllib.request import urlopen
-def rlUrlRead(name):
- return urlopen(name).read()
+from urllib.request import urlopen, Request
+def rlUrlRead(name, headers=None):
+ if headers==None: headers = {}
+ headers.setdefault('User-Agent','ReportLabAgent')
+ return urlopen(Request(name,headers=headers)).read()
def open_for_read(name,mode='b'):
#auto initialized function`
@@ -581,7 +583,9 @@
path = os.path.join(dir,name+ext)
if os.path.isfile(path):
spec = importlib_util.spec_from_file_location(name,path)
- return spec.loader.load_module()
+ module = importlib_util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
raise ImportError('no suitable file found')
def rl_get_module(name,dir):
@@ -1353,3 +1357,32 @@
def get(self,k,default=None):
return self.store.get(k,default)
+
+class rl_warn:
+ __shared_state = {} #resistance is futile we are a BORG
+ def __init__(self):
+ self.__dict__ = self.__shared_state
+
+ def __call__(self,message):
+ if not self.__shared_state:
+ self.__warnings_seen = {}
+ from reportlab.rl_config import register_reset
+ register_reset(self.__reset)
+ import inspect, warnings
+ self.__inspect = inspect
+ self.__warnings = warnings
+
+ if message not in self.__warnings_seen:
+ self.__warnings.warn(message,stacklevel=2)
+ frame = self.__inspect.stack()[1]
+ self.__warnings_seen.setdefault(message,set()).add(
+ f'in {frame.function} @ {frame.filename}:{frame.lineno}')
+
+ def __reset(self):
+ self.__warnings_seen.clear()
+
+ @property
+ def warnings_seen(self):
+ return self.__warnings_seen
+
+rl_warn = rl_warn()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/license.txt
new/reportlab-4.5.1/src/reportlab/license.txt
--- old/reportlab-4.4.9/src/reportlab/license.txt 2024-12-13
09:46:59.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/license.txt 1970-01-01
01:00:00.000000000 +0100
@@ -1,29 +0,0 @@
-#####################################################################################
-#
-# Copyright (c) 2000-2018, ReportLab Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
modification,
-# are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above
copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above
copyright notice,
-# this list of conditions and the following disclaimer in
the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the company nor the names of its
contributors may be
-# used to endorse or promote products derived from this
software without
-# specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
-# IN NO EVENT SHALL THE OFFICERS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED
-# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS;
-# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER
-# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING
-# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF
-# SUCH DAMAGE.
-#
-#####################################################################################
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/pdfbase/acroform.py
new/reportlab-4.5.1/src/reportlab/pdfbase/acroform.py
--- old/reportlab-4.4.9/src/reportlab/pdfbase/acroform.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/pdfbase/acroform.py 2026-04-16
09:57:11.000000000 +0200
@@ -7,6 +7,8 @@
from reportlab.lib.utils import isStr, asNative
import weakref
+_useDefault=object()
+
visibilities = dict(
visible=0,
hidden=0,
@@ -133,6 +135,8 @@
return obj._s
return str(obj)
+_NotSet = object()
+
class AcroForm(PDFObject):
formFontNames = {
"Helvetica": "Helv",
@@ -160,6 +164,10 @@
self.extras = {}
@property
+ def useDefault(self):
+ return _useDefault
+
+ @property
def canv(self):
_canv = self._canv()
if _canv is None:
@@ -209,7 +217,7 @@
fillColor=None,
borderColor=None,
textColor=None,
- borderWidth=1,
+ borderWidth=None,
borderStyle='solid',
size=20,
dashLen=3,
@@ -218,9 +226,10 @@
ds = size
if shape=='square':
stream('q')
- streamFill = self.streamFillColor(fillColor)
- stream('1 g 1 G %(streamFill)s 0 0 %(size)s %(size)s re f')
- if borderWidth!=None:
+ if opaqueColor(fillColor):
+ streamFill = self.streamFillColor(fillColor)
+ stream('1 g 1 G %(streamFill)s 0 0 %(size)s %(size)s re f')
+ if borderWidth is not None and opaqueColor(borderColor):
streamStroke = self.streamStrokeColor(borderColor)
hbw = borderWidth*0.5
smbw = size - borderWidth
@@ -234,7 +243,7 @@
dash = ''
stream('%(streamStroke)s%(dash)s %(borderWidth)s w %(hbw)s
%(hbw)s %(smbw)s %(smbw)s re s')
- if borderStyle in ('bevelled','inset'):
+ if borderStyle in ('bevelled','inset') and
opaqueColor(fillColor):
_2bw = 2*borderWidth
sm2bw = size - _2bw
ds = sm2bw
@@ -249,12 +258,13 @@
elif shape=='circle':
cas = lambda _r,**_casKwds:
self.circleArcStream(size,_r,**_casKwds)
r = size*0.5
- streamFill = self.streamFillColor(fillColor)
- stream('q 1 g 1 G %(streamFill)s')
- stream(cas(r))
- stream('f')
- stream('Q')
- if borderWidth!=None:
+ if opaqueColor(fillColor):
+ streamFill = self.streamFillColor(fillColor)
+ stream('q 1 g 1 G %(streamFill)s')
+ stream(cas(r))
+ stream('f')
+ stream('Q')
+ if borderWidth is not None and opaqueColor(borderColor):
stream('q')
streamStroke = self.streamStrokeColor(borderColor)
hbw = borderWidth*0.5
@@ -270,7 +280,7 @@
stream(cas(r-hbw))
stream('s')
stream('Q')
- if borderStyle in ('bevelled','inset'):
+ if borderStyle in ('bevelled','inset') and
opaqueColor(fillColor):
_3bwh = 3*hbw
ds = size - _3bwh
bbs0 = Blacker(fillColor,0.5)
@@ -289,8 +299,12 @@
stream(cas(r-_3bwh,rotated=True,arcs=a1))
stream('S Q')
if value=='Yes':
- textFillColor = self.streamFillColor(textColor)
- textStrokeColor = self.streamStrokeColor(textColor)
+ if opaqueColor(textColor):
+ textFillColor = self.streamFillColor(textColor)
+ textStrokeColor = self.streamStrokeColor(textColor)
+ else:
+ textFillColor = '0 g'
+ textStrokeColor = '0 G'
stream('q %(textFillColor)s %(textStrokeColor)s')
cbm = cbmarks[buttonStyle]
if shape=='circle' and buttonStyle=='circle':
@@ -351,18 +365,57 @@
def getRefStr(self,obj):
return asNative(self.getRef(obj).format(self.canv._doc))
- @staticmethod
- def stdColors(t,b,f):
- if isinstance(f,CMYKColor) or isinstance(t,CMYKColor) or
isinstance(b,CMYKColor):
- return (t or CMYKColor(0,0,0,0.9), b or CMYKColor(0,0,0,0.9), f
or CMYKColor(0.12,0.157,0,0))
- else:
- return (t or Color(0.1,0.1,0.1), b or Color(0.1,0.1,0.1), f or
Color(0.8,0.843,1))
+ _defaultValues = dict(
+ cmyk_textColor=CMYKColor(0,0,0,0.9),
+ cmyk_borderColor=CMYKColor(0,0,0,0.9),
+ cmyk_fillColor=CMYKColor(0.12,0.157,0,0),
+ rgb_textColor=Color(0.1,0.1,0.1),
+ rgb_borderColor=Color(0.1,0.1,0.1),
+ rgb_fillColor=Color(0.8,0.843,1),
+ borderWidth=1,
+ )
+
+ def setDefault(self, name, value):
+ if '_defaultValues' not in self.__dict__:
+ self._defaultValues = self.__class__._defaultValues.clone()
+
+ d = self._defaultValues()
+ if name in d.keys():
+ if '_' in name:
+ #its a colour name
+ if name[:4]=='cmyk':
+ if isinstance(value,(CMYKColor,NoneType)):
+ d[name] = value
+ return
+ elif isinstance(value,(Color,NoneType)):
+ d[name] = value
+ return
+ elif isinstance(value(float,int,NoneType)):
+ #it's a number
+ d[name] = value
+ return
+ raise ValueError(f'Invalid value in
acroForm.setDefault({name!r},{value!r})')
+ raise ValueError(f'Invalid name in
acroForm.setDefault({name!r},{value!r})')
+
+ def getDefaults(self,textColor,borderColor,fillColor, borderWidth):
+ pfx = (isinstance(fillColor,CMYKColor)
+ or isinstance(textColor,CMYKColor)
+ or isinstance(borderColor,CMYKColor))
+ if not pfx: pfx =
getattr(self.canv._enforceColorSpace,'__name__','_enforceRGB')!='_enforceRGB'
+
+ pfx = 'cmyk_' if pfx else 'rgb_'
+ d = self._defaultValues
+ return (textColor if textColor is not _useDefault else
d[pfx+'textColor'],
+ borderColor if borderColor is not _useDefault else
d[pfx+'borderColor'],
+ fillColor if fillColor is not _useDefault else
d[pfx+'fillColor'],
+ borderWidth if borderWidth is not _useDefault else
d['borderWidth'],
+ )
@staticmethod
def varyColors(key,t,b,f):
if key!='N':
func = Whiter if key=='R' else Blacker
- t,b,f = [func(c,0.9) for c in (t,b,f)]
+ t,b,f = [func(c,0.9) if c is not None else None for c in (t,b,f)]
return t,b,f
def
checkForceBorder(self,x,y,width,height,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor):
@@ -370,7 +423,7 @@
canv = self.canv
canv.saveState()
canv.resetTransforms()
- if borderWidth!=None:
+ if borderWidth is not None and opaqueColor(borderColor):
hbw = 0.5*borderWidth
canv.setLineWidth(borderWidth)
canv.setStrokeColor(borderColor)
@@ -381,22 +434,24 @@
height -= 2*hbw
x += hbw
y += hbw
- canv.setFillColor(fillColor)
+ doFill = opaqueColor(fillColor)
+ if doFill:
+ canv.setFillColor(fillColor)
if shape=='square':
- canv.rect(x,y,width,height,stroke=s,fill=1)
+ canv.rect(x,y,width,height,stroke=s,fill=1 if doFill else 0)
else:
r = min(width,height) * 0.5
- canv.circle(x+r,y+r,r,stroke=s,fill=1)
+ canv.circle(x+r,y+r,r,stroke=s,fill=1 if doFill else 0)
canv.restoreState()
def checkbox(self,
checked=False,
buttonStyle='check',
shape='square',
- fillColor=None,
- borderColor=None,
- textColor=None,
- borderWidth=1,
+ fillColor=_useDefault,
+ borderColor=_useDefault,
+ textColor=_useDefault,
+ borderWidth=_useDefault,
borderStyle='solid',
size=20,
x=0,
@@ -410,7 +465,7 @@
dashLen = 3,
):
initialValue = 'Yes' if checked else 'Off'
-
textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor)
+ textColor,borderColor,fillColor,borderWidth =
self.getDefaults(textColor,borderColor,fillColor,borderWidth)
canv = self.canv
if relative:
x, y = self.canv.absolutePosition(x,y)
@@ -461,11 +516,11 @@
name = 'AFF%03d' % len(self.fields)
if borderWidth: CB['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
CB['T'] = PDFString(name)
- MK = dict(
- CA='(%s)' % ZDSyms[buttonStyle],
- BC=PDFArray(self.colorTuple(borderColor)),
- BG=PDFArray(self.colorTuple(fillColor)),
- )
+ MK = dict(CA='(%s)' % ZDSyms[buttonStyle])
+ if borderColor is not None:
+ MK['BC'] = PDFArray(self.colorTuple(borderColor))
+ if fillColor is not None:
+ MK['BG'] = PDFArray(self.colorTuple(fillColor))
CB['MK'] = PDFDictionary(MK)
CB = PDFDictionary(CB)
self.canv._addAnnotation(CB)
@@ -477,10 +532,10 @@
selected=False,
buttonStyle='circle',
shape='circle',
- fillColor=None,
- borderColor=None,
- textColor=None,
- borderWidth=1,
+ fillColor=_useDefault,
+ borderColor=_useDefault,
+ textColor=_useDefault,
+ borderWidth=_useDefault,
borderStyle='solid',
size=20,
x=0,
@@ -506,7 +561,7 @@
if not value:
raise ValueError('bad value %r for radio.%s' % (value,name))
initialValue = value if selected else 'Off'
-
textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor)
+ textColor,borderColor,fillColor,borderWidth =
self.getDefaults(textColor,borderColor,fillColor,borderWidth)
if initialValue==value:
if group.V is not None:
@@ -559,11 +614,11 @@
H=PDFName('N'),
)
#RB['T'] = PDFString(name)
- MK = dict(
- CA='(%s)' % ZDSyms[buttonStyle],
- BC=PDFArray(self.colorTuple(borderColor)),
- BG=PDFArray(self.colorTuple(fillColor)),
- )
+ MK = dict(CA='(%s)' % ZDSyms[buttonStyle])
+ if borderColor is not None:
+ MK['BC'] = PDFArray(self.colorTuple(borderColor))
+ if fillColor is not None:
+ MK['BG'] = PDFArray(self.colorTuple(fillColor))
if borderWidth: RB['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
RB['MK'] = PDFDictionary(MK)
RB = PDFDictionary(RB)
@@ -602,7 +657,7 @@
fillColor=None,
borderColor=None,
textColor=None,
- borderWidth=1,
+ borderWidth=None,
borderStyle='solid',
width=120,
height=36,
@@ -618,6 +673,12 @@
if opaqueColor(fillColor):
streamFill = self.streamFillColor(fillColor)
stream('%(streamFill)s\n0 0 %(width)s %(height)s re\nf')
+ # Pre-initialise border-derived variables so they are always defined
for
+ # the /Tx clipping-region stream that follows, even when no border is
drawn.
+ hbw = _2bw = 0
+ bww = width
+ bwh = height
+ undash = ''
if borderWidth!=None and borderWidth>0 and opaqueColor(borderColor):
hbw = borderWidth*0.5
bww = width - borderWidth
@@ -655,7 +716,7 @@
_4bw = 4*borderWidth
w4bw = width - _4bw
h4bw = height - _4bw
- textFill = self.streamFillColor(textColor)
+ textFill = self.streamFillColor(textColor) if opaqueColor(textColor)
else '0 g'
stream('/Tx BMC \nq\n%(_2bw)s %(_2bw)s %(w4bw)s %(h4bw)s re\nW\nn')
leading = 1.2 * fontSize
if wkind=='listbox':
@@ -736,10 +797,10 @@
def _textfield(self,
value='',
- fillColor=None,
- borderColor=None,
- textColor=None,
- borderWidth=1,
+ fillColor=_useDefault,
+ borderColor=_useDefault,
+ textColor=_useDefault,
+ borderWidth=_useDefault,
borderStyle='solid',
width=120,
height=36,
@@ -759,9 +820,14 @@
dashLen=3,
):
rFontName, iFontName = self.makeFont(fontName)
+ # Decide which widget dictionary entries to write before fontSize gets
+ # its rendering default. Omitting /DA, /MK /BG, and /MK /BC when they
+ # were not explicitly requested lets the field inherit appearance from
+ # the AcroForm-level /DA//DR or the PDF viewer's built-in default.
+ _write_da = textColor is not None or fontName is not None or fontSize
is not None
if fontSize is None:
fontSize = 12
-
textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor)
+ textColor,borderColor,fillColor,borderWidth =
self.getDefaults(textColor,borderColor,fillColor,borderWidth)
canv = self.canv
if relative:
x, y = self.canv.absolutePosition(x,y)
@@ -864,8 +930,13 @@
F = makeFlags(annotationFlags,annotationFlagValues),
Ff = Ff,
#H=PDFName('N'),
- DA=PDFString('/%s %d Tf %s' % (iFontName,fontSize,
self.streamFillColor(textColor))),
)
+ # Write /DA only when font or text color was explicitly requested.
+ # Omitting /DA lets the widget inherit its default appearance from the
+ # AcroForm /DA string instead of overriding it with a widget-level
entry.
+ if _write_da:
+ da_color = self.streamFillColor(textColor) if
opaqueColor(textColor) else '0 g'
+ TF['DA'] = PDFString('/%s %d Tf %s' % (iFontName,fontSize,
da_color))
if Opt: TF['Opt'] = Opt
if I: TF['I'] = PDFArray(I)
if maxlen:
@@ -875,15 +946,17 @@
if not name:
name = 'AFF%03d' % len(self.fields)
TF['T'] = PDFString(name)
- MK = dict(
- BG=PDFArray(self.colorTuple(fillColor)),
- )
+ MK = {}
+ if fillColor is not None:
+ MK['BG'] = PDFArray(self.colorTuple(fillColor))
# Acrobat seems to draw a thin border when BS is defined, so only
# include this if there actually is a border to draw
if borderWidth:
TF['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
- MK['BC'] = PDFArray(self.colorTuple(borderColor))
- TF['MK'] = PDFDictionary(MK)
+ if borderColor is not None:
+ MK['BC'] = PDFArray(self.colorTuple(borderColor))
+ if MK:
+ TF['MK'] = PDFDictionary(MK)
TF = PDFDictionary(TF)
self.canv._addAnnotation(TF)
@@ -892,10 +965,10 @@
def textfield(self,
value='',
- fillColor=None,
- borderColor=None,
- textColor=None,
- borderWidth=1,
+ fillColor=_useDefault,
+ borderColor=_useDefault,
+ textColor=_useDefault,
+ borderWidth=_useDefault,
borderStyle='solid',
width=120,
height=36,
@@ -938,10 +1011,10 @@
def listbox(self,
value='',
- fillColor=None,
- borderColor=None,
- textColor=None,
- borderWidth=1,
+ fillColor=_useDefault,
+ borderColor=_useDefault,
+ textColor=_useDefault,
+ borderWidth=_useDefault,
borderStyle='solid',
width=120,
height=36,
@@ -985,10 +1058,10 @@
)
def choice(self,
value='',
- fillColor=None,
- borderColor=None,
- textColor=None,
- borderWidth=1,
+ fillColor=_useDefault,
+ borderColor=_useDefault,
+ textColor=_useDefault,
+ borderWidth=_useDefault,
borderStyle='solid',
width=120,
height=36,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/platypus/tables.py
new/reportlab-4.5.1/src/reportlab/platypus/tables.py
--- old/reportlab-4.4.9/src/reportlab/platypus/tables.py 2025-12-21
11:57:02.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/platypus/tables.py 2026-05-05
08:11:09.000000000 +0200
@@ -27,7 +27,8 @@
from reportlab import rl_config
from reportlab.lib.styles import PropertySet, ParagraphStyle, _baseFontName
from reportlab.lib import colors
-from reportlab.lib.utils import annotateException, IdentStr, flatten, isStr,
asNative, strTypes, __UNSET__
+from reportlab.lib.utils import annotateException, IdentStr, flatten, isStr,
asNative, strTypes, __UNSET__, \
+ rl_warn
from reportlab.lib.validators import isListOfNumbersOrNone
from reportlab.lib.rl_accel import fp_str
from reportlab.lib.abag import ABag as CellFrame
@@ -437,7 +438,10 @@
if b: break
if b: break
if rh: #find tallest row, it's of great interest'
- tallest = '(tallest row %d)' % int(max(rh))
+ try:
+ tallest = f'(tallest row {max(rh)})'
+ except:
+ tallest = ''
else:
tallest = ''
if vx:
@@ -486,8 +490,11 @@
if not V: return 0,0
aW = w - s.leftPadding - s.rightPadding
aH = aH - s.topPadding - s.bottomPadding
- if aW<0:
- raise ValueError(f'{self.identity()}: flowable given negative
availWidth={aW} == width={w} - leftPadding={s.leftPadding} -
rightPadding={s.rightPadding}')
+ if aW<0: #something is wrong
+ if not rl_config.allowTableBoundsErrors&2: #bit 1 1 --> ignore
+ raise ValueError(f'{self.identity()}: flowable given negative
availWidth={aW} width={w} - leftPadding={s.leftPadding} -
rightPadding={s.rightPadding}')
+ elif rl_config.allowTableBoundsErrors&8: #bit 3 1 --> turn
ignore into warn
+ rl_warn(f'{self.identity()}: flowable given negative
availWidth={aW} width={w} - leftPadding={s.leftPadding} -
rightPadding={s.rightPadding}')
t = 0
w = 0
canv = getattr(self,'canv',None)
@@ -664,9 +671,12 @@
dW,t = self._listCellGeom(v,w or
self._listValueWidth(v),s)
if canv: canv._fontname, canv._fontsize,
canv._leading = saved
dW = dW + s.leftPadding + s.rightPadding
- if not rl_config.allowTableBoundsErrors and dW>w:
- from reportlab.platypus.doctemplate import
LayoutError
- raise LayoutError("Flowable %s (%sx%s points)
too wide for cell(%d,%d) (%sx* points) in\n%s" %
(v[0].identity(30),fp_str(dW),fp_str(t),i,j, fp_str(w), self.identity(30)))
+ if dW>(w or 0): #something wrong
+ if not rl_config.allowTableBoundsErrors&1:
#bit 0 1 --> ignore
+ from reportlab.platypus.doctemplate import
LayoutError
+ raise LayoutError(f"Flowable
{v[0].identity(30)} ({fp_str(dW)}x{fp_str(t)} points) too wide for
cell({i},{j}) (fp_str(w)x* points) in\n{self.identity(30)}")
+ elif rl_config.allowTableBoundsErrors&4:
#bit 2 1 --> ignore
+ rl_warn(f"Flowable {v[0].identity(30)}
({fp_str(dW)}x{fp_str(t)} points) too wide for cell({i},{j}) (fp_str(w)x*
points) in\n{self.identity(30)}")
else:
v = (v is not None and str(v) or '').split("\n")
t = (s.leading or 1.2*s.fontsize)*len(v)
@@ -2167,7 +2177,11 @@
def split(self, availWidth, availHeight):
self._calc(availWidth, availHeight)
if self.splitByRow or self.splitInRow:
- if not rl_config.allowTableBoundsErrors and
self._width>availWidth: return []
+ if self._width>availWidth: #something wrong
+ if not rl_config.allowTableBoundsErrors&1:
+ return []
+ elif rl_config.allowTableBoundsErrors&4:
+ rl_warn(f'{self.identity(30)} split called with inadequate
width')
# If self.splitByRow is true, first try with doInRowSplit as false.
# Otherwise, first try with doInRowSplit as true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/rl_config.py
new/reportlab-4.5.1/src/reportlab/rl_config.py
--- old/reportlab-4.4.9/src/reportlab/rl_config.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab/rl_config.py 2026-05-01
12:16:20.000000000 +0200
@@ -129,13 +129,13 @@
_setOpt(k,v,conv)
_registered_resets=[]
-def register_reset(func):
+def register_reset(func, callback=None):
'''register a function to be called by rl_config._reset'''
_registered_resets[:] = [x for x in _registered_resets if x()]
L = [x for x in _registered_resets if x() is func]
if L: return
- from weakref import ref
- _registered_resets.append(ref(func))
+ from weakref import ref, WeakMethod
+ _registered_resets.append((WeakMethod if hasattr(func,'__self__') else
ref)(func,callback))
def _reset():
'''attempt to reset reportlab and friends'''
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab/rl_settings.py
new/reportlab-4.5.1/src/reportlab/rl_settings.py
--- old/reportlab-4.4.9/src/reportlab/rl_settings.py 2025-04-17
12:06:52.000000000 +0200
+++ new/reportlab-4.5.1/src/reportlab/rl_settings.py 2026-05-01
12:16:20.000000000 +0200
@@ -73,7 +73,11 @@
defCWRF
unShapedFontGlob'''.split())
-allowTableBoundsErrors = 1 # set to 0 to die on too large elements in
tables in debug (recommend 1 for production use)
+allowTableBoundsErrors = 1 # bit 0 --> ignore overall
width excession
+ # bit 1 --> ignore
negative available width
+ # bit 2 --> turn bit 0 to
a warning
+ # bit 3 --> turn bit 1
into a warning
+ # (recommend 1 for
production use)
shapeChecking = 1
defaultEncoding = 'WinAnsiEncoding' # 'WinAnsi' or 'MacRoman'
defaultGraphicsFontName= 'Times-Roman' #initializer for
STATE_DEFAULTS in shapes.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab.egg-info/PKG-INFO
new/reportlab-4.5.1/src/reportlab.egg-info/PKG-INFO
--- old/reportlab-4.4.9/src/reportlab.egg-info/PKG-INFO 2026-01-15
10:23:11.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab.egg-info/PKG-INFO 2026-05-12
11:20:50.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: reportlab
-Version: 4.4.9
+Version: 4.5.1
Summary: The Reportlab Toolkit
Home-page: https://www.reportlab.com/
Author: Andy Robinson, Robin Becker, the ReportLab team and the community
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/src/reportlab.egg-info/SOURCES.txt
new/reportlab-4.5.1/src/reportlab.egg-info/SOURCES.txt
--- old/reportlab-4.4.9/src/reportlab.egg-info/SOURCES.txt 2026-01-15
10:23:11.000000000 +0100
+++ new/reportlab-4.5.1/src/reportlab.egg-info/SOURCES.txt 2026-05-12
11:20:50.000000000 +0200
@@ -66,7 +66,6 @@
docs/userguide/testfile.txt
src/reportlab/MANIFEST.in
src/reportlab/__init__.py
-src/reportlab/license.txt
src/reportlab/rl_config.py
src/reportlab/rl_settings.py
src/reportlab.egg-info/PKG-INFO
@@ -188,7 +187,6 @@
src/reportlab/lib/colors.py
src/reportlab/lib/corp.py
src/reportlab/lib/enums.py
-src/reportlab/lib/extformat.py
src/reportlab/lib/fontfinder.py
src/reportlab/lib/fonts.py
src/reportlab/lib/formatters.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/tests/render-out/Drawing14.py
new/reportlab-4.5.1/tests/render-out/Drawing14.py
--- old/reportlab-4.4.9/tests/render-out/Drawing14.py 2026-01-15
10:19:21.000000000 +0100
+++ new/reportlab-4.5.1/tests/render-out/Drawing14.py 2026-05-11
16:19:31.000000000 +0200
@@ -6,8 +6,8 @@
def __init__(self,width=400,height=200,*args,**kw):
Drawing.__init__(self,width,height,*args,**kw)
self.transform = (1,0,0,1,0,0)
- self.add(Image(0,0,None,None,<PIL.GifImagePlugin.GifImageFile
image mode=P size=10x7 at
0x7F8A50052710>,fillColor=Color(0,0,0,1),fillOpacity=None,strokeColor=Color(0,0,0,1),strokeWidth=1,strokeLineCap=0,strokeLineJoin=0,strokeMiterLimit=0,strokeDashArray=None,strokeOpacity=None))
- self.add(Image(380,186,20,14,<PIL.GifImagePlugin.GifImageFile
image mode=P size=10x7 at
0x7F8A5015A990>,fillColor=Color(0,0,0,1),fillOpacity=None,strokeColor=Color(0,0,0,1),strokeWidth=1,strokeLineCap=0,strokeLineJoin=0,strokeMiterLimit=0,strokeDashArray=None,strokeOpacity=None))
+ self.add(Image(0,0,None,None,<PIL.GifImagePlugin.GifImageFile
image mode=P size=10x7 at
0x7F2C085596D0>,fillColor=Color(0,0,0,1),fillOpacity=None,strokeColor=Color(0,0,0,1),strokeWidth=1,strokeLineCap=0,strokeLineJoin=0,strokeMiterLimit=0,strokeDashArray=None,strokeOpacity=None))
+ self.add(Image(380,186,20,14,<PIL.GifImagePlugin.GifImageFile
image mode=P size=10x7 at
0x7F2C094C0F50>,fillColor=Color(0,0,0,1),fillOpacity=None,strokeColor=Color(0,0,0,1),strokeWidth=1,strokeLineCap=0,strokeLineJoin=0,strokeMiterLimit=0,strokeDashArray=None,strokeOpacity=None))
if __name__=="__main__": #NORUNTESTS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/tests/runAll.py
new/reportlab-4.5.1/tests/runAll.py
--- old/reportlab-4.4.9/tests/runAll.py 2024-12-13 09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/tests/runAll.py 2026-05-01 12:13:26.000000000 +0200
@@ -113,6 +113,8 @@
for _ in a[10:].split(',') if _.strip()]
for a in sys.argv if a.startswith('--exclude=')
],[])
+ for a in ('--verbosity=','--exclude=','--failfast'):
+ sys.argv[:] = [_ for _ in sys.argv if not _.startswith(a)]
testSuite =
makeSuite(folder,nonImportable=NI,exclude=exclude,pattern=pattern+(not haveSRC
and 'c' or ''))
result =
unittest.TextTestRunner(verbosity=verbosity,failfast=failfast).run(testSuite)
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/tests/test_lib_colors.py
new/reportlab-4.5.1/tests/test_lib_colors.py
--- old/reportlab-4.4.9/tests/test_lib_colors.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/tests/test_lib_colors.py 2026-04-20
17:20:17.000000000 +0200
@@ -64,10 +64,39 @@
allRed = [colors.red, [1, 0, 0], (1, 0, 0),
b'red', b'RED', b'0xFF0000', b'0xff0000',b'rgb(255,0,0)',
u'red', u'RED', u'0xFF0000', u'0xff0000',u'rgb(255,0,0)',
+ 'rgb(1.,0,0)', 'rgb(1.,0,0/100%)', 'rgb(255,0,0/100%)',
+ 'rgb(100.% 0% 0%/100%)',
]
for thing in allRed:
- assert colors.toColor(thing) ==
colors.red,"colors.toColor(%s)-->%s != colors.red(%s)" %
(ascii(thing),ascii(colors.toColor(thing)),colors.red)
+
self.assertEqual(colors.toColor(thing),colors.red,"colors.toColor(%s)-->%s !=
colors.red(%s)" % (ascii(thing),ascii(colors.toColor(thing)),colors.red))
+ self.assertEqual(colors.toColor('rgba(1 0 0
0.5)'),colors.Color(1/255,0,0,.5))
+ self.assertEqual(colors.toColor('rgb(255 0
0/50%)'),colors.Color(1,0,0,0.5))
+
+
+ def compare(c, x, nd=5):
+ for a in ('red','green','blue','alpha'):
+ if round(getattr(c,a),nd)!=round(getattr(x,a),nd): return False
+ return True
+
+ #compare str here to avoid float diffs caused by 255 divisions
+ toColor = colors.toColor
+ Color = colors.Color
+ CMYKColor = colors.CMYKColor
+ for value, expected in (
+ ("rgba(.10,.10,.10,.10)", "Color(.1,.1,.1,.1)"),
+ ("rgba(10.%,10.%,10.%,10.%)",
"Color(.101961,.101961,.101961,.1)"),
+ ("rgba(10%,10%,10%,10%)",
"Color(.101961,.101961,.101961,.1)"),
+ ("rgb(10%,10%,10%/10%)",
"Color(.101961,.101961,.101961,.1)"),
+ ("rgb(10%,10%,10%/.1)",
"Color(.101961,.101961,.101961,.1)"),
+ ("rgba(255,255,255,1)", "Color(1,1,1,1)"),
+ ("rgba(1,1,1,1.)", "Color(.003922,.003922,.003922,1)"),
+ ("rgb(1,1,1/1.)", "Color(.003922,.003922,.003922,1)"),
+ ("cmyk(1,1,1,1/.1)", "CMYKColor(1,1,1,1,alpha=0.1)"),
+ ("cmyka(1,1,1,1,.1)", "CMYKColor(1,1,1,1,alpha=0.1)"),
+ ):
+ self.assertTrue(compare(toColor(toColor(value)),eval(expected)),
+ f'toColor({value!r}) --> {toColor(value)!r} !=
{expected!r}')
def test2a(self):
'''attempt to test toColor against simple attacks'''
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/tests/test_lib_rl_safe_eval.py
new/reportlab-4.5.1/tests/test_lib_rl_safe_eval.py
--- old/reportlab-4.4.9/tests/test_lib_rl_safe_eval.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/tests/test_lib_rl_safe_eval.py 2026-02-12
10:27:46.000000000 +0100
@@ -138,17 +138,17 @@
except:
print('expr=%r' % expr)
annotateException('\nexpr=%r\n' % expr)
- self.assertEqual(answer,result,"rl_safe_eval(%r) = %r not expected %r"
% (expr,result,answer))
+ self.assertEqual(answer,result,f"rl_safe_eval({expr!r}) = {result!r})
not expected {answer!r}")
def skips(self,*args,**kwds):
raise unittest.SkipTest
def fails(self, expr, g=None, l=None, timeout=None, exception=BadCode):
try:
result = rl_safe_eval(expr,g,l,timeout=timeout)
- self.assertEqual(True,False,"rl_safe_eval(%r)=%r did not raise %s"
% (expr,result,exception.__name__))
+ self.assertEqual(True,False,f"rl_safe_eval({expr!r}) = {result!r}
did not raise {exception.__name__}")
except exception:
return
except:
- self.assertEqual(True,False,"rl_safe_eval(%r) raised %s: %s
instead of %s" %
(expr,sys.exc_info()[0].__name__,str(sys.exc_info()[1]),exception.__name__))
+ self.assertEqual(True,False,"rl_safe_eval({expr!r}) raised
{sys.exc_info()[0].__name__}: {str(sys.exc_info()[1])} instead of
{exception.__name__}")
GA = 'ga'
class SafeEvalTestBasics(unittest.TestCase):
@@ -158,6 +158,25 @@
def test_002(self):
self.assertTrue(rl_safe_eval("GA=='ga'"))
+class SafeExecTestBasics(unittest.TestCase):
+ def test_funcdef_blocks(self):
+ '''test security issue found by Ethan Kim lt ethan 4t cremit d0t io
gt'''
+ codes = (
+ '().__class__.__bases__[0]',
+ 'def f():\n'
+ '\tfor c in ().__class__.__bases__[0].__subclasses__():\n'
+ '\t\tif c.__name__ == "_wrap_close":\n'
+ '\t\t\treturn c.__init__.__globals__["system"]("id")\n'
+ 'f()\n',
+ )
+ blocked = 0
+ for c in codes:
+ try:
+ rl_safe_exec(c,{},{})
+ except BadCode:
+ blocked += 1
+ self.assertEqual(blocked,2,f'{blocked=} expected 2; probably BadCode
detection in FunctionDef fails')
+
class ExtendedLiteralEval(unittest.TestCase):
def test_001(self):
S = colors.getAllNamedColors().copy()
@@ -197,7 +216,7 @@
self.assertEqual(showVal(expr),expected,f"rl_extended_literal_eval({expr!r}) is
not equal to expected {expected}")
def makeSuite():
- return
makeSuiteForClasses(SafeEvalTestCase,SafeEvalTestBasics,ExtendedLiteralEval)
+ return
makeSuiteForClasses(SafeEvalTestCase,SafeEvalTestBasics,SafeExecTestBasics,ExtendedLiteralEval)
if __name__ == "__main__": #noruntests
unittest.TextTestRunner().run(makeSuite())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/reportlab-4.4.9/tests/test_pdfbase_pdfform.py
new/reportlab-4.5.1/tests/test_pdfbase_pdfform.py
--- old/reportlab-4.4.9/tests/test_pdfbase_pdfform.py 2024-12-13
09:47:00.000000000 +0100
+++ new/reportlab-4.5.1/tests/test_pdfbase_pdfform.py 2026-04-15
19:07:57.000000000 +0200
@@ -1,10 +1,14 @@
from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, outputfile,
printLocation, NearTestCase
setOutDir(__name__)
import unittest
+from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfform
class PdfFormTestCase(NearTestCase):
+ def _annotationDict(self, canv, index=1):
+ return canv._doc.idToObject['Annot.NUMBER%d' % index].dict
+
def testMultipleUsage(self):
for i in range(2):
c =
canvas.Canvas(outputfile('test_pdfbase_pdfform_multiple_usage_%s.pdf'%i))
@@ -12,6 +16,42 @@
pdfform.buttonFieldAbsolute(c, 'button', 'Off', 200, 200)
c.save()
+ def testTextfieldOmitsWidgetStyleEntriesWhenUnset(self):
+ # Regression test: omitted widget style args should not force
+ # widget-level appearance entries that block AcroForm inheritance.
+ canv = canvas.Canvas(BytesIO())
+ canv.acroForm.textfield(name='tf-none', x=10, y=10, width=100,
height=20,
+ textColor=None, fillColor=None, borderColor=None,
+ borderWidth=None)
+
+ annot = self._annotationDict(canv)
+ self.assertNotIn('DA', annot)
+ self.assertNotIn('MK', annot)
+ self.assertNotIn('BS', annot)
+
+ def testCheckboxOmitsMkColorsWhenUnset(self):
+ # Regression test: checkboxes still need /MK for /CA, but unset
+ # colors must not emit /MK /BG or /MK /BC.
+ canv = canvas.Canvas(BytesIO())
+ canv.acroForm.checkbox(name='cb-none', x=10, y=10, fillColor=None,
borderColor=None)
+
+ annot = self._annotationDict(canv)
+ self.assertIn('MK', annot)
+ mk = annot['MK'].dict
+ self.assertIn('CA', mk)
+ self.assertNotIn('BG', mk)
+ self.assertNotIn('BC', mk)
+
+ def testForceBorderWithoutFillColorDoesNotWriteBackground(self):
+ # Regression test: forceBorder with no fill color should not imply
+ # a widget background entry.
+ canv = canvas.Canvas(BytesIO())
+ canv.acroForm.textfield(name='tf-force-border', x=10, y=10, width=100,
height=20, fillColor=None, borderColor=None, forceBorder=True)
+ canv.save()
+
+ annot = self._annotationDict(canv)
+ self.assertNotIn('MK', annot)
+
def testAAbsoluteAndRelativeFields(self):
#the old test1 in pdfform
c = canvas.Canvas(outputfile("test_pdfbase_pdfform_formtest.pdf"))
@@ -56,14 +96,14 @@
af.checkbox(name='cb1C',tooltip='Field
cb1C',checked=True,x=72,y=72+2*36,buttonStyle='cross',borderWidth=2,
borderColor=red, fillColor=green, textColor=blue,forceBorder=True)
af.checkbox(name='cb1D',tooltip='Field
cb1D',checked=True,x=72,y=72+3*36,buttonStyle='star',borderWidth=2,
borderColor=red, fillColor=green, textColor=blue,forceBorder=True)
af.checkbox(name='cb1E',tooltip='Field
cb1E',checked=True,x=72,y=72+4*36,buttonStyle='diamond',borderStyle='bevelled',
borderWidth=2, borderColor=red, fillColor=green,
textColor=blue,forceBorder=True)
- af.checkbox(name='cb1F',tooltip='Field
cb1F',checked=True,x=72,y=72+5*36,buttonStyle='check', borderWidth=2,
borderColor=red, fillColor=None, textColor=None,forceBorder=True)
- af.checkbox(name='cb1H',tooltip='Field
cb1H',checked=True,x=72,y=72+6*36,buttonStyle='check',
borderStyle='underlined',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
- af.checkbox(name='cb1G',tooltip='Field
cb1G',checked=True,x=72,y=72+7*36,buttonStyle='check',
borderStyle='dashed',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
- af.checkbox(name='cb1I',tooltip='Field
cb1I',checked=True,x=72,y=72+8*36,buttonStyle='check',
borderStyle='inset',borderWidth=1, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
+ af.checkbox(name='cb1F',tooltip='Field
cb1F',checked=True,x=72,y=72+5*36,buttonStyle='check', borderWidth=2,
borderColor=red,forceBorder=True)
+ af.checkbox(name='cb1H',tooltip='Field
cb1H',checked=True,x=72,y=72+6*36,buttonStyle='check',
borderStyle='underlined',borderWidth=2, borderColor=red,forceBorder=True)
+ af.checkbox(name='cb1G',tooltip='Field
cb1G',checked=True,x=72,y=72+7*36,buttonStyle='check',
borderStyle='dashed',borderWidth=2, borderColor=red,forceBorder=True)
+ af.checkbox(name='cb1I',tooltip='Field
cb1I',checked=True,x=72,y=72+8*36,buttonStyle='check',
borderStyle='inset',borderWidth=1, borderColor=red,forceBorder=True)
af.checkbox(name='cb1J',tooltip='Field
cb1J',checked=True,x=72,y=72+9*36,buttonStyle='check',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
- af.checkbox(name='cb1K',tooltip='Field
cb1K',checked=True,x=72,y=72+10*36,buttonStyle='check', borderWidth=1,
borderColor=None, fillColor=None, textColor=None,forceBorder=True)
- af.checkbox(name='cb1L',tooltip='Field
cb1L',checked=False,x=72,y=800,buttonStyle='check',borderWidth=None,
borderColor=None, fillColor=None, textColor=None,forceBorder=True)
- af.checkbox(name='cb1M',tooltip='Field
cb1M',checked=False,x=72,y=600,buttonStyle='check',borderWidth=2,
borderColor=blue, fillColor=None, textColor=None,forceBorder=True)
+ af.checkbox(name='cb1K',tooltip='Field
cb1K',checked=True,x=72,y=72+10*36,buttonStyle='check',
borderWidth=1,forceBorder=True)
+ af.checkbox(name='cb1L',tooltip='Field
cb1L',checked=False,x=72,y=800,buttonStyle='check',borderWidth=None,
borderColor=af.useDefault, fillColor=af.useDefault,
textColor=af.useDefault,forceBorder=True)
+ af.checkbox(name='cb1M',tooltip='Field
cb1M',checked=False,x=72,y=600,buttonStyle='check',borderWidth=2,
borderColor=blue,forceBorder=True)
af.radio(name='rb1A',tooltip='Field rb1A', value='V1',
selected=False,x=144,y=72+0*36,buttonStyle='circle',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
af.radio(name='rb1A',tooltip='Field rb1A', value='V2',
selected=True,x=144,y=72+1*36,buttonStyle='circle',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
af.radio(name='rb1B',tooltip='Field rb1B', value='V1',
selected=False,x=144+36,y=72+0*36,buttonStyle='check',
borderStyle='solid',shape='square', borderWidth=2, borderColor=green,
fillColor=red, textColor=blue,forceBorder=True)
@@ -82,14 +122,14 @@
af.checkbox(name='cb2C',tooltip='Field
cb2C',checked=True,x=72,y=72+2*36,buttonStyle='cross',borderWidth=2,
borderColor=red, fillColor=green, textColor=blue,forceBorder=True)
af.checkbox(name='cb2D',tooltip='Field
cb2D',checked=True,x=72,y=72+3*36,buttonStyle='star',borderWidth=2,
borderColor=red, fillColor=green, textColor=blue,forceBorder=True)
af.checkbox(name='cb2E',tooltip='Field
cb2E',checked=True,x=72,y=72+4*36,buttonStyle='diamond',borderStyle='bevelled',
borderWidth=2, borderColor=red, fillColor=green,
textColor=blue,forceBorder=True)
- af.checkbox(name='cb2F',tooltip='Field
cb2F',checked=True,x=72,y=72+5*36,buttonStyle='check', borderWidth=2,
borderColor=red, fillColor=None, textColor=None,forceBorder=True)
- af.checkbox(name='cb2H',tooltip='Field
cb2H',checked=True,x=72,y=72+6*36,buttonStyle='check',
borderStyle='underlined',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
- af.checkbox(name='cb2G',tooltip='Field
cb2G',checked=True,x=72,y=72+7*36,buttonStyle='check',
borderStyle='dashed',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
- af.checkbox(name='cb2I',tooltip='Field
cb2I',checked=True,x=72,y=72+8*36,buttonStyle='check',
borderStyle='inset',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
+ af.checkbox(name='cb2F',tooltip='Field
cb2F',checked=True,x=72,y=72+5*36,buttonStyle='check', borderWidth=2,
borderColor=red,forceBorder=True)
+ af.checkbox(name='cb2H',tooltip='Field
cb2H',checked=True,x=72,y=72+6*36,buttonStyle='check',
borderStyle='underlined',borderWidth=2, borderColor=red,forceBorder=True)
+ af.checkbox(name='cb2G',tooltip='Field
cb2G',checked=True,x=72,y=72+7*36,buttonStyle='check',
borderStyle='dashed',borderWidth=2, borderColor=red,forceBorder=True)
+ af.checkbox(name='cb2I',tooltip='Field
cb2I',checked=True,x=72,y=72+8*36,buttonStyle='check',
borderStyle='inset',borderWidth=2, borderColor=red,forceBorder=True)
af.checkbox(name='cb2J',tooltip='Field
cb2J',checked=True,x=72,y=72+9*36,buttonStyle='check',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
- af.checkbox(name='cb2K',tooltip='Field
cb2K',checked=True,x=72,y=72+10*36,buttonStyle='check', borderWidth=1,
borderColor=None, fillColor=None, textColor=None,forceBorder=True)
- af.checkbox(name='cb2L',tooltip='Field
cb2L',checked=False,x=72,y=800,buttonStyle='check',borderWidth=None,
borderColor=None, fillColor=None, textColor=None,forceBorder=True)
- af.checkbox(name='cb2M',tooltip='Field
cb2M',checked=False,x=72,y=600,buttonStyle='check',borderWidth=2,
borderColor=blue, fillColor=None, textColor=None,forceBorder=True)
+ af.checkbox(name='cb2K',tooltip='Field
cb2K',checked=True,x=72,y=72+10*36,buttonStyle='check',
borderWidth=1,forceBorder=True)
+ af.checkbox(name='cb2L',tooltip='Field
cb2L',checked=False,x=72,y=800,buttonStyle='check', borderWidth=None,
forceBorder=True)
+ af.checkbox(name='cb2M',tooltip='Field
cb2M',checked=False,x=72,y=600,buttonStyle='check',borderWidth=2,
borderColor=blue,forceBorder=True)
af.radio(name='rb2A',tooltip='Field rb2A', value='V1',
selected=False,x=144,y=72+0*36,buttonStyle='circle',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
af.radio(name='rb2A',tooltip='Field rb2A', value='V2',
selected=True,x=144,y=72+1*36,buttonStyle='circle',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
af.radio(name='rb2B',tooltip='Field rb2B', value='V1',
selected=False,x=144+36,y=72+0*36,buttonStyle='check',
borderStyle='solid',shape='square', borderWidth=2, borderColor=green,
fillColor=red, textColor=blue,forceBorder=True)
@@ -108,14 +148,14 @@
af.checkboxRelative(name='cb3C',tooltip='Field
cb3C',checked=True,x=72,y=72+2*36,buttonStyle='cross',borderWidth=2,
borderColor=red, fillColor=green, textColor=blue,forceBorder=True)
af.checkboxRelative(name='cb3D',tooltip='Field
cb3D',checked=True,x=72,y=72+3*36,buttonStyle='star',borderWidth=2,
borderColor=red, fillColor=green, textColor=blue,forceBorder=True)
af.checkboxRelative(name='cb3E',tooltip='Field
cb3E',checked=True,x=72,y=72+4*36,buttonStyle='diamond',borderStyle='bevelled',
borderWidth=2, borderColor=red, fillColor=green,
textColor=blue,forceBorder=True)
- af.checkboxRelative(name='cb3F',tooltip='Field
cb3F',checked=True,x=72,y=72+5*36,buttonStyle='check', borderWidth=2,
borderColor=red, fillColor=None, textColor=None,forceBorder=True)
- af.checkboxRelative(name='cb3H',tooltip='Field
cb3H',checked=True,x=72,y=72+6*36,buttonStyle='check',
borderStyle='underlined',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
- af.checkboxRelative(name='cb3G',tooltip='Field
cb3G',checked=True,x=72,y=72+7*36,buttonStyle='check',
borderStyle='dashed',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
- af.checkboxRelative(name='cb3I',tooltip='Field
cb3I',checked=True,x=72,y=72+8*36,buttonStyle='check',
borderStyle='inset',borderWidth=2, borderColor=red, fillColor=None,
textColor=None,forceBorder=True)
+ af.checkboxRelative(name='cb3F',tooltip='Field
cb3F',checked=True,x=72,y=72+5*36,buttonStyle='check', borderWidth=2,
borderColor=red,forceBorder=True)
+ af.checkboxRelative(name='cb3H',tooltip='Field
cb3H',checked=True,x=72,y=72+6*36,buttonStyle='check',
borderStyle='underlined',borderWidth=2, borderColor=red,forceBorder=True)
+ af.checkboxRelative(name='cb3G',tooltip='Field
cb3G',checked=True,x=72,y=72+7*36,buttonStyle='check',
borderStyle='dashed',borderWidth=2, borderColor=red,forceBorder=True)
+ af.checkboxRelative(name='cb3I',tooltip='Field
cb3I',checked=True,x=72,y=72+8*36,buttonStyle='check',
borderStyle='inset',borderWidth=2, borderColor=red,forceBorder=True)
af.checkboxRelative(name='cb3J',tooltip='Field
cb3J',checked=True,x=72,y=72+9*36,buttonStyle='check',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
- af.checkboxRelative(name='cb3K',tooltip='Field
cb3K',checked=True,x=72,y=72+10*36,buttonStyle='check', borderWidth=1,
borderColor=None, fillColor=None, textColor=None,forceBorder=True)
- af.checkboxRelative(name='cb3L',tooltip='Field
cb3L',checked=False,x=72,y=800,buttonStyle='check',borderWidth=None,
borderColor=None, fillColor=None, textColor=None,forceBorder=True)
- af.checkboxRelative(name='cb3M',tooltip='Field
cb3M',checked=False,x=72,y=600,buttonStyle='check',borderWidth=2,
borderColor=blue, fillColor=None, textColor=None,forceBorder=True)
+ af.checkboxRelative(name='cb3K',tooltip='Field
cb3K',checked=True,x=72,y=72+10*36,buttonStyle='check',
borderWidth=1,forceBorder=True)
+ af.checkboxRelative(name='cb3L',tooltip='Field
cb3L',checked=False,x=72,y=800,buttonStyle='check',borderWidth=None,forceBorder=True)
+ af.checkboxRelative(name='cb3M',tooltip='Field
cb3M',checked=False,x=72,y=600,buttonStyle='check',borderWidth=2,
borderColor=blue,forceBorder=True)
af.radioRelative(name='rb3A',tooltip='Field rb3A', value='V1',
selected=False,x=144,y=72+0*36,buttonStyle='circle',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
af.radioRelative(name='rb3A',tooltip='Field rb3A', value='V2',
selected=True,x=144,y=72+1*36,buttonStyle='circle',
borderStyle='solid',shape='circle', borderWidth=2, borderColor=red,
fillColor=green, textColor=blue,forceBorder=True)
af.radioRelative(name='rb3B',tooltip='Field rb3B', value='V1',
selected=False,x=144+36,y=72+0*36,buttonStyle='check',
borderStyle='solid',shape='square', borderWidth=2, borderColor=green,
fillColor=red, textColor=blue,forceBorder=True)