Merge pull request #1445 from radarhere/pdf

Added PDF multipage saving
This commit is contained in:
wiredfool 2015-10-01 05:33:11 -07:00
commit 92f5133469
3 changed files with 115 additions and 75 deletions

View File

@ -51,17 +51,21 @@ def _endobj(fp):
fp.write("endobj\n")
def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True)
##
# (Internal) Image save plugin for the PDF format.
def _save(im, fp, filename):
def _save(im, fp, filename, save_all=False):
resolution = im.encoderinfo.get("resolution", 72.0)
#
# make sure image data is available
im.load()
xref = [0]*(5+1) # placeholders
xref = [0]
class TextWriter(object):
def __init__(self, fp):
@ -78,11 +82,6 @@ def _save(im, fp, filename):
fp.write("%PDF-1.2\n")
fp.write("% created by PIL PDF driver " + __version__ + "\n")
#
# Get image characteristics
width, height = im.size
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
# Flatedecode (zip compression).
@ -125,7 +124,7 @@ def _save(im, fp, filename):
#
# catalogue
xref[1] = fp.tell()
xref.append(fp.tell())
_obj(
fp, 1,
Type="/Catalog",
@ -134,15 +133,27 @@ def _save(im, fp, filename):
#
# pages
numberOfPages = 1
if save_all:
try:
numberOfPages = im.n_frames
except AttributeError:
# Image format does not have n_frames. It is a single frame image
pass
pages = [str(pageNumber*3+4)+" 0 R"
for pageNumber in range(0, numberOfPages)]
xref[2] = fp.tell()
xref.append(fp.tell())
_obj(
fp, 2,
Type="/Pages",
Count=1,
Kids="[4 0 R]")
Count=len(pages),
Kids="["+"\n".join(pages)+"]")
_endobj(fp)
for pageNumber in range(0, numberOfPages):
im.seek(pageNumber)
#
# image
@ -165,9 +176,14 @@ def _save(im, fp, filename):
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
xref[3] = fp.tell()
#
# Get image characteristics
width, height = im.size
xref.append(fp.tell())
_obj(
fp, 3,
fp, pageNumber*3+3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
@ -187,16 +203,18 @@ def _save(im, fp, filename):
#
# page
xref[4] = fp.tell()
_obj(fp, 4)
xref.append(fp.tell())
_obj(fp, pageNumber*3+4)
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image 3 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
"/XObject << /image %d 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
procset,
pageNumber*3+3,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
int(height * 72.0 / resolution),
pageNumber*3+5))
_endobj(fp)
#
@ -209,8 +227,8 @@ def _save(im, fp, filename):
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref[5] = fp.tell()
_obj(fp, 5, Length=len(op.fp.getvalue()))
xref.append(fp.tell())
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())
@ -232,6 +250,7 @@ def _save(im, fp, filename):
# --------------------------------------------------------------------
Image.register_save("PDF", _save)
Image.register_save_all("PDF", _save_all)
Image.register_extension("PDF", ".pdf")

View File

@ -1,16 +1,19 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image
import os.path
class TestFilePdf(PillowTestCase):
def helper_save_as_pdf(self, mode):
def helper_save_as_pdf(self, mode, save_all=False):
# Arrange
im = hopper(mode)
outfile = self.tempfile("temp_" + mode + ".pdf")
# Act
if save_all:
im.save(outfile, save_all=True)
else:
im.save(outfile)
# Assert
@ -58,6 +61,19 @@ class TestFilePdf(PillowTestCase):
self.assertRaises(ValueError, lambda: im.save(outfile))
def test_save_all(self):
# Single frame image
self.helper_save_as_pdf("RGB", save_all=True)
# Multiframe image
im = Image.open("Tests/images/dispose_bgnd.gif")
outfile = self.tempfile('temp.pdf')
im.save(outfile, save_all=True)
self.assertTrue(os.path.isfile(outfile))
self.assertGreater(os.path.getsize(outfile), 0)
if __name__ == '__main__':
unittest.main()

View File

@ -786,6 +786,11 @@ PIL can write PDF (Acrobat) images. Such images are written as binary PDF 1.1
files, using either JPEG or HEX encoding depending on the image mode (and
whether JPEG support is available or not).
When calling :py:meth:`~PIL.Image.Image.save`, if a multiframe image is used,
by default, only the first image will be saved. To save all frames, each frame
to a separate page of the PDF, the ``save_all`` parameter must be present and
set to ``True``.
PIXAR (read only)
^^^^^^^^^^^^^^^^^