You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
8.1 KiB

4 years ago
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # PDF (Acrobat) file handling
  6. #
  7. # History:
  8. # 1996-07-16 fl Created
  9. # 1997-01-18 fl Fixed header
  10. # 2004-02-21 fl Fixes for 1/L/CMYK images, etc.
  11. # 2004-02-24 fl Fixes for 1 and P images.
  12. #
  13. # Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved.
  14. # Copyright (c) 1996-1997 by Fredrik Lundh.
  15. #
  16. # See the README file for information on usage and redistribution.
  17. #
  18. ##
  19. # Image plugin for PDF images (output only).
  20. ##
  21. from . import Image, ImageFile, ImageSequence, PdfParser
  22. import io
  23. import os
  24. import time
  25. __version__ = "0.5"
  26. #
  27. # --------------------------------------------------------------------
  28. # object ids:
  29. # 1. catalogue
  30. # 2. pages
  31. # 3. image
  32. # 4. page
  33. # 5. page contents
  34. def _save_all(im, fp, filename):
  35. _save(im, fp, filename, save_all=True)
  36. ##
  37. # (Internal) Image save plugin for the PDF format.
  38. def _save(im, fp, filename, save_all=False):
  39. is_appending = im.encoderinfo.get("append", False)
  40. if is_appending:
  41. existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b")
  42. else:
  43. existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
  44. resolution = im.encoderinfo.get("resolution", 72.0)
  45. info = {
  46. "title": None if is_appending else os.path.splitext(
  47. os.path.basename(filename)
  48. )[0],
  49. "author": None,
  50. "subject": None,
  51. "keywords": None,
  52. "creator": None,
  53. "producer": None,
  54. "creationDate": None if is_appending else time.gmtime(),
  55. "modDate": None if is_appending else time.gmtime()
  56. }
  57. for k, default in info.items():
  58. v = im.encoderinfo.get(k) if k in im.encoderinfo else default
  59. if v:
  60. existing_pdf.info[k[0].upper() + k[1:]] = v
  61. #
  62. # make sure image data is available
  63. im.load()
  64. existing_pdf.start_writing()
  65. existing_pdf.write_header()
  66. existing_pdf.write_comment("created by PIL PDF driver " + __version__)
  67. #
  68. # pages
  69. ims = [im]
  70. if save_all:
  71. append_images = im.encoderinfo.get("append_images", [])
  72. for append_im in append_images:
  73. append_im.encoderinfo = im.encoderinfo.copy()
  74. ims.append(append_im)
  75. numberOfPages = 0
  76. image_refs = []
  77. page_refs = []
  78. contents_refs = []
  79. for im in ims:
  80. im_numberOfPages = 1
  81. if save_all:
  82. try:
  83. im_numberOfPages = im.n_frames
  84. except AttributeError:
  85. # Image format does not have n_frames.
  86. # It is a single frame image
  87. pass
  88. numberOfPages += im_numberOfPages
  89. for i in range(im_numberOfPages):
  90. image_refs.append(existing_pdf.next_object_id(0))
  91. page_refs.append(existing_pdf.next_object_id(0))
  92. contents_refs.append(existing_pdf.next_object_id(0))
  93. existing_pdf.pages.append(page_refs[-1])
  94. #
  95. # catalog and list of pages
  96. existing_pdf.write_catalog()
  97. pageNumber = 0
  98. for imSequence in ims:
  99. im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence]
  100. for im in im_pages:
  101. # FIXME: Should replace ASCIIHexDecode with RunLengthDecode
  102. # (packbits) or LZWDecode (tiff/lzw compression). Note that
  103. # PDF 1.2 also supports Flatedecode (zip compression).
  104. bits = 8
  105. params = None
  106. if im.mode == "1":
  107. filter = "ASCIIHexDecode"
  108. colorspace = PdfParser.PdfName("DeviceGray")
  109. procset = "ImageB" # grayscale
  110. bits = 1
  111. elif im.mode == "L":
  112. filter = "DCTDecode"
  113. # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
  114. colorspace = PdfParser.PdfName("DeviceGray")
  115. procset = "ImageB" # grayscale
  116. elif im.mode == "P":
  117. filter = "ASCIIHexDecode"
  118. palette = im.im.getpalette("RGB")
  119. colorspace = [
  120. PdfParser.PdfName("Indexed"),
  121. PdfParser.PdfName("DeviceRGB"),
  122. 255,
  123. PdfParser.PdfBinary(palette)
  124. ]
  125. procset = "ImageI" # indexed color
  126. elif im.mode == "RGB":
  127. filter = "DCTDecode"
  128. colorspace = PdfParser.PdfName("DeviceRGB")
  129. procset = "ImageC" # color images
  130. elif im.mode == "CMYK":
  131. filter = "DCTDecode"
  132. colorspace = PdfParser.PdfName("DeviceCMYK")
  133. procset = "ImageC" # color images
  134. else:
  135. raise ValueError("cannot save mode %s" % im.mode)
  136. #
  137. # image
  138. op = io.BytesIO()
  139. if filter == "ASCIIHexDecode":
  140. if bits == 1:
  141. # FIXME: the hex encoder doesn't support packed 1-bit
  142. # images; do things the hard way...
  143. data = im.tobytes("raw", "1")
  144. im = Image.new("L", (len(data), 1), None)
  145. im.putdata(data)
  146. ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
  147. elif filter == "DCTDecode":
  148. Image.SAVE["JPEG"](im, op, filename)
  149. elif filter == "FlateDecode":
  150. ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
  151. elif filter == "RunLengthDecode":
  152. ImageFile._save(im, op,
  153. [("packbits", (0, 0)+im.size, 0, im.mode)])
  154. else:
  155. raise ValueError("unsupported PDF filter (%s)" % filter)
  156. #
  157. # Get image characteristics
  158. width, height = im.size
  159. existing_pdf.write_obj(image_refs[pageNumber],
  160. stream=op.getvalue(),
  161. Type=PdfParser.PdfName("XObject"),
  162. Subtype=PdfParser.PdfName("Image"),
  163. Width=width, # * 72.0 / resolution,
  164. Height=height, # * 72.0 / resolution,
  165. Filter=PdfParser.PdfName(filter),
  166. BitsPerComponent=bits,
  167. DecodeParams=params,
  168. ColorSpace=colorspace)
  169. #
  170. # page
  171. existing_pdf.write_page(page_refs[pageNumber],
  172. Resources=PdfParser.PdfDict(
  173. ProcSet=[
  174. PdfParser.PdfName("PDF"),
  175. PdfParser.PdfName(procset)
  176. ],
  177. XObject=PdfParser.PdfDict(
  178. image=image_refs[pageNumber]
  179. )
  180. ),
  181. MediaBox=[
  182. 0,
  183. 0,
  184. int(width * 72.0 / resolution),
  185. int(height * 72.0 / resolution)
  186. ],
  187. Contents=contents_refs[pageNumber])
  188. #
  189. # page contents
  190. page_contents = PdfParser.make_bytes(
  191. "q %d 0 0 %d 0 0 cm /image Do Q\n" % (
  192. int(width * 72.0 / resolution),
  193. int(height * 72.0 / resolution)))
  194. existing_pdf.write_obj(contents_refs[pageNumber],
  195. stream=page_contents)
  196. pageNumber += 1
  197. #
  198. # trailer
  199. existing_pdf.write_xref_and_trailer()
  200. if hasattr(fp, "flush"):
  201. fp.flush()
  202. existing_pdf.close()
  203. #
  204. # --------------------------------------------------------------------
  205. Image.register_save("PDF", _save)
  206. Image.register_save_all("PDF", _save_all)
  207. Image.register_extension("PDF", ".pdf")
  208. Image.register_mime("PDF", "application/pdf")