{
    "file_diffs": [
        {
            "old_file_content": "from helper import unittest, PillowTestCase, hopper\nfrom PIL import Image, pdfParser\nimport os\nimport os.path\nimport tempfile\n\n\nclass TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, **kwargs):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        im.save(outfile, **kwargs)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        return outfile\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    def test_pdf_open(self):\n        # fail on empty buffer\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray())\n        # fail on a buffer full of null bytes\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray(65536))\n        # make an empty PDF object\n        empty_pdf = pdfParser.PdfParser()\n        self.assertEqual(len(empty_pdf.pages), 0)\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\")\n        # open the PDF file\n        hopper_pdf = pdfParser.PdfParser(filename=pdf_filename)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from a buffer with a non-zero offset\n        with open(pdf_filename, \"rb\") as f:\n            content = b\"xyzzy\" + f.read()\n        hopper_pdf = pdfParser.PdfParser(buf=content, start_offset=5)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from an already open file\n        with open(pdf_filename, \"rb\") as f:\n            hopper_pdf = pdfParser.PdfParser(f=f)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n\n    def test_pdf_append_fails_on_nonexistent_file(self):\n        im = hopper(\"RGB\")\n        temp_dir = tempfile.mkdtemp()\n        try:\n            self.assertRaises(IOError, im.save, os.path.join(temp_dir, \"nonexistent.pdf\"), append=True)\n        finally:\n            os.rmdir(temp_dir)\n\n    def test_pdf_append(self):\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\", producer=\"pdfParser\")\n        # open it, check pages and info\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 1)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n        # append some info\n        pdf.info[b\"Title\"] = pdfParser.encode_text(\"abc\")\n        pdf.info[b\"Author\"] = pdfParser.encode_text(\"def\")\n        pdf.info[b\"Subject\"] = pdfParser.encode_text(\"ghi\")\n        pdf.info[b\"Keywords\"] = pdfParser.encode_text(\"jkl\")\n        pdf.info[b\"Creator\"] = pdfParser.encode_text(\"hopper()\")\n        with open(pdf_filename, \"r+b\") as f:\n            f.seek(0, os.SEEK_END)\n            pdf.write_xref_and_trailer(f)\n        # open it again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        # append two images\n        mode_CMYK = hopper(\"CMYK\")\n        mode_P = hopper(\"P\")\n        mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])\n        # open the PDF again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 3)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n\n    def test_pdf_parser(self):\n        pdfParser.selftest()\n\n\nif __name__ == '__main__':\n    unittest.main()\n",
            "new_file_content": "from helper import unittest, PillowTestCase, hopper\nfrom PIL import Image, pdfParser\nimport io\nimport os\nimport os.path\nimport tempfile\n\n\nclass TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, **kwargs):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        im.save(outfile, **kwargs)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        return outfile\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    def test_pdf_open(self):\n        # fail on empty buffer\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray())\n        # fail on a buffer full of null bytes\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray(65536))\n        # make an empty PDF object\n        empty_pdf = pdfParser.PdfParser()\n        self.assertEqual(len(empty_pdf.pages), 0)\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\")\n        # open the PDF file\n        hopper_pdf = pdfParser.PdfParser(filename=pdf_filename)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from a buffer with a non-zero offset\n        with open(pdf_filename, \"rb\") as f:\n            content = b\"xyzzy\" + f.read()\n        hopper_pdf = pdfParser.PdfParser(buf=content, start_offset=5)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from an already open file\n        with open(pdf_filename, \"rb\") as f:\n            hopper_pdf = pdfParser.PdfParser(f=f)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n\n    def test_pdf_append_fails_on_nonexistent_file(self):\n        im = hopper(\"RGB\")\n        temp_dir = tempfile.mkdtemp()\n        try:\n            self.assertRaises(IOError, im.save, os.path.join(temp_dir, \"nonexistent.pdf\"), append=True)\n        finally:\n            os.rmdir(temp_dir)\n\n    def test_pdf_append(self):\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\", producer=\"pdfParser\")\n        # open it, check pages and info\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 1)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n        # append some info\n        pdf.info[b\"Title\"] = pdfParser.encode_text(\"abc\")\n        pdf.info[b\"Author\"] = pdfParser.encode_text(\"def\")\n        pdf.info[b\"Subject\"] = pdfParser.encode_text(\"ghi\")\n        pdf.info[b\"Keywords\"] = pdfParser.encode_text(\"jkl\")\n        pdf.info[b\"Creator\"] = pdfParser.encode_text(\"hopper()\")\n        with open(pdf_filename, \"r+b\") as f:\n            f.seek(0, os.SEEK_END)\n            pdf.write_xref_and_trailer(f)\n        # open it again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        # append two images\n        mode_CMYK = hopper(\"CMYK\")\n        mode_P = hopper(\"P\")\n        mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])\n        # open the PDF again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 3)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n\n    def test_pdf_append_to_bytesio(self):\n        im = hopper(\"RGB\")\n        f = io.BytesIO()\n        im.save(f, format=\"PDF\")\n        initial_size = len(f.getvalue())\n        self.assertGreater(initial_size, 0)\n        im = hopper(\"P\")\n        f = io.BytesIO(f.getvalue())\n        im.save(f, format=\"PDF\", append=True)\n        self.assertGreater(len(f.getvalue()), initial_size)\n\n    def test_pdf_parser(self):\n        pdfParser.selftest()\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": "8edf875cd",
                "new_commit_hash": "fe460d965",
                "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": 1,
                            "length": 5
                        },
                        "new_range": {
                            "start": 1,
                            "length": 6
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "from helper import unittest, PillowTestCase, hopper",
                                "type": "context"
                            },
                            {
                                "content": "from PIL import Image, pdfParser",
                                "type": "context"
                            },
                            {
                                "content": "import io",
                                "type": "added"
                            },
                            {
                                "content": "import os",
                                "type": "context"
                            },
                            {
                                "content": "import os.path",
                                "type": "context"
                            },
                            {
                                "content": "import tempfile",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_file_pdf.py",
                            "type": "import",
                            "name": "io",
                            "content": "import io",
                            "ast_type_str": "Import",
                            "start_lineno": 3,
                            "end_lineno": 3,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 162,
                            "length": 6
                        },
                        "new_range": {
                            "start": 163,
                            "length": 17
                        },
                        "section": "class TestFilePdf(PillowTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_pdf_append_to_bytesio(self):",
                                "type": "added"
                            },
                            {
                                "content": "        im = hopper(\"RGB\")",
                                "type": "added"
                            },
                            {
                                "content": "        f = io.BytesIO()",
                                "type": "added"
                            },
                            {
                                "content": "        im.save(f, format=\"PDF\")",
                                "type": "added"
                            },
                            {
                                "content": "        initial_size = len(f.getvalue())",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertGreater(initial_size, 0)",
                                "type": "added"
                            },
                            {
                                "content": "        im = hopper(\"P\")",
                                "type": "added"
                            },
                            {
                                "content": "        f = io.BytesIO(f.getvalue())",
                                "type": "added"
                            },
                            {
                                "content": "        im.save(f, format=\"PDF\", append=True)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertGreater(len(f.getvalue()), initial_size)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_pdf_parser(self):",
                                "type": "context"
                            },
                            {
                                "content": "        pdfParser.selftest()",
                                "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, **kwargs):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        im.save(outfile, **kwargs)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        return outfile\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    def test_pdf_open(self):\n        # fail on empty buffer\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray())\n        # fail on a buffer full of null bytes\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray(65536))\n        # make an empty PDF object\n        empty_pdf = pdfParser.PdfParser()\n        self.assertEqual(len(empty_pdf.pages), 0)\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\")\n        # open the PDF file\n        hopper_pdf = pdfParser.PdfParser(filename=pdf_filename)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from a buffer with a non-zero offset\n        with open(pdf_filename, \"rb\") as f:\n            content = b\"xyzzy\" + f.read()\n        hopper_pdf = pdfParser.PdfParser(buf=content, start_offset=5)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from an already open file\n        with open(pdf_filename, \"rb\") as f:\n            hopper_pdf = pdfParser.PdfParser(f=f)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n\n    def test_pdf_append_fails_on_nonexistent_file(self):\n        im = hopper(\"RGB\")\n        temp_dir = tempfile.mkdtemp()\n        try:\n            self.assertRaises(IOError, im.save, os.path.join(temp_dir, \"nonexistent.pdf\"), append=True)\n        finally:\n            os.rmdir(temp_dir)\n\n    def test_pdf_append(self):\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\", producer=\"pdfParser\")\n        # open it, check pages and info\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 1)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n        # append some info\n        pdf.info[b\"Title\"] = pdfParser.encode_text(\"abc\")\n        pdf.info[b\"Author\"] = pdfParser.encode_text(\"def\")\n        pdf.info[b\"Subject\"] = pdfParser.encode_text(\"ghi\")\n        pdf.info[b\"Keywords\"] = pdfParser.encode_text(\"jkl\")\n        pdf.info[b\"Creator\"] = pdfParser.encode_text(\"hopper()\")\n        with open(pdf_filename, \"r+b\") as f:\n            f.seek(0, os.SEEK_END)\n            pdf.write_xref_and_trailer(f)\n        # open it again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        # append two images\n        mode_CMYK = hopper(\"CMYK\")\n        mode_P = hopper(\"P\")\n        mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])\n        # open the PDF again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 3)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n\n    def test_pdf_append_to_bytesio(self):\n        im = hopper(\"RGB\")\n        f = io.BytesIO()\n        im.save(f, format=\"PDF\")\n        initial_size = len(f.getvalue())\n        self.assertGreater(initial_size, 0)\n        im = hopper(\"P\")\n        f = io.BytesIO(f.getvalue())\n        im.save(f, format=\"PDF\", append=True)\n        self.assertGreater(len(f.getvalue()), initial_size)\n\n    def test_pdf_parser(self):\n        pdfParser.selftest()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 9,
                            "end_lineno": 178,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_file_pdf.py",
                            "type": "method",
                            "name": "TestFilePdf.test_pdf_append_to_bytesio",
                            "content": "class TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, **kwargs):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        im.save(outfile, **kwargs)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        return outfile\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    def test_pdf_open(self):\n        # fail on empty buffer\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray())\n        # fail on a buffer full of null bytes\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray(65536))\n        # make an empty PDF object\n        empty_pdf = pdfParser.PdfParser()\n        self.assertEqual(len(empty_pdf.pages), 0)\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\")\n        # open the PDF file\n        hopper_pdf = pdfParser.PdfParser(filename=pdf_filename)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from a buffer with a non-zero offset\n        with open(pdf_filename, \"rb\") as f:\n            content = b\"xyzzy\" + f.read()\n        hopper_pdf = pdfParser.PdfParser(buf=content, start_offset=5)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from an already open file\n        with open(pdf_filename, \"rb\") as f:\n            hopper_pdf = pdfParser.PdfParser(f=f)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n\n    def test_pdf_append_fails_on_nonexistent_file(self):\n        im = hopper(\"RGB\")\n        temp_dir = tempfile.mkdtemp()\n        try:\n            self.assertRaises(IOError, im.save, os.path.join(temp_dir, \"nonexistent.pdf\"), append=True)\n        finally:\n            os.rmdir(temp_dir)\n\n    def test_pdf_append(self):\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\", producer=\"pdfParser\")\n        # open it, check pages and info\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 1)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n        # append some info\n        pdf.info[b\"Title\"] = pdfParser.encode_text(\"abc\")\n        pdf.info[b\"Author\"] = pdfParser.encode_text(\"def\")\n        pdf.info[b\"Subject\"] = pdfParser.encode_text(\"ghi\")\n        pdf.info[b\"Keywords\"] = pdfParser.encode_text(\"jkl\")\n        pdf.info[b\"Creator\"] = pdfParser.encode_text(\"hopper()\")\n        with open(pdf_filename, \"r+b\") as f:\n            f.seek(0, os.SEEK_END)\n            pdf.write_xref_and_trailer(f)\n        # open it again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        # append two images\n        mode_CMYK = hopper(\"CMYK\")\n        mode_P = hopper(\"P\")\n        mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])\n        # open the PDF again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 3)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n\n    def test_pdf_append_to_bytesio(self):\n        im = hopper(\"RGB\")\n        f = io.BytesIO()\n        im.save(f, format=\"PDF\")\n        initial_size = len(f.getvalue())\n        self.assertGreater(initial_size, 0)\n        im = hopper(\"P\")\n        f = io.BytesIO(f.getvalue())\n        im.save(f, format=\"PDF\", append=True)\n        self.assertGreater(len(f.getvalue()), initial_size)\n\n    def test_pdf_parser(self):\n        pdfParser.selftest()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 166,
                            "end_lineno": 175,
                            "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, **kwargs):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        im.save(outfile, **kwargs)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        return outfile\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    def test_pdf_open(self):\n        # fail on empty buffer\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray())\n        # fail on a buffer full of null bytes\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray(65536))\n        # make an empty PDF object\n        empty_pdf = pdfParser.PdfParser()\n        self.assertEqual(len(empty_pdf.pages), 0)\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\")\n        # open the PDF file\n        hopper_pdf = pdfParser.PdfParser(filename=pdf_filename)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from a buffer with a non-zero offset\n        with open(pdf_filename, \"rb\") as f:\n            content = b\"xyzzy\" + f.read()\n        hopper_pdf = pdfParser.PdfParser(buf=content, start_offset=5)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from an already open file\n        with open(pdf_filename, \"rb\") as f:\n            hopper_pdf = pdfParser.PdfParser(f=f)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n\n    def test_pdf_append_fails_on_nonexistent_file(self):\n        im = hopper(\"RGB\")\n        temp_dir = tempfile.mkdtemp()\n        try:\n            self.assertRaises(IOError, im.save, os.path.join(temp_dir, \"nonexistent.pdf\"), append=True)\n        finally:\n            os.rmdir(temp_dir)\n\n    def test_pdf_append(self):\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\", producer=\"pdfParser\")\n        # open it, check pages and info\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 1)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n        # append some info\n        pdf.info[b\"Title\"] = pdfParser.encode_text(\"abc\")\n        pdf.info[b\"Author\"] = pdfParser.encode_text(\"def\")\n        pdf.info[b\"Subject\"] = pdfParser.encode_text(\"ghi\")\n        pdf.info[b\"Keywords\"] = pdfParser.encode_text(\"jkl\")\n        pdf.info[b\"Creator\"] = pdfParser.encode_text(\"hopper()\")\n        with open(pdf_filename, \"r+b\") as f:\n            f.seek(0, os.SEEK_END)\n            pdf.write_xref_and_trailer(f)\n        # open it again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        # append two images\n        mode_CMYK = hopper(\"CMYK\")\n        mode_P = hopper(\"P\")\n        mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])\n        # open the PDF again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 3)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n\n    def test_pdf_append_to_bytesio(self):\n        im = hopper(\"RGB\")\n        f = io.BytesIO()\n        im.save(f, format=\"PDF\")\n        initial_size = len(f.getvalue())\n        self.assertGreater(initial_size, 0)\n        im = hopper(\"P\")\n        f = io.BytesIO(f.getvalue())\n        im.save(f, format=\"PDF\", append=True)\n        self.assertGreater(len(f.getvalue()), initial_size)\n\n    def test_pdf_parser(self):\n        pdfParser.selftest()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 9,
                                "end_lineno": 178,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "import codecs\nimport collections\nimport io\nimport mmap\nimport re\nimport sys\n\ntry:\n    from UserDict import UserDict\nexcept ImportError:\n    UserDict = collections.UserDict\n\n\nif str == bytes:  # Python 2.x\n    make_bytes = lambda s: s  # pragma: no cover\nelse:  # Python 3.x\n    def make_bytes(s):\n        return s.encode(\"us-ascii\")\n\n\n# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656\ndef encode_text(s):\n    return codecs.BOM_UTF16_BE + s.encode(\"utf_16_be\")\n\n\nPDFDocEncoding = {\n    0x16: u\"\\u0017\",\n    0x18: u\"\\u02D8\",\n    0x19: u\"\\u02C7\",\n    0x1A: u\"\\u02C6\",\n    0x1B: u\"\\u02D9\",\n    0x1C: u\"\\u02DD\",\n    0x1D: u\"\\u02DB\",\n    0x1E: u\"\\u02DA\",\n    0x1F: u\"\\u02DC\",\n    0x80: u\"\\u2022\",\n    0x81: u\"\\u2020\",\n    0x82: u\"\\u2021\",\n    0x83: u\"\\u2026\",\n    0x84: u\"\\u2014\",\n    0x85: u\"\\u2013\",\n    0x86: u\"\\u0192\",\n    0x87: u\"\\u2044\",\n    0x88: u\"\\u2039\",\n    0x89: u\"\\u203A\",\n    0x8A: u\"\\u2212\",\n    0x8B: u\"\\u2030\",\n    0x8C: u\"\\u201E\",\n    0x8D: u\"\\u201C\",\n    0x8E: u\"\\u201D\",\n    0x8F: u\"\\u2018\",\n    0x90: u\"\\u2019\",\n    0x91: u\"\\u201A\",\n    0x92: u\"\\u2122\",\n    0x93: u\"\\uFB01\",\n    0x94: u\"\\uFB02\",\n    0x95: u\"\\u0141\",\n    0x96: u\"\\u0152\",\n    0x97: u\"\\u0160\",\n    0x98: u\"\\u0178\",\n    0x99: u\"\\u017D\",\n    0x9A: u\"\\u0131\",\n    0x9B: u\"\\u0142\",\n    0x9C: u\"\\u0153\",\n    0x9D: u\"\\u0161\",\n    0x9E: u\"\\u017E\",\n    0xA0: u\"\\u20AC\",\n    }\ndef decode_text(b):\n    if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:\n        return b[len(codecs.BOM_UTF16_BE):].decode(\"utf_16_be\")\n    elif str == bytes:  # Python 2.x\n        return u\"\".join(PDFDocEncoding.get(ord(byte), byte) for byte in b)\n    else:\n        return \"\".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)\n\n\nclass PdfFormatError(RuntimeError):\n    pass\n\n\ndef check_format_condition(condition, error_message):\n    if not condition:\n        raise PdfFormatError(error_message)\n\n\nclass IndirectReference(collections.namedtuple(\"IndirectReferenceTuple\", [\"object_id\", \"generation\"])):\n    def __str__(self):\n        return \"%s %s R\" % self\n\n    def __bytes__(self):\n        return self.__str__().encode(\"us-ascii\")\n\n    def __eq__(self, other):\n        return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation\n\n    def __ne__(self, other):\n        return not (self == other)\n\n\nclass IndirectObjectDef(IndirectReference):\n    def __str__(self):\n        return \"%s %s obj\" % self\n\n\nclass XrefTable:\n    def __init__(self):\n        self.existing_entries = {}          # object ID => (offset, generation)\n        self.new_entries = {}               # object ID => (offset, generation)\n        self.deleted_entries = {0: 65536}   # object ID => generation\n        self.reading_finished = False\n\n    def __setitem__(self, key, value):\n        if self.reading_finished:\n            self.new_entries[key] = value\n        else:\n            self.existing_entries[key] = value\n        if key in self.deleted_entries:\n            del self.deleted_entries[key]\n\n    def __getitem__(self, key):\n        try:\n            return self.new_entries[key]\n        except KeyError:\n            return self.existing_entries[key]\n\n    def __delitem__(self, key):\n        if key in self.new_entries:\n            generation = self.new_entries[key][1] + 1\n            del self.new_entries[key]\n            self.deleted_entries[key] = generation\n        elif key in self.existing_entries:\n            generation = self.existing_entries[key][1] + 1\n            self.deleted_entries[key] = generation\n        elif key in self.deleted_entries:\n            generation = self.deleted_entries[key]\n        else:\n            raise IndexError(\"object ID \" + str(key) + \" cannot be deleted because it doesn't exist\")\n\n    def __contains__(self, key):\n        return key in self.existing_entries or key in self.new_entries\n\n    def __len__(self):\n        return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys()))\n\n    def keys(self):\n        return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys())\n\n    def write(self, f):\n        keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))\n        deleted_keys = sorted(set(self.deleted_entries.keys()))\n        startxref = f.tell()\n        f.write(b\"xref\\n\")\n        while keys:\n            # find a contiguous sequence of object IDs\n            prev = None\n            for index, key in enumerate(keys):\n                if prev is None or prev+1 == key:\n                    prev = key\n                else:\n                    contiguous_keys = keys[:index]\n                    keys = keys[index:]\n                    break\n            else:\n                contiguous_keys = keys\n                keys = None\n            f.write(make_bytes(\"%d %d\\n\" % (contiguous_keys[0], len(contiguous_keys))))\n            for object_id in contiguous_keys:\n                if object_id in self.new_entries:\n                    f.write(make_bytes(\"%010d %05d n \\n\" % self.new_entries[object_id]))\n                else:\n                    this_deleted_object_id = deleted_keys.pop(0)\n                    assert object_id == this_deleted_object_id\n                    try:\n                        next_in_linked_list = deleted_keys[0]\n                    except IndexError:\n                        next_in_linked_list = 0\n                    f.write(make_bytes(\"%010d %05d f \\n\" % (next_in_linked_list, self.deleted_entries[object_id])))\n        return startxref\n\n\nclass PdfName():\n    def __init__(self, name):\n        if isinstance(name, PdfName):\n            self.name = name.name\n        elif isinstance(name, bytes):\n            self.name = name\n        else:\n            self.name = name.encode(\"utf-8\")\n\n    @classmethod\n    def from_pdf_stream(klass, data):\n        return klass(PdfParser.interpret_name(data))\n\n    allowed_chars = set(range(33,127)) - set((ord(c) for c in \"#%/()<>[]{}\"))\n    def __bytes__(self):\n        if str == bytes:  # Python 2.x\n            result = bytearray(b\"/\")\n            for b in self.name:\n                if ord(b) in self.allowed_chars:\n                    result.append(b)\n                else:\n                    result.extend(b\"#%02X\" % ord(b))\n        else:  # Python 3.x\n            result = bytearray(b\"/\")\n            for b in self.name:\n                if b in self.allowed_chars:\n                    result.append(b)\n                else:\n                    result.extend(make_bytes(\"#%02X\" % b))\n        return bytes(result)\n\n    __str__ = __bytes__\n\n\nclass PdfArray(list):\n    def __bytes__(self):\n        return b\"[ \" + b\" \".join(pdf_repr(x) for x in self) + b\" ]\"\n\n    __str__ = __bytes__\n\n\nclass PdfDict(UserDict):\n    #def __init__(self, *args, orig_ref=None, pdf=None, **kwargs):\n    def __init__(self, *args, **kwargs):\n        UserDict.__init__(self, *args, **kwargs)\n        #self.orig_ref = kwargs.pop(\"orig_ref\", None)\n        #self.pdf = kwargs.pop(\"pdf\", None)\n        #self.is_changed = False\n\n    def __setitem__(self, key, value):\n        self.is_changed = True\n        UserDict.__setitem__(self, key, value)\n\n    def __bytes__(self):\n        #if self.orig_ref is not None:\n        #    if self.is_changed:\n        #        if self.pdf is not None:\n        #            del self.pdf.xref_table[self.orig_ref.object_id]\n        #    else:\n        #        return bytes(self.orig_ref)\n        out = bytearray(b\"<<\")\n        for key, value in self.items():\n            if value is None:\n                continue\n            value = pdf_repr(value)\n            out.extend(b\"\\n\")\n            out.extend(bytes(PdfName(key)))\n            out.extend(b\" \")\n            out.extend(value)\n            #out += b\"\\n%s %s\" % (PdfName(key), value)\n        out.extend(b\"\\n>>\")\n        return bytes(out)\n        #return out + b\"\\n>>\"\n\n    __str__ = __bytes__\n\n    #def write(self, f, orig_ref=None, pdf=None):\n    #    self.orig_ref = orig_ref\n    #    self.pdf = pdf\n    #    f.write(bytes(self))\n\n\nclass PdfBinary:\n    def __init__(self, data):\n        self.data = data\n\n    if str == bytes:  # Python 2.x\n        def __str__(self):\n            return \"<%s>\" % \"\".join(\"%02X\" % ord(b) for b in self.data)\n\n    else:  # Python 3.x\n        def __bytes__(self):\n            return make_bytes(\"<%s>\" % \"\".join(\"%02X\" % b for b in self.data))\n\n\ndef pdf_repr(x):\n    if x is True:\n        return b\"true\"\n    elif x is False:\n        return b\"false\"\n    elif x is None:\n        return b\"null\"\n    elif isinstance(x, PdfName) or isinstance(x, PdfDict) or isinstance(x, PdfArray) or isinstance(x, PdfBinary):\n        return bytes(x)\n    elif isinstance(x, int):\n        return str(x).encode(\"us-ascii\")\n    elif isinstance(x, dict):\n        return bytes(PdfDict(x))\n    elif isinstance(x, list):\n        return bytes(PdfArray(x))\n    elif isinstance(x, str) and str != bytes:\n        return pdf_repr(x.encode(\"utf-8\"))\n    elif isinstance(x, bytes):\n        return b\"(\" + x.replace(b\"\\\\\", b\"\\\\\\\\\").replace(b\"(\", b\"\\\\(\").replace(b\")\", b\"\\\\)\") + b\")\"  # XXX escape more chars? handle binary garbage\n    else:\n        return bytes(x)\n\n\nclass PdfParser:\n    \"\"\"Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf\n    Supports PDF up to 1.4\n    \"\"\"\n\n    def __init__(self, filename=None, f=None, buf=None, start_offset=0):\n        self.filename = filename\n        self.buf = buf\n        self.start_offset = start_offset\n        if buf is not None:\n            self.read_pdf_info()\n        elif f is not None:\n            self.read_pdf_info_from_file(f)\n        elif filename is not None:\n            with open(filename, \"rb\") as f:\n                self.read_pdf_info_from_file(f)\n        else:\n            self.file_size_total = self.file_size_this = 0\n            self.root = PdfDict()\n            self.root_ref = None\n            self.info = PdfDict()\n            self.info_ref = None\n            self.page_tree_root = {}\n            self.pages = []\n            self.pages_ref = None\n            self.last_xref_section_offset = None\n            self.trailer_dict = {}\n            self.xref_table = XrefTable()\n        self.xref_table.reading_finished = True\n\n    def write_header(self, f):\n        f.write(b\"%PDF-1.4\\n\")\n\n    def write_comment(self, f, s):\n        f.write((\"%% %s\\n\" % (s,)).encode(\"utf-8\"))\n\n    def write_catalog(self, f):\n        self.del_root()\n        self.root_ref = self.next_object_id(f.tell())\n        self.pages_ref = self.next_object_id(0)\n        self.write_obj(f, self.root_ref,\n            Type=PdfName(b\"Catalog\"),\n            Pages=self.pages_ref)\n        self.write_obj(f, self.pages_ref,\n            Type=PdfName(\"Pages\"),\n            Count=len(self.pages),\n            Kids=self.pages)\n        return self.root_ref\n\n    def write_xref_and_trailer(self, f, new_root_ref=None):\n        if new_root_ref:\n            self.del_root()\n            self.root_ref = new_root_ref\n        if self.info:\n            self.info_ref = self.write_obj(f, None, self.info)\n        start_xref = self.xref_table.write(f)\n        num_entries = len(self.xref_table)\n        trailer_dict = {b\"Root\": self.root_ref, b\"Size\": num_entries}\n        if self.last_xref_section_offset is not None:\n            trailer_dict[b\"Prev\"] = self.last_xref_section_offset\n        if self.info:\n            trailer_dict[b\"Info\"] = self.info_ref\n        self.last_xref_section_offset = start_xref\n        f.write(b\"trailer\\n\" + bytes(PdfDict(trailer_dict)) + make_bytes(\"\\nstartxref\\n%d\\n%%%%EOF\" % start_xref))\n\n    def write_page(self, f, ref, *objs, **dict_obj):\n        if isinstance(ref, int):\n            ref = self.pages[ref]\n        if \"Type\" not in dict_obj:\n            dict_obj[\"Type\"] = PdfName(\"Page\")\n        if \"Parent\" not in dict_obj:\n            dict_obj[\"Parent\"] = self.pages_ref\n        return self.write_obj(f, ref, *objs, **dict_obj)\n\n    def write_obj(self, f, ref, *objs, **dict_obj):\n        if ref is None:\n            ref = self.next_object_id(f.tell())\n        else:\n            self.xref_table[ref.object_id] = (f.tell(), ref.generation)\n        f.write(bytes(IndirectObjectDef(*ref)))\n        stream = dict_obj.pop(\"stream\", None)\n        if stream is not None:\n            dict_obj[\"Length\"] = len(stream)\n        if dict_obj:\n            f.write(pdf_repr(dict_obj))\n        for obj in objs:\n            f.write(pdf_repr(obj))\n        if stream is not None:\n            f.write(b\"stream\\n\")\n            f.write(stream)\n            f.write(b\"\\nendstream\\n\")\n        f.write(b\"endobj\\n\")\n        return ref\n\n    def del_root(self):\n        if self.root_ref is None:\n            return\n        del self.xref_table[self.root_ref.object_id]\n        del self.xref_table[self.root[b\"Pages\"].object_id]\n        # XXX TODO delete Pages tree recursively\n\n    def read_pdf_info_from_file(self, f):\n        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\n        try:\n            self.read_pdf_info()\n        finally:\n            self.buf.close()\n            self.buf = None\n\n    def read_pdf_info(self):\n        self.file_size_total = len(self.buf)\n        self.file_size_this = self.file_size_total - self.start_offset\n        self.read_trailer()\n        self.root_ref = self.trailer_dict[b\"Root\"]\n        self.info_ref = self.trailer_dict.get(b\"Info\", None)\n        self.root = PdfDict(self.read_indirect(self.root_ref))\n        if self.info_ref is None:\n            self.info = PdfDict()\n        else:\n            self.info = PdfDict(self.read_indirect(self.info_ref))\n        #print(repr(self.root))\n        check_format_condition(b\"Type\" in self.root, \"/Type missing in Root\")\n        check_format_condition(self.root[b\"Type\"] == b\"Catalog\", \"/Type in Root is not /Catalog\")\n        check_format_condition(b\"Pages\" in self.root, \"/Pages missing in Root\")\n        check_format_condition(isinstance(self.root[b\"Pages\"], IndirectReference), \"/Pages in Root is not an indirect reference\")\n        self.pages_ref = self.root[b\"Pages\"]\n        self.page_tree_root = self.read_indirect(self.pages_ref)\n        #print(\"page_tree_root: \" + str(self.page_tree_root))\n        self.pages = self.linearize_page_tree(self.page_tree_root)\n        #print(\"pages: \" + str(self.pages))\n\n    def next_object_id(self, offset=None):\n        try:\n            # TODO: support reuse of deleted objects\n            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)\n        except ValueError:\n            reference = IndirectReference(1, 0)\n        if offset is not None:\n            self.xref_table[reference.object_id] = (offset, 0)\n        return reference\n\n    delimiter = br\"[][()<>{}/%]\"\n    delimiter_or_ws = br\"[][()<>{}/%\\000\\011\\012\\014\\015\\040]\"\n    whitespace = br\"[\\000\\011\\012\\014\\015\\040]\"\n    whitespace_or_hex = br\"[\\000\\011\\012\\014\\015\\0400-9a-fA-F]\"\n    whitespace_optional = whitespace + b\"*\"\n    whitespace_mandatory = whitespace + b\"+\"\n    newline_only = br\"[\\r\\n]+\"\n    newline = whitespace_optional + newline_only + whitespace_optional\n    re_trailer_end = re.compile(whitespace_mandatory + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional + br\"$\", re.DOTALL)\n    re_trailer_prev = re.compile(whitespace_optional + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*?\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional, re.DOTALL)\n    def read_trailer(self):\n        search_start_offset = len(self.buf) - 16384\n        if search_start_offset < self.start_offset:\n            search_start_offset = self.start_offset\n        #data_at_end = self.buf[search_start_offset:]\n        #m = self.re_trailer_end.search(data_at_end)\n        m = self.re_trailer_end.search(self.buf, search_start_offset)\n        check_format_condition(m, \"trailer end not found\")\n        # make sure we found the LAST trailer\n        last_match = m\n        while m:\n            last_match = m\n            m = self.re_trailer_end.search(self.buf, m.start()+16)\n        if not m:\n            m = last_match\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        self.last_xref_section_offset = int(m.group(2))\n        self.trailer_dict = self.interpret_trailer(trailer_data)\n        self.xref_table = XrefTable()\n        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)\n        #print(self.xref_table)\n        if b\"Prev\" in self.trailer_dict:\n            self.read_prev_trailer(self.trailer_dict[b\"Prev\"])\n\n    def read_prev_trailer(self, xref_section_offset):\n        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)\n        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])\n        check_format_condition(m, \"previous trailer not found\")\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        check_format_condition(int(m.group(2)) == xref_section_offset, \"xref section offset in previous trailer doesn't match what was expected\")\n        trailer_dict = self.interpret_trailer(trailer_data)\n        if b\"Prev\" in trailer_dict:\n            self.read_prev_trailer(trailer_dict[b\"Prev\"])\n\n    re_whitespace_optional = re.compile(whitespace_optional)\n    re_name = re.compile(whitespace_optional + br\"/([!-$&'*-.0-;=?-Z\\\\^-z|~]+)(?=\" + delimiter_or_ws + br\")\")\n    re_dict_start = re.compile(whitespace_optional + br\"\\<\\<\")\n    re_dict_end = re.compile(whitespace_optional + br\"\\>\\>\" + whitespace_optional)\n    @classmethod\n    def interpret_trailer(klass, trailer_data):\n        trailer = {}\n        offset = 0\n        while True:\n            m = klass.re_name.match(trailer_data, offset)\n            if not m:\n                m = klass.re_dict_end.match(trailer_data, offset)\n                check_format_condition(m and m.end() == len(trailer_data), \"name not found in trailer, remaining data: \" + repr(trailer_data[offset:]))\n                break\n            key = klass.interpret_name(m.group(1))\n            #print(key)\n            value, offset = klass.get_value(trailer_data, m.end())\n            #print(value)\n            trailer[key] = value\n        check_format_condition(b\"Size\" in trailer and isinstance(trailer[b\"Size\"], int), \"/Size not in trailer or not an integer\")\n        check_format_condition(b\"Root\" in trailer and isinstance(trailer[b\"Root\"], IndirectReference), \"/Root not in trailer or not an indirect reference\")\n        return trailer\n\n    re_hashes_in_name = re.compile(br\"([^#]*)(#([0-9a-fA-F]{2}))?\")\n    @classmethod\n    def interpret_name(klass, raw, as_text=False):\n        name = b\"\"\n        for m in klass.re_hashes_in_name.finditer(raw):\n            if m.group(3):\n                name += m.group(1) + bytearray.fromhex(m.group(3).decode(\"us-ascii\"))\n            else:\n                name += m.group(1)\n        if as_text:\n            return name.decode(\"utf-8\")\n        else:\n            return bytes(name)\n\n    re_null = re.compile(whitespace_optional + br\"null(?=\" + delimiter_or_ws + br\")\")\n    re_true = re.compile(whitespace_optional + br\"true(?=\" + delimiter_or_ws + br\")\")\n    re_false = re.compile(whitespace_optional + br\"false(?=\" + delimiter_or_ws + br\")\")\n    re_int = re.compile(whitespace_optional + br\"([-+]?[0-9]+)(?=\" + delimiter_or_ws + br\")\")\n    re_real = re.compile(whitespace_optional + br\"([-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+))(?=\" + delimiter_or_ws + br\")\")\n    re_array_start = re.compile(whitespace_optional + br\"\\[\")\n    re_array_end = re.compile(whitespace_optional + br\"]\")\n    re_string_hex = re.compile(whitespace_optional + br\"\\<(\" + whitespace_or_hex + br\"*)\\>\")\n    re_string_lit = re.compile(whitespace_optional + br\"\\(\")\n    re_indirect_reference = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"R(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_start = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"obj(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_end = re.compile(whitespace_optional + br\"endobj(?=\" + delimiter_or_ws + br\")\")\n    re_comment = re.compile(br\"(\" + whitespace_optional + br\"%[^\\r\\n]*\" + newline + br\")*\")\n    @classmethod\n    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):\n        #if max_nesting == 0:\n        #    return None, None\n        m = klass.re_comment.match(data, offset)\n        if m:\n            offset = m.end()\n        m = klass.re_indirect_def_start.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),\n                \"indirect object definition different than expected\")\n            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)\n            if offset is None:\n                return object, None\n            m = klass.re_indirect_def_end.match(data, offset)\n            check_format_condition(m, \"indirect object definition end not found\")\n            return object, m.end()\n        check_format_condition(not expect_indirect, \"indirect object definition not found\")\n        m = klass.re_indirect_reference.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()\n        m = klass.re_dict_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = {}\n            #print(\"<<\")\n            m = klass.re_dict_end.match(data, offset)\n            while not m:\n                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                #print (\"key \" + str(key))\n                if offset is None:\n                    return result, None\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result[key] = value\n                #print (\"value \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_dict_end.match(data, offset)\n            #print(\">>\")\n            return result, m.end()\n        m = klass.re_array_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = []\n            m = klass.re_array_end.match(data, offset)\n            while not m:\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result.append(value)\n                #print (\"item \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_array_end.match(data, offset)\n            return result, m.end()\n        m = klass.re_null.match(data, offset)\n        if m:\n            return None, m.end()\n        m = klass.re_true.match(data, offset)\n        if m:\n            return True, m.end()\n        m = klass.re_false.match(data, offset)\n        if m:\n            return False, m.end()\n        m = klass.re_name.match(data, offset)\n        if m:\n            return klass.interpret_name(m.group(1)), m.end()\n        m = klass.re_int.match(data, offset)\n        if m:\n            return int(m.group(1)), m.end()\n        m = klass.re_real.match(data, offset)\n        if m:\n            return float(m.group(1)), m.end()  # XXX Decimal instead of float???\n        m = klass.re_string_hex.match(data, offset)\n        if m:\n            hex_string = bytearray([b for b in m.group(1) if b in b\"0123456789abcdefABCDEF\"])  # filter out whitespace\n            if len(hex_string) % 2 == 1:\n                hex_string.append(ord(b\"0\"))  # append a 0 if the length is not even - yes, at the end\n            return bytearray.fromhex(hex_string.decode(\"us-ascii\")), m.end()\n        m = klass.re_string_lit.match(data, offset)\n        if m:\n            return klass.get_literal_string(data, m.end())\n        # XXX TODO: stream\n        #return None, offset  # fallback (only for debugging)\n        raise PdfFormatError(\"unrecognized object: \" + repr(data[offset:offset+32]))\n\n\n    re_lit_str_token = re.compile(br\"(\\\\[nrtbf()\\\\])|(\\\\[0-9]{1,3})|(\\\\(\\r\\n|\\r|\\n))|(\\r\\n|\\r|\\n)|(\\()|(\\))\")\n    escaped_chars = {\n        b\"n\": b\"\\n\",\n        b\"r\": b\"\\r\",\n        b\"t\": b\"\\t\",\n        b\"b\": b\"\\b\",\n        b\"f\": b\"\\f\",\n        b\"(\": b\"(\",\n        b\")\": b\")\",\n        ord(b\"n\"): b\"\\n\",\n        ord(b\"r\"): b\"\\r\",\n        ord(b\"t\"): b\"\\t\",\n        ord(b\"b\"): b\"\\b\",\n        ord(b\"f\"): b\"\\f\",\n        ord(b\"(\"): b\"(\",\n        ord(b\")\"): b\")\",\n        }\n    @classmethod\n    def get_literal_string(klass, data, offset):\n        nesting_depth = 0\n        result = bytearray()\n        for m in klass.re_lit_str_token.finditer(data, offset):\n            result.extend(data[offset:m.start()])\n            if m.group(1):\n                result.extend(klass.escaped_chars[m.group(1)[1]])\n            elif m.group(2):\n                #result.append(eval(m.group(1)))\n                result.append(int(m.group(2)[1:], 8))\n            elif m.group(3):\n                pass\n            elif m.group(5):\n                result.extend(b\"\\n\")\n            elif m.group(6):\n                result.extend(b\"(\")\n                nesting_depth += 1\n            elif m.group(7):\n                if nesting_depth == 0:\n                    return bytes(result), m.end()\n                result.extend(b\")\")\n                nesting_depth -= 1\n            offset = m.end()\n        raise PdfFormatError(\"unfinished literal string\")\n\n\n    re_xref_section_start = re.compile(whitespace_optional + br\"xref\" + newline)\n    re_xref_subsection_start = re.compile(whitespace_optional + br\"([0-9]+)\" + whitespace_mandatory + br\"([0-9]+)\" + whitespace_optional + newline_only)\n    re_xref_entry = re.compile(br\"([0-9]{10}) ([0-9]{5}) ([fn])( \\r| \\n|\\r\\n)\")\n    def read_xref_table(self, xref_section_offset):\n        subsection_found = False\n        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)\n        check_format_condition(m, \"xref section start not found\")\n        offset = m.end()\n        while True:\n            m = self.re_xref_subsection_start.match(self.buf, offset)\n            if not m:\n                check_format_condition(subsection_found, \"xref subsection start not found\")\n                break\n            subsection_found = True\n            offset = m.end()\n            first_object = int(m.group(1))\n            num_objects = int(m.group(2))\n            for i in range(first_object, first_object+num_objects):\n                m = self.re_xref_entry.match(self.buf, offset)\n                check_format_condition(m, \"xref entry not found\")\n                offset = m.end()\n                is_free = m.group(3) == b\"f\"\n                generation = int(m.group(2))\n                if not is_free:\n                    new_entry = (int(m.group(1)), generation)\n                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, \"xref entry duplicated (and not identical)\")\n                    self.xref_table[i] = new_entry\n        return offset\n\n\n    def read_indirect(self, ref, max_nesting=-1):\n        offset, generation = self.xref_table[ref[0]]\n        assert generation == ref[1]\n        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]\n\n\n    def linearize_page_tree(self, node=None):\n        if node is None:\n            node = self.page_tree_root\n        check_format_condition(node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\")\n        pages = []\n        for kid in node[b\"Kids\"]:\n            kid_object = self.read_indirect(kid, max_nesting=3)\n            if kid_object[b\"Type\"] == b\"Page\":\n                pages.append(kid)\n            else:\n                pages.extend(self.linearize_page_tree(node=kid_object))\n        return pages\n\n\ndef selftest():\n    assert encode_text(\"abc\") == b\"\\xFE\\xFF\\x00a\\x00b\\x00c\"\n    assert decode_text(b\"\\xFE\\xFF\\x00a\\x00b\\x00c\") == \"abc\"\n    assert decode_text(b\"abc\") == \"abc\"\n    assert decode_text(b\"\\x1B a \\x1C\") == u\"\\u02D9 a \\u02DD\"\n    assert PdfParser.interpret_name(b\"Name#23Hash\") == b\"Name#Hash\"\n    assert PdfParser.interpret_name(b\"Name#23Hash\", as_text=True) == \"Name#Hash\"\n    assert IndirectReference(1,2) == IndirectReference(1,2)\n    assert IndirectReference(1,2) != IndirectReference(1,3)\n    assert IndirectReference(1,2) != IndirectObjectDef(1,2)\n    assert IndirectReference(1,2) != (1,2)\n    assert IndirectObjectDef(1,2) == IndirectObjectDef(1,2)\n    assert IndirectObjectDef(1,2) != IndirectObjectDef(1,3)\n    assert IndirectObjectDef(1,2) != IndirectReference(1,2)\n    assert IndirectObjectDef(1,2) != (1,2)\n    assert bytes(IndirectReference(1,2)) == b\"1 2 R\"\n    assert bytes(IndirectObjectDef(*IndirectReference(1,2))) == b\"1 2 obj\"\n    assert bytes(PdfName(b\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert bytes(PdfName(\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert bytes(PdfDict({b\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert bytes(PdfDict({\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(IndirectReference(1,2)) == b\"1 2 R\"\n    assert pdf_repr(IndirectObjectDef(*IndirectReference(1,2))) == b\"1 2 obj\"\n    assert pdf_repr(PdfName(b\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert pdf_repr(PdfName(\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert pdf_repr(PdfDict({b\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(PdfDict({\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(123) == b\"123\"\n    assert pdf_repr(True) == b\"true\"\n    assert pdf_repr(False) == b\"false\"\n    assert pdf_repr(None) == b\"null\"\n    assert pdf_repr(b\"a)/b\\\\(c\") == br\"(a\\)/b\\\\\\(c)\"\n    assert pdf_repr([123, True, {\"a\": PdfName(b\"b\")}]) == b\"[ 123 true <<\\n/a /b\\n>> ]\"\n    assert pdf_repr(PdfBinary(b\"\\x90\\x1F\\xA0\")) == b\"<901FA0>\"\n    assert PdfParser.get_value(b\"1 2 R \", 0) == (IndirectReference(1, 2), 5)\n    assert PdfParser.get_value(b\"true[\", 0) == (True, 4)\n    assert PdfParser.get_value(b\"false%\", 0) == (False, 5)\n    assert PdfParser.get_value(b\"null<\", 0) == (None, 4)\n    assert PdfParser.get_value(b\"%cmt\\n %cmt\\n 123\\n\", 0) == (123, 15)\n    assert PdfParser.get_value(b\"<901FA3>\", 0) == (b\"\\x90\\x1F\\xA3\", 8)\n    assert PdfParser.get_value(b\"asd < 9 0 1 f A > qwe\", 3) == (b\"\\x90\\x1F\\xA0\", 17)\n    assert PdfParser.get_value(b\"(asd)\", 0) == (b\"asd\", 5)\n    assert PdfParser.get_value(b\"(asd(qwe)zxc)zzz(aaa)\", 0) == (b\"asd(qwe)zxc\", 13)\n    assert PdfParser.get_value(b\"(Two \\\\\\nwords.)\", 0) == (b\"Two words.\", 14)\n    assert PdfParser.get_value(b\"(Two\\nlines.)\", 0) == (b\"Two\\nlines.\", 12)\n    assert PdfParser.get_value(b\"(Two\\r\\nlines.)\", 0) == (b\"Two\\nlines.\", 13)\n    assert PdfParser.get_value(b\"(Two\\\\nlines.)\", 0) == (b\"Two\\nlines.\", 13)\n    assert PdfParser.get_value(b\"(One\\\\(paren).\", 0) == (b\"One(paren\", 12)\n    assert PdfParser.get_value(b\"(One\\\\)paren).\", 0) == (b\"One)paren\", 12)\n    assert PdfParser.get_value(b\"(\\\\0053)\", 0) == (b\"\\x053\", 7)\n    assert PdfParser.get_value(b\"(\\\\053)\", 0) == (b\"\\x2B\", 6)\n    assert PdfParser.get_value(b\"(\\\\53)\", 0) == (b\"\\x2B\", 5)\n    assert PdfParser.get_value(b\"(\\\\53a)\", 0) == (b\"\\x2Ba\", 6)\n    assert PdfParser.get_value(b\"(\\\\1111)\", 0) == (b\"\\x491\", 7)\n\n\nif __name__ == \"__main__\":\n    selftest()\n",
            "new_file_content": "import codecs\nimport collections\nimport io\nimport mmap\nimport re\nimport sys\n\ntry:\n    from UserDict import UserDict\nexcept ImportError:\n    UserDict = collections.UserDict\n\n\nif str == bytes:  # Python 2.x\n    make_bytes = lambda s: s  # pragma: no cover\nelse:  # Python 3.x\n    def make_bytes(s):\n        return s.encode(\"us-ascii\")\n\n\n# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656\ndef encode_text(s):\n    return codecs.BOM_UTF16_BE + s.encode(\"utf_16_be\")\n\n\nPDFDocEncoding = {\n    0x16: u\"\\u0017\",\n    0x18: u\"\\u02D8\",\n    0x19: u\"\\u02C7\",\n    0x1A: u\"\\u02C6\",\n    0x1B: u\"\\u02D9\",\n    0x1C: u\"\\u02DD\",\n    0x1D: u\"\\u02DB\",\n    0x1E: u\"\\u02DA\",\n    0x1F: u\"\\u02DC\",\n    0x80: u\"\\u2022\",\n    0x81: u\"\\u2020\",\n    0x82: u\"\\u2021\",\n    0x83: u\"\\u2026\",\n    0x84: u\"\\u2014\",\n    0x85: u\"\\u2013\",\n    0x86: u\"\\u0192\",\n    0x87: u\"\\u2044\",\n    0x88: u\"\\u2039\",\n    0x89: u\"\\u203A\",\n    0x8A: u\"\\u2212\",\n    0x8B: u\"\\u2030\",\n    0x8C: u\"\\u201E\",\n    0x8D: u\"\\u201C\",\n    0x8E: u\"\\u201D\",\n    0x8F: u\"\\u2018\",\n    0x90: u\"\\u2019\",\n    0x91: u\"\\u201A\",\n    0x92: u\"\\u2122\",\n    0x93: u\"\\uFB01\",\n    0x94: u\"\\uFB02\",\n    0x95: u\"\\u0141\",\n    0x96: u\"\\u0152\",\n    0x97: u\"\\u0160\",\n    0x98: u\"\\u0178\",\n    0x99: u\"\\u017D\",\n    0x9A: u\"\\u0131\",\n    0x9B: u\"\\u0142\",\n    0x9C: u\"\\u0153\",\n    0x9D: u\"\\u0161\",\n    0x9E: u\"\\u017E\",\n    0xA0: u\"\\u20AC\",\n    }\ndef decode_text(b):\n    if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:\n        return b[len(codecs.BOM_UTF16_BE):].decode(\"utf_16_be\")\n    elif str == bytes:  # Python 2.x\n        return u\"\".join(PDFDocEncoding.get(ord(byte), byte) for byte in b)\n    else:\n        return \"\".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)\n\n\nclass PdfFormatError(RuntimeError):\n    pass\n\n\ndef check_format_condition(condition, error_message):\n    if not condition:\n        raise PdfFormatError(error_message)\n\n\nclass IndirectReference(collections.namedtuple(\"IndirectReferenceTuple\", [\"object_id\", \"generation\"])):\n    def __str__(self):\n        return \"%s %s R\" % self\n\n    def __bytes__(self):\n        return self.__str__().encode(\"us-ascii\")\n\n    def __eq__(self, other):\n        return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation\n\n    def __ne__(self, other):\n        return not (self == other)\n\n\nclass IndirectObjectDef(IndirectReference):\n    def __str__(self):\n        return \"%s %s obj\" % self\n\n\nclass XrefTable:\n    def __init__(self):\n        self.existing_entries = {}          # object ID => (offset, generation)\n        self.new_entries = {}               # object ID => (offset, generation)\n        self.deleted_entries = {0: 65536}   # object ID => generation\n        self.reading_finished = False\n\n    def __setitem__(self, key, value):\n        if self.reading_finished:\n            self.new_entries[key] = value\n        else:\n            self.existing_entries[key] = value\n        if key in self.deleted_entries:\n            del self.deleted_entries[key]\n\n    def __getitem__(self, key):\n        try:\n            return self.new_entries[key]\n        except KeyError:\n            return self.existing_entries[key]\n\n    def __delitem__(self, key):\n        if key in self.new_entries:\n            generation = self.new_entries[key][1] + 1\n            del self.new_entries[key]\n            self.deleted_entries[key] = generation\n        elif key in self.existing_entries:\n            generation = self.existing_entries[key][1] + 1\n            self.deleted_entries[key] = generation\n        elif key in self.deleted_entries:\n            generation = self.deleted_entries[key]\n        else:\n            raise IndexError(\"object ID \" + str(key) + \" cannot be deleted because it doesn't exist\")\n\n    def __contains__(self, key):\n        return key in self.existing_entries or key in self.new_entries\n\n    def __len__(self):\n        return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys()))\n\n    def keys(self):\n        return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys())\n\n    def write(self, f):\n        keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))\n        deleted_keys = sorted(set(self.deleted_entries.keys()))\n        startxref = f.tell()\n        f.write(b\"xref\\n\")\n        while keys:\n            # find a contiguous sequence of object IDs\n            prev = None\n            for index, key in enumerate(keys):\n                if prev is None or prev+1 == key:\n                    prev = key\n                else:\n                    contiguous_keys = keys[:index]\n                    keys = keys[index:]\n                    break\n            else:\n                contiguous_keys = keys\n                keys = None\n            f.write(make_bytes(\"%d %d\\n\" % (contiguous_keys[0], len(contiguous_keys))))\n            for object_id in contiguous_keys:\n                if object_id in self.new_entries:\n                    f.write(make_bytes(\"%010d %05d n \\n\" % self.new_entries[object_id]))\n                else:\n                    this_deleted_object_id = deleted_keys.pop(0)\n                    assert object_id == this_deleted_object_id\n                    try:\n                        next_in_linked_list = deleted_keys[0]\n                    except IndexError:\n                        next_in_linked_list = 0\n                    f.write(make_bytes(\"%010d %05d f \\n\" % (next_in_linked_list, self.deleted_entries[object_id])))\n        return startxref\n\n\nclass PdfName():\n    def __init__(self, name):\n        if isinstance(name, PdfName):\n            self.name = name.name\n        elif isinstance(name, bytes):\n            self.name = name\n        else:\n            self.name = name.encode(\"utf-8\")\n\n    @classmethod\n    def from_pdf_stream(klass, data):\n        return klass(PdfParser.interpret_name(data))\n\n    allowed_chars = set(range(33,127)) - set((ord(c) for c in \"#%/()<>[]{}\"))\n    def __bytes__(self):\n        if str == bytes:  # Python 2.x\n            result = bytearray(b\"/\")\n            for b in self.name:\n                if ord(b) in self.allowed_chars:\n                    result.append(b)\n                else:\n                    result.extend(b\"#%02X\" % ord(b))\n        else:  # Python 3.x\n            result = bytearray(b\"/\")\n            for b in self.name:\n                if b in self.allowed_chars:\n                    result.append(b)\n                else:\n                    result.extend(make_bytes(\"#%02X\" % b))\n        return bytes(result)\n\n    __str__ = __bytes__\n\n\nclass PdfArray(list):\n    def __bytes__(self):\n        return b\"[ \" + b\" \".join(pdf_repr(x) for x in self) + b\" ]\"\n\n    __str__ = __bytes__\n\n\nclass PdfDict(UserDict):\n    #def __init__(self, *args, orig_ref=None, pdf=None, **kwargs):\n    def __init__(self, *args, **kwargs):\n        UserDict.__init__(self, *args, **kwargs)\n        #self.orig_ref = kwargs.pop(\"orig_ref\", None)\n        #self.pdf = kwargs.pop(\"pdf\", None)\n        #self.is_changed = False\n\n    def __setitem__(self, key, value):\n        self.is_changed = True\n        UserDict.__setitem__(self, key, value)\n\n    def __bytes__(self):\n        #if self.orig_ref is not None:\n        #    if self.is_changed:\n        #        if self.pdf is not None:\n        #            del self.pdf.xref_table[self.orig_ref.object_id]\n        #    else:\n        #        return bytes(self.orig_ref)\n        out = bytearray(b\"<<\")\n        for key, value in self.items():\n            if value is None:\n                continue\n            value = pdf_repr(value)\n            out.extend(b\"\\n\")\n            out.extend(bytes(PdfName(key)))\n            out.extend(b\" \")\n            out.extend(value)\n            #out += b\"\\n%s %s\" % (PdfName(key), value)\n        out.extend(b\"\\n>>\")\n        return bytes(out)\n        #return out + b\"\\n>>\"\n\n    __str__ = __bytes__\n\n    #def write(self, f, orig_ref=None, pdf=None):\n    #    self.orig_ref = orig_ref\n    #    self.pdf = pdf\n    #    f.write(bytes(self))\n\n\nclass PdfBinary:\n    def __init__(self, data):\n        self.data = data\n\n    if str == bytes:  # Python 2.x\n        def __str__(self):\n            return \"<%s>\" % \"\".join(\"%02X\" % ord(b) for b in self.data)\n\n    else:  # Python 3.x\n        def __bytes__(self):\n            return make_bytes(\"<%s>\" % \"\".join(\"%02X\" % b for b in self.data))\n\n\ndef pdf_repr(x):\n    if x is True:\n        return b\"true\"\n    elif x is False:\n        return b\"false\"\n    elif x is None:\n        return b\"null\"\n    elif isinstance(x, PdfName) or isinstance(x, PdfDict) or isinstance(x, PdfArray) or isinstance(x, PdfBinary):\n        return bytes(x)\n    elif isinstance(x, int):\n        return str(x).encode(\"us-ascii\")\n    elif isinstance(x, dict):\n        return bytes(PdfDict(x))\n    elif isinstance(x, list):\n        return bytes(PdfArray(x))\n    elif isinstance(x, str) and str != bytes:\n        return pdf_repr(x.encode(\"utf-8\"))\n    elif isinstance(x, bytes):\n        return b\"(\" + x.replace(b\"\\\\\", b\"\\\\\\\\\").replace(b\"(\", b\"\\\\(\").replace(b\")\", b\"\\\\)\") + b\")\"  # XXX escape more chars? handle binary garbage\n    else:\n        return bytes(x)\n\n\nclass PdfParser:\n    \"\"\"Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf\n    Supports PDF up to 1.4\n    \"\"\"\n\n    def __init__(self, filename=None, f=None, buf=None, start_offset=0):\n        self.filename = filename\n        self.buf = buf\n        self.start_offset = start_offset\n        if buf is not None:\n            self.read_pdf_info()\n        elif f is not None:\n            self.read_pdf_info_from_file(f)\n        elif filename is not None:\n            with open(filename, \"rb\") as f:\n                self.read_pdf_info_from_file(f)\n        else:\n            self.file_size_total = self.file_size_this = 0\n            self.root = PdfDict()\n            self.root_ref = None\n            self.info = PdfDict()\n            self.info_ref = None\n            self.page_tree_root = {}\n            self.pages = []\n            self.pages_ref = None\n            self.last_xref_section_offset = None\n            self.trailer_dict = {}\n            self.xref_table = XrefTable()\n        self.xref_table.reading_finished = True\n\n    def write_header(self, f):\n        f.write(b\"%PDF-1.4\\n\")\n\n    def write_comment(self, f, s):\n        f.write((\"%% %s\\n\" % (s,)).encode(\"utf-8\"))\n\n    def write_catalog(self, f):\n        self.del_root()\n        self.root_ref = self.next_object_id(f.tell())\n        self.pages_ref = self.next_object_id(0)\n        self.write_obj(f, self.root_ref,\n            Type=PdfName(b\"Catalog\"),\n            Pages=self.pages_ref)\n        self.write_obj(f, self.pages_ref,\n            Type=PdfName(\"Pages\"),\n            Count=len(self.pages),\n            Kids=self.pages)\n        return self.root_ref\n\n    def write_xref_and_trailer(self, f, new_root_ref=None):\n        if new_root_ref:\n            self.del_root()\n            self.root_ref = new_root_ref\n        if self.info:\n            self.info_ref = self.write_obj(f, None, self.info)\n        start_xref = self.xref_table.write(f)\n        num_entries = len(self.xref_table)\n        trailer_dict = {b\"Root\": self.root_ref, b\"Size\": num_entries}\n        if self.last_xref_section_offset is not None:\n            trailer_dict[b\"Prev\"] = self.last_xref_section_offset\n        if self.info:\n            trailer_dict[b\"Info\"] = self.info_ref\n        self.last_xref_section_offset = start_xref\n        f.write(b\"trailer\\n\" + bytes(PdfDict(trailer_dict)) + make_bytes(\"\\nstartxref\\n%d\\n%%%%EOF\" % start_xref))\n\n    def write_page(self, f, ref, *objs, **dict_obj):\n        if isinstance(ref, int):\n            ref = self.pages[ref]\n        if \"Type\" not in dict_obj:\n            dict_obj[\"Type\"] = PdfName(\"Page\")\n        if \"Parent\" not in dict_obj:\n            dict_obj[\"Parent\"] = self.pages_ref\n        return self.write_obj(f, ref, *objs, **dict_obj)\n\n    def write_obj(self, f, ref, *objs, **dict_obj):\n        if ref is None:\n            ref = self.next_object_id(f.tell())\n        else:\n            self.xref_table[ref.object_id] = (f.tell(), ref.generation)\n        f.write(bytes(IndirectObjectDef(*ref)))\n        stream = dict_obj.pop(\"stream\", None)\n        if stream is not None:\n            dict_obj[\"Length\"] = len(stream)\n        if dict_obj:\n            f.write(pdf_repr(dict_obj))\n        for obj in objs:\n            f.write(pdf_repr(obj))\n        if stream is not None:\n            f.write(b\"stream\\n\")\n            f.write(stream)\n            f.write(b\"\\nendstream\\n\")\n        f.write(b\"endobj\\n\")\n        return ref\n\n    def del_root(self):\n        if self.root_ref is None:\n            return\n        del self.xref_table[self.root_ref.object_id]\n        del self.xref_table[self.root[b\"Pages\"].object_id]\n        # XXX TODO delete Pages tree recursively\n\n    def read_pdf_info_from_file(self, f):\n        if hasattr(f, \"getbuffer\"):\n            self.buf = f.getbuffer()\n            need_close = False\n        elif hasattr(f, \"getvalue\"):\n            self.buf = f.getvalue()\n            need_close = False\n        else:\n            self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\n            need_close = True\n        try:\n            self.read_pdf_info()\n        finally:\n            if need_close:\n                self.buf.close()\n            self.buf = None\n\n    def read_pdf_info(self):\n        self.file_size_total = len(self.buf)\n        self.file_size_this = self.file_size_total - self.start_offset\n        self.read_trailer()\n        self.root_ref = self.trailer_dict[b\"Root\"]\n        self.info_ref = self.trailer_dict.get(b\"Info\", None)\n        self.root = PdfDict(self.read_indirect(self.root_ref))\n        if self.info_ref is None:\n            self.info = PdfDict()\n        else:\n            self.info = PdfDict(self.read_indirect(self.info_ref))\n        #print(repr(self.root))\n        check_format_condition(b\"Type\" in self.root, \"/Type missing in Root\")\n        check_format_condition(self.root[b\"Type\"] == b\"Catalog\", \"/Type in Root is not /Catalog\")\n        check_format_condition(b\"Pages\" in self.root, \"/Pages missing in Root\")\n        check_format_condition(isinstance(self.root[b\"Pages\"], IndirectReference), \"/Pages in Root is not an indirect reference\")\n        self.pages_ref = self.root[b\"Pages\"]\n        self.page_tree_root = self.read_indirect(self.pages_ref)\n        #print(\"page_tree_root: \" + str(self.page_tree_root))\n        self.pages = self.linearize_page_tree(self.page_tree_root)\n        #print(\"pages: \" + str(self.pages))\n\n    def next_object_id(self, offset=None):\n        try:\n            # TODO: support reuse of deleted objects\n            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)\n        except ValueError:\n            reference = IndirectReference(1, 0)\n        if offset is not None:\n            self.xref_table[reference.object_id] = (offset, 0)\n        return reference\n\n    delimiter = br\"[][()<>{}/%]\"\n    delimiter_or_ws = br\"[][()<>{}/%\\000\\011\\012\\014\\015\\040]\"\n    whitespace = br\"[\\000\\011\\012\\014\\015\\040]\"\n    whitespace_or_hex = br\"[\\000\\011\\012\\014\\015\\0400-9a-fA-F]\"\n    whitespace_optional = whitespace + b\"*\"\n    whitespace_mandatory = whitespace + b\"+\"\n    newline_only = br\"[\\r\\n]+\"\n    newline = whitespace_optional + newline_only + whitespace_optional\n    re_trailer_end = re.compile(whitespace_mandatory + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional + br\"$\", re.DOTALL)\n    re_trailer_prev = re.compile(whitespace_optional + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*?\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional, re.DOTALL)\n    def read_trailer(self):\n        search_start_offset = len(self.buf) - 16384\n        if search_start_offset < self.start_offset:\n            search_start_offset = self.start_offset\n        #data_at_end = self.buf[search_start_offset:]\n        #m = self.re_trailer_end.search(data_at_end)\n        m = self.re_trailer_end.search(self.buf, search_start_offset)\n        check_format_condition(m, \"trailer end not found\")\n        # make sure we found the LAST trailer\n        last_match = m\n        while m:\n            last_match = m\n            m = self.re_trailer_end.search(self.buf, m.start()+16)\n        if not m:\n            m = last_match\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        self.last_xref_section_offset = int(m.group(2))\n        self.trailer_dict = self.interpret_trailer(trailer_data)\n        self.xref_table = XrefTable()\n        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)\n        #print(self.xref_table)\n        if b\"Prev\" in self.trailer_dict:\n            self.read_prev_trailer(self.trailer_dict[b\"Prev\"])\n\n    def read_prev_trailer(self, xref_section_offset):\n        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)\n        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])\n        check_format_condition(m, \"previous trailer not found\")\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        check_format_condition(int(m.group(2)) == xref_section_offset, \"xref section offset in previous trailer doesn't match what was expected\")\n        trailer_dict = self.interpret_trailer(trailer_data)\n        if b\"Prev\" in trailer_dict:\n            self.read_prev_trailer(trailer_dict[b\"Prev\"])\n\n    re_whitespace_optional = re.compile(whitespace_optional)\n    re_name = re.compile(whitespace_optional + br\"/([!-$&'*-.0-;=?-Z\\\\^-z|~]+)(?=\" + delimiter_or_ws + br\")\")\n    re_dict_start = re.compile(whitespace_optional + br\"\\<\\<\")\n    re_dict_end = re.compile(whitespace_optional + br\"\\>\\>\" + whitespace_optional)\n    @classmethod\n    def interpret_trailer(klass, trailer_data):\n        trailer = {}\n        offset = 0\n        while True:\n            m = klass.re_name.match(trailer_data, offset)\n            if not m:\n                m = klass.re_dict_end.match(trailer_data, offset)\n                check_format_condition(m and m.end() == len(trailer_data), \"name not found in trailer, remaining data: \" + repr(trailer_data[offset:]))\n                break\n            key = klass.interpret_name(m.group(1))\n            #print(key)\n            value, offset = klass.get_value(trailer_data, m.end())\n            #print(value)\n            trailer[key] = value\n        check_format_condition(b\"Size\" in trailer and isinstance(trailer[b\"Size\"], int), \"/Size not in trailer or not an integer\")\n        check_format_condition(b\"Root\" in trailer and isinstance(trailer[b\"Root\"], IndirectReference), \"/Root not in trailer or not an indirect reference\")\n        return trailer\n\n    re_hashes_in_name = re.compile(br\"([^#]*)(#([0-9a-fA-F]{2}))?\")\n    @classmethod\n    def interpret_name(klass, raw, as_text=False):\n        name = b\"\"\n        for m in klass.re_hashes_in_name.finditer(raw):\n            if m.group(3):\n                name += m.group(1) + bytearray.fromhex(m.group(3).decode(\"us-ascii\"))\n            else:\n                name += m.group(1)\n        if as_text:\n            return name.decode(\"utf-8\")\n        else:\n            return bytes(name)\n\n    re_null = re.compile(whitespace_optional + br\"null(?=\" + delimiter_or_ws + br\")\")\n    re_true = re.compile(whitespace_optional + br\"true(?=\" + delimiter_or_ws + br\")\")\n    re_false = re.compile(whitespace_optional + br\"false(?=\" + delimiter_or_ws + br\")\")\n    re_int = re.compile(whitespace_optional + br\"([-+]?[0-9]+)(?=\" + delimiter_or_ws + br\")\")\n    re_real = re.compile(whitespace_optional + br\"([-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+))(?=\" + delimiter_or_ws + br\")\")\n    re_array_start = re.compile(whitespace_optional + br\"\\[\")\n    re_array_end = re.compile(whitespace_optional + br\"]\")\n    re_string_hex = re.compile(whitespace_optional + br\"\\<(\" + whitespace_or_hex + br\"*)\\>\")\n    re_string_lit = re.compile(whitespace_optional + br\"\\(\")\n    re_indirect_reference = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"R(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_start = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"obj(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_end = re.compile(whitespace_optional + br\"endobj(?=\" + delimiter_or_ws + br\")\")\n    re_comment = re.compile(br\"(\" + whitespace_optional + br\"%[^\\r\\n]*\" + newline + br\")*\")\n    @classmethod\n    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):\n        #if max_nesting == 0:\n        #    return None, None\n        m = klass.re_comment.match(data, offset)\n        if m:\n            offset = m.end()\n        m = klass.re_indirect_def_start.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),\n                \"indirect object definition different than expected\")\n            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)\n            if offset is None:\n                return object, None\n            m = klass.re_indirect_def_end.match(data, offset)\n            check_format_condition(m, \"indirect object definition end not found\")\n            return object, m.end()\n        check_format_condition(not expect_indirect, \"indirect object definition not found\")\n        m = klass.re_indirect_reference.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()\n        m = klass.re_dict_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = {}\n            #print(\"<<\")\n            m = klass.re_dict_end.match(data, offset)\n            while not m:\n                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                #print (\"key \" + str(key))\n                if offset is None:\n                    return result, None\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result[key] = value\n                #print (\"value \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_dict_end.match(data, offset)\n            #print(\">>\")\n            return result, m.end()\n        m = klass.re_array_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = []\n            m = klass.re_array_end.match(data, offset)\n            while not m:\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result.append(value)\n                #print (\"item \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_array_end.match(data, offset)\n            return result, m.end()\n        m = klass.re_null.match(data, offset)\n        if m:\n            return None, m.end()\n        m = klass.re_true.match(data, offset)\n        if m:\n            return True, m.end()\n        m = klass.re_false.match(data, offset)\n        if m:\n            return False, m.end()\n        m = klass.re_name.match(data, offset)\n        if m:\n            return klass.interpret_name(m.group(1)), m.end()\n        m = klass.re_int.match(data, offset)\n        if m:\n            return int(m.group(1)), m.end()\n        m = klass.re_real.match(data, offset)\n        if m:\n            return float(m.group(1)), m.end()  # XXX Decimal instead of float???\n        m = klass.re_string_hex.match(data, offset)\n        if m:\n            hex_string = bytearray([b for b in m.group(1) if b in b\"0123456789abcdefABCDEF\"])  # filter out whitespace\n            if len(hex_string) % 2 == 1:\n                hex_string.append(ord(b\"0\"))  # append a 0 if the length is not even - yes, at the end\n            return bytearray.fromhex(hex_string.decode(\"us-ascii\")), m.end()\n        m = klass.re_string_lit.match(data, offset)\n        if m:\n            return klass.get_literal_string(data, m.end())\n        # XXX TODO: stream\n        #return None, offset  # fallback (only for debugging)\n        raise PdfFormatError(\"unrecognized object: \" + repr(data[offset:offset+32]))\n\n\n    re_lit_str_token = re.compile(br\"(\\\\[nrtbf()\\\\])|(\\\\[0-9]{1,3})|(\\\\(\\r\\n|\\r|\\n))|(\\r\\n|\\r|\\n)|(\\()|(\\))\")\n    escaped_chars = {\n        b\"n\": b\"\\n\",\n        b\"r\": b\"\\r\",\n        b\"t\": b\"\\t\",\n        b\"b\": b\"\\b\",\n        b\"f\": b\"\\f\",\n        b\"(\": b\"(\",\n        b\")\": b\")\",\n        ord(b\"n\"): b\"\\n\",\n        ord(b\"r\"): b\"\\r\",\n        ord(b\"t\"): b\"\\t\",\n        ord(b\"b\"): b\"\\b\",\n        ord(b\"f\"): b\"\\f\",\n        ord(b\"(\"): b\"(\",\n        ord(b\")\"): b\")\",\n        }\n    @classmethod\n    def get_literal_string(klass, data, offset):\n        nesting_depth = 0\n        result = bytearray()\n        for m in klass.re_lit_str_token.finditer(data, offset):\n            result.extend(data[offset:m.start()])\n            if m.group(1):\n                result.extend(klass.escaped_chars[m.group(1)[1]])\n            elif m.group(2):\n                #result.append(eval(m.group(1)))\n                result.append(int(m.group(2)[1:], 8))\n            elif m.group(3):\n                pass\n            elif m.group(5):\n                result.extend(b\"\\n\")\n            elif m.group(6):\n                result.extend(b\"(\")\n                nesting_depth += 1\n            elif m.group(7):\n                if nesting_depth == 0:\n                    return bytes(result), m.end()\n                result.extend(b\")\")\n                nesting_depth -= 1\n            offset = m.end()\n        raise PdfFormatError(\"unfinished literal string\")\n\n\n    re_xref_section_start = re.compile(whitespace_optional + br\"xref\" + newline)\n    re_xref_subsection_start = re.compile(whitespace_optional + br\"([0-9]+)\" + whitespace_mandatory + br\"([0-9]+)\" + whitespace_optional + newline_only)\n    re_xref_entry = re.compile(br\"([0-9]{10}) ([0-9]{5}) ([fn])( \\r| \\n|\\r\\n)\")\n    def read_xref_table(self, xref_section_offset):\n        subsection_found = False\n        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)\n        check_format_condition(m, \"xref section start not found\")\n        offset = m.end()\n        while True:\n            m = self.re_xref_subsection_start.match(self.buf, offset)\n            if not m:\n                check_format_condition(subsection_found, \"xref subsection start not found\")\n                break\n            subsection_found = True\n            offset = m.end()\n            first_object = int(m.group(1))\n            num_objects = int(m.group(2))\n            for i in range(first_object, first_object+num_objects):\n                m = self.re_xref_entry.match(self.buf, offset)\n                check_format_condition(m, \"xref entry not found\")\n                offset = m.end()\n                is_free = m.group(3) == b\"f\"\n                generation = int(m.group(2))\n                if not is_free:\n                    new_entry = (int(m.group(1)), generation)\n                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, \"xref entry duplicated (and not identical)\")\n                    self.xref_table[i] = new_entry\n        return offset\n\n\n    def read_indirect(self, ref, max_nesting=-1):\n        offset, generation = self.xref_table[ref[0]]\n        assert generation == ref[1]\n        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]\n\n\n    def linearize_page_tree(self, node=None):\n        if node is None:\n            node = self.page_tree_root\n        check_format_condition(node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\")\n        pages = []\n        for kid in node[b\"Kids\"]:\n            kid_object = self.read_indirect(kid, max_nesting=3)\n            if kid_object[b\"Type\"] == b\"Page\":\n                pages.append(kid)\n            else:\n                pages.extend(self.linearize_page_tree(node=kid_object))\n        return pages\n\n\ndef selftest():\n    assert encode_text(\"abc\") == b\"\\xFE\\xFF\\x00a\\x00b\\x00c\"\n    assert decode_text(b\"\\xFE\\xFF\\x00a\\x00b\\x00c\") == \"abc\"\n    assert decode_text(b\"abc\") == \"abc\"\n    assert decode_text(b\"\\x1B a \\x1C\") == u\"\\u02D9 a \\u02DD\"\n    assert PdfParser.interpret_name(b\"Name#23Hash\") == b\"Name#Hash\"\n    assert PdfParser.interpret_name(b\"Name#23Hash\", as_text=True) == \"Name#Hash\"\n    assert IndirectReference(1,2) == IndirectReference(1,2)\n    assert IndirectReference(1,2) != IndirectReference(1,3)\n    assert IndirectReference(1,2) != IndirectObjectDef(1,2)\n    assert IndirectReference(1,2) != (1,2)\n    assert IndirectObjectDef(1,2) == IndirectObjectDef(1,2)\n    assert IndirectObjectDef(1,2) != IndirectObjectDef(1,3)\n    assert IndirectObjectDef(1,2) != IndirectReference(1,2)\n    assert IndirectObjectDef(1,2) != (1,2)\n    assert bytes(IndirectReference(1,2)) == b\"1 2 R\"\n    assert bytes(IndirectObjectDef(*IndirectReference(1,2))) == b\"1 2 obj\"\n    assert bytes(PdfName(b\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert bytes(PdfName(\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert bytes(PdfDict({b\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert bytes(PdfDict({\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(IndirectReference(1,2)) == b\"1 2 R\"\n    assert pdf_repr(IndirectObjectDef(*IndirectReference(1,2))) == b\"1 2 obj\"\n    assert pdf_repr(PdfName(b\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert pdf_repr(PdfName(\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert pdf_repr(PdfDict({b\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(PdfDict({\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(123) == b\"123\"\n    assert pdf_repr(True) == b\"true\"\n    assert pdf_repr(False) == b\"false\"\n    assert pdf_repr(None) == b\"null\"\n    assert pdf_repr(b\"a)/b\\\\(c\") == br\"(a\\)/b\\\\\\(c)\"\n    assert pdf_repr([123, True, {\"a\": PdfName(b\"b\")}]) == b\"[ 123 true <<\\n/a /b\\n>> ]\"\n    assert pdf_repr(PdfBinary(b\"\\x90\\x1F\\xA0\")) == b\"<901FA0>\"\n    assert PdfParser.get_value(b\"1 2 R \", 0) == (IndirectReference(1, 2), 5)\n    assert PdfParser.get_value(b\"true[\", 0) == (True, 4)\n    assert PdfParser.get_value(b\"false%\", 0) == (False, 5)\n    assert PdfParser.get_value(b\"null<\", 0) == (None, 4)\n    assert PdfParser.get_value(b\"%cmt\\n %cmt\\n 123\\n\", 0) == (123, 15)\n    assert PdfParser.get_value(b\"<901FA3>\", 0) == (b\"\\x90\\x1F\\xA3\", 8)\n    assert PdfParser.get_value(b\"asd < 9 0 1 f A > qwe\", 3) == (b\"\\x90\\x1F\\xA0\", 17)\n    assert PdfParser.get_value(b\"(asd)\", 0) == (b\"asd\", 5)\n    assert PdfParser.get_value(b\"(asd(qwe)zxc)zzz(aaa)\", 0) == (b\"asd(qwe)zxc\", 13)\n    assert PdfParser.get_value(b\"(Two \\\\\\nwords.)\", 0) == (b\"Two words.\", 14)\n    assert PdfParser.get_value(b\"(Two\\nlines.)\", 0) == (b\"Two\\nlines.\", 12)\n    assert PdfParser.get_value(b\"(Two\\r\\nlines.)\", 0) == (b\"Two\\nlines.\", 13)\n    assert PdfParser.get_value(b\"(Two\\\\nlines.)\", 0) == (b\"Two\\nlines.\", 13)\n    assert PdfParser.get_value(b\"(One\\\\(paren).\", 0) == (b\"One(paren\", 12)\n    assert PdfParser.get_value(b\"(One\\\\)paren).\", 0) == (b\"One)paren\", 12)\n    assert PdfParser.get_value(b\"(\\\\0053)\", 0) == (b\"\\x053\", 7)\n    assert PdfParser.get_value(b\"(\\\\053)\", 0) == (b\"\\x2B\", 6)\n    assert PdfParser.get_value(b\"(\\\\53)\", 0) == (b\"\\x2B\", 5)\n    assert PdfParser.get_value(b\"(\\\\53a)\", 0) == (b\"\\x2Ba\", 6)\n    assert PdfParser.get_value(b\"(\\\\1111)\", 0) == (b\"\\x491\", 7)\n\n\nif __name__ == \"__main__\":\n    selftest()\n",
            "header": {
                "file": {
                    "path": "src/PIL/pdfParser.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "3a386f493",
                "new_commit_hash": "c5755dae4",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/pdfParser.py"
            },
            "plus_file": {
                "path": "b/src/PIL/pdfParser.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 399,
                            "length": 11
                        },
                        "new_range": {
                            "start": 399,
                            "length": 20
                        },
                        "section": "class PdfParser:"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        # XXX TODO delete Pages tree recursively",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def read_pdf_info_from_file(self, f):",
                                "type": "context"
                            },
                            {
                                "content": "        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)",
                                "type": "deleted"
                            },
                            {
                                "content": "        if hasattr(f, \"getbuffer\"):",
                                "type": "added"
                            },
                            {
                                "content": "            self.buf = f.getbuffer()",
                                "type": "added"
                            },
                            {
                                "content": "            need_close = False",
                                "type": "added"
                            },
                            {
                                "content": "        elif hasattr(f, \"getvalue\"):",
                                "type": "added"
                            },
                            {
                                "content": "            self.buf = f.getvalue()",
                                "type": "added"
                            },
                            {
                                "content": "            need_close = False",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "added"
                            },
                            {
                                "content": "            self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)",
                                "type": "added"
                            },
                            {
                                "content": "            need_close = True",
                                "type": "added"
                            },
                            {
                                "content": "        try:",
                                "type": "context"
                            },
                            {
                                "content": "            self.read_pdf_info()",
                                "type": "context"
                            },
                            {
                                "content": "        finally:",
                                "type": "context"
                            },
                            {
                                "content": "            self.buf.close()",
                                "type": "deleted"
                            },
                            {
                                "content": "            if need_close:",
                                "type": "added"
                            },
                            {
                                "content": "                self.buf.close()",
                                "type": "added"
                            },
                            {
                                "content": "            self.buf = None",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def read_pdf_info(self):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/pdfParser.py",
                            "type": "method",
                            "name": "PdfParser.read_pdf_info_from_file",
                            "content": "class PdfParser:\n    \"\"\"Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf\n    Supports PDF up to 1.4\n    \"\"\"\n\n    def __init__(self, filename=None, f=None, buf=None, start_offset=0):\n        self.filename = filename\n        self.buf = buf\n        self.start_offset = start_offset\n        if buf is not None:\n            self.read_pdf_info()\n        elif f is not None:\n            self.read_pdf_info_from_file(f)\n        elif filename is not None:\n            with open(filename, \"rb\") as f:\n                self.read_pdf_info_from_file(f)\n        else:\n            self.file_size_total = self.file_size_this = 0\n            self.root = PdfDict()\n            self.root_ref = None\n            self.info = PdfDict()\n            self.info_ref = None\n            self.page_tree_root = {}\n            self.pages = []\n            self.pages_ref = None\n            self.last_xref_section_offset = None\n            self.trailer_dict = {}\n            self.xref_table = XrefTable()\n        self.xref_table.reading_finished = True\n\n    def write_header(self, f):\n        f.write(b\"%PDF-1.4\\n\")\n\n    def write_comment(self, f, s):\n        f.write((\"%% %s\\n\" % (s,)).encode(\"utf-8\"))\n\n    def write_catalog(self, f):\n        self.del_root()\n        self.root_ref = self.next_object_id(f.tell())\n        self.pages_ref = self.next_object_id(0)\n        self.write_obj(f, self.root_ref,\n            Type=PdfName(b\"Catalog\"),\n            Pages=self.pages_ref)\n        self.write_obj(f, self.pages_ref,\n            Type=PdfName(\"Pages\"),\n            Count=len(self.pages),\n            Kids=self.pages)\n        return self.root_ref\n\n    def write_xref_and_trailer(self, f, new_root_ref=None):\n        if new_root_ref:\n            self.del_root()\n            self.root_ref = new_root_ref\n        if self.info:\n            self.info_ref = self.write_obj(f, None, self.info)\n        start_xref = self.xref_table.write(f)\n        num_entries = len(self.xref_table)\n        trailer_dict = {b\"Root\": self.root_ref, b\"Size\": num_entries}\n        if self.last_xref_section_offset is not None:\n            trailer_dict[b\"Prev\"] = self.last_xref_section_offset\n        if self.info:\n            trailer_dict[b\"Info\"] = self.info_ref\n        self.last_xref_section_offset = start_xref\n        f.write(b\"trailer\\n\" + bytes(PdfDict(trailer_dict)) + make_bytes(\"\\nstartxref\\n%d\\n%%%%EOF\" % start_xref))\n\n    def write_page(self, f, ref, *objs, **dict_obj):\n        if isinstance(ref, int):\n            ref = self.pages[ref]\n        if \"Type\" not in dict_obj:\n            dict_obj[\"Type\"] = PdfName(\"Page\")\n        if \"Parent\" not in dict_obj:\n            dict_obj[\"Parent\"] = self.pages_ref\n        return self.write_obj(f, ref, *objs, **dict_obj)\n\n    def write_obj(self, f, ref, *objs, **dict_obj):\n        if ref is None:\n            ref = self.next_object_id(f.tell())\n        else:\n            self.xref_table[ref.object_id] = (f.tell(), ref.generation)\n        f.write(bytes(IndirectObjectDef(*ref)))\n        stream = dict_obj.pop(\"stream\", None)\n        if stream is not None:\n            dict_obj[\"Length\"] = len(stream)\n        if dict_obj:\n            f.write(pdf_repr(dict_obj))\n        for obj in objs:\n            f.write(pdf_repr(obj))\n        if stream is not None:\n            f.write(b\"stream\\n\")\n            f.write(stream)\n            f.write(b\"\\nendstream\\n\")\n        f.write(b\"endobj\\n\")\n        return ref\n\n    def del_root(self):\n        if self.root_ref is None:\n            return\n        del self.xref_table[self.root_ref.object_id]\n        del self.xref_table[self.root[b\"Pages\"].object_id]\n        # XXX TODO delete Pages tree recursively\n\n    def read_pdf_info_from_file(self, f):\n        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\n        try:\n            self.read_pdf_info()\n        finally:\n            self.buf.close()\n            self.buf = None\n\n    def read_pdf_info(self):\n        self.file_size_total = len(self.buf)\n        self.file_size_this = self.file_size_total - self.start_offset\n        self.read_trailer()\n        self.root_ref = self.trailer_dict[b\"Root\"]\n        self.info_ref = self.trailer_dict.get(b\"Info\", None)\n        self.root = PdfDict(self.read_indirect(self.root_ref))\n        if self.info_ref is None:\n            self.info = PdfDict()\n        else:\n            self.info = PdfDict(self.read_indirect(self.info_ref))\n        #print(repr(self.root))\n        check_format_condition(b\"Type\" in self.root, \"/Type missing in Root\")\n        check_format_condition(self.root[b\"Type\"] == b\"Catalog\", \"/Type in Root is not /Catalog\")\n        check_format_condition(b\"Pages\" in self.root, \"/Pages missing in Root\")\n        check_format_condition(isinstance(self.root[b\"Pages\"], IndirectReference), \"/Pages in Root is not an indirect reference\")\n        self.pages_ref = self.root[b\"Pages\"]\n        self.page_tree_root = self.read_indirect(self.pages_ref)\n        #print(\"page_tree_root: \" + str(self.page_tree_root))\n        self.pages = self.linearize_page_tree(self.page_tree_root)\n        #print(\"pages: \" + str(self.pages))\n\n    def next_object_id(self, offset=None):\n        try:\n            # TODO: support reuse of deleted objects\n            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)\n        except ValueError:\n            reference = IndirectReference(1, 0)\n        if offset is not None:\n            self.xref_table[reference.object_id] = (offset, 0)\n        return reference\n\n    delimiter = br\"[][()<>{}/%]\"\n    delimiter_or_ws = br\"[][()<>{}/%\\000\\011\\012\\014\\015\\040]\"\n    whitespace = br\"[\\000\\011\\012\\014\\015\\040]\"\n    whitespace_or_hex = br\"[\\000\\011\\012\\014\\015\\0400-9a-fA-F]\"\n    whitespace_optional = whitespace + b\"*\"\n    whitespace_mandatory = whitespace + b\"+\"\n    newline_only = br\"[\\r\\n]+\"\n    newline = whitespace_optional + newline_only + whitespace_optional\n    re_trailer_end = re.compile(whitespace_mandatory + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional + br\"$\", re.DOTALL)\n    re_trailer_prev = re.compile(whitespace_optional + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*?\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional, re.DOTALL)\n    def read_trailer(self):\n        search_start_offset = len(self.buf) - 16384\n        if search_start_offset < self.start_offset:\n            search_start_offset = self.start_offset\n        #data_at_end = self.buf[search_start_offset:]\n        #m = self.re_trailer_end.search(data_at_end)\n        m = self.re_trailer_end.search(self.buf, search_start_offset)\n        check_format_condition(m, \"trailer end not found\")\n        # make sure we found the LAST trailer\n        last_match = m\n        while m:\n            last_match = m\n            m = self.re_trailer_end.search(self.buf, m.start()+16)\n        if not m:\n            m = last_match\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        self.last_xref_section_offset = int(m.group(2))\n        self.trailer_dict = self.interpret_trailer(trailer_data)\n        self.xref_table = XrefTable()\n        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)\n        #print(self.xref_table)\n        if b\"Prev\" in self.trailer_dict:\n            self.read_prev_trailer(self.trailer_dict[b\"Prev\"])\n\n    def read_prev_trailer(self, xref_section_offset):\n        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)\n        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])\n        check_format_condition(m, \"previous trailer not found\")\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        check_format_condition(int(m.group(2)) == xref_section_offset, \"xref section offset in previous trailer doesn't match what was expected\")\n        trailer_dict = self.interpret_trailer(trailer_data)\n        if b\"Prev\" in trailer_dict:\n            self.read_prev_trailer(trailer_dict[b\"Prev\"])\n\n    re_whitespace_optional = re.compile(whitespace_optional)\n    re_name = re.compile(whitespace_optional + br\"/([!-$&'*-.0-;=?-Z\\\\^-z|~]+)(?=\" + delimiter_or_ws + br\")\")\n    re_dict_start = re.compile(whitespace_optional + br\"\\<\\<\")\n    re_dict_end = re.compile(whitespace_optional + br\"\\>\\>\" + whitespace_optional)\n    @classmethod\n    def interpret_trailer(klass, trailer_data):\n        trailer = {}\n        offset = 0\n        while True:\n            m = klass.re_name.match(trailer_data, offset)\n            if not m:\n                m = klass.re_dict_end.match(trailer_data, offset)\n                check_format_condition(m and m.end() == len(trailer_data), \"name not found in trailer, remaining data: \" + repr(trailer_data[offset:]))\n                break\n            key = klass.interpret_name(m.group(1))\n            #print(key)\n            value, offset = klass.get_value(trailer_data, m.end())\n            #print(value)\n            trailer[key] = value\n        check_format_condition(b\"Size\" in trailer and isinstance(trailer[b\"Size\"], int), \"/Size not in trailer or not an integer\")\n        check_format_condition(b\"Root\" in trailer and isinstance(trailer[b\"Root\"], IndirectReference), \"/Root not in trailer or not an indirect reference\")\n        return trailer\n\n    re_hashes_in_name = re.compile(br\"([^#]*)(#([0-9a-fA-F]{2}))?\")\n    @classmethod\n    def interpret_name(klass, raw, as_text=False):\n        name = b\"\"\n        for m in klass.re_hashes_in_name.finditer(raw):\n            if m.group(3):\n                name += m.group(1) + bytearray.fromhex(m.group(3).decode(\"us-ascii\"))\n            else:\n                name += m.group(1)\n        if as_text:\n            return name.decode(\"utf-8\")\n        else:\n            return bytes(name)\n\n    re_null = re.compile(whitespace_optional + br\"null(?=\" + delimiter_or_ws + br\")\")\n    re_true = re.compile(whitespace_optional + br\"true(?=\" + delimiter_or_ws + br\")\")\n    re_false = re.compile(whitespace_optional + br\"false(?=\" + delimiter_or_ws + br\")\")\n    re_int = re.compile(whitespace_optional + br\"([-+]?[0-9]+)(?=\" + delimiter_or_ws + br\")\")\n    re_real = re.compile(whitespace_optional + br\"([-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+))(?=\" + delimiter_or_ws + br\")\")\n    re_array_start = re.compile(whitespace_optional + br\"\\[\")\n    re_array_end = re.compile(whitespace_optional + br\"]\")\n    re_string_hex = re.compile(whitespace_optional + br\"\\<(\" + whitespace_or_hex + br\"*)\\>\")\n    re_string_lit = re.compile(whitespace_optional + br\"\\(\")\n    re_indirect_reference = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"R(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_start = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"obj(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_end = re.compile(whitespace_optional + br\"endobj(?=\" + delimiter_or_ws + br\")\")\n    re_comment = re.compile(br\"(\" + whitespace_optional + br\"%[^\\r\\n]*\" + newline + br\")*\")\n    @classmethod\n    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):\n        #if max_nesting == 0:\n        #    return None, None\n        m = klass.re_comment.match(data, offset)\n        if m:\n            offset = m.end()\n        m = klass.re_indirect_def_start.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),\n                \"indirect object definition different than expected\")\n            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)\n            if offset is None:\n                return object, None\n            m = klass.re_indirect_def_end.match(data, offset)\n            check_format_condition(m, \"indirect object definition end not found\")\n            return object, m.end()\n        check_format_condition(not expect_indirect, \"indirect object definition not found\")\n        m = klass.re_indirect_reference.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()\n        m = klass.re_dict_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = {}\n            #print(\"<<\")\n            m = klass.re_dict_end.match(data, offset)\n            while not m:\n                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                #print (\"key \" + str(key))\n                if offset is None:\n                    return result, None\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result[key] = value\n                #print (\"value \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_dict_end.match(data, offset)\n            #print(\">>\")\n            return result, m.end()\n        m = klass.re_array_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = []\n            m = klass.re_array_end.match(data, offset)\n            while not m:\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result.append(value)\n                #print (\"item \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_array_end.match(data, offset)\n            return result, m.end()\n        m = klass.re_null.match(data, offset)\n        if m:\n            return None, m.end()\n        m = klass.re_true.match(data, offset)\n        if m:\n            return True, m.end()\n        m = klass.re_false.match(data, offset)\n        if m:\n            return False, m.end()\n        m = klass.re_name.match(data, offset)\n        if m:\n            return klass.interpret_name(m.group(1)), m.end()\n        m = klass.re_int.match(data, offset)\n        if m:\n            return int(m.group(1)), m.end()\n        m = klass.re_real.match(data, offset)\n        if m:\n            return float(m.group(1)), m.end()  # XXX Decimal instead of float???\n        m = klass.re_string_hex.match(data, offset)\n        if m:\n            hex_string = bytearray([b for b in m.group(1) if b in b\"0123456789abcdefABCDEF\"])  # filter out whitespace\n            if len(hex_string) % 2 == 1:\n                hex_string.append(ord(b\"0\"))  # append a 0 if the length is not even - yes, at the end\n            return bytearray.fromhex(hex_string.decode(\"us-ascii\")), m.end()\n        m = klass.re_string_lit.match(data, offset)\n        if m:\n            return klass.get_literal_string(data, m.end())\n        # XXX TODO: stream\n        #return None, offset  # fallback (only for debugging)\n        raise PdfFormatError(\"unrecognized object: \" + repr(data[offset:offset+32]))\n\n\n    re_lit_str_token = re.compile(br\"(\\\\[nrtbf()\\\\])|(\\\\[0-9]{1,3})|(\\\\(\\r\\n|\\r|\\n))|(\\r\\n|\\r|\\n)|(\\()|(\\))\")\n    escaped_chars = {\n        b\"n\": b\"\\n\",\n        b\"r\": b\"\\r\",\n        b\"t\": b\"\\t\",\n        b\"b\": b\"\\b\",\n        b\"f\": b\"\\f\",\n        b\"(\": b\"(\",\n        b\")\": b\")\",\n        ord(b\"n\"): b\"\\n\",\n        ord(b\"r\"): b\"\\r\",\n        ord(b\"t\"): b\"\\t\",\n        ord(b\"b\"): b\"\\b\",\n        ord(b\"f\"): b\"\\f\",\n        ord(b\"(\"): b\"(\",\n        ord(b\")\"): b\")\",\n        }\n    @classmethod\n    def get_literal_string(klass, data, offset):\n        nesting_depth = 0\n        result = bytearray()\n        for m in klass.re_lit_str_token.finditer(data, offset):\n            result.extend(data[offset:m.start()])\n            if m.group(1):\n                result.extend(klass.escaped_chars[m.group(1)[1]])\n            elif m.group(2):\n                #result.append(eval(m.group(1)))\n                result.append(int(m.group(2)[1:], 8))\n            elif m.group(3):\n                pass\n            elif m.group(5):\n                result.extend(b\"\\n\")\n            elif m.group(6):\n                result.extend(b\"(\")\n                nesting_depth += 1\n            elif m.group(7):\n                if nesting_depth == 0:\n                    return bytes(result), m.end()\n                result.extend(b\")\")\n                nesting_depth -= 1\n            offset = m.end()\n        raise PdfFormatError(\"unfinished literal string\")\n\n\n    re_xref_section_start = re.compile(whitespace_optional + br\"xref\" + newline)\n    re_xref_subsection_start = re.compile(whitespace_optional + br\"([0-9]+)\" + whitespace_mandatory + br\"([0-9]+)\" + whitespace_optional + newline_only)\n    re_xref_entry = re.compile(br\"([0-9]{10}) ([0-9]{5}) ([fn])( \\r| \\n|\\r\\n)\")\n    def read_xref_table(self, xref_section_offset):\n        subsection_found = False\n        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)\n        check_format_condition(m, \"xref section start not found\")\n        offset = m.end()\n        while True:\n            m = self.re_xref_subsection_start.match(self.buf, offset)\n            if not m:\n                check_format_condition(subsection_found, \"xref subsection start not found\")\n                break\n            subsection_found = True\n            offset = m.end()\n            first_object = int(m.group(1))\n            num_objects = int(m.group(2))\n            for i in range(first_object, first_object+num_objects):\n                m = self.re_xref_entry.match(self.buf, offset)\n                check_format_condition(m, \"xref entry not found\")\n                offset = m.end()\n                is_free = m.group(3) == b\"f\"\n                generation = int(m.group(2))\n                if not is_free:\n                    new_entry = (int(m.group(1)), generation)\n                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, \"xref entry duplicated (and not identical)\")\n                    self.xref_table[i] = new_entry\n        return offset\n\n\n    def read_indirect(self, ref, max_nesting=-1):\n        offset, generation = self.xref_table[ref[0]]\n        assert generation == ref[1]\n        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]\n\n\n    def linearize_page_tree(self, node=None):\n        if node is None:\n            node = self.page_tree_root\n        check_format_condition(node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\")\n        pages = []\n        for kid in node[b\"Kids\"]:\n            kid_object = self.read_indirect(kid, max_nesting=3)\n            if kid_object[b\"Type\"] == b\"Page\":\n                pages.append(kid)\n            else:\n                pages.extend(self.linearize_page_tree(node=kid_object))\n        return pages",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 401,
                            "end_lineno": 407,
                            "parent": {
                                "file_name": "src/PIL/pdfParser.py",
                                "type": "class",
                                "name": "PdfParser",
                                "content": "class PdfParser:\n    \"\"\"Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf\n    Supports PDF up to 1.4\n    \"\"\"\n\n    def __init__(self, filename=None, f=None, buf=None, start_offset=0):\n        self.filename = filename\n        self.buf = buf\n        self.start_offset = start_offset\n        if buf is not None:\n            self.read_pdf_info()\n        elif f is not None:\n            self.read_pdf_info_from_file(f)\n        elif filename is not None:\n            with open(filename, \"rb\") as f:\n                self.read_pdf_info_from_file(f)\n        else:\n            self.file_size_total = self.file_size_this = 0\n            self.root = PdfDict()\n            self.root_ref = None\n            self.info = PdfDict()\n            self.info_ref = None\n            self.page_tree_root = {}\n            self.pages = []\n            self.pages_ref = None\n            self.last_xref_section_offset = None\n            self.trailer_dict = {}\n            self.xref_table = XrefTable()\n        self.xref_table.reading_finished = True\n\n    def write_header(self, f):\n        f.write(b\"%PDF-1.4\\n\")\n\n    def write_comment(self, f, s):\n        f.write((\"%% %s\\n\" % (s,)).encode(\"utf-8\"))\n\n    def write_catalog(self, f):\n        self.del_root()\n        self.root_ref = self.next_object_id(f.tell())\n        self.pages_ref = self.next_object_id(0)\n        self.write_obj(f, self.root_ref,\n            Type=PdfName(b\"Catalog\"),\n            Pages=self.pages_ref)\n        self.write_obj(f, self.pages_ref,\n            Type=PdfName(\"Pages\"),\n            Count=len(self.pages),\n            Kids=self.pages)\n        return self.root_ref\n\n    def write_xref_and_trailer(self, f, new_root_ref=None):\n        if new_root_ref:\n            self.del_root()\n            self.root_ref = new_root_ref\n        if self.info:\n            self.info_ref = self.write_obj(f, None, self.info)\n        start_xref = self.xref_table.write(f)\n        num_entries = len(self.xref_table)\n        trailer_dict = {b\"Root\": self.root_ref, b\"Size\": num_entries}\n        if self.last_xref_section_offset is not None:\n            trailer_dict[b\"Prev\"] = self.last_xref_section_offset\n        if self.info:\n            trailer_dict[b\"Info\"] = self.info_ref\n        self.last_xref_section_offset = start_xref\n        f.write(b\"trailer\\n\" + bytes(PdfDict(trailer_dict)) + make_bytes(\"\\nstartxref\\n%d\\n%%%%EOF\" % start_xref))\n\n    def write_page(self, f, ref, *objs, **dict_obj):\n        if isinstance(ref, int):\n            ref = self.pages[ref]\n        if \"Type\" not in dict_obj:\n            dict_obj[\"Type\"] = PdfName(\"Page\")\n        if \"Parent\" not in dict_obj:\n            dict_obj[\"Parent\"] = self.pages_ref\n        return self.write_obj(f, ref, *objs, **dict_obj)\n\n    def write_obj(self, f, ref, *objs, **dict_obj):\n        if ref is None:\n            ref = self.next_object_id(f.tell())\n        else:\n            self.xref_table[ref.object_id] = (f.tell(), ref.generation)\n        f.write(bytes(IndirectObjectDef(*ref)))\n        stream = dict_obj.pop(\"stream\", None)\n        if stream is not None:\n            dict_obj[\"Length\"] = len(stream)\n        if dict_obj:\n            f.write(pdf_repr(dict_obj))\n        for obj in objs:\n            f.write(pdf_repr(obj))\n        if stream is not None:\n            f.write(b\"stream\\n\")\n            f.write(stream)\n            f.write(b\"\\nendstream\\n\")\n        f.write(b\"endobj\\n\")\n        return ref\n\n    def del_root(self):\n        if self.root_ref is None:\n            return\n        del self.xref_table[self.root_ref.object_id]\n        del self.xref_table[self.root[b\"Pages\"].object_id]\n        # XXX TODO delete Pages tree recursively\n\n    def read_pdf_info_from_file(self, f):\n        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\n        try:\n            self.read_pdf_info()\n        finally:\n            self.buf.close()\n            self.buf = None\n\n    def read_pdf_info(self):\n        self.file_size_total = len(self.buf)\n        self.file_size_this = self.file_size_total - self.start_offset\n        self.read_trailer()\n        self.root_ref = self.trailer_dict[b\"Root\"]\n        self.info_ref = self.trailer_dict.get(b\"Info\", None)\n        self.root = PdfDict(self.read_indirect(self.root_ref))\n        if self.info_ref is None:\n            self.info = PdfDict()\n        else:\n            self.info = PdfDict(self.read_indirect(self.info_ref))\n        #print(repr(self.root))\n        check_format_condition(b\"Type\" in self.root, \"/Type missing in Root\")\n        check_format_condition(self.root[b\"Type\"] == b\"Catalog\", \"/Type in Root is not /Catalog\")\n        check_format_condition(b\"Pages\" in self.root, \"/Pages missing in Root\")\n        check_format_condition(isinstance(self.root[b\"Pages\"], IndirectReference), \"/Pages in Root is not an indirect reference\")\n        self.pages_ref = self.root[b\"Pages\"]\n        self.page_tree_root = self.read_indirect(self.pages_ref)\n        #print(\"page_tree_root: \" + str(self.page_tree_root))\n        self.pages = self.linearize_page_tree(self.page_tree_root)\n        #print(\"pages: \" + str(self.pages))\n\n    def next_object_id(self, offset=None):\n        try:\n            # TODO: support reuse of deleted objects\n            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)\n        except ValueError:\n            reference = IndirectReference(1, 0)\n        if offset is not None:\n            self.xref_table[reference.object_id] = (offset, 0)\n        return reference\n\n    delimiter = br\"[][()<>{}/%]\"\n    delimiter_or_ws = br\"[][()<>{}/%\\000\\011\\012\\014\\015\\040]\"\n    whitespace = br\"[\\000\\011\\012\\014\\015\\040]\"\n    whitespace_or_hex = br\"[\\000\\011\\012\\014\\015\\0400-9a-fA-F]\"\n    whitespace_optional = whitespace + b\"*\"\n    whitespace_mandatory = whitespace + b\"+\"\n    newline_only = br\"[\\r\\n]+\"\n    newline = whitespace_optional + newline_only + whitespace_optional\n    re_trailer_end = re.compile(whitespace_mandatory + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional + br\"$\", re.DOTALL)\n    re_trailer_prev = re.compile(whitespace_optional + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*?\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional, re.DOTALL)\n    def read_trailer(self):\n        search_start_offset = len(self.buf) - 16384\n        if search_start_offset < self.start_offset:\n            search_start_offset = self.start_offset\n        #data_at_end = self.buf[search_start_offset:]\n        #m = self.re_trailer_end.search(data_at_end)\n        m = self.re_trailer_end.search(self.buf, search_start_offset)\n        check_format_condition(m, \"trailer end not found\")\n        # make sure we found the LAST trailer\n        last_match = m\n        while m:\n            last_match = m\n            m = self.re_trailer_end.search(self.buf, m.start()+16)\n        if not m:\n            m = last_match\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        self.last_xref_section_offset = int(m.group(2))\n        self.trailer_dict = self.interpret_trailer(trailer_data)\n        self.xref_table = XrefTable()\n        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)\n        #print(self.xref_table)\n        if b\"Prev\" in self.trailer_dict:\n            self.read_prev_trailer(self.trailer_dict[b\"Prev\"])\n\n    def read_prev_trailer(self, xref_section_offset):\n        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)\n        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])\n        check_format_condition(m, \"previous trailer not found\")\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        check_format_condition(int(m.group(2)) == xref_section_offset, \"xref section offset in previous trailer doesn't match what was expected\")\n        trailer_dict = self.interpret_trailer(trailer_data)\n        if b\"Prev\" in trailer_dict:\n            self.read_prev_trailer(trailer_dict[b\"Prev\"])\n\n    re_whitespace_optional = re.compile(whitespace_optional)\n    re_name = re.compile(whitespace_optional + br\"/([!-$&'*-.0-;=?-Z\\\\^-z|~]+)(?=\" + delimiter_or_ws + br\")\")\n    re_dict_start = re.compile(whitespace_optional + br\"\\<\\<\")\n    re_dict_end = re.compile(whitespace_optional + br\"\\>\\>\" + whitespace_optional)\n    @classmethod\n    def interpret_trailer(klass, trailer_data):\n        trailer = {}\n        offset = 0\n        while True:\n            m = klass.re_name.match(trailer_data, offset)\n            if not m:\n                m = klass.re_dict_end.match(trailer_data, offset)\n                check_format_condition(m and m.end() == len(trailer_data), \"name not found in trailer, remaining data: \" + repr(trailer_data[offset:]))\n                break\n            key = klass.interpret_name(m.group(1))\n            #print(key)\n            value, offset = klass.get_value(trailer_data, m.end())\n            #print(value)\n            trailer[key] = value\n        check_format_condition(b\"Size\" in trailer and isinstance(trailer[b\"Size\"], int), \"/Size not in trailer or not an integer\")\n        check_format_condition(b\"Root\" in trailer and isinstance(trailer[b\"Root\"], IndirectReference), \"/Root not in trailer or not an indirect reference\")\n        return trailer\n\n    re_hashes_in_name = re.compile(br\"([^#]*)(#([0-9a-fA-F]{2}))?\")\n    @classmethod\n    def interpret_name(klass, raw, as_text=False):\n        name = b\"\"\n        for m in klass.re_hashes_in_name.finditer(raw):\n            if m.group(3):\n                name += m.group(1) + bytearray.fromhex(m.group(3).decode(\"us-ascii\"))\n            else:\n                name += m.group(1)\n        if as_text:\n            return name.decode(\"utf-8\")\n        else:\n            return bytes(name)\n\n    re_null = re.compile(whitespace_optional + br\"null(?=\" + delimiter_or_ws + br\")\")\n    re_true = re.compile(whitespace_optional + br\"true(?=\" + delimiter_or_ws + br\")\")\n    re_false = re.compile(whitespace_optional + br\"false(?=\" + delimiter_or_ws + br\")\")\n    re_int = re.compile(whitespace_optional + br\"([-+]?[0-9]+)(?=\" + delimiter_or_ws + br\")\")\n    re_real = re.compile(whitespace_optional + br\"([-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+))(?=\" + delimiter_or_ws + br\")\")\n    re_array_start = re.compile(whitespace_optional + br\"\\[\")\n    re_array_end = re.compile(whitespace_optional + br\"]\")\n    re_string_hex = re.compile(whitespace_optional + br\"\\<(\" + whitespace_or_hex + br\"*)\\>\")\n    re_string_lit = re.compile(whitespace_optional + br\"\\(\")\n    re_indirect_reference = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"R(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_start = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"obj(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_end = re.compile(whitespace_optional + br\"endobj(?=\" + delimiter_or_ws + br\")\")\n    re_comment = re.compile(br\"(\" + whitespace_optional + br\"%[^\\r\\n]*\" + newline + br\")*\")\n    @classmethod\n    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):\n        #if max_nesting == 0:\n        #    return None, None\n        m = klass.re_comment.match(data, offset)\n        if m:\n            offset = m.end()\n        m = klass.re_indirect_def_start.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),\n                \"indirect object definition different than expected\")\n            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)\n            if offset is None:\n                return object, None\n            m = klass.re_indirect_def_end.match(data, offset)\n            check_format_condition(m, \"indirect object definition end not found\")\n            return object, m.end()\n        check_format_condition(not expect_indirect, \"indirect object definition not found\")\n        m = klass.re_indirect_reference.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()\n        m = klass.re_dict_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = {}\n            #print(\"<<\")\n            m = klass.re_dict_end.match(data, offset)\n            while not m:\n                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                #print (\"key \" + str(key))\n                if offset is None:\n                    return result, None\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result[key] = value\n                #print (\"value \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_dict_end.match(data, offset)\n            #print(\">>\")\n            return result, m.end()\n        m = klass.re_array_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = []\n            m = klass.re_array_end.match(data, offset)\n            while not m:\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result.append(value)\n                #print (\"item \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_array_end.match(data, offset)\n            return result, m.end()\n        m = klass.re_null.match(data, offset)\n        if m:\n            return None, m.end()\n        m = klass.re_true.match(data, offset)\n        if m:\n            return True, m.end()\n        m = klass.re_false.match(data, offset)\n        if m:\n            return False, m.end()\n        m = klass.re_name.match(data, offset)\n        if m:\n            return klass.interpret_name(m.group(1)), m.end()\n        m = klass.re_int.match(data, offset)\n        if m:\n            return int(m.group(1)), m.end()\n        m = klass.re_real.match(data, offset)\n        if m:\n            return float(m.group(1)), m.end()  # XXX Decimal instead of float???\n        m = klass.re_string_hex.match(data, offset)\n        if m:\n            hex_string = bytearray([b for b in m.group(1) if b in b\"0123456789abcdefABCDEF\"])  # filter out whitespace\n            if len(hex_string) % 2 == 1:\n                hex_string.append(ord(b\"0\"))  # append a 0 if the length is not even - yes, at the end\n            return bytearray.fromhex(hex_string.decode(\"us-ascii\")), m.end()\n        m = klass.re_string_lit.match(data, offset)\n        if m:\n            return klass.get_literal_string(data, m.end())\n        # XXX TODO: stream\n        #return None, offset  # fallback (only for debugging)\n        raise PdfFormatError(\"unrecognized object: \" + repr(data[offset:offset+32]))\n\n\n    re_lit_str_token = re.compile(br\"(\\\\[nrtbf()\\\\])|(\\\\[0-9]{1,3})|(\\\\(\\r\\n|\\r|\\n))|(\\r\\n|\\r|\\n)|(\\()|(\\))\")\n    escaped_chars = {\n        b\"n\": b\"\\n\",\n        b\"r\": b\"\\r\",\n        b\"t\": b\"\\t\",\n        b\"b\": b\"\\b\",\n        b\"f\": b\"\\f\",\n        b\"(\": b\"(\",\n        b\")\": b\")\",\n        ord(b\"n\"): b\"\\n\",\n        ord(b\"r\"): b\"\\r\",\n        ord(b\"t\"): b\"\\t\",\n        ord(b\"b\"): b\"\\b\",\n        ord(b\"f\"): b\"\\f\",\n        ord(b\"(\"): b\"(\",\n        ord(b\")\"): b\")\",\n        }\n    @classmethod\n    def get_literal_string(klass, data, offset):\n        nesting_depth = 0\n        result = bytearray()\n        for m in klass.re_lit_str_token.finditer(data, offset):\n            result.extend(data[offset:m.start()])\n            if m.group(1):\n                result.extend(klass.escaped_chars[m.group(1)[1]])\n            elif m.group(2):\n                #result.append(eval(m.group(1)))\n                result.append(int(m.group(2)[1:], 8))\n            elif m.group(3):\n                pass\n            elif m.group(5):\n                result.extend(b\"\\n\")\n            elif m.group(6):\n                result.extend(b\"(\")\n                nesting_depth += 1\n            elif m.group(7):\n                if nesting_depth == 0:\n                    return bytes(result), m.end()\n                result.extend(b\")\")\n                nesting_depth -= 1\n            offset = m.end()\n        raise PdfFormatError(\"unfinished literal string\")\n\n\n    re_xref_section_start = re.compile(whitespace_optional + br\"xref\" + newline)\n    re_xref_subsection_start = re.compile(whitespace_optional + br\"([0-9]+)\" + whitespace_mandatory + br\"([0-9]+)\" + whitespace_optional + newline_only)\n    re_xref_entry = re.compile(br\"([0-9]{10}) ([0-9]{5}) ([fn])( \\r| \\n|\\r\\n)\")\n    def read_xref_table(self, xref_section_offset):\n        subsection_found = False\n        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)\n        check_format_condition(m, \"xref section start not found\")\n        offset = m.end()\n        while True:\n            m = self.re_xref_subsection_start.match(self.buf, offset)\n            if not m:\n                check_format_condition(subsection_found, \"xref subsection start not found\")\n                break\n            subsection_found = True\n            offset = m.end()\n            first_object = int(m.group(1))\n            num_objects = int(m.group(2))\n            for i in range(first_object, first_object+num_objects):\n                m = self.re_xref_entry.match(self.buf, offset)\n                check_format_condition(m, \"xref entry not found\")\n                offset = m.end()\n                is_free = m.group(3) == b\"f\"\n                generation = int(m.group(2))\n                if not is_free:\n                    new_entry = (int(m.group(1)), generation)\n                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, \"xref entry duplicated (and not identical)\")\n                    self.xref_table[i] = new_entry\n        return offset\n\n\n    def read_indirect(self, ref, max_nesting=-1):\n        offset, generation = self.xref_table[ref[0]]\n        assert generation == ref[1]\n        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]\n\n\n    def linearize_page_tree(self, node=None):\n        if node is None:\n            node = self.page_tree_root\n        check_format_condition(node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\")\n        pages = []\n        for kid in node[b\"Kids\"]:\n            kid_object = self.read_indirect(kid, max_nesting=3)\n            if kid_object[b\"Type\"] == b\"Page\":\n                pages.append(kid)\n            else:\n                pages.extend(self.linearize_page_tree(node=kid_object))\n        return pages",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 300,
                                "end_lineno": 719,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "src/PIL/pdfParser.py",
                            "type": "class",
                            "name": "PdfParser",
                            "content": "class PdfParser:\n    \"\"\"Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf\n    Supports PDF up to 1.4\n    \"\"\"\n\n    def __init__(self, filename=None, f=None, buf=None, start_offset=0):\n        self.filename = filename\n        self.buf = buf\n        self.start_offset = start_offset\n        if buf is not None:\n            self.read_pdf_info()\n        elif f is not None:\n            self.read_pdf_info_from_file(f)\n        elif filename is not None:\n            with open(filename, \"rb\") as f:\n                self.read_pdf_info_from_file(f)\n        else:\n            self.file_size_total = self.file_size_this = 0\n            self.root = PdfDict()\n            self.root_ref = None\n            self.info = PdfDict()\n            self.info_ref = None\n            self.page_tree_root = {}\n            self.pages = []\n            self.pages_ref = None\n            self.last_xref_section_offset = None\n            self.trailer_dict = {}\n            self.xref_table = XrefTable()\n        self.xref_table.reading_finished = True\n\n    def write_header(self, f):\n        f.write(b\"%PDF-1.4\\n\")\n\n    def write_comment(self, f, s):\n        f.write((\"%% %s\\n\" % (s,)).encode(\"utf-8\"))\n\n    def write_catalog(self, f):\n        self.del_root()\n        self.root_ref = self.next_object_id(f.tell())\n        self.pages_ref = self.next_object_id(0)\n        self.write_obj(f, self.root_ref,\n            Type=PdfName(b\"Catalog\"),\n            Pages=self.pages_ref)\n        self.write_obj(f, self.pages_ref,\n            Type=PdfName(\"Pages\"),\n            Count=len(self.pages),\n            Kids=self.pages)\n        return self.root_ref\n\n    def write_xref_and_trailer(self, f, new_root_ref=None):\n        if new_root_ref:\n            self.del_root()\n            self.root_ref = new_root_ref\n        if self.info:\n            self.info_ref = self.write_obj(f, None, self.info)\n        start_xref = self.xref_table.write(f)\n        num_entries = len(self.xref_table)\n        trailer_dict = {b\"Root\": self.root_ref, b\"Size\": num_entries}\n        if self.last_xref_section_offset is not None:\n            trailer_dict[b\"Prev\"] = self.last_xref_section_offset\n        if self.info:\n            trailer_dict[b\"Info\"] = self.info_ref\n        self.last_xref_section_offset = start_xref\n        f.write(b\"trailer\\n\" + bytes(PdfDict(trailer_dict)) + make_bytes(\"\\nstartxref\\n%d\\n%%%%EOF\" % start_xref))\n\n    def write_page(self, f, ref, *objs, **dict_obj):\n        if isinstance(ref, int):\n            ref = self.pages[ref]\n        if \"Type\" not in dict_obj:\n            dict_obj[\"Type\"] = PdfName(\"Page\")\n        if \"Parent\" not in dict_obj:\n            dict_obj[\"Parent\"] = self.pages_ref\n        return self.write_obj(f, ref, *objs, **dict_obj)\n\n    def write_obj(self, f, ref, *objs, **dict_obj):\n        if ref is None:\n            ref = self.next_object_id(f.tell())\n        else:\n            self.xref_table[ref.object_id] = (f.tell(), ref.generation)\n        f.write(bytes(IndirectObjectDef(*ref)))\n        stream = dict_obj.pop(\"stream\", None)\n        if stream is not None:\n            dict_obj[\"Length\"] = len(stream)\n        if dict_obj:\n            f.write(pdf_repr(dict_obj))\n        for obj in objs:\n            f.write(pdf_repr(obj))\n        if stream is not None:\n            f.write(b\"stream\\n\")\n            f.write(stream)\n            f.write(b\"\\nendstream\\n\")\n        f.write(b\"endobj\\n\")\n        return ref\n\n    def del_root(self):\n        if self.root_ref is None:\n            return\n        del self.xref_table[self.root_ref.object_id]\n        del self.xref_table[self.root[b\"Pages\"].object_id]\n        # XXX TODO delete Pages tree recursively\n\n    def read_pdf_info_from_file(self, f):\n        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\n        try:\n            self.read_pdf_info()\n        finally:\n            self.buf.close()\n            self.buf = None\n\n    def read_pdf_info(self):\n        self.file_size_total = len(self.buf)\n        self.file_size_this = self.file_size_total - self.start_offset\n        self.read_trailer()\n        self.root_ref = self.trailer_dict[b\"Root\"]\n        self.info_ref = self.trailer_dict.get(b\"Info\", None)\n        self.root = PdfDict(self.read_indirect(self.root_ref))\n        if self.info_ref is None:\n            self.info = PdfDict()\n        else:\n            self.info = PdfDict(self.read_indirect(self.info_ref))\n        #print(repr(self.root))\n        check_format_condition(b\"Type\" in self.root, \"/Type missing in Root\")\n        check_format_condition(self.root[b\"Type\"] == b\"Catalog\", \"/Type in Root is not /Catalog\")\n        check_format_condition(b\"Pages\" in self.root, \"/Pages missing in Root\")\n        check_format_condition(isinstance(self.root[b\"Pages\"], IndirectReference), \"/Pages in Root is not an indirect reference\")\n        self.pages_ref = self.root[b\"Pages\"]\n        self.page_tree_root = self.read_indirect(self.pages_ref)\n        #print(\"page_tree_root: \" + str(self.page_tree_root))\n        self.pages = self.linearize_page_tree(self.page_tree_root)\n        #print(\"pages: \" + str(self.pages))\n\n    def next_object_id(self, offset=None):\n        try:\n            # TODO: support reuse of deleted objects\n            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)\n        except ValueError:\n            reference = IndirectReference(1, 0)\n        if offset is not None:\n            self.xref_table[reference.object_id] = (offset, 0)\n        return reference\n\n    delimiter = br\"[][()<>{}/%]\"\n    delimiter_or_ws = br\"[][()<>{}/%\\000\\011\\012\\014\\015\\040]\"\n    whitespace = br\"[\\000\\011\\012\\014\\015\\040]\"\n    whitespace_or_hex = br\"[\\000\\011\\012\\014\\015\\0400-9a-fA-F]\"\n    whitespace_optional = whitespace + b\"*\"\n    whitespace_mandatory = whitespace + b\"+\"\n    newline_only = br\"[\\r\\n]+\"\n    newline = whitespace_optional + newline_only + whitespace_optional\n    re_trailer_end = re.compile(whitespace_mandatory + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional + br\"$\", re.DOTALL)\n    re_trailer_prev = re.compile(whitespace_optional + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*?\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional, re.DOTALL)\n    def read_trailer(self):\n        search_start_offset = len(self.buf) - 16384\n        if search_start_offset < self.start_offset:\n            search_start_offset = self.start_offset\n        #data_at_end = self.buf[search_start_offset:]\n        #m = self.re_trailer_end.search(data_at_end)\n        m = self.re_trailer_end.search(self.buf, search_start_offset)\n        check_format_condition(m, \"trailer end not found\")\n        # make sure we found the LAST trailer\n        last_match = m\n        while m:\n            last_match = m\n            m = self.re_trailer_end.search(self.buf, m.start()+16)\n        if not m:\n            m = last_match\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        self.last_xref_section_offset = int(m.group(2))\n        self.trailer_dict = self.interpret_trailer(trailer_data)\n        self.xref_table = XrefTable()\n        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)\n        #print(self.xref_table)\n        if b\"Prev\" in self.trailer_dict:\n            self.read_prev_trailer(self.trailer_dict[b\"Prev\"])\n\n    def read_prev_trailer(self, xref_section_offset):\n        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)\n        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])\n        check_format_condition(m, \"previous trailer not found\")\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        check_format_condition(int(m.group(2)) == xref_section_offset, \"xref section offset in previous trailer doesn't match what was expected\")\n        trailer_dict = self.interpret_trailer(trailer_data)\n        if b\"Prev\" in trailer_dict:\n            self.read_prev_trailer(trailer_dict[b\"Prev\"])\n\n    re_whitespace_optional = re.compile(whitespace_optional)\n    re_name = re.compile(whitespace_optional + br\"/([!-$&'*-.0-;=?-Z\\\\^-z|~]+)(?=\" + delimiter_or_ws + br\")\")\n    re_dict_start = re.compile(whitespace_optional + br\"\\<\\<\")\n    re_dict_end = re.compile(whitespace_optional + br\"\\>\\>\" + whitespace_optional)\n    @classmethod\n    def interpret_trailer(klass, trailer_data):\n        trailer = {}\n        offset = 0\n        while True:\n            m = klass.re_name.match(trailer_data, offset)\n            if not m:\n                m = klass.re_dict_end.match(trailer_data, offset)\n                check_format_condition(m and m.end() == len(trailer_data), \"name not found in trailer, remaining data: \" + repr(trailer_data[offset:]))\n                break\n            key = klass.interpret_name(m.group(1))\n            #print(key)\n            value, offset = klass.get_value(trailer_data, m.end())\n            #print(value)\n            trailer[key] = value\n        check_format_condition(b\"Size\" in trailer and isinstance(trailer[b\"Size\"], int), \"/Size not in trailer or not an integer\")\n        check_format_condition(b\"Root\" in trailer and isinstance(trailer[b\"Root\"], IndirectReference), \"/Root not in trailer or not an indirect reference\")\n        return trailer\n\n    re_hashes_in_name = re.compile(br\"([^#]*)(#([0-9a-fA-F]{2}))?\")\n    @classmethod\n    def interpret_name(klass, raw, as_text=False):\n        name = b\"\"\n        for m in klass.re_hashes_in_name.finditer(raw):\n            if m.group(3):\n                name += m.group(1) + bytearray.fromhex(m.group(3).decode(\"us-ascii\"))\n            else:\n                name += m.group(1)\n        if as_text:\n            return name.decode(\"utf-8\")\n        else:\n            return bytes(name)\n\n    re_null = re.compile(whitespace_optional + br\"null(?=\" + delimiter_or_ws + br\")\")\n    re_true = re.compile(whitespace_optional + br\"true(?=\" + delimiter_or_ws + br\")\")\n    re_false = re.compile(whitespace_optional + br\"false(?=\" + delimiter_or_ws + br\")\")\n    re_int = re.compile(whitespace_optional + br\"([-+]?[0-9]+)(?=\" + delimiter_or_ws + br\")\")\n    re_real = re.compile(whitespace_optional + br\"([-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+))(?=\" + delimiter_or_ws + br\")\")\n    re_array_start = re.compile(whitespace_optional + br\"\\[\")\n    re_array_end = re.compile(whitespace_optional + br\"]\")\n    re_string_hex = re.compile(whitespace_optional + br\"\\<(\" + whitespace_or_hex + br\"*)\\>\")\n    re_string_lit = re.compile(whitespace_optional + br\"\\(\")\n    re_indirect_reference = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"R(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_start = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"obj(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_end = re.compile(whitespace_optional + br\"endobj(?=\" + delimiter_or_ws + br\")\")\n    re_comment = re.compile(br\"(\" + whitespace_optional + br\"%[^\\r\\n]*\" + newline + br\")*\")\n    @classmethod\n    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):\n        #if max_nesting == 0:\n        #    return None, None\n        m = klass.re_comment.match(data, offset)\n        if m:\n            offset = m.end()\n        m = klass.re_indirect_def_start.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),\n                \"indirect object definition different than expected\")\n            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)\n            if offset is None:\n                return object, None\n            m = klass.re_indirect_def_end.match(data, offset)\n            check_format_condition(m, \"indirect object definition end not found\")\n            return object, m.end()\n        check_format_condition(not expect_indirect, \"indirect object definition not found\")\n        m = klass.re_indirect_reference.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()\n        m = klass.re_dict_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = {}\n            #print(\"<<\")\n            m = klass.re_dict_end.match(data, offset)\n            while not m:\n                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                #print (\"key \" + str(key))\n                if offset is None:\n                    return result, None\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result[key] = value\n                #print (\"value \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_dict_end.match(data, offset)\n            #print(\">>\")\n            return result, m.end()\n        m = klass.re_array_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = []\n            m = klass.re_array_end.match(data, offset)\n            while not m:\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result.append(value)\n                #print (\"item \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_array_end.match(data, offset)\n            return result, m.end()\n        m = klass.re_null.match(data, offset)\n        if m:\n            return None, m.end()\n        m = klass.re_true.match(data, offset)\n        if m:\n            return True, m.end()\n        m = klass.re_false.match(data, offset)\n        if m:\n            return False, m.end()\n        m = klass.re_name.match(data, offset)\n        if m:\n            return klass.interpret_name(m.group(1)), m.end()\n        m = klass.re_int.match(data, offset)\n        if m:\n            return int(m.group(1)), m.end()\n        m = klass.re_real.match(data, offset)\n        if m:\n            return float(m.group(1)), m.end()  # XXX Decimal instead of float???\n        m = klass.re_string_hex.match(data, offset)\n        if m:\n            hex_string = bytearray([b for b in m.group(1) if b in b\"0123456789abcdefABCDEF\"])  # filter out whitespace\n            if len(hex_string) % 2 == 1:\n                hex_string.append(ord(b\"0\"))  # append a 0 if the length is not even - yes, at the end\n            return bytearray.fromhex(hex_string.decode(\"us-ascii\")), m.end()\n        m = klass.re_string_lit.match(data, offset)\n        if m:\n            return klass.get_literal_string(data, m.end())\n        # XXX TODO: stream\n        #return None, offset  # fallback (only for debugging)\n        raise PdfFormatError(\"unrecognized object: \" + repr(data[offset:offset+32]))\n\n\n    re_lit_str_token = re.compile(br\"(\\\\[nrtbf()\\\\])|(\\\\[0-9]{1,3})|(\\\\(\\r\\n|\\r|\\n))|(\\r\\n|\\r|\\n)|(\\()|(\\))\")\n    escaped_chars = {\n        b\"n\": b\"\\n\",\n        b\"r\": b\"\\r\",\n        b\"t\": b\"\\t\",\n        b\"b\": b\"\\b\",\n        b\"f\": b\"\\f\",\n        b\"(\": b\"(\",\n        b\")\": b\")\",\n        ord(b\"n\"): b\"\\n\",\n        ord(b\"r\"): b\"\\r\",\n        ord(b\"t\"): b\"\\t\",\n        ord(b\"b\"): b\"\\b\",\n        ord(b\"f\"): b\"\\f\",\n        ord(b\"(\"): b\"(\",\n        ord(b\")\"): b\")\",\n        }\n    @classmethod\n    def get_literal_string(klass, data, offset):\n        nesting_depth = 0\n        result = bytearray()\n        for m in klass.re_lit_str_token.finditer(data, offset):\n            result.extend(data[offset:m.start()])\n            if m.group(1):\n                result.extend(klass.escaped_chars[m.group(1)[1]])\n            elif m.group(2):\n                #result.append(eval(m.group(1)))\n                result.append(int(m.group(2)[1:], 8))\n            elif m.group(3):\n                pass\n            elif m.group(5):\n                result.extend(b\"\\n\")\n            elif m.group(6):\n                result.extend(b\"(\")\n                nesting_depth += 1\n            elif m.group(7):\n                if nesting_depth == 0:\n                    return bytes(result), m.end()\n                result.extend(b\")\")\n                nesting_depth -= 1\n            offset = m.end()\n        raise PdfFormatError(\"unfinished literal string\")\n\n\n    re_xref_section_start = re.compile(whitespace_optional + br\"xref\" + newline)\n    re_xref_subsection_start = re.compile(whitespace_optional + br\"([0-9]+)\" + whitespace_mandatory + br\"([0-9]+)\" + whitespace_optional + newline_only)\n    re_xref_entry = re.compile(br\"([0-9]{10}) ([0-9]{5}) ([fn])( \\r| \\n|\\r\\n)\")\n    def read_xref_table(self, xref_section_offset):\n        subsection_found = False\n        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)\n        check_format_condition(m, \"xref section start not found\")\n        offset = m.end()\n        while True:\n            m = self.re_xref_subsection_start.match(self.buf, offset)\n            if not m:\n                check_format_condition(subsection_found, \"xref subsection start not found\")\n                break\n            subsection_found = True\n            offset = m.end()\n            first_object = int(m.group(1))\n            num_objects = int(m.group(2))\n            for i in range(first_object, first_object+num_objects):\n                m = self.re_xref_entry.match(self.buf, offset)\n                check_format_condition(m, \"xref entry not found\")\n                offset = m.end()\n                is_free = m.group(3) == b\"f\"\n                generation = int(m.group(2))\n                if not is_free:\n                    new_entry = (int(m.group(1)), generation)\n                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, \"xref entry duplicated (and not identical)\")\n                    self.xref_table[i] = new_entry\n        return offset\n\n\n    def read_indirect(self, ref, max_nesting=-1):\n        offset, generation = self.xref_table[ref[0]]\n        assert generation == ref[1]\n        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]\n\n\n    def linearize_page_tree(self, node=None):\n        if node is None:\n            node = self.page_tree_root\n        check_format_condition(node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\")\n        pages = []\n        for kid in node[b\"Kids\"]:\n            kid_object = self.read_indirect(kid, max_nesting=3)\n            if kid_object[b\"Type\"] == b\"Page\":\n                pages.append(kid)\n            else:\n                pages.extend(self.linearize_page_tree(node=kid_object))\n        return pages",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 300,
                            "end_lineno": 719,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "84f87471f5e5ad972b2c304bd4505b54cbf3a5e0^",
    "new_commit_hash": "84f87471f5e5ad972b2c304bd4505b54cbf3a5e0",
    "commit_message": "issue #2959: support io.BytesIO objects",
    "commit_date": "2018-01-25T00:44:59+01:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "84f87471f5e5ad972b2c304bd4505b54cbf3a5e0",
    "test_file_codes": [
        "from helper import unittest, PillowTestCase, hopper\nfrom PIL import Image, pdfParser\nimport io\nimport os\nimport os.path\nimport tempfile\n\n\nclass TestFilePdf(PillowTestCase):\n\n    def helper_save_as_pdf(self, mode, **kwargs):\n        # Arrange\n        im = hopper(mode)\n        outfile = self.tempfile(\"temp_\" + mode + \".pdf\")\n\n        # Act\n        im.save(outfile, **kwargs)\n\n        # Assert\n        self.assertTrue(os.path.isfile(outfile))\n        self.assertGreater(os.path.getsize(outfile), 0)\n\n        return outfile\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    def test_pdf_open(self):\n        # fail on empty buffer\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray())\n        # fail on a buffer full of null bytes\n        self.assertRaises(pdfParser.PdfFormatError, pdfParser.PdfParser, buf=bytearray(65536))\n        # make an empty PDF object\n        empty_pdf = pdfParser.PdfParser()\n        self.assertEqual(len(empty_pdf.pages), 0)\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\")\n        # open the PDF file\n        hopper_pdf = pdfParser.PdfParser(filename=pdf_filename)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from a buffer with a non-zero offset\n        with open(pdf_filename, \"rb\") as f:\n            content = b\"xyzzy\" + f.read()\n        hopper_pdf = pdfParser.PdfParser(buf=content, start_offset=5)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n        # read a PDF file from an already open file\n        with open(pdf_filename, \"rb\") as f:\n            hopper_pdf = pdfParser.PdfParser(f=f)\n        self.assertEqual(len(hopper_pdf.pages), 1)\n\n    def test_pdf_append_fails_on_nonexistent_file(self):\n        im = hopper(\"RGB\")\n        temp_dir = tempfile.mkdtemp()\n        try:\n            self.assertRaises(IOError, im.save, os.path.join(temp_dir, \"nonexistent.pdf\"), append=True)\n        finally:\n            os.rmdir(temp_dir)\n\n    def test_pdf_append(self):\n        # make a PDF file\n        pdf_filename = self.helper_save_as_pdf(\"RGB\", producer=\"pdfParser\")\n        # open it, check pages and info\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 1)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n        # append some info\n        pdf.info[b\"Title\"] = pdfParser.encode_text(\"abc\")\n        pdf.info[b\"Author\"] = pdfParser.encode_text(\"def\")\n        pdf.info[b\"Subject\"] = pdfParser.encode_text(\"ghi\")\n        pdf.info[b\"Keywords\"] = pdfParser.encode_text(\"jkl\")\n        pdf.info[b\"Creator\"] = pdfParser.encode_text(\"hopper()\")\n        with open(pdf_filename, \"r+b\") as f:\n            f.seek(0, os.SEEK_END)\n            pdf.write_xref_and_trailer(f)\n        # open it again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 1)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        # append two images\n        mode_CMYK = hopper(\"CMYK\")\n        mode_P = hopper(\"P\")\n        mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])\n        # open the PDF again, check pages and info again\n        pdf = pdfParser.PdfParser(pdf_filename)\n        self.assertEqual(len(pdf.pages), 3)\n        self.assertEqual(len(pdf.info), 6)\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Title\"]), \"abc\")\n        self.assertEqual(pdfParser.decode_text(pdf.info[b\"Producer\"]), \"pdfParser\")\n\n    def test_pdf_append_to_bytesio(self):\n        im = hopper(\"RGB\")\n        f = io.BytesIO()\n        im.save(f, format=\"PDF\")\n        initial_size = len(f.getvalue())\n        self.assertGreater(initial_size, 0)\n        im = hopper(\"P\")\n        f = io.BytesIO(f.getvalue())\n        im.save(f, format=\"PDF\", append=True)\n        self.assertGreater(len(f.getvalue()), initial_size)\n\n    def test_pdf_parser(self):\n        pdfParser.selftest()\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 13ms\nInstalled 12 packages in 126ms\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 670ms\nPrepared 1 package in 16.44s\nInstalled 1 package in 1ms\n + pillow==5.1.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0)\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_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0\n............\ncollected 12 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_pdf_append\nPASSED test_1::TestFilePdf::test_pdf_append_fails_on_nonexistent_file\nPASSED test_1::TestFilePdf::test_pdf_append_to_bytesio\nPASSED test_1::TestFilePdf::test_pdf_open\nPASSED test_1::TestFilePdf::test_pdf_parser\nPASSED test_1::TestFilePdf::test_rgb\nPASSED test_1::TestFilePdf::test_save_all\nPASSED test_1::TestFilePdf::test_unsupported_mode\n=================== 12 passed in 0.78s ===================\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_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0\n........=== orphaned temp file: /tmp/temp__j6uhc7_RGB.pdf\n..=== orphaned temp file: /tmp/tempfphywlgi_RGB.pdf\n.=== orphaned temp file: /tmp/tempqwi5e7t3.pdf\n=== orphaned temp file: /tmp/tempsj8aagp2_RGB.pdf\n.=== orphaned temp file: /tmp/temp71wu19ur_LA.pdf\n\ncollected 12 items\n\n=================================== ERRORS =====================================\n__________ TestFilePdf.test_pdf_append_to_bytesio __________\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_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0/r2e_tests/test_1.py\", line 174, in test_pdf_append_to_bytesio\n    im.save(f, format=\"PDF\", append=True)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0/src/PIL/Image.py\", line 1933, in save\n    save_handler(self, fp, filename)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0/src/PIL/PdfImagePlugin.py\", line 59, in _save\n    existing_pdf = pdfParser.PdfParser(f=fp, filename=filename)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0/src/PIL/pdfParser.py\", line 312, in __init__\n    self.read_pdf_info_from_file(f)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_84f87471f5e5ad972b2c304bd4505b54cbf3a5e0/src/PIL/pdfParser.py\", line 402, in read_pdf_info_from_file\n    self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\nio.UnsupportedOperation: fileno\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_pdf_append\nPASSED test_1::TestFilePdf::test_pdf_append_fails_on_nonexistent_file\nERROR test_1::TestFilePdf::test_pdf_append_to_bytesio\nPASSED test_1::TestFilePdf::test_pdf_open\nPASSED test_1::TestFilePdf::test_pdf_parser\nPASSED test_1::TestFilePdf::test_rgb\nPASSED test_1::TestFilePdf::test_save_all\nPASSED test_1::TestFilePdf::test_unsupported_mode\n=================== 1 error, 11 passed in 0.64s ===================\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_pdf.py src/PIL/pdfParser.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `io.UnsupportedOperation: fileno` when Using `io.BytesIO` with `PdfParser`

**Description:**
When attempting to append to a PDF using `io.BytesIO` objects with PIL's `PdfParser`, an `io.UnsupportedOperation: fileno` error is raised. This occurs because `PdfParser` tries to call the `fileno()` method on the `BytesIO` object, which is not supported.

**Example Code:**
```python
import io
from PIL import Image, pdfParser

# Create a new RGB image and save it as PDF to a BytesIO object
im = Image.new("RGB", (100, 100))
f = io.BytesIO()
im.save(f, format="PDF")

# Attempt to append a new P-mode image to the existing PDF
im = Image.new("P", (100, 100))
im.save(f, format="PDF", append=True)
```

**Expected Behavior:**
The PDF should be successfully appended with the new image without raising any errors when using `io.BytesIO` objects.

**Actual Behavior:**
An `io.UnsupportedOperation: fileno` error is raised because `PdfParser` attempts to use the unsupported `fileno()` method on the `BytesIO` object.

[/ISSUE]
--- END ISSUE ---

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

--- BEGIN FILE ---
```
### src/PIL/pdfParser.py
import codecs
import collections
import io
import mmap
import re
import sys

try:
    from UserDict import UserDict
except ImportError:
    UserDict = collections.UserDict


if str == bytes:  # Python 2.x
    make_bytes = lambda s: s  # pragma: no cover
else:  # Python 3.x
    def make_bytes(s):
        return s.encode("us-ascii")


# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656
def encode_text(s):
    return codecs.BOM_UTF16_BE + s.encode("utf_16_be")


PDFDocEncoding = {
    0x16: u"\u0017",
    0x18: u"\u02D8",
    0x19: u"\u02C7",
    0x1A: u"\u02C6",
    0x1B: u"\u02D9",
    0x1C: u"\u02DD",
    0x1D: u"\u02DB",
    0x1E: u"\u02DA",
    0x1F: u"\u02DC",
    0x80: u"\u2022",
    0x81: u"\u2020",
    0x82: u"\u2021",
    0x83: u"\u2026",
    0x84: u"\u2014",
    0x85: u"\u2013",
    0x86: u"\u0192",
    0x87: u"\u2044",
    0x88: u"\u2039",
    0x89: u"\u203A",
    0x8A: u"\u2212",
    0x8B: u"\u2030",
    0x8C: u"\u201E",
    0x8D: u"\u201C",
    0x8E: u"\u201D",
    0x8F: u"\u2018",
    0x90: u"\u2019",
    0x91: u"\u201A",
    0x92: u"\u2122",
    0x93: u"\uFB01",
    0x94: u"\uFB02",
    0x95: u"\u0141",
    0x96: u"\u0152",
    0x97: u"\u0160",
    0x98: u"\u0178",
    0x99: u"\u017D",
    0x9A: u"\u0131",
    0x9B: u"\u0142",
    0x9C: u"\u0153",
    0x9D: u"\u0161",
    0x9E: u"\u017E",
    0xA0: u"\u20AC",
    }
def decode_text(b):
    if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
        return b[len(codecs.BOM_UTF16_BE):].decode("utf_16_be")
    elif str == bytes:  # Python 2.x
        return u"".join(PDFDocEncoding.get(ord(byte), byte) for byte in b)
    else:
        return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)


class PdfFormatError(RuntimeError):
    pass


def check_format_condition(condition, error_message):
    if not condition:
        raise PdfFormatError(error_message)


class IndirectReference(collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])):
    def __str__(self):
        return "%s %s R" % self

    def __bytes__(self):
        return self.__str__().encode("us-ascii")

    def __eq__(self, other):
        return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation

    def __ne__(self, other):
        return not (self == other)


class IndirectObjectDef(IndirectReference):
    def __str__(self):
        return "%s %s obj" % self


class XrefTable:
    def __init__(self):
        self.existing_entries = {}          # object ID => (offset, generation)
        self.new_entries = {}               # object ID => (offset, generation)
        self.deleted_entries = {0: 65536}   # object ID => generation
        self.reading_finished = False

    def __setitem__(self, key, value):
        if self.reading_finished:
            self.new_entries[key] = value
        else:
            self.existing_entries[key] = value
        if key in self.deleted_entries:
            del self.deleted_entries[key]

    def __getitem__(self, key):
        try:
            return self.new_entries[key]
        except KeyError:
            return self.existing_entries[key]

    def __delitem__(self, key):
        if key in self.new_entries:
            generation = self.new_entries[key][1] + 1
            del self.new_entries[key]
            self.deleted_entries[key] = generation
        elif key in self.existing_entries:
            generation = self.existing_entries[key][1] + 1
            self.deleted_entries[key] = generation
        elif key in self.deleted_entries:
            generation = self.deleted_entries[key]
        else:
            raise IndexError("object ID " + str(key) + " cannot be deleted because it doesn't exist")

    def __contains__(self, key):
        return key in self.existing_entries or key in self.new_entries

    def __len__(self):
        return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys()))

    def keys(self):
        return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys())

    def write(self, f):
        keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))
        deleted_keys = sorted(set(self.deleted_entries.keys()))
        startxref = f.tell()
        f.write(b"xref\n")
        while keys:
            # find a contiguous sequence of object IDs
            prev = None
            for index, key in enumerate(keys):
                if prev is None or prev+1 == key:
                    prev = key
                else:
                    contiguous_keys = keys[:index]
                    keys = keys[index:]
                    break
            else:
                contiguous_keys = keys
                keys = None
            f.write(make_bytes("%d %d\n" % (contiguous_keys[0], len(contiguous_keys))))
            for object_id in contiguous_keys:
                if object_id in self.new_entries:
                    f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id]))
                else:
                    this_deleted_object_id = deleted_keys.pop(0)
                    assert object_id == this_deleted_object_id
                    try:
                        next_in_linked_list = deleted_keys[0]
                    except IndexError:
                        next_in_linked_list = 0
                    f.write(make_bytes("%010d %05d f \n" % (next_in_linked_list, self.deleted_entries[object_id])))
        return startxref


class PdfName():
    def __init__(self, name):
        if isinstance(name, PdfName):
            self.name = name.name
        elif isinstance(name, bytes):
            self.name = name
        else:
            self.name = name.encode("utf-8")

    @classmethod
    def from_pdf_stream(klass, data):
        return klass(PdfParser.interpret_name(data))

    allowed_chars = set(range(33,127)) - set((ord(c) for c in "#%/()<>[]{}"))
    def __bytes__(self):
        if str == bytes:  # Python 2.x
            result = bytearray(b"/")
            for b in self.name:
                if ord(b) in self.allowed_chars:
                    result.append(b)
                else:
                    result.extend(b"#%02X" % ord(b))
        else:  # Python 3.x
            result = bytearray(b"/")
            for b in self.name:
                if b in self.allowed_chars:
                    result.append(b)
                else:
                    result.extend(make_bytes("#%02X" % b))
        return bytes(result)

    __str__ = __bytes__


class PdfArray(list):
    def __bytes__(self):
        return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"

    __str__ = __bytes__


class PdfDict(UserDict):
    #def __init__(self, *args, orig_ref=None, pdf=None, **kwargs):
    def __init__(self, *args, **kwargs):
        UserDict.__init__(self, *args, **kwargs)
        #self.orig_ref = kwargs.pop("orig_ref", None)
        #self.pdf = kwargs.pop("pdf", None)
        #self.is_changed = False

    def __setitem__(self, key, value):
        self.is_changed = True
        UserDict.__setitem__(self, key, value)

    def __bytes__(self):
        #if self.orig_ref is not None:
        #    if self.is_changed:
        #        if self.pdf is not None:
        #            del self.pdf.xref_table[self.orig_ref.object_id]
        #    else:
        #        return bytes(self.orig_ref)
        out = bytearray(b"<<")
        for key, value in self.items():
            if value is None:
                continue
            value = pdf_repr(value)
            out.extend(b"\n")
            out.extend(bytes(PdfName(key)))
            out.extend(b" ")
            out.extend(value)
            #out += b"\n%s %s" % (PdfName(key), value)
        out.extend(b"\n>>")
        return bytes(out)
        #return out + b"\n>>"

    __str__ = __bytes__

    #def write(self, f, orig_ref=None, pdf=None):
    #    self.orig_ref = orig_ref
    #    self.pdf = pdf
    #    f.write(bytes(self))


class PdfBinary:
    def __init__(self, data):
        self.data = data

    if str == bytes:  # Python 2.x
        def __str__(self):
            return "<%s>" % "".join("%02X" % ord(b) for b in self.data)

    else:  # Python 3.x
        def __bytes__(self):
            return make_bytes("<%s>" % "".join("%02X" % b for b in self.data))


def pdf_repr(x):
    if x is True:
        return b"true"
    elif x is False:
        return b"false"
    elif x is None:
        return b"null"
    elif isinstance(x, PdfName) or isinstance(x, PdfDict) or isinstance(x, PdfArray) or isinstance(x, PdfBinary):
        return bytes(x)
    elif isinstance(x, int):
        return str(x).encode("us-ascii")
    elif isinstance(x, dict):
        return bytes(PdfDict(x))
    elif isinstance(x, list):
        return bytes(PdfArray(x))
    elif isinstance(x, str) and str != bytes:
        return pdf_repr(x.encode("utf-8"))
    elif isinstance(x, bytes):
        return b"(" + x.replace(b"\\", b"\\\\").replace(b"(", b"\\(").replace(b")", b"\\)") + b")"  # XXX escape more chars? handle binary garbage
    else:
        return bytes(x)


class PdfParser:
    """Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf
    Supports PDF up to 1.4
    """

    def __init__(self, filename=None, f=None, buf=None, start_offset=0):
        self.filename = filename
        self.buf = buf
        self.start_offset = start_offset
        if buf is not None:
            self.read_pdf_info()
        elif f is not None:
            self.read_pdf_info_from_file(f)
        elif filename is not None:
            with open(filename, "rb") as f:
                self.read_pdf_info_from_file(f)
        else:
            self.file_size_total = self.file_size_this = 0
            self.root = PdfDict()
            self.root_ref = None
            self.info = PdfDict()
            self.info_ref = None
            self.page_tree_root = {}
            self.pages = []
            self.pages_ref = None
            self.last_xref_section_offset = None
            self.trailer_dict = {}
            self.xref_table = XrefTable()
        self.xref_table.reading_finished = True

    def write_header(self, f):
        f.write(b"%PDF-1.4\n")

    def write_comment(self, f, s):
        f.write(("%% %s\n" % (s,)).encode("utf-8"))

    def write_catalog(self, f):
        self.del_root()
        self.root_ref = self.next_object_id(f.tell())
        self.pages_ref = self.next_object_id(0)
        self.write_obj(f, self.root_ref,
            Type=PdfName(b"Catalog"),
            Pages=self.pages_ref)
        self.write_obj(f, self.pages_ref,
            Type=PdfName("Pages"),
            Count=len(self.pages),
            Kids=self.pages)
        return self.root_ref

    def write_xref_and_trailer(self, f, new_root_ref=None):
        if new_root_ref:
            self.del_root()
            self.root_ref = new_root_ref
        if self.info:
            self.info_ref = self.write_obj(f, None, self.info)
        start_xref = self.xref_table.write(f)
        num_entries = len(self.xref_table)
        trailer_dict = {b"Root": self.root_ref, b"Size": num_entries}
        if self.last_xref_section_offset is not None:
            trailer_dict[b"Prev"] = self.last_xref_section_offset
        if self.info:
            trailer_dict[b"Info"] = self.info_ref
        self.last_xref_section_offset = start_xref
        f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref))

    def write_page(self, f, ref, *objs, **dict_obj):
        if isinstance(ref, int):
            ref = self.pages[ref]
        if "Type" not in dict_obj:
            dict_obj["Type"] = PdfName("Page")
        if "Parent" not in dict_obj:
            dict_obj["Parent"] = self.pages_ref
        return self.write_obj(f, ref, *objs, **dict_obj)

    def write_obj(self, f, ref, *objs, **dict_obj):
        if ref is None:
            ref = self.next_object_id(f.tell())
        else:
            self.xref_table[ref.object_id] = (f.tell(), ref.generation)
        f.write(bytes(IndirectObjectDef(*ref)))
        stream = dict_obj.pop("stream", None)
        if stream is not None:
            dict_obj["Length"] = len(stream)
        if dict_obj:
            f.write(pdf_repr(dict_obj))
        for obj in objs:
            f.write(pdf_repr(obj))
        if stream is not None:
            f.write(b"stream\n")
            f.write(stream)
            f.write(b"\nendstream\n")
        f.write(b"endobj\n")
        return ref

    def del_root(self):
        if self.root_ref is None:
            return
        del self.xref_table[self.root_ref.object_id]
        del self.xref_table[self.root[b"Pages"].object_id]
        # XXX TODO delete Pages tree recursively

    def read_pdf_info_from_file(self, f):
        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
        try:
            self.read_pdf_info()
        finally:
            self.buf.close()
            self.buf = None

    def read_pdf_info(self):
        self.file_size_total = len(self.buf)
        self.file_size_this = self.file_size_total - self.start_offset
        self.read_trailer()
        self.root_ref = self.trailer_dict[b"Root"]
        self.info_ref = self.trailer_dict.get(b"Info", None)
        self.root = PdfDict(self.read_indirect(self.root_ref))
        if self.info_ref is None:
            self.info = PdfDict()
        else:
            self.info = PdfDict(self.read_indirect(self.info_ref))
        #print(repr(self.root))
        check_format_condition(b"Type" in self.root, "/Type missing in Root")
        check_format_condition(self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog")
        check_format_condition(b"Pages" in self.root, "/Pages missing in Root")
        check_format_condition(isinstance(self.root[b"Pages"], IndirectReference), "/Pages in Root is not an indirect reference")
        self.pages_ref = self.root[b"Pages"]
        self.page_tree_root = self.read_indirect(self.pages_ref)
        #print("page_tree_root: " + str(self.page_tree_root))
        self.pages = self.linearize_page_tree(self.page_tree_root)
        #print("pages: " + str(self.pages))

    def next_object_id(self, offset=None):
        try:
            # TODO: support reuse of deleted objects
            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)
        except ValueError:
            reference = IndirectReference(1, 0)
        if offset is not None:
            self.xref_table[reference.object_id] = (offset, 0)
        return reference

    delimiter = br"[][()<>{}/%]"
    delimiter_or_ws = br"[][()<>{}/%\000\011\012\014\015\040]"
    whitespace = br"[\000\011\012\014\015\040]"
    whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]"
    whitespace_optional = whitespace + b"*"
    whitespace_mandatory = whitespace + b"+"
    newline_only = br"[\r\n]+"
    newline = whitespace_optional + newline_only + whitespace_optional
    re_trailer_end = re.compile(whitespace_mandatory + br"trailer" + whitespace_optional + br"\<\<(.*\>\>)" + newline \
        + br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL)
    re_trailer_prev = re.compile(whitespace_optional + br"trailer" + whitespace_optional + br"\<\<(.*?\>\>)" + newline \
        + br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional, re.DOTALL)
    def read_trailer(self):
        search_start_offset = len(self.buf) - 16384
        if search_start_offset < self.start_offset:
            search_start_offset = self.start_offset
        #data_at_end = self.buf[search_start_offset:]
        #m = self.re_trailer_end.search(data_at_end)
        m = self.re_trailer_end.search(self.buf, search_start_offset)
        check_format_condition(m, "trailer end not found")
        # make sure we found the LAST trailer
        last_match = m
        while m:
            last_match = m
            m = self.re_trailer_end.search(self.buf, m.start()+16)
        if not m:
            m = last_match
        trailer_data = m.group(1)
        #print(trailer_data)
        self.last_xref_section_offset = int(m.group(2))
        self.trailer_dict = self.interpret_trailer(trailer_data)
        self.xref_table = XrefTable()
        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)
        #print(self.xref_table)
        if b"Prev" in self.trailer_dict:
            self.read_prev_trailer(self.trailer_dict[b"Prev"])

    def read_prev_trailer(self, xref_section_offset):
        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)
        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])
        check_format_condition(m, "previous trailer not found")
        trailer_data = m.group(1)
        #print(trailer_data)
        check_format_condition(int(m.group(2)) == xref_section_offset, "xref section offset in previous trailer doesn't match what was expected")
        trailer_dict = self.interpret_trailer(trailer_data)
        if b"Prev" in trailer_dict:
            self.read_prev_trailer(trailer_dict[b"Prev"])

    re_whitespace_optional = re.compile(whitespace_optional)
    re_name = re.compile(whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + delimiter_or_ws + br")")
    re_dict_start = re.compile(whitespace_optional + br"\<\<")
    re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional)
    @classmethod
    def interpret_trailer(klass, trailer_data):
        trailer = {}
        offset = 0
        while True:
            m = klass.re_name.match(trailer_data, offset)
            if not m:
                m = klass.re_dict_end.match(trailer_data, offset)
                check_format_condition(m and m.end() == len(trailer_data), "name not found in trailer, remaining data: " + repr(trailer_data[offset:]))
                break
            key = klass.interpret_name(m.group(1))
            #print(key)
            value, offset = klass.get_value(trailer_data, m.end())
            #print(value)
            trailer[key] = value
        check_format_condition(b"Size" in trailer and isinstance(trailer[b"Size"], int), "/Size not in trailer or not an integer")
        check_format_condition(b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), "/Root not in trailer or not an indirect reference")
        return trailer

    re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?")
    @classmethod
    def interpret_name(klass, raw, as_text=False):
        name = b""
        for m in klass.re_hashes_in_name.finditer(raw):
            if m.group(3):
                name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii"))
            else:
                name += m.group(1)
        if as_text:
            return name.decode("utf-8")
        else:
            return bytes(name)

    re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")")
    re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
    re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
    re_int = re.compile(whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")")
    re_real = re.compile(whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + delimiter_or_ws + br")")
    re_array_start = re.compile(whitespace_optional + br"\[")
    re_array_end = re.compile(whitespace_optional + br"]")
    re_string_hex = re.compile(whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>")
    re_string_lit = re.compile(whitespace_optional + br"\(")
    re_indirect_reference = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + br")")
    re_indirect_def_start = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + delimiter_or_ws + br")")
    re_indirect_def_end = re.compile(whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")")
    re_comment = re.compile(br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*")
    @classmethod
    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):
        #if max_nesting == 0:
        #    return None, None
        m = klass.re_comment.match(data, offset)
        if m:
            offset = m.end()
        m = klass.re_indirect_def_start.match(data, offset)
        if m:
            assert int(m.group(1)) > 0
            assert int(m.group(2)) >= 0
            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),
                "indirect object definition different than expected")
            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)
            if offset is None:
                return object, None
            m = klass.re_indirect_def_end.match(data, offset)
            check_format_condition(m, "indirect object definition end not found")
            return object, m.end()
        check_format_condition(not expect_indirect, "indirect object definition not found")
        m = klass.re_indirect_reference.match(data, offset)
        if m:
            assert int(m.group(1)) > 0
            assert int(m.group(2)) >= 0
            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()
        m = klass.re_dict_start.match(data, offset)
        if m:
            offset = m.end()
            result = {}
            #print("<<")
            m = klass.re_dict_end.match(data, offset)
            while not m:
                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)
                #print ("key " + str(key))
                if offset is None:
                    return result, None
                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)
                result[key] = value
                #print ("value " + str(value))
                if offset is None:
                    return result, None
                m = klass.re_dict_end.match(data, offset)
            #print(">>")
            return result, m.end()
        m = klass.re_array_start.match(data, offset)
        if m:
            offset = m.end()
            result = []
            m = klass.re_array_end.match(data, offset)
            while not m:
                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)
                result.append(value)
                #print ("item " + str(value))
                if offset is None:
                    return result, None
                m = klass.re_array_end.match(data, offset)
            return result, m.end()
        m = klass.re_null.match(data, offset)
        if m:
            return None, m.end()
        m = klass.re_true.match(data, offset)
        if m:
            return True, m.end()
        m = klass.re_false.match(data, offset)
        if m:
            return False, m.end()
        m = klass.re_name.match(data, offset)
        if m:
            return klass.interpret_name(m.group(1)), m.end()
        m = klass.re_int.match(data, offset)
        if m:
            return int(m.group(1)), m.end()
        m = klass.re_real.match(data, offset)
        if m:
            return float(m.group(1)), m.end()  # XXX Decimal instead of float???
        m = klass.re_string_hex.match(data, offset)
        if m:
            hex_string = bytearray([b for b in m.group(1) if b in b"0123456789abcdefABCDEF"])  # filter out whitespace
            if len(hex_string) % 2 == 1:
                hex_string.append(ord(b"0"))  # append a 0 if the length is not even - yes, at the end
            return bytearray.fromhex(hex_string.decode("us-ascii")), m.end()
        m = klass.re_string_lit.match(data, offset)
        if m:
            return klass.get_literal_string(data, m.end())
        # XXX TODO: stream
        #return None, offset  # fallback (only for debugging)
        raise PdfFormatError("unrecognized object: " + repr(data[offset:offset+32]))


    re_lit_str_token = re.compile(br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))")
    escaped_chars = {
        b"n": b"\n",
        b"r": b"\r",
        b"t": b"\t",
        b"b": b"\b",
        b"f": b"\f",
        b"(": b"(",
        b")": b")",
        ord(b"n"): b"\n",
        ord(b"r"): b"\r",
        ord(b"t"): b"\t",
        ord(b"b"): b"\b",
        ord(b"f"): b"\f",
        ord(b"("): b"(",
        ord(b")"): b")",
        }
    @classmethod
    def get_literal_string(klass, data, offset):
        nesting_depth = 0
        result = bytearray()
        for m in klass.re_lit_str_token.finditer(data, offset):
            result.extend(data[offset:m.start()])
            if m.group(1):
                result.extend(klass.escaped_chars[m.group(1)[1]])
            elif m.group(2):
                #result.append(eval(m.group(1)))
                result.append(int(m.group(2)[1:], 8))
            elif m.group(3):
                pass
            elif m.group(5):
                result.extend(b"\n")
            elif m.group(6):
                result.extend(b"(")
                nesting_depth += 1
            elif m.group(7):
                if nesting_depth == 0:
                    return bytes(result), m.end()
                result.extend(b")")
                nesting_depth -= 1
            offset = m.end()
        raise PdfFormatError("unfinished literal string")


    re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline)
    re_xref_subsection_start = re.compile(whitespace_optional + br"([0-9]+)" + whitespace_mandatory + br"([0-9]+)" + whitespace_optional + newline_only)
    re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
    def read_xref_table(self, xref_section_offset):
        subsection_found = False
        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)
        check_format_condition(m, "xref section start not found")
        offset = m.end()
        while True:
            m = self.re_xref_subsection_start.match(self.buf, offset)
            if not m:
                check_format_condition(subsection_found, "xref subsection start not found")
                break
            subsection_found = True
            offset = m.end()
            first_object = int(m.group(1))
            num_objects = int(m.group(2))
            for i in range(first_object, first_object+num_objects):
                m = self.re_xref_entry.match(self.buf, offset)
                check_format_condition(m, "xref entry not found")
                offset = m.end()
                is_free = m.group(3) == b"f"
                generation = int(m.group(2))
                if not is_free:
                    new_entry = (int(m.group(1)), generation)
                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, "xref entry duplicated (and not identical)")
                    self.xref_table[i] = new_entry
        return offset


    def read_indirect(self, ref, max_nesting=-1):
        offset, generation = self.xref_table[ref[0]]
        assert generation == ref[1]
        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]


    def linearize_page_tree(self, node=None):
        if node is None:
            node = self.page_tree_root
        check_format_condition(node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages")
        pages = []
        for kid in node[b"Kids"]:
            kid_object = self.read_indirect(kid, max_nesting=3)
            if kid_object[b"Type"] == b"Page":
                pages.append(kid)
            else:
                pages.extend(self.linearize_page_tree(node=kid_object))
        return pages


def selftest():
    assert encode_text("abc") == b"\xFE\xFF\x00a\x00b\x00c"
    assert decode_text(b"\xFE\xFF\x00a\x00b\x00c") == "abc"
    assert decode_text(b"abc") == "abc"
    assert decode_text(b"\x1B a \x1C") == u"\u02D9 a \u02DD"
    assert PdfParser.interpret_name(b"Name#23Hash") == b"Name#Hash"
    assert PdfParser.interpret_name(b"Name#23Hash", as_text=True) == "Name#Hash"
    assert IndirectReference(1,2) == IndirectReference(1,2)
    assert IndirectReference(1,2) != IndirectReference(1,3)
    assert IndirectReference(1,2) != IndirectObjectDef(1,2)
    assert IndirectReference(1,2) != (1,2)
    assert IndirectObjectDef(1,2) == IndirectObjectDef(1,2)
    assert IndirectObjectDef(1,2) != IndirectObjectDef(1,3)
    assert IndirectObjectDef(1,2) != IndirectReference(1,2)
    assert IndirectObjectDef(1,2) != (1,2)
    assert bytes(IndirectReference(1,2)) == b"1 2 R"
    assert bytes(IndirectObjectDef(*IndirectReference(1,2))) == b"1 2 obj"
    assert bytes(PdfName(b"Name#Hash")) == b"/Name#23Hash"
    assert bytes(PdfName("Name#Hash")) == b"/Name#23Hash"
    assert bytes(PdfDict({b"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert bytes(PdfDict({"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert pdf_repr(IndirectReference(1,2)) == b"1 2 R"
    assert pdf_repr(IndirectObjectDef(*IndirectReference(1,2))) == b"1 2 obj"
    assert pdf_repr(PdfName(b"Name#Hash")) == b"/Name#23Hash"
    assert pdf_repr(PdfName("Name#Hash")) == b"/Name#23Hash"
    assert pdf_repr(PdfDict({b"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert pdf_repr(PdfDict({"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert pdf_repr(123) == b"123"
    assert pdf_repr(True) == b"true"
    assert pdf_repr(False) == b"false"
    assert pdf_repr(None) == b"null"
    assert pdf_repr(b"a)/b\\(c") == br"(a\)/b\\\(c)"
    assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
    assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
    assert PdfParser.get_value(b"1 2 R ", 0) == (IndirectReference(1, 2), 5)
    assert PdfParser.get_value(b"true[", 0) == (True, 4)
    assert PdfParser.get_value(b"false%", 0) == (False, 5)
    assert PdfParser.get_value(b"null<", 0) == (None, 4)
    assert PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0) == (123, 15)
    assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1F\xA3", 8)
    assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1F\xA0", 17)
    assert PdfParser.get_value(b"(asd)", 0) == (b"asd", 5)
    assert PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0) == (b"asd(qwe)zxc", 13)
    assert PdfParser.get_value(b"(Two \\\nwords.)", 0) == (b"Two words.", 14)
    assert PdfParser.get_value(b"(Two\nlines.)", 0) == (b"Two\nlines.", 12)
    assert PdfParser.get_value(b"(Two\r\nlines.)", 0) == (b"Two\nlines.", 13)
    assert PdfParser.get_value(b"(Two\\nlines.)", 0) == (b"Two\nlines.", 13)
    assert PdfParser.get_value(b"(One\\(paren).", 0) == (b"One(paren", 12)
    assert PdfParser.get_value(b"(One\\)paren).", 0) == (b"One)paren", 12)
    assert PdfParser.get_value(b"(\\0053)", 0) == (b"\x053", 7)
    assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2B", 6)
    assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2B", 5)
    assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2Ba", 6)
    assert PdfParser.get_value(b"(\\1111)", 0) == (b"\x491", 7)


if __name__ == "__main__":
    selftest()

```
--- 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:** `io.UnsupportedOperation: fileno` when Using `io.BytesIO` with `PdfParser`

**Description:**
When attempting to append to a PDF using `io.BytesIO` objects with PIL's `PdfParser`, an `io.UnsupportedOperation: fileno` error is raised. This occurs because `PdfParser` tries to call the `fileno()` method on the `BytesIO` object, which is not supported.

**Example Code:**
```python
import io
from PIL import Image, pdfParser

# Create a new RGB image and save it as PDF to a BytesIO object
im = Image.new("RGB", (100, 100))
f = io.BytesIO()
im.save(f, format="PDF")

# Attempt to append a new P-mode image to the existing PDF
im = Image.new("P", (100, 100))
im.save(f, format="PDF", append=True)
```

**Expected Behavior:**
The PDF should be successfully appended with the new image without raising any errors when using `io.BytesIO` objects.

**Actual Behavior:**
An `io.UnsupportedOperation: fileno` error is raised because `PdfParser` attempts to use the unsupported `fileno()` method on the `BytesIO` object.

[/ISSUE]
{
    "TestFilePdf.test_cmyk_mode": "PASSED",
    "TestFilePdf.test_greyscale": "PASSED",
    "TestFilePdf.test_monochrome": "PASSED",
    "TestFilePdf.test_p_mode": "PASSED",
    "TestFilePdf.test_pdf_append": "PASSED",
    "TestFilePdf.test_pdf_append_fails_on_nonexistent_file": "PASSED",
    "TestFilePdf.test_pdf_append_to_bytesio": "PASSED",
    "TestFilePdf.test_pdf_open": "PASSED",
    "TestFilePdf.test_pdf_parser": "PASSED",
    "TestFilePdf.test_rgb": "PASSED",
    "TestFilePdf.test_save_all": "PASSED",
    "TestFilePdf.test_unsupported_mode": "PASSED"
}
diff --git a/src/PIL/pdfParser.py b/src/PIL/pdfParser.py
index 3a386f493..c5755dae4 100644
--- a/src/PIL/pdfParser.py
+++ b/src/PIL/pdfParser.py
@@ -399,11 +399,20 @@ class PdfParser:
         # XXX TODO delete Pages tree recursively
 
     def read_pdf_info_from_file(self, f):
-        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+        if hasattr(f, "getbuffer"):
+            self.buf = f.getbuffer()
+            need_close = False
+        elif hasattr(f, "getvalue"):
+            self.buf = f.getvalue()
+            need_close = False
+        else:
+            self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+            need_close = True
         try:
             self.read_pdf_info()
         finally:
-            self.buf.close()
+            if need_close:
+                self.buf.close()
             self.buf = None
 
     def read_pdf_info(self):
 

{"src/PIL/pdfParser.py": "import codecs\nimport collections\nimport io\nimport mmap\nimport re\nimport sys\n\ntry:\n    from UserDict import UserDict\nexcept ImportError:\n    UserDict = collections.UserDict\n\n\nif str == bytes:  # Python 2.x\n    make_bytes = lambda s: s  # pragma: no cover\nelse:  # Python 3.x\n    def make_bytes(s):\n        return s.encode(\"us-ascii\")\n\n\n# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656\ndef encode_text(s):\n    return codecs.BOM_UTF16_BE + s.encode(\"utf_16_be\")\n\n\nPDFDocEncoding = {\n    0x16: u\"\\u0017\",\n    0x18: u\"\\u02D8\",\n    0x19: u\"\\u02C7\",\n    0x1A: u\"\\u02C6\",\n    0x1B: u\"\\u02D9\",\n    0x1C: u\"\\u02DD\",\n    0x1D: u\"\\u02DB\",\n    0x1E: u\"\\u02DA\",\n    0x1F: u\"\\u02DC\",\n    0x80: u\"\\u2022\",\n    0x81: u\"\\u2020\",\n    0x82: u\"\\u2021\",\n    0x83: u\"\\u2026\",\n    0x84: u\"\\u2014\",\n    0x85: u\"\\u2013\",\n    0x86: u\"\\u0192\",\n    0x87: u\"\\u2044\",\n    0x88: u\"\\u2039\",\n    0x89: u\"\\u203A\",\n    0x8A: u\"\\u2212\",\n    0x8B: u\"\\u2030\",\n    0x8C: u\"\\u201E\",\n    0x8D: u\"\\u201C\",\n    0x8E: u\"\\u201D\",\n    0x8F: u\"\\u2018\",\n    0x90: u\"\\u2019\",\n    0x91: u\"\\u201A\",\n    0x92: u\"\\u2122\",\n    0x93: u\"\\uFB01\",\n    0x94: u\"\\uFB02\",\n    0x95: u\"\\u0141\",\n    0x96: u\"\\u0152\",\n    0x97: u\"\\u0160\",\n    0x98: u\"\\u0178\",\n    0x99: u\"\\u017D\",\n    0x9A: u\"\\u0131\",\n    0x9B: u\"\\u0142\",\n    0x9C: u\"\\u0153\",\n    0x9D: u\"\\u0161\",\n    0x9E: u\"\\u017E\",\n    0xA0: u\"\\u20AC\",\n    }\ndef decode_text(b):\n    if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:\n        return b[len(codecs.BOM_UTF16_BE):].decode(\"utf_16_be\")\n    elif str == bytes:  # Python 2.x\n        return u\"\".join(PDFDocEncoding.get(ord(byte), byte) for byte in b)\n    else:\n        return \"\".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)\n\n\nclass PdfFormatError(RuntimeError):\n    pass\n\n\ndef check_format_condition(condition, error_message):\n    if not condition:\n        raise PdfFormatError(error_message)\n\n\nclass IndirectReference(collections.namedtuple(\"IndirectReferenceTuple\", [\"object_id\", \"generation\"])):\n    def __str__(self):\n        return \"%s %s R\" % self\n\n    def __bytes__(self):\n        return self.__str__().encode(\"us-ascii\")\n\n    def __eq__(self, other):\n        return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation\n\n    def __ne__(self, other):\n        return not (self == other)\n\n\nclass IndirectObjectDef(IndirectReference):\n    def __str__(self):\n        return \"%s %s obj\" % self\n\n\nclass XrefTable:\n    def __init__(self):\n        self.existing_entries = {}          # object ID => (offset, generation)\n        self.new_entries = {}               # object ID => (offset, generation)\n        self.deleted_entries = {0: 65536}   # object ID => generation\n        self.reading_finished = False\n\n    def __setitem__(self, key, value):\n        if self.reading_finished:\n            self.new_entries[key] = value\n        else:\n            self.existing_entries[key] = value\n        if key in self.deleted_entries:\n            del self.deleted_entries[key]\n\n    def __getitem__(self, key):\n        try:\n            return self.new_entries[key]\n        except KeyError:\n            return self.existing_entries[key]\n\n    def __delitem__(self, key):\n        if key in self.new_entries:\n            generation = self.new_entries[key][1] + 1\n            del self.new_entries[key]\n            self.deleted_entries[key] = generation\n        elif key in self.existing_entries:\n            generation = self.existing_entries[key][1] + 1\n            self.deleted_entries[key] = generation\n        elif key in self.deleted_entries:\n            generation = self.deleted_entries[key]\n        else:\n            raise IndexError(\"object ID \" + str(key) + \" cannot be deleted because it doesn't exist\")\n\n    def __contains__(self, key):\n        return key in self.existing_entries or key in self.new_entries\n\n    def __len__(self):\n        return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys()))\n\n    def keys(self):\n        return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys())\n\n    def write(self, f):\n        keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))\n        deleted_keys = sorted(set(self.deleted_entries.keys()))\n        startxref = f.tell()\n        f.write(b\"xref\\n\")\n        while keys:\n            # find a contiguous sequence of object IDs\n            prev = None\n            for index, key in enumerate(keys):\n                if prev is None or prev+1 == key:\n                    prev = key\n                else:\n                    contiguous_keys = keys[:index]\n                    keys = keys[index:]\n                    break\n            else:\n                contiguous_keys = keys\n                keys = None\n            f.write(make_bytes(\"%d %d\\n\" % (contiguous_keys[0], len(contiguous_keys))))\n            for object_id in contiguous_keys:\n                if object_id in self.new_entries:\n                    f.write(make_bytes(\"%010d %05d n \\n\" % self.new_entries[object_id]))\n                else:\n                    this_deleted_object_id = deleted_keys.pop(0)\n                    assert object_id == this_deleted_object_id\n                    try:\n                        next_in_linked_list = deleted_keys[0]\n                    except IndexError:\n                        next_in_linked_list = 0\n                    f.write(make_bytes(\"%010d %05d f \\n\" % (next_in_linked_list, self.deleted_entries[object_id])))\n        return startxref\n\n\nclass PdfName():\n    def __init__(self, name):\n        if isinstance(name, PdfName):\n            self.name = name.name\n        elif isinstance(name, bytes):\n            self.name = name\n        else:\n            self.name = name.encode(\"utf-8\")\n\n    @classmethod\n    def from_pdf_stream(klass, data):\n        return klass(PdfParser.interpret_name(data))\n\n    allowed_chars = set(range(33,127)) - set((ord(c) for c in \"#%/()<>[]{}\"))\n    def __bytes__(self):\n        if str == bytes:  # Python 2.x\n            result = bytearray(b\"/\")\n            for b in self.name:\n                if ord(b) in self.allowed_chars:\n                    result.append(b)\n                else:\n                    result.extend(b\"#%02X\" % ord(b))\n        else:  # Python 3.x\n            result = bytearray(b\"/\")\n            for b in self.name:\n                if b in self.allowed_chars:\n                    result.append(b)\n                else:\n                    result.extend(make_bytes(\"#%02X\" % b))\n        return bytes(result)\n\n    __str__ = __bytes__\n\n\nclass PdfArray(list):\n    def __bytes__(self):\n        return b\"[ \" + b\" \".join(pdf_repr(x) for x in self) + b\" ]\"\n\n    __str__ = __bytes__\n\n\nclass PdfDict(UserDict):\n    #def __init__(self, *args, orig_ref=None, pdf=None, **kwargs):\n    def __init__(self, *args, **kwargs):\n        UserDict.__init__(self, *args, **kwargs)\n        #self.orig_ref = kwargs.pop(\"orig_ref\", None)\n        #self.pdf = kwargs.pop(\"pdf\", None)\n        #self.is_changed = False\n\n    def __setitem__(self, key, value):\n        self.is_changed = True\n        UserDict.__setitem__(self, key, value)\n\n    def __bytes__(self):\n        #if self.orig_ref is not None:\n        #    if self.is_changed:\n        #        if self.pdf is not None:\n        #            del self.pdf.xref_table[self.orig_ref.object_id]\n        #    else:\n        #        return bytes(self.orig_ref)\n        out = bytearray(b\"<<\")\n        for key, value in self.items():\n            if value is None:\n                continue\n            value = pdf_repr(value)\n            out.extend(b\"\\n\")\n            out.extend(bytes(PdfName(key)))\n            out.extend(b\" \")\n            out.extend(value)\n            #out += b\"\\n%s %s\" % (PdfName(key), value)\n        out.extend(b\"\\n>>\")\n        return bytes(out)\n        #return out + b\"\\n>>\"\n\n    __str__ = __bytes__\n\n    #def write(self, f, orig_ref=None, pdf=None):\n    #    self.orig_ref = orig_ref\n    #    self.pdf = pdf\n    #    f.write(bytes(self))\n\n\nclass PdfBinary:\n    def __init__(self, data):\n        self.data = data\n\n    if str == bytes:  # Python 2.x\n        def __str__(self):\n            return \"<%s>\" % \"\".join(\"%02X\" % ord(b) for b in self.data)\n\n    else:  # Python 3.x\n        def __bytes__(self):\n            return make_bytes(\"<%s>\" % \"\".join(\"%02X\" % b for b in self.data))\n\n\ndef pdf_repr(x):\n    if x is True:\n        return b\"true\"\n    elif x is False:\n        return b\"false\"\n    elif x is None:\n        return b\"null\"\n    elif isinstance(x, PdfName) or isinstance(x, PdfDict) or isinstance(x, PdfArray) or isinstance(x, PdfBinary):\n        return bytes(x)\n    elif isinstance(x, int):\n        return str(x).encode(\"us-ascii\")\n    elif isinstance(x, dict):\n        return bytes(PdfDict(x))\n    elif isinstance(x, list):\n        return bytes(PdfArray(x))\n    elif isinstance(x, str) and str != bytes:\n        return pdf_repr(x.encode(\"utf-8\"))\n    elif isinstance(x, bytes):\n        return b\"(\" + x.replace(b\"\\\\\", b\"\\\\\\\\\").replace(b\"(\", b\"\\\\(\").replace(b\")\", b\"\\\\)\") + b\")\"  # XXX escape more chars? handle binary garbage\n    else:\n        return bytes(x)\n\n\nclass PdfParser:\n    \"\"\"Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf\n    Supports PDF up to 1.4\n    \"\"\"\n\n    def __init__(self, filename=None, f=None, buf=None, start_offset=0):\n        self.filename = filename\n        self.buf = buf\n        self.start_offset = start_offset\n        if buf is not None:\n            self.read_pdf_info()\n        elif f is not None:\n            self.read_pdf_info_from_file(f)\n        elif filename is not None:\n            with open(filename, \"rb\") as f:\n                self.read_pdf_info_from_file(f)\n        else:\n            self.file_size_total = self.file_size_this = 0\n            self.root = PdfDict()\n            self.root_ref = None\n            self.info = PdfDict()\n            self.info_ref = None\n            self.page_tree_root = {}\n            self.pages = []\n            self.pages_ref = None\n            self.last_xref_section_offset = None\n            self.trailer_dict = {}\n            self.xref_table = XrefTable()\n        self.xref_table.reading_finished = True\n\n    def write_header(self, f):\n        f.write(b\"%PDF-1.4\\n\")\n\n    def write_comment(self, f, s):\n        f.write((\"%% %s\\n\" % (s,)).encode(\"utf-8\"))\n\n    def write_catalog(self, f):\n        self.del_root()\n        self.root_ref = self.next_object_id(f.tell())\n        self.pages_ref = self.next_object_id(0)\n        self.write_obj(f, self.root_ref,\n            Type=PdfName(b\"Catalog\"),\n            Pages=self.pages_ref)\n        self.write_obj(f, self.pages_ref,\n            Type=PdfName(\"Pages\"),\n            Count=len(self.pages),\n            Kids=self.pages)\n        return self.root_ref\n\n    def write_xref_and_trailer(self, f, new_root_ref=None):\n        if new_root_ref:\n            self.del_root()\n            self.root_ref = new_root_ref\n        if self.info:\n            self.info_ref = self.write_obj(f, None, self.info)\n        start_xref = self.xref_table.write(f)\n        num_entries = len(self.xref_table)\n        trailer_dict = {b\"Root\": self.root_ref, b\"Size\": num_entries}\n        if self.last_xref_section_offset is not None:\n            trailer_dict[b\"Prev\"] = self.last_xref_section_offset\n        if self.info:\n            trailer_dict[b\"Info\"] = self.info_ref\n        self.last_xref_section_offset = start_xref\n        f.write(b\"trailer\\n\" + bytes(PdfDict(trailer_dict)) + make_bytes(\"\\nstartxref\\n%d\\n%%%%EOF\" % start_xref))\n\n    def write_page(self, f, ref, *objs, **dict_obj):\n        if isinstance(ref, int):\n            ref = self.pages[ref]\n        if \"Type\" not in dict_obj:\n            dict_obj[\"Type\"] = PdfName(\"Page\")\n        if \"Parent\" not in dict_obj:\n            dict_obj[\"Parent\"] = self.pages_ref\n        return self.write_obj(f, ref, *objs, **dict_obj)\n\n    def write_obj(self, f, ref, *objs, **dict_obj):\n        if ref is None:\n            ref = self.next_object_id(f.tell())\n        else:\n            self.xref_table[ref.object_id] = (f.tell(), ref.generation)\n        f.write(bytes(IndirectObjectDef(*ref)))\n        stream = dict_obj.pop(\"stream\", None)\n        if stream is not None:\n            dict_obj[\"Length\"] = len(stream)\n        if dict_obj:\n            f.write(pdf_repr(dict_obj))\n        for obj in objs:\n            f.write(pdf_repr(obj))\n        if stream is not None:\n            f.write(b\"stream\\n\")\n            f.write(stream)\n            f.write(b\"\\nendstream\\n\")\n        f.write(b\"endobj\\n\")\n        return ref\n\n    def del_root(self):\n        if self.root_ref is None:\n            return\n        del self.xref_table[self.root_ref.object_id]\n        del self.xref_table[self.root[b\"Pages\"].object_id]\n        # XXX TODO delete Pages tree recursively\n\n    def read_pdf_info_from_file(self, f):\n        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\n        try:\n            self.read_pdf_info()\n        finally:\n            self.buf.close()\n            self.buf = None\n\n    def read_pdf_info(self):\n        self.file_size_total = len(self.buf)\n        self.file_size_this = self.file_size_total - self.start_offset\n        self.read_trailer()\n        self.root_ref = self.trailer_dict[b\"Root\"]\n        self.info_ref = self.trailer_dict.get(b\"Info\", None)\n        self.root = PdfDict(self.read_indirect(self.root_ref))\n        if self.info_ref is None:\n            self.info = PdfDict()\n        else:\n            self.info = PdfDict(self.read_indirect(self.info_ref))\n        #print(repr(self.root))\n        check_format_condition(b\"Type\" in self.root, \"/Type missing in Root\")\n        check_format_condition(self.root[b\"Type\"] == b\"Catalog\", \"/Type in Root is not /Catalog\")\n        check_format_condition(b\"Pages\" in self.root, \"/Pages missing in Root\")\n        check_format_condition(isinstance(self.root[b\"Pages\"], IndirectReference), \"/Pages in Root is not an indirect reference\")\n        self.pages_ref = self.root[b\"Pages\"]\n        self.page_tree_root = self.read_indirect(self.pages_ref)\n        #print(\"page_tree_root: \" + str(self.page_tree_root))\n        self.pages = self.linearize_page_tree(self.page_tree_root)\n        #print(\"pages: \" + str(self.pages))\n\n    def next_object_id(self, offset=None):\n        try:\n            # TODO: support reuse of deleted objects\n            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)\n        except ValueError:\n            reference = IndirectReference(1, 0)\n        if offset is not None:\n            self.xref_table[reference.object_id] = (offset, 0)\n        return reference\n\n    delimiter = br\"[][()<>{}/%]\"\n    delimiter_or_ws = br\"[][()<>{}/%\\000\\011\\012\\014\\015\\040]\"\n    whitespace = br\"[\\000\\011\\012\\014\\015\\040]\"\n    whitespace_or_hex = br\"[\\000\\011\\012\\014\\015\\0400-9a-fA-F]\"\n    whitespace_optional = whitespace + b\"*\"\n    whitespace_mandatory = whitespace + b\"+\"\n    newline_only = br\"[\\r\\n]+\"\n    newline = whitespace_optional + newline_only + whitespace_optional\n    re_trailer_end = re.compile(whitespace_mandatory + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional + br\"$\", re.DOTALL)\n    re_trailer_prev = re.compile(whitespace_optional + br\"trailer\" + whitespace_optional + br\"\\<\\<(.*?\\>\\>)\" + newline \\\n        + br\"startxref\" + newline + br\"([0-9]+)\" + newline + br\"%%EOF\" + whitespace_optional, re.DOTALL)\n    def read_trailer(self):\n        search_start_offset = len(self.buf) - 16384\n        if search_start_offset < self.start_offset:\n            search_start_offset = self.start_offset\n        #data_at_end = self.buf[search_start_offset:]\n        #m = self.re_trailer_end.search(data_at_end)\n        m = self.re_trailer_end.search(self.buf, search_start_offset)\n        check_format_condition(m, \"trailer end not found\")\n        # make sure we found the LAST trailer\n        last_match = m\n        while m:\n            last_match = m\n            m = self.re_trailer_end.search(self.buf, m.start()+16)\n        if not m:\n            m = last_match\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        self.last_xref_section_offset = int(m.group(2))\n        self.trailer_dict = self.interpret_trailer(trailer_data)\n        self.xref_table = XrefTable()\n        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)\n        #print(self.xref_table)\n        if b\"Prev\" in self.trailer_dict:\n            self.read_prev_trailer(self.trailer_dict[b\"Prev\"])\n\n    def read_prev_trailer(self, xref_section_offset):\n        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)\n        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])\n        check_format_condition(m, \"previous trailer not found\")\n        trailer_data = m.group(1)\n        #print(trailer_data)\n        check_format_condition(int(m.group(2)) == xref_section_offset, \"xref section offset in previous trailer doesn't match what was expected\")\n        trailer_dict = self.interpret_trailer(trailer_data)\n        if b\"Prev\" in trailer_dict:\n            self.read_prev_trailer(trailer_dict[b\"Prev\"])\n\n    re_whitespace_optional = re.compile(whitespace_optional)\n    re_name = re.compile(whitespace_optional + br\"/([!-$&'*-.0-;=?-Z\\\\^-z|~]+)(?=\" + delimiter_or_ws + br\")\")\n    re_dict_start = re.compile(whitespace_optional + br\"\\<\\<\")\n    re_dict_end = re.compile(whitespace_optional + br\"\\>\\>\" + whitespace_optional)\n    @classmethod\n    def interpret_trailer(klass, trailer_data):\n        trailer = {}\n        offset = 0\n        while True:\n            m = klass.re_name.match(trailer_data, offset)\n            if not m:\n                m = klass.re_dict_end.match(trailer_data, offset)\n                check_format_condition(m and m.end() == len(trailer_data), \"name not found in trailer, remaining data: \" + repr(trailer_data[offset:]))\n                break\n            key = klass.interpret_name(m.group(1))\n            #print(key)\n            value, offset = klass.get_value(trailer_data, m.end())\n            #print(value)\n            trailer[key] = value\n        check_format_condition(b\"Size\" in trailer and isinstance(trailer[b\"Size\"], int), \"/Size not in trailer or not an integer\")\n        check_format_condition(b\"Root\" in trailer and isinstance(trailer[b\"Root\"], IndirectReference), \"/Root not in trailer or not an indirect reference\")\n        return trailer\n\n    re_hashes_in_name = re.compile(br\"([^#]*)(#([0-9a-fA-F]{2}))?\")\n    @classmethod\n    def interpret_name(klass, raw, as_text=False):\n        name = b\"\"\n        for m in klass.re_hashes_in_name.finditer(raw):\n            if m.group(3):\n                name += m.group(1) + bytearray.fromhex(m.group(3).decode(\"us-ascii\"))\n            else:\n                name += m.group(1)\n        if as_text:\n            return name.decode(\"utf-8\")\n        else:\n            return bytes(name)\n\n    re_null = re.compile(whitespace_optional + br\"null(?=\" + delimiter_or_ws + br\")\")\n    re_true = re.compile(whitespace_optional + br\"true(?=\" + delimiter_or_ws + br\")\")\n    re_false = re.compile(whitespace_optional + br\"false(?=\" + delimiter_or_ws + br\")\")\n    re_int = re.compile(whitespace_optional + br\"([-+]?[0-9]+)(?=\" + delimiter_or_ws + br\")\")\n    re_real = re.compile(whitespace_optional + br\"([-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+))(?=\" + delimiter_or_ws + br\")\")\n    re_array_start = re.compile(whitespace_optional + br\"\\[\")\n    re_array_end = re.compile(whitespace_optional + br\"]\")\n    re_string_hex = re.compile(whitespace_optional + br\"\\<(\" + whitespace_or_hex + br\"*)\\>\")\n    re_string_lit = re.compile(whitespace_optional + br\"\\(\")\n    re_indirect_reference = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"R(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_start = re.compile(whitespace_optional + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"([-+]?[0-9]+)\" + whitespace_mandatory + br\"obj(?=\" + delimiter_or_ws + br\")\")\n    re_indirect_def_end = re.compile(whitespace_optional + br\"endobj(?=\" + delimiter_or_ws + br\")\")\n    re_comment = re.compile(br\"(\" + whitespace_optional + br\"%[^\\r\\n]*\" + newline + br\")*\")\n    @classmethod\n    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):\n        #if max_nesting == 0:\n        #    return None, None\n        m = klass.re_comment.match(data, offset)\n        if m:\n            offset = m.end()\n        m = klass.re_indirect_def_start.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),\n                \"indirect object definition different than expected\")\n            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)\n            if offset is None:\n                return object, None\n            m = klass.re_indirect_def_end.match(data, offset)\n            check_format_condition(m, \"indirect object definition end not found\")\n            return object, m.end()\n        check_format_condition(not expect_indirect, \"indirect object definition not found\")\n        m = klass.re_indirect_reference.match(data, offset)\n        if m:\n            assert int(m.group(1)) > 0\n            assert int(m.group(2)) >= 0\n            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()\n        m = klass.re_dict_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = {}\n            #print(\"<<\")\n            m = klass.re_dict_end.match(data, offset)\n            while not m:\n                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                #print (\"key \" + str(key))\n                if offset is None:\n                    return result, None\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result[key] = value\n                #print (\"value \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_dict_end.match(data, offset)\n            #print(\">>\")\n            return result, m.end()\n        m = klass.re_array_start.match(data, offset)\n        if m:\n            offset = m.end()\n            result = []\n            m = klass.re_array_end.match(data, offset)\n            while not m:\n                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)\n                result.append(value)\n                #print (\"item \" + str(value))\n                if offset is None:\n                    return result, None\n                m = klass.re_array_end.match(data, offset)\n            return result, m.end()\n        m = klass.re_null.match(data, offset)\n        if m:\n            return None, m.end()\n        m = klass.re_true.match(data, offset)\n        if m:\n            return True, m.end()\n        m = klass.re_false.match(data, offset)\n        if m:\n            return False, m.end()\n        m = klass.re_name.match(data, offset)\n        if m:\n            return klass.interpret_name(m.group(1)), m.end()\n        m = klass.re_int.match(data, offset)\n        if m:\n            return int(m.group(1)), m.end()\n        m = klass.re_real.match(data, offset)\n        if m:\n            return float(m.group(1)), m.end()  # XXX Decimal instead of float???\n        m = klass.re_string_hex.match(data, offset)\n        if m:\n            hex_string = bytearray([b for b in m.group(1) if b in b\"0123456789abcdefABCDEF\"])  # filter out whitespace\n            if len(hex_string) % 2 == 1:\n                hex_string.append(ord(b\"0\"))  # append a 0 if the length is not even - yes, at the end\n            return bytearray.fromhex(hex_string.decode(\"us-ascii\")), m.end()\n        m = klass.re_string_lit.match(data, offset)\n        if m:\n            return klass.get_literal_string(data, m.end())\n        # XXX TODO: stream\n        #return None, offset  # fallback (only for debugging)\n        raise PdfFormatError(\"unrecognized object: \" + repr(data[offset:offset+32]))\n\n\n    re_lit_str_token = re.compile(br\"(\\\\[nrtbf()\\\\])|(\\\\[0-9]{1,3})|(\\\\(\\r\\n|\\r|\\n))|(\\r\\n|\\r|\\n)|(\\()|(\\))\")\n    escaped_chars = {\n        b\"n\": b\"\\n\",\n        b\"r\": b\"\\r\",\n        b\"t\": b\"\\t\",\n        b\"b\": b\"\\b\",\n        b\"f\": b\"\\f\",\n        b\"(\": b\"(\",\n        b\")\": b\")\",\n        ord(b\"n\"): b\"\\n\",\n        ord(b\"r\"): b\"\\r\",\n        ord(b\"t\"): b\"\\t\",\n        ord(b\"b\"): b\"\\b\",\n        ord(b\"f\"): b\"\\f\",\n        ord(b\"(\"): b\"(\",\n        ord(b\")\"): b\")\",\n        }\n    @classmethod\n    def get_literal_string(klass, data, offset):\n        nesting_depth = 0\n        result = bytearray()\n        for m in klass.re_lit_str_token.finditer(data, offset):\n            result.extend(data[offset:m.start()])\n            if m.group(1):\n                result.extend(klass.escaped_chars[m.group(1)[1]])\n            elif m.group(2):\n                #result.append(eval(m.group(1)))\n                result.append(int(m.group(2)[1:], 8))\n            elif m.group(3):\n                pass\n            elif m.group(5):\n                result.extend(b\"\\n\")\n            elif m.group(6):\n                result.extend(b\"(\")\n                nesting_depth += 1\n            elif m.group(7):\n                if nesting_depth == 0:\n                    return bytes(result), m.end()\n                result.extend(b\")\")\n                nesting_depth -= 1\n            offset = m.end()\n        raise PdfFormatError(\"unfinished literal string\")\n\n\n    re_xref_section_start = re.compile(whitespace_optional + br\"xref\" + newline)\n    re_xref_subsection_start = re.compile(whitespace_optional + br\"([0-9]+)\" + whitespace_mandatory + br\"([0-9]+)\" + whitespace_optional + newline_only)\n    re_xref_entry = re.compile(br\"([0-9]{10}) ([0-9]{5}) ([fn])( \\r| \\n|\\r\\n)\")\n    def read_xref_table(self, xref_section_offset):\n        subsection_found = False\n        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)\n        check_format_condition(m, \"xref section start not found\")\n        offset = m.end()\n        while True:\n            m = self.re_xref_subsection_start.match(self.buf, offset)\n            if not m:\n                check_format_condition(subsection_found, \"xref subsection start not found\")\n                break\n            subsection_found = True\n            offset = m.end()\n            first_object = int(m.group(1))\n            num_objects = int(m.group(2))\n            for i in range(first_object, first_object+num_objects):\n                m = self.re_xref_entry.match(self.buf, offset)\n                check_format_condition(m, \"xref entry not found\")\n                offset = m.end()\n                is_free = m.group(3) == b\"f\"\n                generation = int(m.group(2))\n                if not is_free:\n                    new_entry = (int(m.group(1)), generation)\n                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, \"xref entry duplicated (and not identical)\")\n                    self.xref_table[i] = new_entry\n        return offset\n\n\n    def read_indirect(self, ref, max_nesting=-1):\n        offset, generation = self.xref_table[ref[0]]\n        assert generation == ref[1]\n        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]\n\n\n    def linearize_page_tree(self, node=None):\n        if node is None:\n            node = self.page_tree_root\n        check_format_condition(node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\")\n        pages = []\n        for kid in node[b\"Kids\"]:\n            kid_object = self.read_indirect(kid, max_nesting=3)\n            if kid_object[b\"Type\"] == b\"Page\":\n                pages.append(kid)\n            else:\n                pages.extend(self.linearize_page_tree(node=kid_object))\n        return pages\n\n\ndef selftest():\n    assert encode_text(\"abc\") == b\"\\xFE\\xFF\\x00a\\x00b\\x00c\"\n    assert decode_text(b\"\\xFE\\xFF\\x00a\\x00b\\x00c\") == \"abc\"\n    assert decode_text(b\"abc\") == \"abc\"\n    assert decode_text(b\"\\x1B a \\x1C\") == u\"\\u02D9 a \\u02DD\"\n    assert PdfParser.interpret_name(b\"Name#23Hash\") == b\"Name#Hash\"\n    assert PdfParser.interpret_name(b\"Name#23Hash\", as_text=True) == \"Name#Hash\"\n    assert IndirectReference(1,2) == IndirectReference(1,2)\n    assert IndirectReference(1,2) != IndirectReference(1,3)\n    assert IndirectReference(1,2) != IndirectObjectDef(1,2)\n    assert IndirectReference(1,2) != (1,2)\n    assert IndirectObjectDef(1,2) == IndirectObjectDef(1,2)\n    assert IndirectObjectDef(1,2) != IndirectObjectDef(1,3)\n    assert IndirectObjectDef(1,2) != IndirectReference(1,2)\n    assert IndirectObjectDef(1,2) != (1,2)\n    assert bytes(IndirectReference(1,2)) == b\"1 2 R\"\n    assert bytes(IndirectObjectDef(*IndirectReference(1,2))) == b\"1 2 obj\"\n    assert bytes(PdfName(b\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert bytes(PdfName(\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert bytes(PdfDict({b\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert bytes(PdfDict({\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(IndirectReference(1,2)) == b\"1 2 R\"\n    assert pdf_repr(IndirectObjectDef(*IndirectReference(1,2))) == b\"1 2 obj\"\n    assert pdf_repr(PdfName(b\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert pdf_repr(PdfName(\"Name#Hash\")) == b\"/Name#23Hash\"\n    assert pdf_repr(PdfDict({b\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(PdfDict({\"Name\": IndirectReference(1,2)})) == b\"<<\\n/Name 1 2 R\\n>>\"\n    assert pdf_repr(123) == b\"123\"\n    assert pdf_repr(True) == b\"true\"\n    assert pdf_repr(False) == b\"false\"\n    assert pdf_repr(None) == b\"null\"\n    assert pdf_repr(b\"a)/b\\\\(c\") == br\"(a\\)/b\\\\\\(c)\"\n    assert pdf_repr([123, True, {\"a\": PdfName(b\"b\")}]) == b\"[ 123 true <<\\n/a /b\\n>> ]\"\n    assert pdf_repr(PdfBinary(b\"\\x90\\x1F\\xA0\")) == b\"<901FA0>\"\n    assert PdfParser.get_value(b\"1 2 R \", 0) == (IndirectReference(1, 2), 5)\n    assert PdfParser.get_value(b\"true[\", 0) == (True, 4)\n    assert PdfParser.get_value(b\"false%\", 0) == (False, 5)\n    assert PdfParser.get_value(b\"null<\", 0) == (None, 4)\n    assert PdfParser.get_value(b\"%cmt\\n %cmt\\n 123\\n\", 0) == (123, 15)\n    assert PdfParser.get_value(b\"<901FA3>\", 0) == (b\"\\x90\\x1F\\xA3\", 8)\n    assert PdfParser.get_value(b\"asd < 9 0 1 f A > qwe\", 3) == (b\"\\x90\\x1F\\xA0\", 17)\n    assert PdfParser.get_value(b\"(asd)\", 0) == (b\"asd\", 5)\n    assert PdfParser.get_value(b\"(asd(qwe)zxc)zzz(aaa)\", 0) == (b\"asd(qwe)zxc\", 13)\n    assert PdfParser.get_value(b\"(Two \\\\\\nwords.)\", 0) == (b\"Two words.\", 14)\n    assert PdfParser.get_value(b\"(Two\\nlines.)\", 0) == (b\"Two\\nlines.\", 12)\n    assert PdfParser.get_value(b\"(Two\\r\\nlines.)\", 0) == (b\"Two\\nlines.\", 13)\n    assert PdfParser.get_value(b\"(Two\\\\nlines.)\", 0) == (b\"Two\\nlines.\", 13)\n    assert PdfParser.get_value(b\"(One\\\\(paren).\", 0) == (b\"One(paren\", 12)\n    assert PdfParser.get_value(b\"(One\\\\)paren).\", 0) == (b\"One)paren\", 12)\n    assert PdfParser.get_value(b\"(\\\\0053)\", 0) == (b\"\\x053\", 7)\n    assert PdfParser.get_value(b\"(\\\\053)\", 0) == (b\"\\x2B\", 6)\n    assert PdfParser.get_value(b\"(\\\\53)\", 0) == (b\"\\x2B\", 5)\n    assert PdfParser.get_value(b\"(\\\\53a)\", 0) == (b\"\\x2Ba\", 6)\n    assert PdfParser.get_value(b\"(\\\\1111)\", 0) == (b\"\\x491\", 7)\n\n\nif __name__ == \"__main__\":\n    selftest()\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:** `io.UnsupportedOperation: fileno` when Using `io.BytesIO` with `PdfParser`

**Description:**
When attempting to append to a PDF using `io.BytesIO` objects with PIL's `PdfParser`, an `io.UnsupportedOperation: fileno` error is raised. This occurs because `PdfParser` tries to call the `fileno()` method on the `BytesIO` object, which is not supported.

**Example Code:**
```python
import io
from PIL import Image, pdfParser

# Create a new RGB image and save it as PDF to a BytesIO object
im = Image.new("RGB", (100, 100))
f = io.BytesIO()
im.save(f, format="PDF")

# Attempt to append a new P-mode image to the existing PDF
im = Image.new("P", (100, 100))
im.save(f, format="PDF", append=True)
```

**Expected Behavior:**
The PDF should be successfully appended with the new image without raising any errors when using `io.BytesIO` objects.

**Actual Behavior:**
An `io.UnsupportedOperation: fileno` error is raised because `PdfParser` attempts to use the unsupported `fileno()` method on the `BytesIO` object.

[/ISSUE]
--- END ISSUE ---

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

--- BEGIN FILE ---
```
### src/PIL/pdfParser.py
import codecs
import collections
import io
import mmap
import re
import sys

try:
    from UserDict import UserDict
except ImportError:
    UserDict = collections.UserDict


if str == bytes:  # Python 2.x
    make_bytes = lambda s: s  # pragma: no cover
else:  # Python 3.x
    def make_bytes(s):
        return s.encode("us-ascii")


# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set on page 656
def encode_text(s):
    return codecs.BOM_UTF16_BE + s.encode("utf_16_be")


PDFDocEncoding = {
    0x16: u"\u0017",
    0x18: u"\u02D8",
    0x19: u"\u02C7",
    0x1A: u"\u02C6",
    0x1B: u"\u02D9",
    0x1C: u"\u02DD",
    0x1D: u"\u02DB",
    0x1E: u"\u02DA",
    0x1F: u"\u02DC",
    0x80: u"\u2022",
    0x81: u"\u2020",
    0x82: u"\u2021",
    0x83: u"\u2026",
    0x84: u"\u2014",
    0x85: u"\u2013",
    0x86: u"\u0192",
    0x87: u"\u2044",
    0x88: u"\u2039",
    0x89: u"\u203A",
    0x8A: u"\u2212",
    0x8B: u"\u2030",
    0x8C: u"\u201E",
    0x8D: u"\u201C",
    0x8E: u"\u201D",
    0x8F: u"\u2018",
    0x90: u"\u2019",
    0x91: u"\u201A",
    0x92: u"\u2122",
    0x93: u"\uFB01",
    0x94: u"\uFB02",
    0x95: u"\u0141",
    0x96: u"\u0152",
    0x97: u"\u0160",
    0x98: u"\u0178",
    0x99: u"\u017D",
    0x9A: u"\u0131",
    0x9B: u"\u0142",
    0x9C: u"\u0153",
    0x9D: u"\u0161",
    0x9E: u"\u017E",
    0xA0: u"\u20AC",
    }
def decode_text(b):
    if b[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
        return b[len(codecs.BOM_UTF16_BE):].decode("utf_16_be")
    elif str == bytes:  # Python 2.x
        return u"".join(PDFDocEncoding.get(ord(byte), byte) for byte in b)
    else:
        return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b)


class PdfFormatError(RuntimeError):
    pass


def check_format_condition(condition, error_message):
    if not condition:
        raise PdfFormatError(error_message)


class IndirectReference(collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])):
    def __str__(self):
        return "%s %s R" % self

    def __bytes__(self):
        return self.__str__().encode("us-ascii")

    def __eq__(self, other):
        return other.__class__ is self.__class__ and other.object_id == self.object_id and other.generation == self.generation

    def __ne__(self, other):
        return not (self == other)


class IndirectObjectDef(IndirectReference):
    def __str__(self):
        return "%s %s obj" % self


class XrefTable:
    def __init__(self):
        self.existing_entries = {}          # object ID => (offset, generation)
        self.new_entries = {}               # object ID => (offset, generation)
        self.deleted_entries = {0: 65536}   # object ID => generation
        self.reading_finished = False

    def __setitem__(self, key, value):
        if self.reading_finished:
            self.new_entries[key] = value
        else:
            self.existing_entries[key] = value
        if key in self.deleted_entries:
            del self.deleted_entries[key]

    def __getitem__(self, key):
        try:
            return self.new_entries[key]
        except KeyError:
            return self.existing_entries[key]

    def __delitem__(self, key):
        if key in self.new_entries:
            generation = self.new_entries[key][1] + 1
            del self.new_entries[key]
            self.deleted_entries[key] = generation
        elif key in self.existing_entries:
            generation = self.existing_entries[key][1] + 1
            self.deleted_entries[key] = generation
        elif key in self.deleted_entries:
            generation = self.deleted_entries[key]
        else:
            raise IndexError("object ID " + str(key) + " cannot be deleted because it doesn't exist")

    def __contains__(self, key):
        return key in self.existing_entries or key in self.new_entries

    def __len__(self):
        return len(set(self.existing_entries.keys()) | set(self.new_entries.keys()) | set(self.deleted_entries.keys()))

    def keys(self):
        return (set(self.existing_entries.keys()) - set(self.deleted_entries.keys())) | set(self.new_entries.keys())

    def write(self, f):
        keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys()))
        deleted_keys = sorted(set(self.deleted_entries.keys()))
        startxref = f.tell()
        f.write(b"xref\n")
        while keys:
            # find a contiguous sequence of object IDs
            prev = None
            for index, key in enumerate(keys):
                if prev is None or prev+1 == key:
                    prev = key
                else:
                    contiguous_keys = keys[:index]
                    keys = keys[index:]
                    break
            else:
                contiguous_keys = keys
                keys = None
            f.write(make_bytes("%d %d\n" % (contiguous_keys[0], len(contiguous_keys))))
            for object_id in contiguous_keys:
                if object_id in self.new_entries:
                    f.write(make_bytes("%010d %05d n \n" % self.new_entries[object_id]))
                else:
                    this_deleted_object_id = deleted_keys.pop(0)
                    assert object_id == this_deleted_object_id
                    try:
                        next_in_linked_list = deleted_keys[0]
                    except IndexError:
                        next_in_linked_list = 0
                    f.write(make_bytes("%010d %05d f \n" % (next_in_linked_list, self.deleted_entries[object_id])))
        return startxref


class PdfName():
    def __init__(self, name):
        if isinstance(name, PdfName):
            self.name = name.name
        elif isinstance(name, bytes):
            self.name = name
        else:
            self.name = name.encode("utf-8")

    @classmethod
    def from_pdf_stream(klass, data):
        return klass(PdfParser.interpret_name(data))

    allowed_chars = set(range(33,127)) - set((ord(c) for c in "#%/()<>[]{}"))
    def __bytes__(self):
        if str == bytes:  # Python 2.x
            result = bytearray(b"/")
            for b in self.name:
                if ord(b) in self.allowed_chars:
                    result.append(b)
                else:
                    result.extend(b"#%02X" % ord(b))
        else:  # Python 3.x
            result = bytearray(b"/")
            for b in self.name:
                if b in self.allowed_chars:
                    result.append(b)
                else:
                    result.extend(make_bytes("#%02X" % b))
        return bytes(result)

    __str__ = __bytes__


class PdfArray(list):
    def __bytes__(self):
        return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"

    __str__ = __bytes__


class PdfDict(UserDict):
    #def __init__(self, *args, orig_ref=None, pdf=None, **kwargs):
    def __init__(self, *args, **kwargs):
        UserDict.__init__(self, *args, **kwargs)
        #self.orig_ref = kwargs.pop("orig_ref", None)
        #self.pdf = kwargs.pop("pdf", None)
        #self.is_changed = False

    def __setitem__(self, key, value):
        self.is_changed = True
        UserDict.__setitem__(self, key, value)

    def __bytes__(self):
        #if self.orig_ref is not None:
        #    if self.is_changed:
        #        if self.pdf is not None:
        #            del self.pdf.xref_table[self.orig_ref.object_id]
        #    else:
        #        return bytes(self.orig_ref)
        out = bytearray(b"<<")
        for key, value in self.items():
            if value is None:
                continue
            value = pdf_repr(value)
            out.extend(b"\n")
            out.extend(bytes(PdfName(key)))
            out.extend(b" ")
            out.extend(value)
            #out += b"\n%s %s" % (PdfName(key), value)
        out.extend(b"\n>>")
        return bytes(out)
        #return out + b"\n>>"

    __str__ = __bytes__

    #def write(self, f, orig_ref=None, pdf=None):
    #    self.orig_ref = orig_ref
    #    self.pdf = pdf
    #    f.write(bytes(self))


class PdfBinary:
    def __init__(self, data):
        self.data = data

    if str == bytes:  # Python 2.x
        def __str__(self):
            return "<%s>" % "".join("%02X" % ord(b) for b in self.data)

    else:  # Python 3.x
        def __bytes__(self):
            return make_bytes("<%s>" % "".join("%02X" % b for b in self.data))


def pdf_repr(x):
    if x is True:
        return b"true"
    elif x is False:
        return b"false"
    elif x is None:
        return b"null"
    elif isinstance(x, PdfName) or isinstance(x, PdfDict) or isinstance(x, PdfArray) or isinstance(x, PdfBinary):
        return bytes(x)
    elif isinstance(x, int):
        return str(x).encode("us-ascii")
    elif isinstance(x, dict):
        return bytes(PdfDict(x))
    elif isinstance(x, list):
        return bytes(PdfArray(x))
    elif isinstance(x, str) and str != bytes:
        return pdf_repr(x.encode("utf-8"))
    elif isinstance(x, bytes):
        return b"(" + x.replace(b"\\", b"\\\\").replace(b"(", b"\\(").replace(b")", b"\\)") + b")"  # XXX escape more chars? handle binary garbage
    else:
        return bytes(x)


class PdfParser:
    """Based on http://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf
    Supports PDF up to 1.4
    """

    def __init__(self, filename=None, f=None, buf=None, start_offset=0):
        self.filename = filename
        self.buf = buf
        self.start_offset = start_offset
        if buf is not None:
            self.read_pdf_info()
        elif f is not None:
            self.read_pdf_info_from_file(f)
        elif filename is not None:
            with open(filename, "rb") as f:
                self.read_pdf_info_from_file(f)
        else:
            self.file_size_total = self.file_size_this = 0
            self.root = PdfDict()
            self.root_ref = None
            self.info = PdfDict()
            self.info_ref = None
            self.page_tree_root = {}
            self.pages = []
            self.pages_ref = None
            self.last_xref_section_offset = None
            self.trailer_dict = {}
            self.xref_table = XrefTable()
        self.xref_table.reading_finished = True

    def write_header(self, f):
        f.write(b"%PDF-1.4\n")

    def write_comment(self, f, s):
        f.write(("%% %s\n" % (s,)).encode("utf-8"))

    def write_catalog(self, f):
        self.del_root()
        self.root_ref = self.next_object_id(f.tell())
        self.pages_ref = self.next_object_id(0)
        self.write_obj(f, self.root_ref,
            Type=PdfName(b"Catalog"),
            Pages=self.pages_ref)
        self.write_obj(f, self.pages_ref,
            Type=PdfName("Pages"),
            Count=len(self.pages),
            Kids=self.pages)
        return self.root_ref

    def write_xref_and_trailer(self, f, new_root_ref=None):
        if new_root_ref:
            self.del_root()
            self.root_ref = new_root_ref
        if self.info:
            self.info_ref = self.write_obj(f, None, self.info)
        start_xref = self.xref_table.write(f)
        num_entries = len(self.xref_table)
        trailer_dict = {b"Root": self.root_ref, b"Size": num_entries}
        if self.last_xref_section_offset is not None:
            trailer_dict[b"Prev"] = self.last_xref_section_offset
        if self.info:
            trailer_dict[b"Info"] = self.info_ref
        self.last_xref_section_offset = start_xref
        f.write(b"trailer\n" + bytes(PdfDict(trailer_dict)) + make_bytes("\nstartxref\n%d\n%%%%EOF" % start_xref))

    def write_page(self, f, ref, *objs, **dict_obj):
        if isinstance(ref, int):
            ref = self.pages[ref]
        if "Type" not in dict_obj:
            dict_obj["Type"] = PdfName("Page")
        if "Parent" not in dict_obj:
            dict_obj["Parent"] = self.pages_ref
        return self.write_obj(f, ref, *objs, **dict_obj)

    def write_obj(self, f, ref, *objs, **dict_obj):
        if ref is None:
            ref = self.next_object_id(f.tell())
        else:
            self.xref_table[ref.object_id] = (f.tell(), ref.generation)
        f.write(bytes(IndirectObjectDef(*ref)))
        stream = dict_obj.pop("stream", None)
        if stream is not None:
            dict_obj["Length"] = len(stream)
        if dict_obj:
            f.write(pdf_repr(dict_obj))
        for obj in objs:
            f.write(pdf_repr(obj))
        if stream is not None:
            f.write(b"stream\n")
            f.write(stream)
            f.write(b"\nendstream\n")
        f.write(b"endobj\n")
        return ref

    def del_root(self):
        if self.root_ref is None:
            return
        del self.xref_table[self.root_ref.object_id]
        del self.xref_table[self.root[b"Pages"].object_id]
        # XXX TODO delete Pages tree recursively

    def read_pdf_info_from_file(self, f):
        self.buf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
        try:
            self.read_pdf_info()
        finally:
            self.buf.close()
            self.buf = None

    def read_pdf_info(self):
        self.file_size_total = len(self.buf)
        self.file_size_this = self.file_size_total - self.start_offset
        self.read_trailer()
        self.root_ref = self.trailer_dict[b"Root"]
        self.info_ref = self.trailer_dict.get(b"Info", None)
        self.root = PdfDict(self.read_indirect(self.root_ref))
        if self.info_ref is None:
            self.info = PdfDict()
        else:
            self.info = PdfDict(self.read_indirect(self.info_ref))
        #print(repr(self.root))
        check_format_condition(b"Type" in self.root, "/Type missing in Root")
        check_format_condition(self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog")
        check_format_condition(b"Pages" in self.root, "/Pages missing in Root")
        check_format_condition(isinstance(self.root[b"Pages"], IndirectReference), "/Pages in Root is not an indirect reference")
        self.pages_ref = self.root[b"Pages"]
        self.page_tree_root = self.read_indirect(self.pages_ref)
        #print("page_tree_root: " + str(self.page_tree_root))
        self.pages = self.linearize_page_tree(self.page_tree_root)
        #print("pages: " + str(self.pages))

    def next_object_id(self, offset=None):
        try:
            # TODO: support reuse of deleted objects
            reference = IndirectReference(max(self.xref_table.keys()) + 1, 0)
        except ValueError:
            reference = IndirectReference(1, 0)
        if offset is not None:
            self.xref_table[reference.object_id] = (offset, 0)
        return reference

    delimiter = br"[][()<>{}/%]"
    delimiter_or_ws = br"[][()<>{}/%\000\011\012\014\015\040]"
    whitespace = br"[\000\011\012\014\015\040]"
    whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]"
    whitespace_optional = whitespace + b"*"
    whitespace_mandatory = whitespace + b"+"
    newline_only = br"[\r\n]+"
    newline = whitespace_optional + newline_only + whitespace_optional
    re_trailer_end = re.compile(whitespace_mandatory + br"trailer" + whitespace_optional + br"\<\<(.*\>\>)" + newline \
        + br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional + br"$", re.DOTALL)
    re_trailer_prev = re.compile(whitespace_optional + br"trailer" + whitespace_optional + br"\<\<(.*?\>\>)" + newline \
        + br"startxref" + newline + br"([0-9]+)" + newline + br"%%EOF" + whitespace_optional, re.DOTALL)
    def read_trailer(self):
        search_start_offset = len(self.buf) - 16384
        if search_start_offset < self.start_offset:
            search_start_offset = self.start_offset
        #data_at_end = self.buf[search_start_offset:]
        #m = self.re_trailer_end.search(data_at_end)
        m = self.re_trailer_end.search(self.buf, search_start_offset)
        check_format_condition(m, "trailer end not found")
        # make sure we found the LAST trailer
        last_match = m
        while m:
            last_match = m
            m = self.re_trailer_end.search(self.buf, m.start()+16)
        if not m:
            m = last_match
        trailer_data = m.group(1)
        #print(trailer_data)
        self.last_xref_section_offset = int(m.group(2))
        self.trailer_dict = self.interpret_trailer(trailer_data)
        self.xref_table = XrefTable()
        self.read_xref_table(xref_section_offset=self.last_xref_section_offset)
        #print(self.xref_table)
        if b"Prev" in self.trailer_dict:
            self.read_prev_trailer(self.trailer_dict[b"Prev"])

    def read_prev_trailer(self, xref_section_offset):
        trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset)
        m = self.re_trailer_prev.search(self.buf[trailer_offset:trailer_offset+16384])
        check_format_condition(m, "previous trailer not found")
        trailer_data = m.group(1)
        #print(trailer_data)
        check_format_condition(int(m.group(2)) == xref_section_offset, "xref section offset in previous trailer doesn't match what was expected")
        trailer_dict = self.interpret_trailer(trailer_data)
        if b"Prev" in trailer_dict:
            self.read_prev_trailer(trailer_dict[b"Prev"])

    re_whitespace_optional = re.compile(whitespace_optional)
    re_name = re.compile(whitespace_optional + br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + delimiter_or_ws + br")")
    re_dict_start = re.compile(whitespace_optional + br"\<\<")
    re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional)
    @classmethod
    def interpret_trailer(klass, trailer_data):
        trailer = {}
        offset = 0
        while True:
            m = klass.re_name.match(trailer_data, offset)
            if not m:
                m = klass.re_dict_end.match(trailer_data, offset)
                check_format_condition(m and m.end() == len(trailer_data), "name not found in trailer, remaining data: " + repr(trailer_data[offset:]))
                break
            key = klass.interpret_name(m.group(1))
            #print(key)
            value, offset = klass.get_value(trailer_data, m.end())
            #print(value)
            trailer[key] = value
        check_format_condition(b"Size" in trailer and isinstance(trailer[b"Size"], int), "/Size not in trailer or not an integer")
        check_format_condition(b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), "/Root not in trailer or not an indirect reference")
        return trailer

    re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?")
    @classmethod
    def interpret_name(klass, raw, as_text=False):
        name = b""
        for m in klass.re_hashes_in_name.finditer(raw):
            if m.group(3):
                name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii"))
            else:
                name += m.group(1)
        if as_text:
            return name.decode("utf-8")
        else:
            return bytes(name)

    re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")")
    re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
    re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
    re_int = re.compile(whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")")
    re_real = re.compile(whitespace_optional + br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + delimiter_or_ws + br")")
    re_array_start = re.compile(whitespace_optional + br"\[")
    re_array_end = re.compile(whitespace_optional + br"]")
    re_string_hex = re.compile(whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>")
    re_string_lit = re.compile(whitespace_optional + br"\(")
    re_indirect_reference = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"R(?=" + delimiter_or_ws + br")")
    re_indirect_def_start = re.compile(whitespace_optional + br"([-+]?[0-9]+)" + whitespace_mandatory + br"([-+]?[0-9]+)" + whitespace_mandatory + br"obj(?=" + delimiter_or_ws + br")")
    re_indirect_def_end = re.compile(whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")")
    re_comment = re.compile(br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*")
    @classmethod
    def get_value(klass, data, offset, expect_indirect=None, max_nesting=-1):
        #if max_nesting == 0:
        #    return None, None
        m = klass.re_comment.match(data, offset)
        if m:
            offset = m.end()
        m = klass.re_indirect_def_start.match(data, offset)
        if m:
            assert int(m.group(1)) > 0
            assert int(m.group(2)) >= 0
            check_format_condition(expect_indirect is None or expect_indirect == IndirectReference(int(m.group(1)), int(m.group(2))),
                "indirect object definition different than expected")
            object, offset = klass.get_value(data, m.end(), max_nesting=max_nesting-1)
            if offset is None:
                return object, None
            m = klass.re_indirect_def_end.match(data, offset)
            check_format_condition(m, "indirect object definition end not found")
            return object, m.end()
        check_format_condition(not expect_indirect, "indirect object definition not found")
        m = klass.re_indirect_reference.match(data, offset)
        if m:
            assert int(m.group(1)) > 0
            assert int(m.group(2)) >= 0
            return IndirectReference(int(m.group(1)), int(m.group(2))), m.end()
        m = klass.re_dict_start.match(data, offset)
        if m:
            offset = m.end()
            result = {}
            #print("<<")
            m = klass.re_dict_end.match(data, offset)
            while not m:
                key, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)
                #print ("key " + str(key))
                if offset is None:
                    return result, None
                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)
                result[key] = value
                #print ("value " + str(value))
                if offset is None:
                    return result, None
                m = klass.re_dict_end.match(data, offset)
            #print(">>")
            return result, m.end()
        m = klass.re_array_start.match(data, offset)
        if m:
            offset = m.end()
            result = []
            m = klass.re_array_end.match(data, offset)
            while not m:
                value, offset = klass.get_value(data, offset, max_nesting=max_nesting-1)
                result.append(value)
                #print ("item " + str(value))
                if offset is None:
                    return result, None
                m = klass.re_array_end.match(data, offset)
            return result, m.end()
        m = klass.re_null.match(data, offset)
        if m:
            return None, m.end()
        m = klass.re_true.match(data, offset)
        if m:
            return True, m.end()
        m = klass.re_false.match(data, offset)
        if m:
            return False, m.end()
        m = klass.re_name.match(data, offset)
        if m:
            return klass.interpret_name(m.group(1)), m.end()
        m = klass.re_int.match(data, offset)
        if m:
            return int(m.group(1)), m.end()
        m = klass.re_real.match(data, offset)
        if m:
            return float(m.group(1)), m.end()  # XXX Decimal instead of float???
        m = klass.re_string_hex.match(data, offset)
        if m:
            hex_string = bytearray([b for b in m.group(1) if b in b"0123456789abcdefABCDEF"])  # filter out whitespace
            if len(hex_string) % 2 == 1:
                hex_string.append(ord(b"0"))  # append a 0 if the length is not even - yes, at the end
            return bytearray.fromhex(hex_string.decode("us-ascii")), m.end()
        m = klass.re_string_lit.match(data, offset)
        if m:
            return klass.get_literal_string(data, m.end())
        # XXX TODO: stream
        #return None, offset  # fallback (only for debugging)
        raise PdfFormatError("unrecognized object: " + repr(data[offset:offset+32]))


    re_lit_str_token = re.compile(br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))")
    escaped_chars = {
        b"n": b"\n",
        b"r": b"\r",
        b"t": b"\t",
        b"b": b"\b",
        b"f": b"\f",
        b"(": b"(",
        b")": b")",
        ord(b"n"): b"\n",
        ord(b"r"): b"\r",
        ord(b"t"): b"\t",
        ord(b"b"): b"\b",
        ord(b"f"): b"\f",
        ord(b"("): b"(",
        ord(b")"): b")",
        }
    @classmethod
    def get_literal_string(klass, data, offset):
        nesting_depth = 0
        result = bytearray()
        for m in klass.re_lit_str_token.finditer(data, offset):
            result.extend(data[offset:m.start()])
            if m.group(1):
                result.extend(klass.escaped_chars[m.group(1)[1]])
            elif m.group(2):
                #result.append(eval(m.group(1)))
                result.append(int(m.group(2)[1:], 8))
            elif m.group(3):
                pass
            elif m.group(5):
                result.extend(b"\n")
            elif m.group(6):
                result.extend(b"(")
                nesting_depth += 1
            elif m.group(7):
                if nesting_depth == 0:
                    return bytes(result), m.end()
                result.extend(b")")
                nesting_depth -= 1
            offset = m.end()
        raise PdfFormatError("unfinished literal string")


    re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline)
    re_xref_subsection_start = re.compile(whitespace_optional + br"([0-9]+)" + whitespace_mandatory + br"([0-9]+)" + whitespace_optional + newline_only)
    re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
    def read_xref_table(self, xref_section_offset):
        subsection_found = False
        m = self.re_xref_section_start.match(self.buf, xref_section_offset + self.start_offset)
        check_format_condition(m, "xref section start not found")
        offset = m.end()
        while True:
            m = self.re_xref_subsection_start.match(self.buf, offset)
            if not m:
                check_format_condition(subsection_found, "xref subsection start not found")
                break
            subsection_found = True
            offset = m.end()
            first_object = int(m.group(1))
            num_objects = int(m.group(2))
            for i in range(first_object, first_object+num_objects):
                m = self.re_xref_entry.match(self.buf, offset)
                check_format_condition(m, "xref entry not found")
                offset = m.end()
                is_free = m.group(3) == b"f"
                generation = int(m.group(2))
                if not is_free:
                    new_entry = (int(m.group(1)), generation)
                    check_format_condition(i not in self.xref_table or self.xref_table[i] == new_entry, "xref entry duplicated (and not identical)")
                    self.xref_table[i] = new_entry
        return offset


    def read_indirect(self, ref, max_nesting=-1):
        offset, generation = self.xref_table[ref[0]]
        assert generation == ref[1]
        return self.get_value(self.buf, offset + self.start_offset, expect_indirect=IndirectReference(*ref), max_nesting=max_nesting)[0]


    def linearize_page_tree(self, node=None):
        if node is None:
            node = self.page_tree_root
        check_format_condition(node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages")
        pages = []
        for kid in node[b"Kids"]:
            kid_object = self.read_indirect(kid, max_nesting=3)
            if kid_object[b"Type"] == b"Page":
                pages.append(kid)
            else:
                pages.extend(self.linearize_page_tree(node=kid_object))
        return pages


def selftest():
    assert encode_text("abc") == b"\xFE\xFF\x00a\x00b\x00c"
    assert decode_text(b"\xFE\xFF\x00a\x00b\x00c") == "abc"
    assert decode_text(b"abc") == "abc"
    assert decode_text(b"\x1B a \x1C") == u"\u02D9 a \u02DD"
    assert PdfParser.interpret_name(b"Name#23Hash") == b"Name#Hash"
    assert PdfParser.interpret_name(b"Name#23Hash", as_text=True) == "Name#Hash"
    assert IndirectReference(1,2) == IndirectReference(1,2)
    assert IndirectReference(1,2) != IndirectReference(1,3)
    assert IndirectReference(1,2) != IndirectObjectDef(1,2)
    assert IndirectReference(1,2) != (1,2)
    assert IndirectObjectDef(1,2) == IndirectObjectDef(1,2)
    assert IndirectObjectDef(1,2) != IndirectObjectDef(1,3)
    assert IndirectObjectDef(1,2) != IndirectReference(1,2)
    assert IndirectObjectDef(1,2) != (1,2)
    assert bytes(IndirectReference(1,2)) == b"1 2 R"
    assert bytes(IndirectObjectDef(*IndirectReference(1,2))) == b"1 2 obj"
    assert bytes(PdfName(b"Name#Hash")) == b"/Name#23Hash"
    assert bytes(PdfName("Name#Hash")) == b"/Name#23Hash"
    assert bytes(PdfDict({b"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert bytes(PdfDict({"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert pdf_repr(IndirectReference(1,2)) == b"1 2 R"
    assert pdf_repr(IndirectObjectDef(*IndirectReference(1,2))) == b"1 2 obj"
    assert pdf_repr(PdfName(b"Name#Hash")) == b"/Name#23Hash"
    assert pdf_repr(PdfName("Name#Hash")) == b"/Name#23Hash"
    assert pdf_repr(PdfDict({b"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert pdf_repr(PdfDict({"Name": IndirectReference(1,2)})) == b"<<\n/Name 1 2 R\n>>"
    assert pdf_repr(123) == b"123"
    assert pdf_repr(True) == b"true"
    assert pdf_repr(False) == b"false"
    assert pdf_repr(None) == b"null"
    assert pdf_repr(b"a)/b\\(c") == br"(a\)/b\\\(c)"
    assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
    assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
    assert PdfParser.get_value(b"1 2 R ", 0) == (IndirectReference(1, 2), 5)
    assert PdfParser.get_value(b"true[", 0) == (True, 4)
    assert PdfParser.get_value(b"false%", 0) == (False, 5)
    assert PdfParser.get_value(b"null<", 0) == (None, 4)
    assert PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0) == (123, 15)
    assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1F\xA3", 8)
    assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1F\xA0", 17)
    assert PdfParser.get_value(b"(asd)", 0) == (b"asd", 5)
    assert PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0) == (b"asd(qwe)zxc", 13)
    assert PdfParser.get_value(b"(Two \\\nwords.)", 0) == (b"Two words.", 14)
    assert PdfParser.get_value(b"(Two\nlines.)", 0) == (b"Two\nlines.", 12)
    assert PdfParser.get_value(b"(Two\r\nlines.)", 0) == (b"Two\nlines.", 13)
    assert PdfParser.get_value(b"(Two\\nlines.)", 0) == (b"Two\nlines.", 13)
    assert PdfParser.get_value(b"(One\\(paren).", 0) == (b"One(paren", 12)
    assert PdfParser.get_value(b"(One\\)paren).", 0) == (b"One)paren", 12)
    assert PdfParser.get_value(b"(\\0053)", 0) == (b"\x053", 7)
    assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2B", 6)
    assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2B", 5)
    assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2Ba", 6)
    assert PdfParser.get_value(b"(\\1111)", 0) == (b"\x491", 7)


if __name__ == "__main__":
    selftest()

```
--- 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