about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/PIL/PdfImagePlugin.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/PIL/PdfImagePlugin.py')
-rw-r--r--.venv/lib/python3.12/site-packages/PIL/PdfImagePlugin.py311
1 files changed, 311 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/PIL/PdfImagePlugin.py b/.venv/lib/python3.12/site-packages/PIL/PdfImagePlugin.py
new file mode 100644
index 00000000..e9c20ddc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/PIL/PdfImagePlugin.py
@@ -0,0 +1,311 @@
+#
+# 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).
+##
+from __future__ import annotations
+
+import io
+import math
+import os
+import time
+from typing import IO, Any
+
+from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
+
+#
+# --------------------------------------------------------------------
+
+# object ids:
+#  1. catalogue
+#  2. pages
+#  3. image
+#  4. page
+#  5. page contents
+
+
+def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
+    _save(im, fp, filename, save_all=True)
+
+
+##
+# (Internal) Image save plugin for the PDF format.
+
+
+def _write_image(
+    im: Image.Image,
+    filename: str | bytes,
+    existing_pdf: PdfParser.PdfParser,
+    image_refs: list[PdfParser.IndirectReference],
+) -> tuple[PdfParser.IndirectReference, str]:
+    # FIXME: Should replace ASCIIHexDecode with RunLengthDecode
+    # (packbits) or LZWDecode (tiff/lzw compression).  Note that
+    # PDF 1.2 also supports Flatedecode (zip compression).
+
+    params = None
+    decode = None
+
+    #
+    # Get image characteristics
+
+    width, height = im.size
+
+    dict_obj: dict[str, Any] = {"BitsPerComponent": 8}
+    if im.mode == "1":
+        if features.check("libtiff"):
+            decode_filter = "CCITTFaxDecode"
+            dict_obj["BitsPerComponent"] = 1
+            params = PdfParser.PdfArray(
+                [
+                    PdfParser.PdfDict(
+                        {
+                            "K": -1,
+                            "BlackIs1": True,
+                            "Columns": width,
+                            "Rows": height,
+                        }
+                    )
+                ]
+            )
+        else:
+            decode_filter = "DCTDecode"
+        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
+        procset = "ImageB"  # grayscale
+    elif im.mode == "L":
+        decode_filter = "DCTDecode"
+        # params = f"<< /Predictor 15 /Columns {width-2} >>"
+        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
+        procset = "ImageB"  # grayscale
+    elif im.mode == "LA":
+        decode_filter = "JPXDecode"
+        # params = f"<< /Predictor 15 /Columns {width-2} >>"
+        procset = "ImageB"  # grayscale
+        dict_obj["SMaskInData"] = 1
+    elif im.mode == "P":
+        decode_filter = "ASCIIHexDecode"
+        palette = im.getpalette()
+        assert palette is not None
+        dict_obj["ColorSpace"] = [
+            PdfParser.PdfName("Indexed"),
+            PdfParser.PdfName("DeviceRGB"),
+            len(palette) // 3 - 1,
+            PdfParser.PdfBinary(palette),
+        ]
+        procset = "ImageI"  # indexed color
+
+        if "transparency" in im.info:
+            smask = im.convert("LA").getchannel("A")
+            smask.encoderinfo = {}
+
+            image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0]
+            dict_obj["SMask"] = image_ref
+    elif im.mode == "RGB":
+        decode_filter = "DCTDecode"
+        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB")
+        procset = "ImageC"  # color images
+    elif im.mode == "RGBA":
+        decode_filter = "JPXDecode"
+        procset = "ImageC"  # color images
+        dict_obj["SMaskInData"] = 1
+    elif im.mode == "CMYK":
+        decode_filter = "DCTDecode"
+        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK")
+        procset = "ImageC"  # color images
+        decode = [1, 0, 1, 0, 1, 0, 1, 0]
+    else:
+        msg = f"cannot save mode {im.mode}"
+        raise ValueError(msg)
+
+    #
+    # image
+
+    op = io.BytesIO()
+
+    if decode_filter == "ASCIIHexDecode":
+        ImageFile._save(im, op, [ImageFile._Tile("hex", (0, 0) + im.size, 0, im.mode)])
+    elif decode_filter == "CCITTFaxDecode":
+        im.save(
+            op,
+            "TIFF",
+            compression="group4",
+            # use a single strip
+            strip_size=math.ceil(width / 8) * height,
+        )
+    elif decode_filter == "DCTDecode":
+        Image.SAVE["JPEG"](im, op, filename)
+    elif decode_filter == "JPXDecode":
+        del dict_obj["BitsPerComponent"]
+        Image.SAVE["JPEG2000"](im, op, filename)
+    else:
+        msg = f"unsupported PDF filter ({decode_filter})"
+        raise ValueError(msg)
+
+    stream = op.getvalue()
+    filter: PdfParser.PdfArray | PdfParser.PdfName
+    if decode_filter == "CCITTFaxDecode":
+        stream = stream[8:]
+        filter = PdfParser.PdfArray([PdfParser.PdfName(decode_filter)])
+    else:
+        filter = PdfParser.PdfName(decode_filter)
+
+    image_ref = image_refs.pop(0)
+    existing_pdf.write_obj(
+        image_ref,
+        stream=stream,
+        Type=PdfParser.PdfName("XObject"),
+        Subtype=PdfParser.PdfName("Image"),
+        Width=width,  # * 72.0 / x_resolution,
+        Height=height,  # * 72.0 / y_resolution,
+        Filter=filter,
+        Decode=decode,
+        DecodeParms=params,
+        **dict_obj,
+    )
+
+    return image_ref, procset
+
+
+def _save(
+    im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
+) -> None:
+    is_appending = im.encoderinfo.get("append", False)
+    filename_str = filename.decode() if isinstance(filename, bytes) else filename
+    if is_appending:
+        existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="r+b")
+    else:
+        existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="w+b")
+
+    dpi = im.encoderinfo.get("dpi")
+    if dpi:
+        x_resolution = dpi[0]
+        y_resolution = dpi[1]
+    else:
+        x_resolution = y_resolution = im.encoderinfo.get("resolution", 72.0)
+
+    info = {
+        "title": (
+            None if is_appending else os.path.splitext(os.path.basename(filename))[0]
+        ),
+        "author": None,
+        "subject": None,
+        "keywords": None,
+        "creator": None,
+        "producer": None,
+        "creationDate": None if is_appending else time.gmtime(),
+        "modDate": None if is_appending else time.gmtime(),
+    }
+    for k, default in info.items():
+        v = im.encoderinfo.get(k) if k in im.encoderinfo else default
+        if v:
+            existing_pdf.info[k[0].upper() + k[1:]] = v
+
+    #
+    # make sure image data is available
+    im.load()
+
+    existing_pdf.start_writing()
+    existing_pdf.write_header()
+    existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
+
+    #
+    # pages
+    ims = [im]
+    if save_all:
+        append_images = im.encoderinfo.get("append_images", [])
+        for append_im in append_images:
+            append_im.encoderinfo = im.encoderinfo.copy()
+            ims.append(append_im)
+    number_of_pages = 0
+    image_refs = []
+    page_refs = []
+    contents_refs = []
+    for im in ims:
+        im_number_of_pages = 1
+        if save_all:
+            im_number_of_pages = getattr(im, "n_frames", 1)
+        number_of_pages += im_number_of_pages
+        for i in range(im_number_of_pages):
+            image_refs.append(existing_pdf.next_object_id(0))
+            if im.mode == "P" and "transparency" in im.info:
+                image_refs.append(existing_pdf.next_object_id(0))
+
+            page_refs.append(existing_pdf.next_object_id(0))
+            contents_refs.append(existing_pdf.next_object_id(0))
+            existing_pdf.pages.append(page_refs[-1])
+
+    #
+    # catalog and list of pages
+    existing_pdf.write_catalog()
+
+    page_number = 0
+    for im_sequence in ims:
+        im_pages: ImageSequence.Iterator | list[Image.Image] = (
+            ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
+        )
+        for im in im_pages:
+            image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)
+
+            #
+            # page
+
+            existing_pdf.write_page(
+                page_refs[page_number],
+                Resources=PdfParser.PdfDict(
+                    ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
+                    XObject=PdfParser.PdfDict(image=image_ref),
+                ),
+                MediaBox=[
+                    0,
+                    0,
+                    im.width * 72.0 / x_resolution,
+                    im.height * 72.0 / y_resolution,
+                ],
+                Contents=contents_refs[page_number],
+            )
+
+            #
+            # page contents
+
+            page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
+                im.width * 72.0 / x_resolution,
+                im.height * 72.0 / y_resolution,
+            )
+
+            existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
+
+            page_number += 1
+
+    #
+    # trailer
+    existing_pdf.write_xref_and_trailer()
+    if hasattr(fp, "flush"):
+        fp.flush()
+    existing_pdf.close()
+
+
+#
+# --------------------------------------------------------------------
+
+
+Image.register_save("PDF", _save)
+Image.register_save_all("PDF", _save_all)
+
+Image.register_extension("PDF", ".pdf")
+
+Image.register_mime("PDF", "application/pdf")