在Pyramid中使用ReportLab将临时图像写入临时PDF的问题
我正在使用Python 3,结合Pyramid和ReportLab来生成动态PDF文件。
我遇到了一个问题,就是在PDF中插入图片。我在一个网页应用中使用ReportLab生成带有图片的PDF,但我的图片并不存储在本地,而是在一个远程服务器上。我把这些图片下载到本地的一个临时目录(我确认它们确实被保存下来了)。但是当我尝试把这些图片添加到PDF时,虽然为图片留出了空间,但图片却没有显示出来。
这是我相关的代码(简化版):
# creates pdf in memory
doc = SimpleDocTemplate(pdfName, pagesize=A4)
elements = []
for item in model['items']:
# image goes here:
if item['IMAGENAME']:
response = getImageFromRemoteServer(item['IMAGENAME'])
dir_filename = directory + item['IMAGENAME']
if response.status_code == 200:
with open(dir_filename, 'wb') as f:
for chunk in response.iter_content():
f.write(chunk)
questions.append(Image(dir_filename, width=2*inch, height=2*inch))
# create and save the pdf
doc.build(elements,canvasmaker=NumberedCanvas)
我按照这里的用户指南 https://www.reportlab.com/docs/reportlab-userguide.pdf 进行了操作,尝试了上面的方法,还尝试了嵌入图片(正如用户指南中在段落部分提到的)以及把图片放在表格里。
我还查看了 这里 的内容,但没有找到帮助。
我真正想问的是,下载一张图片并把它放进PDF的正确方法是什么?
编辑:修正了代码的缩进
编辑2:
问题解决了,我终于能够把图片放进PDF了。我不太确定是什么原因让它成功的。唯一知道的变化是,现在我使用urllib来发送请求,而之前没有使用。以下是我工作的代码(为了问题简化了,实际上我的代码更复杂和封装):
doc = SimpleDocTemplate(pdfName, pagesize=A4)
# array of elements in the pdf
elements = []
for question in model['questions']:
# image goes here:
if question['IMAGEFILE']:
filename = question['IMAGEFILE']
dir_filename = directory + filename
url = get_url(settings, filename)
response = urllib.request.urlopen(url)
raw_data = response.read()
f = open(dir_filename, 'wb')
f.write(raw_data)
f.close()
response.close()
myImage = Image(dir_filename)
myImage.drawHeight = 2* inch
myImage.drawWidth = 2* inch
myImage.hAlign = "LEFT"
elements.append(myImage)
# create and save the pdf
doc.build(elements)
2 个回答
很可能是因为 lazy
这个参数导致你第一个代码示例没有显示图片。在临时文件的上下文管理器之外触发 reportlab PDF 渲染,可能会导致这种情况发生。
reportlab.platypus.flowables.py
(使用版本 3.1.8)
class Image(Flowable):
"""an image (digital picture). Formats supported by PIL/Java 1.4 (the Python/Java Imaging Library
are supported. At the present time images as flowables are always centered horozontally
in the frame. We allow for two kinds of lazyness to allow for many images in a document
which could lead to file handle starvation.
lazy=1 don't open image until required.
lazy=2 open image when required then shut it.
"""
_fixedWidth = 1
_fixedHeight = 1
def __init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1):
"""If size to draw at not specified, get it from the image."""
self.hAlign = 'CENTER'
self._mask = mask
fp = hasattr(filename,'read')
if fp:
self._file = filename
self.filename = repr(filename)
...
代码示例的最后三行告诉你,可以传递一个有 read
方法的对象。实际上,调用 urllib.request.urlopen(url)
就会返回这样的对象。利用这个内存缓冲区,你可以创建一个 Image 实例。这样就不需要对文件系统有写入权限,也不需要在 PDF 渲染后删除这些文件。运用我们新学到的知识,可以让代码更易读。由于你的使用场景涉及到使用内存缓冲区来获取远程资源,支持 Python 文件 API 的方法会是组装 PDF 文件的更简洁的方式。
from contextlib import closing
import urllib.request
doc = SimpleDocTemplate(pdfName, pagesize=A4)
# array of elements in the pdf
elements = []
for question in model['questions']:
# download image and create Image from file-like object
if question['IMAGEFILE']:
filename = question['IMAGEFILE']
image_url = get_url(settings, filename)
with closing(urllib.request.urlopen(image_url)) as image_file:
myImage = Image(image_file, width=2*inch, height=2*inch)
myImage.hAlign = "LEFT"
elements.append(myImage)
# create and save the pdf
doc.build(elements)
参考资料
让你的代码不依赖于文件的来源。把获取文件或资源的部分和生成文档的部分分开。确保你的工具可以处理本地文件。把加载文件的代码放在一个专门的类或函数里。这个封装很重要。这周我在看thumbor的加载类时又注意到了这一点。
如果这样做有效,那你就知道reportlab、PIL和你的应用基本上都能正常工作了。
接下来,让你的代码也能处理远程文件,使用类似http://path/to/remote/files
的地址。
之后,你可以根据环境或使用场景来切换使用本地文件加载器或HTTP加载器。
另一个选择是让你的代码使用类似file://path/to/file
的地址来处理本地文件。
这样,从本地文件切换到远程文件时,唯一需要改变的就是网址。你可能需要一个支持这种方式的Python库。requests库非常适合下载文件,可能也支持file://
这种网址格式。