Re: pillow patch

Claude Paroz <[email protected]> Mon, 28 Feb 2022 13:55:30 +0100
Newsgroups gmane.comp.python.reportlab.user
Message-ID <[email protected]>
Le 18.02.22 à 14:37, Claude Paroz a écrit :
> Hi,
> 
> As far as I can see, pillow is a required dependency of ReportLab, so 
> the attached patch simplifies some parts of the code to account for that.
> I also upped the pillow minimal version as versions before 9.0.0 have 
> known security vulnerabilities.

Attached is the same patch rebased after another conflicting patch was 
applied to main source tree.

Claude
-- 
www.2xlibre.net
0001-PIL-pillow-is-a-required-dependency.patch (text/x-patch, 20 KB)
From f01f7f7578b7b37129cc4a27aad97e99cfaea153 Mon Sep 17 00:00:00 2001
From: Claude Paroz <[email protected]>
Date: Mon, 14 Feb 2022 18:19:52 +0100
Subject: [PATCH] PIL (pillow) is a required dependency

---
 setup.py                          |   2 +-
 src/reportlab/lib/utils.py        |  38 +----
 src/reportlab/pdfgen/pdfimages.py |   3 +-
 tests/test_pdfgen_general.py      | 257 ++++++++++++++----------------
 tests/test_platypus_general.py    |  14 +-
 tests/test_platypus_images.py     |  13 +-
 6 files changed, 137 insertions(+), 190 deletions(-)

diff --git a/setup.py b/setup.py
index 199b4193..4b0d3e9d 100644
--- a/setup.py
+++ b/setup.py
@@ -767,7 +767,7 @@ def main():
                 ],
             
             #this probably only works for setuptools, but distutils seems to ignore it
-            install_requires=['pillow>=4.0.0'],
+            install_requires=['pillow>=9.0.0'],
             python_requires='>=3.6, <4',
             extras_require={
                 'RLPYCAIRO': ['rlPyCairo>=0.0.5'],
diff --git a/src/reportlab/lib/utils.py b/src/reportlab/lib/utils.py
index 811d6906..a9ad4754 100644
--- a/src/reportlab/lib/utils.py
+++ b/src/reportlab/lib/utils.py
@@ -11,6 +11,7 @@ from io import BytesIO
 
 from reportlab.lib.rltempfile import get_rl_tempfile, get_rl_tempdir
 from . rl_safe_eval import rl_safe_exec, rl_safe_eval, safer_globals
+from PIL import Image
 
 class __UNSET__:
     @staticmethod
@@ -389,19 +390,6 @@ def recursiveImport(modulename, baseDir=None, noCWD=0, debug=0):
         if debug:
             print('===== restore sys.path=%s' % repr(opath))
 
-# Image Capability Detection.  Set a flag haveImages
-# to tell us if PIL library is present.
-# define PIL_Image as either None, or an alias for the PIL.Image
-# module, as there are 2 ways to import it
-try:
-    from PIL import Image
-except ImportError:
-    try:
-        import Image
-    except ImportError:
-        Image = None
-haveImages = Image is not None
-
 class ArgvDictValue:
     '''A type to allow clients of getArgvDict to specify a conversion function'''
     def __init__(self,value,func):
@@ -672,26 +660,12 @@ class ImageReader:
                     self.fp.close()
                     del self.fp #will become a property in the next statement
                     self.__class__=LazyImageReader
-                if haveImages:
-                    #detect which library we are using and open the image
-                    if not self._image:
-                        self._image = self._read_image(self.fp)
-                        self.check_pil_image_size(self._image)
-                    if getattr(self._image,'format',None)=='JPEG': self.jpeg_fh = self._jpeg_fh
-                else:
-                    from reportlab.pdfbase.pdfutils import readJPEGInfo
-                    try:
-                        self._width,self._height, c, dpi = readJPEGInfo(self.fp)
-                    except:
-                        annotateException('\nImaging Library not available, unable to import bitmaps only jpegs\nfileName=%r identity=%s'%(fileName,self.identity()))
-                    size = self._width*self._height*c
-                    if self._max_image_size is not None and size>self._max+image_size:
-                        raise MemoryError('JPEG %s color %s x %s image would use %s > %s bytes'
-                                            %(c,self._width,self._height,size,self._max_image_size))
+                #detect which library we are using and open the image
+                if not self._image:
+                    self._image = self._read_image(self.fp)
+                    self.check_pil_image_size(self._image)
+                if getattr(self._image,'format',None)=='JPEG':
                     self.jpeg_fh = self._jpeg_fh
-                    self._data = self.fp.read()
-                    self._dataA=None
-                    self.fp.seek(0)
             except:
                 annotateException('\nfileName=%r identity=%s'%(fileName,self.identity()))
 
diff --git a/src/reportlab/pdfgen/pdfimages.py b/src/reportlab/pdfgen/pdfimages.py
index d4dba07c..7c3ca202 100644
--- a/src/reportlab/pdfgen/pdfimages.py
+++ b/src/reportlab/pdfgen/pdfimages.py
@@ -11,7 +11,7 @@ import reportlab
 from reportlab import rl_config
 from reportlab.pdfbase import pdfutils
 from reportlab.pdfbase import pdfdoc
-from reportlab.lib.utils import haveImages, isStr
+from reportlab.lib.utils import isStr
 from reportlab.lib.rl_accel import fp_str, asciiBase85Encode
 from reportlab.lib.boxstuff import aspectRatioFix
 
@@ -74,7 +74,6 @@ class PDFImage:
     def cache_imagedata(self):
         image = self.image
         if not pdfutils.cachedImageExists(image):
-            if not haveImages: return
             pdfutils.cacheImageFile(image)
 
         #now we have one cached, slurp it in
diff --git a/tests/test_pdfgen_general.py b/tests/test_pdfgen_general.py
index 599cbd88..1551168f 100644
--- a/tests/test_pdfgen_general.py
+++ b/tests/test_pdfgen_general.py
@@ -12,7 +12,7 @@ from io import BytesIO
 from reportlab.pdfgen import canvas   # gmcm 2000/10/13, pdfgen now a package
 from reportlab.lib.units import inch, cm
 from reportlab.lib import colors
-from reportlab.lib.utils import haveImages, fileName2FSEnc
+from reportlab.lib.utils import fileName2FSEnc
 from reportlab.lib.boxstuff import rectCorner
 
 #################################################################
@@ -719,9 +719,6 @@ cost to performance.""")
     framePage(c, "Images")
     c.setFont('Times-Roman', 12)
     t = c.beginText(inch, 10 * inch)
-    if not haveImages:
-        c.drawString(inch, 11*inch,
-                     "Python Imaging Library not found! Below you see rectangles instead of images.")
 
     t.textLines("""PDFgen uses the Python Imaging Library
         to process a very wide variety of image formats.
@@ -736,28 +733,20 @@ cost to performance.""")
 
     c.drawText(t)
 
-    if haveImages:
-        from reportlab.lib.testutils import testsFolder
-        gif = os.path.join(testsFolder,'pythonpowered.gif')
-        c.drawInlineImage(gif,2*inch, 7*inch)
-        c.drawInlineImage(os.path.join(testsFolder,'pythonpowered-gs.gif'),4*inch, 7.5*inch)
-        tif = os.path.join(testsFolder,'test-cross.tiff')   #example of a mode '1' image
-        c.drawInlineImage(tif,1*inch, 1*inch)
-        from reportlab.lib.utils import Image as PilImage
-        if PilImage:
-            c.drawInlineImage(PilImage.open(tif),1.25*inch, 1*inch)
-    else:
-        c.rect(2*inch, 7*inch, 110, 44)
-        c.rect(4*inch, 7*inch, 110, 44)
+    from reportlab.lib.testutils import testsFolder
+    gif = os.path.join(testsFolder,'pythonpowered.gif')
+    c.drawInlineImage(gif,2*inch, 7*inch)
+    c.drawInlineImage(os.path.join(testsFolder,'pythonpowered-gs.gif'),4*inch, 7.5*inch)
+    tif = os.path.join(testsFolder,'test-cross.tiff')   #example of a mode '1' image
+    c.drawInlineImage(tif,1*inch, 1*inch)
+    from reportlab.lib.utils import Image as PilImage
+    c.drawInlineImage(PilImage.open(tif),1.25*inch, 1*inch)
 
     c.line(1.5*inch, 7*inch, 4*inch, 7*inch)
     c.line(2*inch, 6.5*inch, 2*inch, 8*inch)
     c.drawString(4.5 * inch, 7.25*inch, 'inline image drawn at natural size')
 
-    if haveImages:
-        c.drawInlineImage(gif,2*inch, 5*inch, inch, inch)
-    else:
-        c.rect(2*inch, 5*inch, inch, inch)
+    c.drawInlineImage(gif,2*inch, 5*inch, inch, inch)
 
     c.line(1.5*inch, 5*inch, 4*inch, 5*inch)
     c.line(2*inch, 4.5*inch, 2*inch, 6*inch)
@@ -767,25 +756,17 @@ cost to performance.""")
     c.drawString(1.5 * inch, 3.75*inch, 'This results in faster generation and much smaller files.')
 
     for i in range(5):
-        if haveImages:
-            (w, h) = c.drawImage(gif, (1.5 + i)*inch, 3*inch)
-        else:
-            (w, h) = (144, 10)
-            c.rect((1.5 + i)*inch, 3*inch, 110, 44)
+        (w, h) = c.drawImage(gif, (1.5 + i)*inch, 3*inch)
 
     myMask = [254,255,222,223,0,1]
     c.drawString(1.5 * inch, 2.5*inch, "The optional 'mask' parameter lets you define transparent colors. We used a color picker")
     c.drawString(1.5 * inch, 2.3*inch, "to determine that the yellow in the image above is RGB=(225,223,0).  We then define a mask")
     c.drawString(1.5 * inch, 2.1*inch, "spanning these RGB values:  %s.  The background vanishes!!" % myMask)
     c.drawString(2.5*inch, 1.2*inch, 'This would normally be obscured')
-    if haveImages:
-        c.drawImage(gif, 1*inch, 1.2*inch, w, h, mask=myMask)
-        c.drawImage(gif, 3*inch, 1.2*inch, w, h, mask='auto')
-        c.drawImage(os.path.join(testsFolder,'test-rgba.png'),5*inch,1.2*inch,width=10,height=10,mask='auto')
-        c.drawImage(os.path.join(testsFolder,'test-indexed.png'),5.5*inch,1.2*inch,width=10,height=10,mask='auto')
-    else:
-        c.rect(1*inch, 1.2*inch, w, h)
-        c.rect(3*inch, 1.2*inch, w, h)
+    c.drawImage(gif, 1*inch, 1.2*inch, w, h, mask=myMask)
+    c.drawImage(gif, 3*inch, 1.2*inch, w, h, mask='auto')
+    c.drawImage(os.path.join(testsFolder,'test-rgba.png'),5*inch,1.2*inch,width=10,height=10,mask='auto')
+    c.drawImage(os.path.join(testsFolder,'test-indexed.png'),5.5*inch,1.2*inch,width=10,height=10,mask='auto')
 
     c.showPage()
     c.drawString(1*inch, 10.25*inch, "For rgba type images we can use the alpha channel if we set mask='auto'.")
@@ -804,114 +785,112 @@ cost to performance.""")
     c.rect(3*inch, 8+14.4*inch, w, h)
     c.rect(1*inch, 6+14.4*inch, w, h)
     c.rect(3*inch, 6+14.4*inch, w, h)
-    if haveImages:
-        from reportlab.lib.testutils import testsFolder
-        png = os.path.join(testsFolder,'solid_red_alpha.png')
-        c.drawImage(png, 1*inch, 8*inch+14.4, w, h, mask=None)
-        c.drawImage(png, 3*inch, 8*inch+14.4, w, h, mask='auto')
-        png = os.path.join(testsFolder,'alpha_test.png')
-        c.drawImage(png, 1*inch, 6*inch+14.4, w, h, mask=None)
-        c.drawImage(png, 3*inch, 6*inch+14.4, w, h, mask='auto')
+    from reportlab.lib.testutils import testsFolder
+    png = os.path.join(testsFolder,'solid_red_alpha.png')
+    c.drawImage(png, 1*inch, 8*inch+14.4, w, h, mask=None)
+    c.drawImage(png, 3*inch, 8*inch+14.4, w, h, mask='auto')
+    png = os.path.join(testsFolder,'alpha_test.png')
+    c.drawImage(png, 1*inch, 6*inch+14.4, w, h, mask=None)
+    c.drawImage(png, 3*inch, 6*inch+14.4, w, h, mask='auto')
     c.showPage()
 
-    if haveImages:
-        import shutil
-        c.drawString(1*inch, 10.25*inch, 'This jpeg is actually a gif')
-        jpg = outputfile('_i_am_actually_a_gif.jpg')
-        shutil.copyfile(gif,jpg)
-        c.drawImage(jpg, 1*inch, 9.25*inch, w, h, mask='auto')
-        tjpg = os.path.join(os.path.dirname(os.path.dirname(gif)),'docs','images','lj8100.jpg')
-        if os.path.isfile(tjpg):
-            c.drawString(4*inch, 10.25*inch, 'This gif is actually a jpeg')
-            tgif = outputfile(os.path.basename('_i_am_actually_a_jpeg.gif'))
-            shutil.copyfile(tjpg,tgif)
-            c.drawImage(tgif, 4*inch, 9.25*inch, w, h, mask='auto')
-
-        c.drawString(inch, 9.0*inch, 'Image positioning tests with preserveAspectRatio')
-
-        #preserveAspectRatio test
-        c.drawString(inch, 8.8*inch, 'Both of these should appear within the boxes, vertically centered')
-
-
-        x, y, w, h = inch, 6.75* inch, 2*inch, 2*inch
-        c.rect(x, y, w, h)
-        (w2, h2) = c.drawImage(gif,  #anchor southwest, drawImage
-                    x, y, width=w, height=h,
-                    preserveAspectRatio=True,
-                    anchor='c'
-                    )
-
-        #now test drawInlineImage across the page
-        x = 5 * inch
-        c.rect(x, y, w, h)
-        (w2, h2) = c.drawInlineImage(gif,  #anchor southwest, drawInlineImage
-                    x, y, width=w, height=h,
-                    preserveAspectRatio=True,
-                    anchor='c'
-                    )
-
-        c.drawString(inch, 5.75*inch,
-        'anchored by respective corners - use both a wide and a tall one as tests')
-        x = 0.25 * inch
-        y = 5*inch
-        for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
-            x += 0.75*inch
-            c.rect(x, y, 0.6*inch, 0.6*inch)
-            c.drawImage(
-                    gif, x, y,
-                    width=0.6*inch, height=0.6*inch,
-                    preserveAspectRatio=True,
-                    anchor=anchor
-                    )
-            c.drawString(x, y-0.1*inch, anchor)
-
-        x = 0.25 * inch
-        y = 4*inch
-        tall_red = os.path.join(testsFolder,'tall_red.png')
-        for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
-            x += 0.75*inch
-            c.rect(x, y, 0.6*inch, 0.6*inch)
-            c.drawImage(
-                    tall_red, x, y,
-                    width=0.6*inch, height=0.6*inch,
-                    preserveAspectRatio=True,
-                    anchor=anchor
-                    )
-            c.drawString(x, y-0.1*inch, anchor)
-
-        #Andy's positioning code fails for this case when the image is not reaching the limit
-        x = 0.25 * inch
-        y = 3*inch
-        tall_red = os.path.join(testsFolder,'tall_red.png')
-        for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
-            x += 0.75*inch
-            c.rect(x, 3*inch, 0.7*inch, 0.7*inch)
-            c.drawImage(
-                    tall_red, x, 3*inch,
-                    width=0.6*inch, height=0.6*inch,
-                    preserveAspectRatio=True,
-                    anchor=anchor
-                    )
-            c.drawString(x, y-0.1*inch, anchor)
-
-        #fix by using anchorAtXY
-        x = 0.25 * inch
-        y = 2*inch
-        tall_red = os.path.join(testsFolder,'tall_red.png')
-        for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
-            x += 0.75*inch
-            c.rect(x, y, 0.7*inch, 0.7*inch)
-            ax, ay = rectCorner(x, y, 0.7*inch, 0.7*inch, anchor)
-            c.drawImage(
-                    tall_red, ax, ay,
-                    width=0.6*inch, height=0.6*inch,
-                    preserveAspectRatio=True,
-                    anchor=anchor,
-                    anchorAtXY=True,
-                    )
-            c.drawString(x, y-0.1*inch, anchor)
+    import shutil
+    c.drawString(1*inch, 10.25*inch, 'This jpeg is actually a gif')
+    jpg = outputfile('_i_am_actually_a_gif.jpg')
+    shutil.copyfile(gif,jpg)
+    c.drawImage(jpg, 1*inch, 9.25*inch, w, h, mask='auto')
+    tjpg = os.path.join(os.path.dirname(os.path.dirname(gif)),'docs','images','lj8100.jpg')
+    if os.path.isfile(tjpg):
+        c.drawString(4*inch, 10.25*inch, 'This gif is actually a jpeg')
+        tgif = outputfile(os.path.basename('_i_am_actually_a_jpeg.gif'))
+        shutil.copyfile(tjpg,tgif)
+        c.drawImage(tgif, 4*inch, 9.25*inch, w, h, mask='auto')
+
+    c.drawString(inch, 9.0*inch, 'Image positioning tests with preserveAspectRatio')
+
+    #preserveAspectRatio test
+    c.drawString(inch, 8.8*inch, 'Both of these should appear within the boxes, vertically centered')
+
+
+    x, y, w, h = inch, 6.75* inch, 2*inch, 2*inch
+    c.rect(x, y, w, h)
+    (w2, h2) = c.drawImage(gif,  #anchor southwest, drawImage
+                x, y, width=w, height=h,
+                preserveAspectRatio=True,
+                anchor='c'
+                )
+
+    #now test drawInlineImage across the page
+    x = 5 * inch
+    c.rect(x, y, w, h)
+    (w2, h2) = c.drawInlineImage(gif,  #anchor southwest, drawInlineImage
+                x, y, width=w, height=h,
+                preserveAspectRatio=True,
+                anchor='c'
+                )
+
+    c.drawString(inch, 5.75*inch,
+    'anchored by respective corners - use both a wide and a tall one as tests')
+    x = 0.25 * inch
+    y = 5*inch
+    for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
+        x += 0.75*inch
+        c.rect(x, y, 0.6*inch, 0.6*inch)
+        c.drawImage(
+                gif, x, y,
+                width=0.6*inch, height=0.6*inch,
+                preserveAspectRatio=True,
+                anchor=anchor
+                )
+        c.drawString(x, y-0.1*inch, anchor)
+
+    x = 0.25 * inch
+    y = 4*inch
+    tall_red = os.path.join(testsFolder,'tall_red.png')
+    for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
+        x += 0.75*inch
+        c.rect(x, y, 0.6*inch, 0.6*inch)
+        c.drawImage(
+                tall_red, x, y,
+                width=0.6*inch, height=0.6*inch,
+                preserveAspectRatio=True,
+                anchor=anchor
+                )
+        c.drawString(x, y-0.1*inch, anchor)
+
+    #Andy's positioning code fails for this case when the image is not reaching the limit
+    x = 0.25 * inch
+    y = 3*inch
+    tall_red = os.path.join(testsFolder,'tall_red.png')
+    for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
+        x += 0.75*inch
+        c.rect(x, 3*inch, 0.7*inch, 0.7*inch)
+        c.drawImage(
+                tall_red, x, 3*inch,
+                width=0.6*inch, height=0.6*inch,
+                preserveAspectRatio=True,
+                anchor=anchor
+                )
+        c.drawString(x, y-0.1*inch, anchor)
+
+    #fix by using anchorAtXY
+    x = 0.25 * inch
+    y = 2*inch
+    tall_red = os.path.join(testsFolder,'tall_red.png')
+    for anchor in ('nw','n','ne','w','c','e','sw','s','se'):
+        x += 0.75*inch
+        c.rect(x, y, 0.7*inch, 0.7*inch)
+        ax, ay = rectCorner(x, y, 0.7*inch, 0.7*inch, anchor)
+        c.drawImage(
+                tall_red, ax, ay,
+                width=0.6*inch, height=0.6*inch,
+                preserveAspectRatio=True,
+                anchor=anchor,
+                anchorAtXY=True,
+                )
+        c.drawString(x, y-0.1*inch, anchor)
 
-        c.showPage()
+    c.showPage()
 
 
 #########################################################################
diff --git a/tests/test_platypus_general.py b/tests/test_platypus_general.py
index ecb611d6..68de73a3 100644
--- a/tests/test_platypus_general.py
+++ b/tests/test_platypus_general.py
@@ -24,16 +24,14 @@ from reportlab.lib.units import inch, cm
 from reportlab.lib.styles import PropertySet, getSampleStyleSheet, ParagraphStyle
 from reportlab.lib import colors
 from reportlab.rl_config import defaultPageSize
-from reportlab.lib.utils import haveImages, _RL_DIR, rl_isfile, open_for_read, fileName2FSEnc, asNative
+from reportlab.lib.utils import _RL_DIR, rl_isfile, open_for_read, fileName2FSEnc, asNative
 import unittest
 from reportlab.lib.testutils import testsFolder
-if haveImages:
-    _GIF = os.path.join(testsFolder,'pythonpowered.gif')
-    if not rl_isfile(_GIF): _GIF = None
-    _GAPNG = os.path.join(testsFolder,'gray-alpha.png')
-    if not rl_isfile(_GAPNG): _GAPNG = None
-else:
-    _GIF = None
+
+_GIF = os.path.join(testsFolder,'pythonpowered.gif')
+if not rl_isfile(_GIF): _GIF = None
+_GAPNG = os.path.join(testsFolder,'gray-alpha.png')
+if not rl_isfile(_GAPNG): _GAPNG = None
 if _GIF: _GIFFSEnc=fileName2FSEnc(_GIF)
 if _GAPNG: _GAPNGFSEnc=fileName2FSEnc(_GAPNG)
 
diff --git a/tests/test_platypus_images.py b/tests/test_platypus_images.py
index e8723616..dfe3ed0d 100644
--- a/tests/test_platypus_images.py
+++ b/tests/test_platypus_images.py
@@ -15,16 +15,13 @@ def run():
                                     ShowBoundaryValue, SimpleDocTemplate, FrameBG, Paragraph, \
                                     FrameBreak
     from reportlab.lib.colors import toColor
-    from reportlab.lib.utils import haveImages, _RL_DIR, rl_isfile, open_for_read, fileName2FSEnc, asNative
+    from reportlab.lib.utils import _RL_DIR, rl_isfile, open_for_read, fileName2FSEnc, asNative
     from reportlab.lib.styles import getSampleStyleSheet
     styleSheet = getSampleStyleSheet()
-    if haveImages:
-        _GIF = os.path.join(testsFolder,'pythonpowered.gif')
-        if not rl_isfile(_GIF): _GIF = None
-        _GAPNG = os.path.join(testsFolder,'gray-alpha.png')
-        if not rl_isfile(_GAPNG): _GAPNG = None
-    else:
-        _GIF = None
+    _GIF = os.path.join(testsFolder,'pythonpowered.gif')
+    if not rl_isfile(_GIF): _GIF = None
+    _GAPNG = os.path.join(testsFolder,'gray-alpha.png')
+    if not rl_isfile(_GAPNG): _GAPNG = None
     if _GIF: _GIFFSEnc=fileName2FSEnc(_GIF)
     if _GAPNG: _GAPNGFSEnc=fileName2FSEnc(_GAPNG)
 
-- 
2.30.2