Pillow/PIL/PdfImagePlugin.py

272 lines
7.4 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
# $Id$
#
# PDF (Acrobat) file handling
#
# History:
# 1996-07-16 fl Created
# 1997-01-18 fl Fixed header
# 2004-02-21 fl Fixes for 1/L/CMYK images, etc.
# 2004-02-24 fl Fixes for 1 and P images.
#
# Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved.
# Copyright (c) 1996-1997 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#
##
# Image plugin for PDF images (output only).
##
2017-05-13 07:26:52 +03:00
from . import Image, ImageFile, ImageSequence
from ._binary import i8
import io
2010-07-31 06:52:47 +04:00
2015-08-25 15:27:18 +03:00
__version__ = "0.4"
2010-07-31 06:52:47 +04:00
#
# --------------------------------------------------------------------
# object ids:
# 1. catalogue
# 2. pages
# 3. image
# 4. page
# 5. page contents
def _obj(fp, obj, **dictionary):
fp.write("%d 0 obj\n" % obj)
if dictionary:
fp.write("<<\n")
for k, v in dictionary.items():
2010-07-31 06:52:47 +04:00
if v is not None:
fp.write("/%s %s\n" % (k, v))
fp.write(">>\n")
2010-07-31 06:52:47 +04:00
2014-05-12 15:56:55 +04:00
2010-07-31 06:52:47 +04:00
def _endobj(fp):
fp.write("endobj\n")
2010-07-31 06:52:47 +04:00
2014-05-12 15:56:55 +04:00
2015-09-29 15:51:52 +03:00
def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True)
2010-07-31 06:52:47 +04:00
##
# (Internal) Image save plugin for the PDF format.
2015-09-29 15:51:52 +03:00
def _save(im, fp, filename, save_all=False):
2010-07-31 06:52:47 +04:00
resolution = im.encoderinfo.get("resolution", 72.0)
#
# make sure image data is available
im.load()
2015-09-29 15:51:52 +03:00
xref = [0]
2010-07-31 06:52:47 +04:00
2015-05-26 17:07:21 +03:00
class TextWriter(object):
def __init__(self, fp):
self.fp = fp
2014-05-12 15:56:55 +04:00
def __getattr__(self, name):
return getattr(self.fp, name)
2014-05-12 15:56:55 +04:00
def write(self, value):
self.fp.write(value.encode('latin-1'))
fp = TextWriter(fp)
fp.write("%PDF-1.2\n")
fp.write("% created by PIL PDF driver " + __version__ + "\n")
2010-07-31 06:52:47 +04:00
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
# or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
# Flatedecode (zip compression).
bits = 8
params = None
if im.mode == "1":
filter = "/ASCIIHexDecode"
colorspace = "/DeviceGray"
2014-05-12 15:56:55 +04:00
procset = "/ImageB" # grayscale
2010-07-31 06:52:47 +04:00
bits = 1
elif im.mode == "L":
filter = "/DCTDecode"
2010-07-31 06:52:47 +04:00
# params = "<< /Predictor 15 /Columns %d >>" % (width-2)
colorspace = "/DeviceGray"
2014-05-12 15:56:55 +04:00
procset = "/ImageB" # grayscale
2010-07-31 06:52:47 +04:00
elif im.mode == "P":
filter = "/ASCIIHexDecode"
colorspace = "[ /Indexed /DeviceRGB 255 <"
2010-07-31 06:52:47 +04:00
palette = im.im.getpalette("RGB")
for i in range(256):
r = i8(palette[i*3])
g = i8(palette[i*3+1])
b = i8(palette[i*3+2])
colorspace += "%02x%02x%02x " % (r, g, b)
colorspace += "> ]"
2014-05-12 15:56:55 +04:00
procset = "/ImageI" # indexed color
2010-07-31 06:52:47 +04:00
elif im.mode == "RGB":
filter = "/DCTDecode"
colorspace = "/DeviceRGB"
2014-05-12 15:56:55 +04:00
procset = "/ImageC" # color images
2010-07-31 06:52:47 +04:00
elif im.mode == "CMYK":
filter = "/DCTDecode"
colorspace = "/DeviceCMYK"
2014-05-12 15:56:55 +04:00
procset = "/ImageC" # color images
2010-07-31 06:52:47 +04:00
else:
raise ValueError("cannot save mode %s" % im.mode)
#
# catalogue
2015-09-29 15:51:52 +03:00
xref.append(fp.tell())
2014-05-12 15:56:55 +04:00
_obj(
fp, 1,
Type="/Catalog",
Pages="2 0 R")
2010-07-31 06:52:47 +04:00
_endobj(fp)
#
# pages
2017-05-13 07:26:52 +03:00
ims = [im]
2015-09-29 15:51:52 +03:00
if save_all:
2017-05-13 07:26:52 +03:00
append_images = im.encoderinfo.get("append_images", [])
for append_im in append_images:
if append_im.mode != im.mode:
append_im = append_im.convert(im.mode)
append_im.encoderinfo = im.encoderinfo.copy()
2017-05-13 07:26:52 +03:00
ims.append(append_im)
numberOfPages = 0
for im in ims:
im_numberOfPages = 1
if save_all:
try:
im_numberOfPages = im.n_frames
except AttributeError:
# Image format does not have n_frames. It is a single frame image
pass
numberOfPages += im_numberOfPages
2015-09-29 15:51:52 +03:00
pages = [str(pageNumber*3+4)+" 0 R"
for pageNumber in range(0, numberOfPages)]
xref.append(fp.tell())
2014-05-12 15:56:55 +04:00
_obj(
fp, 2,
Type="/Pages",
2015-09-29 15:51:52 +03:00
Count=len(pages),
Kids="["+"\n".join(pages)+"]")
2010-07-31 06:52:47 +04:00
_endobj(fp)
2017-05-13 07:26:52 +03:00
pageNumber = 0
for imSequence in ims:
for im in ImageSequence.Iterator(imSequence):
#
# image
op = io.BytesIO()
if filter == "/ASCIIHexDecode":
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
#
# Get image characteristics
width, height = im.size
xref.append(fp.tell())
_obj(
fp, pageNumber*3+3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
fp.write("stream\n")
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
_endobj(fp)
#
# page
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 %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),
pageNumber*3+5))
_endobj(fp)
#
# page contents
op = TextWriter(io.BytesIO())
op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref.append(fp.tell())
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
fp.write("stream\n")
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
_endobj(fp)
pageNumber += 1
2010-07-31 06:52:47 +04:00
#
# trailer
startxref = fp.tell()
fp.write("xref\n0 %d\n0000000000 65535 f \n" % len(xref))
2010-07-31 06:52:47 +04:00
for x in xref[1:]:
fp.write("%010d 00000 n \n" % x)
fp.write("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref))
fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
if hasattr(fp, "flush"):
fp.flush()
2010-07-31 06:52:47 +04:00
#
# --------------------------------------------------------------------
Image.register_save("PDF", _save)
2015-09-29 15:51:52 +03:00
Image.register_save_all("PDF", _save_all)
2010-07-31 06:52:47 +04:00
Image.register_extension("PDF", ".pdf")
Image.register_mime("PDF", "application/pdf")