将 matplotlib 对象加载到 reportlab

loading matplotlib object into reportlab

我正在尝试将 matplotlib 对象加载到 reportlab 中。 这是我的代码:

from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Image
from matplotlib import pyplot as plt

def __get_img_data():
    """
    returns the binary image data of the plot
    """
    img_file = NamedTemporaryFile(delete=False)
    plt.savefig(img_file.name)
    img_data = open(img_file.name + '.png', 'rb').read()
    os.remove(img_file.name)
    os.remove(img_file.name + '.png')
    return img_data

def get_plot():
    # HERE I PLOT SOME STUFF
    img_data = __get_img_data()
    plt.close()
    return img_data

class NumberedCanvas(canvas.Canvas):
    def __init__(self):
        pass

class ReportTemplate:
    def __init__(self):
        pass
    def _header_footer(self, canvas, doc):
        pass

    def get_data(self):
        elements = []
        elements.append('hello')
        ## HERE I WANT TO ADD THE IMAGE
        imgdata = get_plot()
        with open('/tmp/x.png', 'wb') as fh:
            fh.write(imgdata)
        im = Image('/tmp/x.png', width=usable_width, height=usable_width)
        elements.append(im)
        os.remove('/tmp/x.png')
        ######
        doc.build(elements, onFirstPage=self._header_footer,\
                  onLaterPages=self._header_footer,\
                  canvasmaker=NumberedCanvas)
        # blah blah
        return obj

我的目标是将绘图图像插入到报告中。 这工作正常,但我不想写入临时文件。 我尝试安装 PIL,因为我读过一些人使用 PIL 的图像库来安装 PIL,但是一旦安装 PIL,我的另一部分代码由于 Pillow 版本不兼容而中断。

我找到了 2 个解决方案:

1:使用名为 pdfrw 的包: Is there a matplotlib flowable for ReportLab?

2:更简单更简洁的方法:

class PdfImage(Flowable):
    def __init__(self, img_data, width=200, height=200):
        self.img_width = width
        self.img_height = height
        self.img_data = img_data

    def wrap(self, width, height):
        return self.img_width, self.img_height

    def drawOn(self, canv, x, y, _sW=0):
        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))
        canv.saveState()
        canv.drawImage(self.img_data, x, y, self.img_width, self.img_height)
        canv.restoreState()


def make_report():
    fig = plt.figure(figsize=(4, 3))
    plt.plot([1,2,3,4],[1,4,9,26])
    plt.ylabel('some numbers')
    imgdata = cStringIO.StringIO()
    fig.savefig(imgdata, format='png')
    imgdata.seek(0)
    image = ImageReader(imgdata)

    doc = SimpleDocTemplate("hello.pdf")
    style = styles["Normal"]
    story = [Spacer(0, inch)]
    img = PdfImage(image, width=200, height=200)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    story.append(img)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    doc.build(story, onFirstPage=myFirstPage, onLaterPages=myLaterPages, canvasmaker=PageNumCanvas)

pdfrw 文档糟透了

pdfrw example discussed in is a bit klunky is because the pdfrw 文档糟糕透顶的唯一原因。由于糟糕的文档,该示例的作者@Larry-Meyn 使用 rst2pdf 的 vectorpdf 扩展作为起点,并且 that 扩展也没有真正记录,and 必须处理 rst2pdf 和 pdfrw 的怪癖(并且比您需要的更通用,因为它可以让 rst2pdf 从预先存在的 PDF 的任意页面显示任意矩形)。令人惊奇的是 Larry 设法让它发挥作用,我向他致敬。

我完全有资格这么说,因为我是 pdfrw 的作者并且对 rst2pdf 做出了一些贡献,包括 vectorpdf 扩展。

但您可能还是想使用 pdfrw

直到一个月前我才真正关注 Whosebug,而 pdfrw 本身也停滞了几年,但我现在在这里,我认为你应该再看看 pdfrw,甚至尽管文档仍然很糟糕。

为什么? 因为如果您输出到 png 文件,您的图像将被 栅格化 ,如果您使用 pdfrw,它将保持 矢量格式 ,这意味着它在任何比例下都会很好看。

所以我修改了你的答案的 png 示例

您的 png 示例不是一个完整的程序 -- doc.build 的参数未定义,样式未定义,缺少一些导入等。但它已经足够接近了获得一些意图并让它发挥作用。

Edit -- 我刚刚注意到这个例子实际上是 Larry 例子的修改版本,所以 that example 仍然很有价值,因为它是在某些方面比这更全面。

在我解决了这些问题并获得了一些输出后,我添加了一个能够使用 png 或 pdf 的选项,因此您可以看到其中的区别。下面的程序将创建两个不同的 PDF 文件,您可以自己比较结果。

import cStringIO
from matplotlib import pyplot as plt
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Image, Flowable
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet

from pdfrw import PdfReader, PdfDict
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerl

styles = getSampleStyleSheet()
style = styles['Normal']

def form_xo_reader(imgdata):
    page, = PdfReader(imgdata).pages
    return pagexobj(page)


class PdfImage(Flowable):
    def __init__(self, img_data, width=200, height=200):
        self.img_width = width
        self.img_height = height
        self.img_data = img_data

    def wrap(self, width, height):
        return self.img_width, self.img_height

    def drawOn(self, canv, x, y, _sW=0):
        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))
        canv.saveState()
        img = self.img_data
        if isinstance(img, PdfDict):
            xscale = self.img_width / img.BBox[2]
            yscale = self.img_height / img.BBox[3]
            canv.translate(x, y)
            canv.scale(xscale, yscale)
            canv.doForm(makerl(canv, img))
        else:
            canv.drawImage(img, x, y, self.img_width, self.img_height)
        canv.restoreState()

def make_report(outfn, use_pdfrw):
    fig = plt.figure(figsize=(4, 3))
    plt.plot([1,2,3,4],[1,4,9,26])
    plt.ylabel('some numbers')
    imgdata = cStringIO.StringIO()
    fig.savefig(imgdata, format='pdf' if use_pdfrw else 'png')
    imgdata.seek(0)
    reader = form_xo_reader if use_pdfrw else ImageReader
    image = reader(imgdata)

    doc = SimpleDocTemplate(outfn)
    style = styles["Normal"]
    story = [Spacer(0, inch)]
    img = PdfImage(image, width=200, height=200)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    story.append(img)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    doc.build(story)

make_report("hello_png.pdf", False)
make_report("hello_pdf.pdf", True)

这种方法有什么缺点?

第一个明显的缺点是现在需要 pdfrw,但这可以从 PyPI 获得。

下一个缺点是,如果您将大量 matplotlib 图放入文档中,我认为这种技术会复制字体等资源,因为我不相信 reportlab 足够聪明,不会注意到重复项。

我相信这个问题可以通过将你所有的图输出到different pages of a single PDF. I haven't actually tried that with matplotlib, but pdfrw is perfectly capable of converting each page of an existing pdf to a separate flowable来解决。

因此,如果您有很多绘图并且它使您的最终 PDF 太大,您可以研究一下,或者只是尝试其中一种 PDF 优化器,看看它是否有帮助。无论如何,这是不同日子的不同问题。