{
    "file_diffs": [
        {
            "old_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PDF (Acrobat) file handling\n#\n# History:\n# 1996-07-16 fl   Created\n# 1997-01-18 fl   Fixed header\n# 2004-02-21 fl   Fixes for 1/L/CMYK images, etc.\n# 2004-02-24 fl   Fixes for 1 and P images.\n#\n# Copyright (c) 1997-2004 by Secret Labs AB.  All rights reserved.\n# Copyright (c) 1996-1997 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\n\n##\n# Image plugin for PDF images (output only).\n##\n\nfrom . import Image, ImageFile, ImageSequence\nfrom ._binary import i8\nimport io\n\n__version__ = \"0.4\"\n\n\n#\n# --------------------------------------------------------------------\n\n# object ids:\n#  1. catalogue\n#  2. pages\n#  3. image\n#  4. page\n#  5. page contents\n\ndef _obj(fp, obj, **dictionary):\n    fp.write(\"%d 0 obj\\n\" % obj)\n    if dictionary:\n        fp.write(\"<<\\n\")\n        for k, v in dictionary.items():\n            if v is not None:\n                fp.write(\"/%s %s\\n\" % (k, v))\n        fp.write(\">>\\n\")\n\n\ndef _endobj(fp):\n    fp.write(\"endobj\\n\")\n\n\ndef _save_all(im, fp, filename):\n    _save(im, fp, filename, save_all=True)\n\n\n##\n# (Internal) Image save plugin for the PDF format.\n\ndef _save(im, fp, filename, save_all=False):\n    resolution = im.encoderinfo.get(\"resolution\", 72.0)\n\n    #\n    # make sure image data is available\n    im.load()\n\n    xref = [0]\n\n    class TextWriter(object):\n        def __init__(self, fp):\n            self.fp = fp\n\n        def __getattr__(self, name):\n            return getattr(self.fp, name)\n\n        def write(self, value):\n            self.fp.write(value.encode('latin-1'))\n\n    fp = TextWriter(fp)\n\n    fp.write(\"%PDF-1.2\\n\")\n    fp.write(\"% created by PIL PDF driver \" + __version__ + \"\\n\")\n\n    # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)\n    # or LZWDecode (tiff/lzw compression).  Note that PDF 1.2 also supports\n    # Flatedecode (zip compression).\n\n    bits = 8\n    params = None\n\n    if im.mode == \"1\":\n        filter = \"/ASCIIHexDecode\"\n        colorspace = \"/DeviceGray\"\n        procset = \"/ImageB\"  # grayscale\n        bits = 1\n    elif im.mode == \"L\":\n        filter = \"/DCTDecode\"\n        # params = \"<< /Predictor 15 /Columns %d >>\" % (width-2)\n        colorspace = \"/DeviceGray\"\n        procset = \"/ImageB\"  # grayscale\n    elif im.mode == \"P\":\n        filter = \"/ASCIIHexDecode\"\n        colorspace = \"[ /Indexed /DeviceRGB 255 <\"\n        palette = im.im.getpalette(\"RGB\")\n        for i in range(256):\n            r = i8(palette[i*3])\n            g = i8(palette[i*3+1])\n            b = i8(palette[i*3+2])\n            colorspace += \"%02x%02x%02x \" % (r, g, b)\n        colorspace += \"> ]\"\n        procset = \"/ImageI\"  # indexed color\n    elif im.mode == \"RGB\":\n        filter = \"/DCTDecode\"\n        colorspace = \"/DeviceRGB\"\n        procset = \"/ImageC\"  # color images\n    elif im.mode == \"CMYK\":\n        filter = \"/DCTDecode\"\n        colorspace = \"/DeviceCMYK\"\n        procset = \"/ImageC\"  # color images\n    else:\n        raise ValueError(\"cannot save mode %s\" % im.mode)\n\n    #\n    # catalogue\n\n    xref.append(fp.tell())\n    _obj(\n        fp, 1,\n        Type=\"/Catalog\",\n        Pages=\"2 0 R\")\n    _endobj(fp)\n\n    #\n    # pages\n    ims = [im]\n    if save_all:\n        append_images = im.encoderinfo.get(\"append_images\", [])\n        for append_im in append_images:\n            if append_im.mode != im.mode:\n                append_im = append_im.convert(im.mode)\n                append_im.encoderinfo = im.encoderinfo.copy()\n            ims.append(append_im)\n    numberOfPages = 0\n    for im in ims:\n        im_numberOfPages = 1\n        if save_all:\n            try:\n                im_numberOfPages = im.n_frames\n            except AttributeError:\n                # Image format does not have n_frames. It is a single frame image\n                pass\n        numberOfPages += im_numberOfPages\n    pages = [str(pageNumber*3+4)+\" 0 R\"\n             for pageNumber in range(0, numberOfPages)]\n\n    xref.append(fp.tell())\n    _obj(\n        fp, 2,\n        Type=\"/Pages\",\n        Count=len(pages),\n        Kids=\"[\"+\"\\n\".join(pages)+\"]\")\n    _endobj(fp)\n\n    pageNumber = 0\n    for imSequence in ims:\n        for im in ImageSequence.Iterator(imSequence):\n            #\n            # image\n\n            op = io.BytesIO()\n\n            if filter == \"/ASCIIHexDecode\":\n                if bits == 1:\n                    # FIXME: the hex encoder doesn't support packed 1-bit\n                    # images; do things the hard way...\n                    data = im.tobytes(\"raw\", \"1\")\n                    im = Image.new(\"L\", (len(data), 1), None)\n                    im.putdata(data)\n                ImageFile._save(im, op, [(\"hex\", (0, 0)+im.size, 0, im.mode)])\n            elif filter == \"/DCTDecode\":\n                Image.SAVE[\"JPEG\"](im, op, filename)\n            elif filter == \"/FlateDecode\":\n                ImageFile._save(im, op, [(\"zip\", (0, 0)+im.size, 0, im.mode)])\n            elif filter == \"/RunLengthDecode\":\n                ImageFile._save(im, op, [(\"packbits\", (0, 0)+im.size, 0, im.mode)])\n            else:\n                raise ValueError(\"unsupported PDF filter (%s)\" % filter)\n\n            #\n            # Get image characteristics\n\n            width, height = im.size\n\n            xref.append(fp.tell())\n            _obj(\n                fp, pageNumber*3+3,\n                Type=\"/XObject\",\n                Subtype=\"/Image\",\n                Width=width,  # * 72.0 / resolution,\n                Height=height,  # * 72.0 / resolution,\n                Length=len(op.getvalue()),\n                Filter=filter,\n                BitsPerComponent=bits,\n                DecodeParams=params,\n                ColorSpace=colorspace)\n\n            fp.write(\"stream\\n\")\n            fp.fp.write(op.getvalue())\n            fp.write(\"\\nendstream\\n\")\n\n            _endobj(fp)\n\n            #\n            # page\n\n            xref.append(fp.tell())\n            _obj(fp, pageNumber*3+4)\n            fp.write(\n                \"<<\\n/Type /Page\\n/Parent 2 0 R\\n\"\n                \"/Resources <<\\n/ProcSet [ /PDF %s ]\\n\"\n                \"/XObject << /image %d 0 R >>\\n>>\\n\"\n                \"/MediaBox [ 0 0 %d %d ]\\n/Contents %d 0 R\\n>>\\n\" % (\n                    procset,\n                    pageNumber*3+3,\n                    int(width * 72.0 / resolution),\n                    int(height * 72.0 / resolution),\n                    pageNumber*3+5))\n            _endobj(fp)\n\n            #\n            # page contents\n\n            op = TextWriter(io.BytesIO())\n\n            op.write(\n                \"q %d 0 0 %d 0 0 cm /image Do Q\\n\" % (\n                    int(width * 72.0 / resolution),\n                    int(height * 72.0 / resolution)))\n\n            xref.append(fp.tell())\n            _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))\n\n            fp.write(\"stream\\n\")\n            fp.fp.write(op.fp.getvalue())\n            fp.write(\"\\nendstream\\n\")\n\n            _endobj(fp)\n\n            pageNumber += 1\n\n    #\n    # trailer\n    startxref = fp.tell()\n    fp.write(\"xref\\n0 %d\\n0000000000 65535 f \\n\" % len(xref))\n    for x in xref[1:]:\n        fp.write(\"%010d 00000 n \\n\" % x)\n    fp.write(\"trailer\\n<<\\n/Size %d\\n/Root 1 0 R\\n>>\\n\" % len(xref))\n    fp.write(\"startxref\\n%d\\n%%%%EOF\\n\" % startxref)\n    if hasattr(fp, \"flush\"):\n        fp.flush()\n\n#\n# --------------------------------------------------------------------\n\nImage.register_save(\"PDF\", _save)\nImage.register_save_all(\"PDF\", _save_all)\n\nImage.register_extension(\"PDF\", \".pdf\")\n\nImage.register_mime(\"PDF\", \"application/pdf\")\n",
            "new_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PDF (Acrobat) file handling\n#\n# History:\n# 1996-07-16 fl   Created\n# 1997-01-18 fl   Fixed header\n# 2004-02-21 fl   Fixes for 1/L/CMYK images, etc.\n# 2004-02-24 fl   Fixes for 1 and P images.\n#\n# Copyright (c) 1997-2004 by Secret Labs AB.  All rights reserved.\n# Copyright (c) 1996-1997 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\n\n##\n# Image plugin for PDF images (output only).\n##\n\nfrom . import Image, ImageFile, ImageSequence\nfrom ._binary import i8\nimport io\n\n__version__ = \"0.4\"\n\n\n#\n# --------------------------------------------------------------------\n\n# object ids:\n#  1. catalogue\n#  2. pages\n#  3. image\n#  4. page\n#  5. page contents\n\ndef _obj(fp, obj, **dictionary):\n    fp.write(\"%d 0 obj\\n\" % obj)\n    if dictionary:\n        fp.write(\"<<\\n\")\n        for k, v in dictionary.items():\n            if v is not None:\n                fp.write(\"/%s %s\\n\" % (k, v))\n        fp.write(\">>\\n\")\n\n\ndef _endobj(fp):\n    fp.write(\"endobj\\n\")\n\n\ndef _save_all(im, fp, filename):\n    _save(im, fp, filename, save_all=True)\n\n\n##\n# (Internal) Image save plugin for the PDF format.\n\ndef _save(im, fp, filename, save_all=False):\n    resolution = im.encoderinfo.get(\"resolution\", 72.0)\n\n    #\n    # make sure image data is available\n    im.load()\n\n    xref = [0]\n\n    class TextWriter(object):\n        def __init__(self, fp):\n            self.fp = fp\n\n        def __getattr__(self, name):\n            return getattr(self.fp, name)\n\n        def write(self, value):\n            self.fp.write(value.encode('latin-1'))\n\n    fp = TextWriter(fp)\n\n    fp.write(\"%PDF-1.2\\n\")\n    fp.write(\"% created by PIL PDF driver \" + __version__ + \"\\n\")\n\n    # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)\n    # or LZWDecode (tiff/lzw compression).  Note that PDF 1.2 also supports\n    # Flatedecode (zip compression).\n\n    bits = 8\n    params = None\n\n    if im.mode == \"1\":\n        filter = \"/ASCIIHexDecode\"\n        colorspace = \"/DeviceGray\"\n        procset = \"/ImageB\"  # grayscale\n        bits = 1\n    elif im.mode == \"L\":\n        filter = \"/DCTDecode\"\n        # params = \"<< /Predictor 15 /Columns %d >>\" % (width-2)\n        colorspace = \"/DeviceGray\"\n        procset = \"/ImageB\"  # grayscale\n    elif im.mode == \"P\":\n        filter = \"/ASCIIHexDecode\"\n        colorspace = \"[ /Indexed /DeviceRGB 255 <\"\n        palette = im.im.getpalette(\"RGB\")\n        for i in range(256):\n            r = i8(palette[i*3])\n            g = i8(palette[i*3+1])\n            b = i8(palette[i*3+2])\n            colorspace += \"%02x%02x%02x \" % (r, g, b)\n        colorspace += \"> ]\"\n        procset = \"/ImageI\"  # indexed color\n    elif im.mode == \"RGB\":\n        filter = \"/DCTDecode\"\n        colorspace = \"/DeviceRGB\"\n        procset = \"/ImageC\"  # color images\n    elif im.mode == \"CMYK\":\n        filter = \"/DCTDecode\"\n        colorspace = \"/DeviceCMYK\"\n        procset = \"/ImageC\"  # color images\n    else:\n        raise ValueError(\"cannot save mode %s\" % im.mode)\n\n    #\n    # catalogue\n\n    xref.append(fp.tell())\n    _obj(\n        fp, 1,\n        Type=\"/Catalog\",\n        Pages=\"2 0 R\")\n    _endobj(fp)\n\n    #\n    # pages\n    ims = [im]\n    if save_all:\n        append_images = im.encoderinfo.get(\"append_images\", [])\n        for append_im in append_images:\n            if append_im.mode != im.mode:\n                append_im = append_im.convert(im.mode)\n            append_im.encoderinfo = im.encoderinfo.copy()\n            ims.append(append_im)\n    numberOfPages = 0\n    for im in ims:\n        im_numberOfPages = 1\n        if save_all:\n            try:\n                im_numberOfPages = im.n_frames\n            except AttributeError:\n                # Image format does not have n_frames. It is a single frame image\n                pass\n        numberOfPages += im_numberOfPages\n    pages = [str(pageNumber*3+4)+\" 0 R\"\n             for pageNumber in range(0, numberOfPages)]\n\n    xref.append(fp.tell())\n    _obj(\n        fp, 2,\n        Type=\"/Pages\",\n        Count=len(pages),\n        Kids=\"[\"+\"\\n\".join(pages)+\"]\")\n    _endobj(fp)\n\n    pageNumber = 0\n    for imSequence in ims:\n        for im in ImageSequence.Iterator(imSequence):\n            #\n            # image\n\n            op = io.BytesIO()\n\n            if filter == \"/ASCIIHexDecode\":\n                if bits == 1:\n                    # FIXME: the hex encoder doesn't support packed 1-bit\n                    # images; do things the hard way...\n                    data = im.tobytes(\"raw\", \"1\")\n                    im = Image.new(\"L\", (len(data), 1), None)\n                    im.putdata(data)\n                ImageFile._save(im, op, [(\"hex\", (0, 0)+im.size, 0, im.mode)])\n            elif filter == \"/DCTDecode\":\n                Image.SAVE[\"JPEG\"](im, op, filename)\n            elif filter == \"/FlateDecode\":\n                ImageFile._save(im, op, [(\"zip\", (0, 0)+im.size, 0, im.mode)])\n            elif filter == \"/RunLengthDecode\":\n                ImageFile._save(im, op, [(\"packbits\", (0, 0)+im.size, 0, im.mode)])\n            else:\n                raise ValueError(\"unsupported PDF filter (%s)\" % filter)\n\n            #\n            # Get image characteristics\n\n            width, height = im.size\n\n            xref.append(fp.tell())\n            _obj(\n                fp, pageNumber*3+3,\n                Type=\"/XObject\",\n                Subtype=\"/Image\",\n                Width=width,  # * 72.0 / resolution,\n                Height=height,  # * 72.0 / resolution,\n                Length=len(op.getvalue()),\n                Filter=filter,\n                BitsPerComponent=bits,\n                DecodeParams=params,\n                ColorSpace=colorspace)\n\n            fp.write(\"stream\\n\")\n            fp.fp.write(op.getvalue())\n            fp.write(\"\\nendstream\\n\")\n\n            _endobj(fp)\n\n            #\n            # page\n\n            xref.append(fp.tell())\n            _obj(fp, pageNumber*3+4)\n            fp.write(\n                \"<<\\n/Type /Page\\n/Parent 2 0 R\\n\"\n                \"/Resources <<\\n/ProcSet [ /PDF %s ]\\n\"\n                \"/XObject << /image %d 0 R >>\\n>>\\n\"\n                \"/MediaBox [ 0 0 %d %d ]\\n/Contents %d 0 R\\n>>\\n\" % (\n                    procset,\n                    pageNumber*3+3,\n                    int(width * 72.0 / resolution),\n                    int(height * 72.0 / resolution),\n                    pageNumber*3+5))\n            _endobj(fp)\n\n            #\n            # page contents\n\n            op = TextWriter(io.BytesIO())\n\n            op.write(\n                \"q %d 0 0 %d 0 0 cm /image Do Q\\n\" % (\n                    int(width * 72.0 / resolution),\n                    int(height * 72.0 / resolution)))\n\n            xref.append(fp.tell())\n            _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))\n\n            fp.write(\"stream\\n\")\n            fp.fp.write(op.fp.getvalue())\n            fp.write(\"\\nendstream\\n\")\n\n            _endobj(fp)\n\n            pageNumber += 1\n\n    #\n    # trailer\n    startxref = fp.tell()\n    fp.write(\"xref\\n0 %d\\n0000000000 65535 f \\n\" % len(xref))\n    for x in xref[1:]:\n        fp.write(\"%010d 00000 n \\n\" % x)\n    fp.write(\"trailer\\n<<\\n/Size %d\\n/Root 1 0 R\\n>>\\n\" % len(xref))\n    fp.write(\"startxref\\n%d\\n%%%%EOF\\n\" % startxref)\n    if hasattr(fp, \"flush\"):\n        fp.flush()\n\n#\n# --------------------------------------------------------------------\n\nImage.register_save(\"PDF\", _save)\nImage.register_save_all(\"PDF\", _save_all)\n\nImage.register_extension(\"PDF\", \".pdf\")\n\nImage.register_mime(\"PDF\", \"application/pdf\")\n",
            "header": {
                "file": {
                    "path": "PIL/PdfImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "e41a59411",
                "new_commit_hash": "86bc9c8e9",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/PIL/PdfImagePlugin.py"
            },
            "plus_file": {
                "path": "b/PIL/PdfImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 139,
                            "length": 7
                        },
                        "new_range": {
                            "start": 139,
                            "length": 7
                        },
                        "section": "def _save(im, fp, filename, save_all=False):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        for append_im in append_images:",
                                "type": "context"
                            },
                            {
                                "content": "            if append_im.mode != im.mode:",
                                "type": "context"
                            },
                            {
                                "content": "                append_im = append_im.convert(im.mode)",
                                "type": "context"
                            },
                            {
                                "content": "                append_im.encoderinfo = im.encoderinfo.copy()",
                                "type": "deleted"
                            },
                            {
                                "content": "            append_im.encoderinfo = im.encoderinfo.copy()",
                                "type": "added"
                            },
                            {
                                "content": "            ims.append(append_im)",
                                "type": "context"
                            },
                            {
                                "content": "    numberOfPages = 0",
                                "type": "context"
                            },
                            {
                                "content": "    for im in ims:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "PIL/PdfImagePlugin.py",
                            "type": "function",
                            "name": "_save",
                            "content": "def _save(im, fp, filename, save_all=False):\n    resolution = im.encoderinfo.get(\"resolution\", 72.0)\n\n    #\n    # make sure image data is available\n    im.load()\n\n    xref = [0]\n\n    class TextWriter(object):\n        def __init__(self, fp):\n            self.fp = fp\n\n        def __getattr__(self, name):\n            return getattr(self.fp, name)\n\n        def write(self, value):\n            self.fp.write(value.encode('latin-1'))\n\n    fp = TextWriter(fp)\n\n    fp.write(\"%PDF-1.2\\n\")\n    fp.write(\"% created by PIL PDF driver \" + __version__ + \"\\n\")\n\n    # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)\n    # or LZWDecode (tiff/lzw compression).  Note that PDF 1.2 also supports\n    # Flatedecode (zip compression).\n\n    bits = 8\n    params = None\n\n    if im.mode == \"1\":\n        filter = \"/ASCIIHexDecode\"\n        colorspace = \"/DeviceGray\"\n        procset = \"/ImageB\"  # grayscale\n        bits = 1\n    elif im.mode == \"L\":\n        filter = \"/DCTDecode\"\n        # params = \"<< /Predictor 15 /Columns %d >>\" % (width-2)\n        colorspace = \"/DeviceGray\"\n        procset = \"/ImageB\"  # grayscale\n    elif im.mode == \"P\":\n        filter = \"/ASCIIHexDecode\"\n        colorspace = \"[ /Indexed /DeviceRGB 255 <\"\n        palette = im.im.getpalette(\"RGB\")\n        for i in range(256):\n            r = i8(palette[i*3])\n            g = i8(palette[i*3+1])\n            b = i8(palette[i*3+2])\n            colorspace += \"%02x%02x%02x \" % (r, g, b)\n        colorspace += \"> ]\"\n        procset = \"/ImageI\"  # indexed color\n    elif im.mode == \"RGB\":\n        filter = \"/DCTDecode\"\n        colorspace = \"/DeviceRGB\"\n        procset = \"/ImageC\"  # color images\n    elif im.mode == \"CMYK\":\n        filter = \"/DCTDecode\"\n        colorspace = \"/DeviceCMYK\"\n        procset = \"/ImageC\"  # color images\n    else:\n        raise ValueError(\"cannot save mode %s\" % im.mode)\n\n    #\n    # catalogue\n\n    xref.append(fp.tell())\n    _obj(\n        fp, 1,\n        Type=\"/Catalog\",\n        Pages=\"2 0 R\")\n    _endobj(fp)\n\n    #\n    # pages\n    ims = [im]\n    if save_all:\n        append_images = im.encoderinfo.get(\"append_images\", [])\n        for append_im in append_images:\n            if append_im.mode != im.mode:\n                append_im = append_im.convert(im.mode)\n                append_im.encoderinfo = im.encoderinfo.copy()\n            ims.append(append_im)\n    numberOfPages = 0\n    for im in ims:\n        im_numberOfPages = 1\n        if save_all:\n            try:\n                im_numberOfPages = im.n_frames\n            except AttributeError:\n                # Image format does not have n_frames. It is a single frame image\n                pass\n        numberOfPages += im_numberOfPages\n    pages = [str(pageNumber*3+4)+\" 0 R\"\n             for pageNumber in range(0, numberOfPages)]\n\n    xref.append(fp.tell())\n    _obj(\n        fp, 2,\n        Type=\"/Pages\",\n        Count=len(pages),\n        Kids=\"[\"+\"\\n\".join(pages)+\"]\")\n    _endobj(fp)\n\n    pageNumber = 0\n    for imSequence in ims:\n        for im in ImageSequence.Iterator(imSequence):\n            #\n            # image\n\n            op = io.BytesIO()\n\n            if filter == \"/ASCIIHexDecode\":\n                if bits == 1:\n                    # FIXME: the hex encoder doesn't support packed 1-bit\n                    # images; do things the hard way...\n                    data = im.tobytes(\"raw\", \"1\")\n                    im = Image.new(\"L\", (len(data), 1), None)\n                    im.putdata(data)\n                ImageFile._save(im, op, [(\"hex\", (0, 0)+im.size, 0, im.mode)])\n            elif filter == \"/DCTDecode\":\n                Image.SAVE[\"JPEG\"](im, op, filename)\n            elif filter == \"/FlateDecode\":\n                ImageFile._save(im, op, [(\"zip\", (0, 0)+im.size, 0, im.mode)])\n            elif filter == \"/RunLengthDecode\":\n                ImageFile._save(im, op, [(\"packbits\", (0, 0)+im.size, 0, im.mode)])\n            else:\n                raise ValueError(\"unsupported PDF filter (%s)\" % filter)\n\n            #\n            # Get image characteristics\n\n            width, height = im.size\n\n            xref.append(fp.tell())\n            _obj(\n                fp, pageNumber*3+3,\n                Type=\"/XObject\",\n                Subtype=\"/Image\",\n                Width=width,  # * 72.0 / resolution,\n                Height=height,  # * 72.0 / resolution,\n                Length=len(op.getvalue()),\n                Filter=filter,\n                BitsPerComponent=bits,\n                DecodeParams=params,\n                ColorSpace=colorspace)\n\n            fp.write(\"stream\\n\")\n            fp.fp.write(op.getvalue())\n            fp.write(\"\\nendstream\\n\")\n\n            _endobj(fp)\n\n            #\n            # page\n\n            xref.append(fp.tell())\n            _obj(fp, pageNumber*3+4)\n            fp.write(\n                \"<<\\n/Type /Page\\n/Parent 2 0 R\\n\"\n                \"/Resources <<\\n/ProcSet [ /PDF %s ]\\n\"\n                \"/XObject << /image %d 0 R >>\\n>>\\n\"\n                \"/MediaBox [ 0 0 %d %d ]\\n/Contents %d 0 R\\n>>\\n\" % (\n                    procset,\n                    pageNumber*3+3,\n                    int(width * 72.0 / resolution),\n                    int(height * 72.0 / resolution),\n                    pageNumber*3+5))\n            _endobj(fp)\n\n            #\n            # page contents\n\n            op = TextWriter(io.BytesIO())\n\n            op.write(\n                \"q %d 0 0 %d 0 0 cm /image Do Q\\n\" % (\n                    int(width * 72.0 / resolution),\n                    int(height * 72.0 / resolution)))\n\n            xref.append(fp.tell())\n            _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))\n\n            fp.write(\"stream\\n\")\n            fp.fp.write(op.fp.getvalue())\n            fp.write(\"\\nendstream\\n\")\n\n            _endobj(fp)\n\n            pageNumber += 1\n\n    #\n    # trailer\n    startxref = fp.tell()\n    fp.write(\"xref\\n0 %d\\n0000000000 65535 f \\n\" % len(xref))\n    for x in xref[1:]:\n        fp.write(\"%010d 00000 n \\n\" % x)\n    fp.write(\"trailer\\n<<\\n/Size %d\\n/Root 1 0 R\\n>>\\n\" % len(xref))\n    fp.write(\"startxref\\n%d\\n%%%%EOF\\n\" % startxref)\n    if hasattr(fp, \"flush\"):\n        fp.flush()",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 61,
                            "end_lineno": 261,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "from helper import unittest, PillowTestCase, hopper\nfrom PIL import Image\nimport os.path\n\n\nclass TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, save_all=False):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        if save_all:\n            im.save(outfile, save_all=True)\n        else:\n            im.save(outfile)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n    def test_monochrome(self):\n        # Arrange\n        mode = \"1\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_greyscale(self):\n        # Arrange\n        mode = \"L\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_rgb(self):\n        # Arrange\n        mode = \"RGB\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_p_mode(self):\n        # Arrange\n        mode = \"P\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_cmyk_mode(self):\n        # Arrange\n        mode = \"CMYK\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_unsupported_mode(self):\n        im = hopper(\"LA\")\n        outfile = self.tempfile(\"temp_LA.pdf\")\n\n        self.assertRaises(ValueError, im.save, outfile)\n\n    def test_save_all(self):\n        # Single frame image\n        self.helper_save_as_pdf(\"RGB\", save_all=True)\n\n        # Multiframe image\n        im = Image.open(\"Tests/images/dispose_bgnd.gif\")\n\n        outfile = self.tempfile('temp.pdf')\n        im.save(outfile, save_all=True)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append images\n        ims = [hopper()]\n        im.copy().save(outfile, save_all=True, append_images=ims)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            for im in ims:\n                yield im\n        im.save(outfile, save_all=True, append_images=imGenerator(ims))\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n\nif __name__ == '__main__':\n    unittest.main()\n",
            "new_file_content": "from helper import unittest, PillowTestCase, hopper\nfrom PIL import Image\nimport os.path\n\n\nclass TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, save_all=False):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        if save_all:\n            im.save(outfile, save_all=True)\n        else:\n            im.save(outfile)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n    def test_monochrome(self):\n        # Arrange\n        mode = \"1\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_greyscale(self):\n        # Arrange\n        mode = \"L\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_rgb(self):\n        # Arrange\n        mode = \"RGB\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_p_mode(self):\n        # Arrange\n        mode = \"P\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_cmyk_mode(self):\n        # Arrange\n        mode = \"CMYK\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_unsupported_mode(self):\n        im = hopper(\"LA\")\n        outfile = self.tempfile(\"temp_LA.pdf\")\n\n        self.assertRaises(ValueError, im.save, outfile)\n\n    def test_save_all(self):\n        # Single frame image\n        self.helper_save_as_pdf(\"RGB\", save_all=True)\n\n        # Multiframe image\n        im = Image.open(\"Tests/images/dispose_bgnd.gif\")\n\n        outfile = self.tempfile('temp.pdf')\n        im.save(outfile, save_all=True)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append images\n        ims = [hopper()]\n        im.copy().save(outfile, save_all=True, append_images=ims)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            for im in ims:\n                yield im\n        im.save(outfile, save_all=True, append_images=imGenerator(ims))\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append JPEG images\n        jpeg = Image.open(\"Tests/images/flower.jpg\")\n        jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n\nif __name__ == '__main__':\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_pdf.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "fd65cc371",
                "new_commit_hash": "ee02d0694",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_pdf.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_pdf.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 90,
                            "length": 6
                        },
                        "new_range": {
                            "start": 90,
                            "length": 13
                        },
                        "section": "class TestFilePdf(PillowTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertTrue(os.path.isfile(outfile))",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertGreater(os.path.getsize(outfile), 0)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        # Append JPEG images",
                                "type": "added"
                            },
                            {
                                "content": "        jpeg = Image.open(\"Tests/images/flower.jpg\")",
                                "type": "added"
                            },
                            {
                                "content": "        jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertTrue(os.path.isfile(outfile))",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertGreater(os.path.getsize(outfile), 0)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "if __name__ == '__main__':",
                                "type": "context"
                            },
                            {
                                "content": "    unittest.main()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_pdf.py",
                            "type": "class",
                            "name": "TestFilePdf",
                            "content": "class TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, save_all=False):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        if save_all:\n            im.save(outfile, save_all=True)\n        else:\n            im.save(outfile)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n    def test_monochrome(self):\n        # Arrange\n        mode = \"1\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_greyscale(self):\n        # Arrange\n        mode = \"L\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_rgb(self):\n        # Arrange\n        mode = \"RGB\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_p_mode(self):\n        # Arrange\n        mode = \"P\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_cmyk_mode(self):\n        # Arrange\n        mode = \"CMYK\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_unsupported_mode(self):\n        im = hopper(\"LA\")\n        outfile = self.tempfile(\"temp_LA.pdf\")\n\n        self.assertRaises(ValueError, im.save, outfile)\n\n    def test_save_all(self):\n        # Single frame image\n        self.helper_save_as_pdf(\"RGB\", save_all=True)\n\n        # Multiframe image\n        im = Image.open(\"Tests/images/dispose_bgnd.gif\")\n\n        outfile = self.tempfile('temp.pdf')\n        im.save(outfile, save_all=True)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append images\n        ims = [hopper()]\n        im.copy().save(outfile, save_all=True, append_images=ims)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            for im in ims:\n                yield im\n        im.save(outfile, save_all=True, append_images=imGenerator(ims))\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append JPEG images\n        jpeg = Image.open(\"Tests/images/flower.jpg\")\n        jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 6,
                            "end_lineno": 98,
                            "parent": null
                        },
                        {
                            "file_name": "Tests/test_file_pdf.py",
                            "type": "method",
                            "name": "TestFilePdf.test_save_all",
                            "content": "class TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, save_all=False):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        if save_all:\n            im.save(outfile, save_all=True)\n        else:\n            im.save(outfile)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n    def test_monochrome(self):\n        # Arrange\n        mode = \"1\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_greyscale(self):\n        # Arrange\n        mode = \"L\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_rgb(self):\n        # Arrange\n        mode = \"RGB\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_p_mode(self):\n        # Arrange\n        mode = \"P\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_cmyk_mode(self):\n        # Arrange\n        mode = \"CMYK\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_unsupported_mode(self):\n        im = hopper(\"LA\")\n        outfile = self.tempfile(\"temp_LA.pdf\")\n\n        self.assertRaises(ValueError, im.save, outfile)\n\n    def test_save_all(self):\n        # Single frame image\n        self.helper_save_as_pdf(\"RGB\", save_all=True)\n\n        # Multiframe image\n        im = Image.open(\"Tests/images/dispose_bgnd.gif\")\n\n        outfile = self.tempfile('temp.pdf')\n        im.save(outfile, save_all=True)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append images\n        ims = [hopper()]\n        im.copy().save(outfile, save_all=True, append_images=ims)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            for im in ims:\n                yield im\n        im.save(outfile, save_all=True, append_images=imGenerator(ims))\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append JPEG images\n        jpeg = Image.open(\"Tests/images/flower.jpg\")\n        jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 64,
                            "end_lineno": 98,
                            "parent": {
                                "file_name": "Tests/test_file_pdf.py",
                                "type": "class",
                                "name": "TestFilePdf",
                                "content": "class TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, save_all=False):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        if save_all:\n            im.save(outfile, save_all=True)\n        else:\n            im.save(outfile)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n    def test_monochrome(self):\n        # Arrange\n        mode = \"1\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_greyscale(self):\n        # Arrange\n        mode = \"L\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_rgb(self):\n        # Arrange\n        mode = \"RGB\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_p_mode(self):\n        # Arrange\n        mode = \"P\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_cmyk_mode(self):\n        # Arrange\n        mode = \"CMYK\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_unsupported_mode(self):\n        im = hopper(\"LA\")\n        outfile = self.tempfile(\"temp_LA.pdf\")\n\n        self.assertRaises(ValueError, im.save, outfile)\n\n    def test_save_all(self):\n        # Single frame image\n        self.helper_save_as_pdf(\"RGB\", save_all=True)\n\n        # Multiframe image\n        im = Image.open(\"Tests/images/dispose_bgnd.gif\")\n\n        outfile = self.tempfile('temp.pdf')\n        im.save(outfile, save_all=True)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append images\n        ims = [hopper()]\n        im.copy().save(outfile, save_all=True, append_images=ims)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            for im in ims:\n                yield im\n        im.save(outfile, save_all=True, append_images=imGenerator(ims))\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append JPEG images\n        jpeg = Image.open(\"Tests/images/flower.jpg\")\n        jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 6,
                                "end_lineno": 98,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "52079cb2975fda98476c7a7f172e5519e67ba612^",
    "new_commit_hash": "52079cb2975fda98476c7a7f172e5519e67ba612",
    "commit_message": "Merge pull request #2805 from ixio/fix-#2804\n\nFix #2804: sets encoderinfo for images when saving multi-page PDF",
    "commit_date": "2017-11-05T15:18:22Z",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "52079cb2975fda98476c7a7f172e5519e67ba612",
    "test_file_codes": [
        "from helper import unittest, PillowTestCase, hopper\nfrom PIL import Image\nimport os.path\n\n\nclass TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, save_all=False):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        if save_all:\n            im.save(outfile, save_all=True)\n        else:\n            im.save(outfile)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n    def test_monochrome(self):\n        # Arrange\n        mode = \"1\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_greyscale(self):\n        # Arrange\n        mode = \"L\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_rgb(self):\n        # Arrange\n        mode = \"RGB\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_p_mode(self):\n        # Arrange\n        mode = \"P\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_cmyk_mode(self):\n        # Arrange\n        mode = \"CMYK\"\n\n        # Act / Assert\n        self.helper_save_as_pdf(mode)\n\n    def test_unsupported_mode(self):\n        im = hopper(\"LA\")\n        outfile = self.tempfile(\"temp_LA.pdf\")\n\n        self.assertRaises(ValueError, im.save, outfile)\n\n    def test_save_all(self):\n        # Single frame image\n        self.helper_save_as_pdf(\"RGB\", save_all=True)\n\n        # Multiframe image\n        im = Image.open(\"Tests/images/dispose_bgnd.gif\")\n\n        outfile = self.tempfile('temp.pdf')\n        im.save(outfile, save_all=True)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append images\n        ims = [hopper()]\n        im.copy().save(outfile, save_all=True, append_images=ims)\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            for im in ims:\n                yield im\n        im.save(outfile, save_all=True, append_images=imGenerator(ims))\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        # Append JPEG images\n        jpeg = Image.open(\"Tests/images/flower.jpg\")\n        jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])\n\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n\nif __name__ == '__main__':\n    unittest.main()\n",
        "#!/usr/bin/env python\nimport unittest\nimport sys\nimport time\nimport traceback\nimport platform\nimport os\n\n\nclass PytestLikeResult(unittest.TestResult):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.start_time = None\n        # Store (test, outcome, info) for each test:\n        #   outcome ∈ { \"passed\", \"failed\", \"error\", \"skipped\" }\n        #   info can hold the exception or skip reason, etc.\n        self.results = []\n\n    def startTestRun(self):\n        \"\"\"Called once before all tests.\"\"\"\n        self.start_time = time.time()\n        # Print a header similar to pytest\n        print(\"=\" * 29 + \" test session starts \" + \"=\" * 29)\n        # You can customize the platform/versions/paths as you like:\n        print(\n            f\"platform {platform.system().lower()} -- Python {platform.python_version()}\"\n        )\n        print(f\"rootdir: {os.path.abspath(os.curdir)}\")\n\n    def stopTestRun(self):\n        \"\"\"Called once after all tests are done.\"\"\"\n        elapsed = time.time() - self.start_time\n        print()  # blank line after final dot\n\n        # Summarize\n        ntests = len(self.results)\n        print(f\"collected {ntests} items\")\n\n        # Show details of failures/errors\n        failures = [r for r in self.results if r[1] == \"failed\"]\n        errors = [r for r in self.results if r[1] == \"error\"]\n\n        if failures:\n            print(\"\\n\" + \"=\" * 35 + \" FAILURES \" + \"=\" * 35)\n            for test, outcome, err_info in failures:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n        if errors:\n            print(\"\\n\" + \"=\" * 35 + \" ERRORS \" + \"=\" * 37)\n            for test, outcome, err_info in errors:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n\n        # Optional short summary lines for passes/fails/skips\n        print(\"\\n==================== short test summary info ====================\")\n        for test, outcome, info in self.results:\n            if outcome in (\"passed\", \"failed\", \"error\", \"skipped\"):\n                print(f\"{outcome.upper()} {test}\")\n\n        # Final one-line summary: “1 failed, 1 errors, 10 passed in 2.04s”\n        passed_count = sum(1 for r in self.results if r[1] == \"passed\")\n        failed_count = len(failures)\n        error_count = len(errors)\n        skipped_count = sum(1 for r in self.results if r[1] == \"skipped\")\n\n        summary_bits = []\n        if failed_count:\n            summary_bits.append(f\"{failed_count} failed\")\n        if error_count:\n            summary_bits.append(f\"{error_count} error{'s' if error_count != 1 else ''}\")\n        if passed_count:\n            summary_bits.append(f\"{passed_count} passed\")\n        if skipped_count:\n            summary_bits.append(f\"{skipped_count} skipped\")\n\n        summary_str = \", \".join(summary_bits) if summary_bits else \"no tests run\"\n        print(\n            f\"=================== {summary_str} in {elapsed:.2f}s ===================\"\n        )\n\n    def startTest(self, test):\n        \"\"\"Called right before each test method.\"\"\"\n        super().startTest(test)\n        # Print a dot or some indicator\n        print(\".\", end=\"\", flush=True)\n\n    def addSuccess(self, test):\n        super().addSuccess(test)\n        self.results.append((self._test_id(test), \"passed\", None))\n\n    def addFailure(self, test, err):\n        super().addFailure(test, err)\n        self.results.append((self._test_id(test), \"failed\", err))\n\n    def addError(self, test, err):\n        super().addError(test, err)\n        self.results.append((self._test_id(test), \"error\", err))\n\n    def addSkip(self, test, reason):\n        super().addSkip(test, reason)\n        self.results.append((self._test_id(test), \"skipped\", reason))\n\n    def _print_traceback(self, err_info):\n        \"\"\"Helper to print the traceback like pytest does.\"\"\"\n        if isinstance(err_info, tuple) and len(err_info) == 3:\n            exc_type, exc_value, tb = err_info\n            traceback.print_exception(exc_type, exc_value, tb, file=sys.stdout)\n        else:\n            print(str(err_info))\n\n    def _test_id(self, test):\n        try:\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}::{test._testMethodName}\"\n        except AttributeError:\n            # Fallback for _ErrorHolder objects\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}\"\n\n\nclass PytestLikeRunner(unittest.TextTestRunner):\n    \"\"\"A custom TextTestRunner that uses the PytestLikeResult.\"\"\"\n\n    resultclass = PytestLikeResult\n\n    def run(self, test):\n        result = self._makeResult()\n        # Overriding run to ensure we call startTestRun/stopTestRun\n        result.startTestRun()\n        test(result)\n        result.stopTestRun()\n        return result\n\n\ndef main():\n    loader = unittest.TestLoader()\n    suite = loader.discover(\"r2e_tests\")  # discover tests in r2e_tests/\n    runner = PytestLikeRunner(verbosity=0)\n    result = runner.run(suite)\n    # Exit with code 0 if all tests passed/skipped, 1 if there were fails/errors\n    sys.exit(not result.wasSuccessful())\n\n\nif __name__ == \"__main__\":\n    main()\n"
    ],
    "test_file_names": [
        "test_1.py",
        "unittest_custom_runner.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Pillow installation attempts...\nVerifying Pillow installation...\n✅ Pillow installation successful!\nSuccessfully installed Pillow\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 12 packages in 16ms\nInstalled 12 packages in 117ms\n + coverage==7.6.10\n + exceptiongroup==1.2.2\n + iniconfig==2.0.0\n + packaging==24.2\n + pluggy==1.5.0\n + pyqt5==5.15.11\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.16.1\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + setuptools==75.8.0\n + tomli==2.2.1\nResolved 1 package in 840ms\nPrepared 1 package in 15.64s\nInstalled 1 package in 1ms\n + pillow==4.4.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612)\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612\n.......\ncollected 7 items\n\n==================== short test summary info ====================\nPASSED test_1::TestFilePdf::test_cmyk_mode\nPASSED test_1::TestFilePdf::test_greyscale\nPASSED test_1::TestFilePdf::test_monochrome\nPASSED test_1::TestFilePdf::test_p_mode\nPASSED test_1::TestFilePdf::test_rgb\nPASSED test_1::TestFilePdf::test_save_all\nPASSED test_1::TestFilePdf::test_unsupported_mode\n=================== 7 passed in 0.13s ===================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612\n.......=== orphaned temp file: /tmp/tempnvr2w17v_LA.pdf\n\ncollected 7 items\n\n=================================== ERRORS =====================================\n__________ TestFilePdf.test_save_all __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612/r2e_tests/test_1.py\", line 95, in test_save_all\n    jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612/PIL/Image.py\", line 1928, in save\n    save_handler(self, fp, filename)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612/PIL/PdfImagePlugin.py\", line 55, in _save_all\n    _save(im, fp, filename, save_all=True)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612/PIL/PdfImagePlugin.py\", line 182, in _save\n    Image.SAVE[\"JPEG\"](im, op, filename)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_52079cb2975fda98476c7a7f172e5519e67ba612/PIL/JpegImagePlugin.py\", line 609, in _save\n    info = im.encoderinfo\nAttributeError: 'Image' object has no attribute 'encoderinfo'\n\n==================== short test summary info ====================\nPASSED test_1::TestFilePdf::test_cmyk_mode\nPASSED test_1::TestFilePdf::test_greyscale\nPASSED test_1::TestFilePdf::test_monochrome\nPASSED test_1::TestFilePdf::test_p_mode\nPASSED test_1::TestFilePdf::test_rgb\nERROR test_1::TestFilePdf::test_save_all\nPASSED test_1::TestFilePdf::test_unsupported_mode\n=================== 1 error, 6 passed in 0.06s ===================\n",
    "old_commit_res_stderr": ""
}
PIL/PdfImagePlugin.py Tests/test_file_pdf.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** AttributeError When Saving Multi-Page PDF with Appended Images

**Description:**
When attempting to save a multi-page PDF by appending images, an `AttributeError` is raised because the `'Image'` object does not have the `encoderinfo` attribute.

**Example Code:**
```python
from PIL import Image

im = Image.open("image1.jpg")
append_im = Image.open("image2.jpg")

im.save("output.pdf", save_all=True, append_images=[append_im])
```

**Expected Behavior:**
The PDF should be saved successfully with multiple pages, each containing the appended images.

**Actual Behavior:**
An error occurs during the save process:
```
AttributeError: 'Image' object has no attribute 'encoderinfo'
```
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### PIL/PdfImagePlugin.py
#
# 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 . import Image, ImageFile, ImageSequence
from ._binary import i8
import io

__version__ = "0.4"


#
# --------------------------------------------------------------------

# 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():
            if v is not None:
                fp.write("/%s %s\n" % (k, v))
        fp.write(">>\n")


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, save_all=False):
    resolution = im.encoderinfo.get("resolution", 72.0)

    #
    # make sure image data is available
    im.load()

    xref = [0]

    class TextWriter(object):
        def __init__(self, fp):
            self.fp = fp

        def __getattr__(self, name):
            return getattr(self.fp, name)

        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")

    # 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"
        procset = "/ImageB"  # grayscale
        bits = 1
    elif im.mode == "L":
        filter = "/DCTDecode"
        # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
        colorspace = "/DeviceGray"
        procset = "/ImageB"  # grayscale
    elif im.mode == "P":
        filter = "/ASCIIHexDecode"
        colorspace = "[ /Indexed /DeviceRGB 255 <"
        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 += "> ]"
        procset = "/ImageI"  # indexed color
    elif im.mode == "RGB":
        filter = "/DCTDecode"
        colorspace = "/DeviceRGB"
        procset = "/ImageC"  # color images
    elif im.mode == "CMYK":
        filter = "/DCTDecode"
        colorspace = "/DeviceCMYK"
        procset = "/ImageC"  # color images
    else:
        raise ValueError("cannot save mode %s" % im.mode)

    #
    # catalogue

    xref.append(fp.tell())
    _obj(
        fp, 1,
        Type="/Catalog",
        Pages="2 0 R")
    _endobj(fp)

    #
    # pages
    ims = [im]
    if save_all:
        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()
            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
    pages = [str(pageNumber*3+4)+" 0 R"
             for pageNumber in range(0, numberOfPages)]

    xref.append(fp.tell())
    _obj(
        fp, 2,
        Type="/Pages",
        Count=len(pages),
        Kids="["+"\n".join(pages)+"]")
    _endobj(fp)

    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

    #
    # trailer
    startxref = fp.tell()
    fp.write("xref\n0 %d\n0000000000 65535 f \n" % len(xref))
    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()

#
# --------------------------------------------------------------------

Image.register_save("PDF", _save)
Image.register_save_all("PDF", _save_all)

Image.register_extension("PDF", ".pdf")

Image.register_mime("PDF", "application/pdf")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** AttributeError When Saving Multi-Page PDF with Appended Images

**Description:**
When attempting to save a multi-page PDF by appending images, an `AttributeError` is raised because the `'Image'` object does not have the `encoderinfo` attribute.

**Example Code:**
```python
from PIL import Image

im = Image.open("image1.jpg")
append_im = Image.open("image2.jpg")

im.save("output.pdf", save_all=True, append_images=[append_im])
```

**Expected Behavior:**
The PDF should be saved successfully with multiple pages, each containing the appended images.

**Actual Behavior:**
An error occurs during the save process:
```
AttributeError: 'Image' object has no attribute 'encoderinfo'
```
[/ISSUE]
{
    "TestFilePdf.test_cmyk_mode": "PASSED",
    "TestFilePdf.test_greyscale": "PASSED",
    "TestFilePdf.test_monochrome": "PASSED",
    "TestFilePdf.test_p_mode": "PASSED",
    "TestFilePdf.test_rgb": "PASSED",
    "TestFilePdf.test_save_all": "PASSED",
    "TestFilePdf.test_unsupported_mode": "PASSED"
}
diff --git a/PIL/PdfImagePlugin.py b/PIL/PdfImagePlugin.py
index e41a59411..86bc9c8e9 100644
--- a/PIL/PdfImagePlugin.py
+++ b/PIL/PdfImagePlugin.py
@@ -139,7 +139,7 @@ def _save(im, fp, filename, save_all=False):
         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()
+            append_im.encoderinfo = im.encoderinfo.copy()
             ims.append(append_im)
     numberOfPages = 0
     for im in ims:

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

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** AttributeError When Saving Multi-Page PDF with Appended Images

**Description:**
When attempting to save a multi-page PDF by appending images, an `AttributeError` is raised because the `'Image'` object does not have the `encoderinfo` attribute.

**Example Code:**
```python
from PIL import Image

im = Image.open("image1.jpg")
append_im = Image.open("image2.jpg")

im.save("output.pdf", save_all=True, append_images=[append_im])
```

**Expected Behavior:**
The PDF should be saved successfully with multiple pages, each containing the appended images.

**Actual Behavior:**
An error occurs during the save process:
```
AttributeError: 'Image' object has no attribute 'encoderinfo'
```
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### PIL/PdfImagePlugin.py
#
# 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 . import Image, ImageFile, ImageSequence
from ._binary import i8
import io

__version__ = "0.4"


#
# --------------------------------------------------------------------

# 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():
            if v is not None:
                fp.write("/%s %s\n" % (k, v))
        fp.write(">>\n")


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, save_all=False):
    resolution = im.encoderinfo.get("resolution", 72.0)

    #
    # make sure image data is available
    im.load()

    xref = [0]

    class TextWriter(object):
        def __init__(self, fp):
            self.fp = fp

        def __getattr__(self, name):
            return getattr(self.fp, name)

        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")

    # 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"
        procset = "/ImageB"  # grayscale
        bits = 1
    elif im.mode == "L":
        filter = "/DCTDecode"
        # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
        colorspace = "/DeviceGray"
        procset = "/ImageB"  # grayscale
    elif im.mode == "P":
        filter = "/ASCIIHexDecode"
        colorspace = "[ /Indexed /DeviceRGB 255 <"
        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 += "> ]"
        procset = "/ImageI"  # indexed color
    elif im.mode == "RGB":
        filter = "/DCTDecode"
        colorspace = "/DeviceRGB"
        procset = "/ImageC"  # color images
    elif im.mode == "CMYK":
        filter = "/DCTDecode"
        colorspace = "/DeviceCMYK"
        procset = "/ImageC"  # color images
    else:
        raise ValueError("cannot save mode %s" % im.mode)

    #
    # catalogue

    xref.append(fp.tell())
    _obj(
        fp, 1,
        Type="/Catalog",
        Pages="2 0 R")
    _endobj(fp)

    #
    # pages
    ims = [im]
    if save_all:
        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()
            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
    pages = [str(pageNumber*3+4)+" 0 R"
             for pageNumber in range(0, numberOfPages)]

    xref.append(fp.tell())
    _obj(
        fp, 2,
        Type="/Pages",
        Count=len(pages),
        Kids="["+"\n".join(pages)+"]")
    _endobj(fp)

    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

    #
    # trailer
    startxref = fp.tell()
    fp.write("xref\n0 %d\n0000000000 65535 f \n" % len(xref))
    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()

#
# --------------------------------------------------------------------

Image.register_save("PDF", _save)
Image.register_save_all("PDF", _save_all)

Image.register_extension("PDF", ".pdf")

Image.register_mime("PDF", "application/pdf")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user