12

I saved some plots from matplotlib into a pdf format because it seems to offer a better quality. How do I include the PDF image into a PDF document using ReportLab? The convenience method Image(filepath) does not work for this format.

Thank you.

user378289
  • 179
  • 2
  • 5

4 Answers4

3

You can use the wonderful pdfrw package together with reportlab and use it to pass file-like objects of matplotlib figures directly into a flowable:

This was answered before, but I want to give a minimal example here, please also look here: https://stackoverflow.com/a/13870512/4497962

from io import BytesIO
import matplotlib.pyplot as plt
from pdfrw import PdfReader, PdfDict
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerl
from reportlab.platypus import Flowable
from reportlab.lib.enums import TA_JUSTIFY,TA_LEFT,TA_CENTER,TA_RIGHT

class PdfImage(Flowable):
    """
    PdfImage wraps the first page from a PDF file as a Flowable
    which can be included into a ReportLab Platypus document.
    Based on the vectorpdf extension in rst2pdf (http://code.google.com/p/rst2pdf/)

    This can be used from the place where you want to return your matplotlib image
    as a Flowable:

        img = BytesIO()

        fig, ax = plt.subplots(figsize=(canvaswidth,canvaswidth))

        ax.plot([1,2,3],[6,5,4],antialiased=True,linewidth=2,color='red',label='a curve')

        fig.savefig(img,format='PDF')

        return(PdfImage(img))

    """

    def __init__(self, filename_or_object, width=None, height=None, kind='direct'):
        # If using StringIO buffer, set pointer to begining
        if hasattr(filename_or_object, 'read'):
            filename_or_object.seek(0)
            #print("read")
        self.page = PdfReader(filename_or_object, decompress=False).pages[0]
        self.xobj = pagexobj(self.page)

        self.imageWidth = width
        self.imageHeight = height
        x1, y1, x2, y2 = self.xobj.BBox

        self._w, self._h = x2 - x1, y2 - y1
        if not self.imageWidth:
            self.imageWidth = self._w
        if not self.imageHeight:
            self.imageHeight = self._h
        self.__ratio = float(self.imageWidth)/self.imageHeight
        if kind in ['direct','absolute'] or width==None or height==None:
            self.drawWidth = width or self.imageWidth
            self.drawHeight = height or self.imageHeight
        elif kind in ['bound','proportional']:
            factor = min(float(width)/self._w,float(height)/self._h)
            self.drawWidth = self._w*factor
            self.drawHeight = self._h*factor

    def wrap(self, availableWidth, availableHeight):
        """
        returns draw- width and height

        convenience function to adapt your image 
        to the available Space that is available
        """
        return self.drawWidth, self.drawHeight

    def drawOn(self, canv, x, y, _sW=0):
        """
        translates Bounding Box and scales the given canvas
        """
        if _sW > 0 and hasattr(self, 'hAlign'):
            a = self.hAlign
            if a in ('CENTER', 'CENTRE', TA_CENTER):
                x += 0.5*_sW
            elif a in ('RIGHT', TA_RIGHT):
                x += _sW
            elif a not in ('LEFT', TA_LEFT):
                raise ValueError("Bad hAlign value " + str(a))

        #xobj_name = makerl(canv._doc, self.xobj)
        xobj_name = makerl(canv, self.xobj)

        xscale = self.drawWidth/self._w
        yscale = self.drawHeight/self._h

        x -= self.xobj.BBox[0] * xscale
        y -= self.xobj.BBox[1] * yscale

        canv.saveState()
        canv.translate(x, y)
        canv.scale(xscale, yscale)
        canv.doForm(xobj_name)
        canv.restoreState()
Community
  • 1
  • 1
skidzo
  • 395
  • 3
  • 12
  • It is exactly what I needed! Thank you a lot for the code! – desa Dec 21 '17 at 23:41
  • If you liked this you might also want to take a look at this: https://pypi.python.org/pypi/autobasedoc/ – skidzo Feb 06 '18 at 12:04
  • Thanks for the code! I'm having some trouble while trying to set the position of an image with the method drawOn What should I pass to its first argument? – Murilo Sitonio May 23 '19 at 20:16
  • Ola Murilo, tudo bem? It's the canvas object: image.drawOn(canv, x, y), see reportlab/pdfgen/canvas.py in class Canvas – skidzo Jun 01 '19 at 12:13
2

According to ReportLab's FAQ this is only possible with ReportLab PLUS:

Can I use vector graphics in my PDFs?

No, the Open Source package doesn't do this. PageCatcher (see the previous answer) allows you to easily incorporate any vector image by saving it as a PDF and then using it exactly as you would an image file, and Report Markup Language accepts PDF files along with JPG, GIF and PNG.

Update: I haven't looked into this for a while but on the page of pdfrw it says:

pdfrw can read and write PDF files, and can also be used to read in PDFs which can then be used inside reportlab.

jnns
  • 5,148
  • 4
  • 47
  • 74
  • That is correct. There are a couple of examples using pdfrw for this both at the pdfrw page, and in some [questions](http://stackoverflow.com/questions/31712386/loading-matplotlib-object-into-reportlab/) here – Patrick Maupin Aug 16 '15 at 02:43
  • If you can take the vector data itself, you can draw on a reportlab canvas. That produces beautiful graphics without images – xxyzzy Sep 23 '18 at 16:35
  • this answer is not correct, see my answer from Jan 23 '17 – skidzo Apr 27 '20 at 16:30
0

You can use svg export from matplotlib and use svglib python library to include vector graphics in reportlab generated PDF files. svglib takes a svg file and makes a drawing object that can be directly used in reportlab.

See also this question for more details: Generating PDFs from SVG input

Jiri
  • 16,425
  • 6
  • 52
  • 68
  • the above is the preferred way, as you don't have to file operations on the storage, and by the way, svglib uses pdfrw... – skidzo Jun 03 '19 at 15:04
-5

Use from reportlab.graphics import renderPDF

coulix
  • 3,328
  • 6
  • 55
  • 81
  • 3
    This is incorrect. renderPDF takes the EXISTING document and generates a PDF. The exact opposite of what the poster wants. – Tyler Eaves Feb 21 '11 at 16:57