diff options
-rw-r--r-- | src/pikepdf/_cpphelpers.py | 71 | ||||
-rw-r--r-- | src/pikepdf/models/image.py | 21 | ||||
-rw-r--r-- | src/qpdf/object.cpp | 32 | ||||
-rw-r--r-- | tests/test_ipython.py | 24 |
4 files changed, 98 insertions, 50 deletions
diff --git a/src/pikepdf/_cpphelpers.py b/src/pikepdf/_cpphelpers.py index aa626d9..c9f1ffd 100644 --- a/src/pikepdf/_cpphelpers.py +++ b/src/pikepdf/_cpphelpers.py @@ -5,11 +5,17 @@ # Copyright (C) 2017, James R. Barlow (https://github.com/jbarlow83/) """ -Support functions called by the C++ library binding layer +Support functions called by the C++ library binding layer. Not intended to be +called from Python, and subject to change at any time. """ import os import sys +from tempfile import NamedTemporaryFile +from subprocess import run, PIPE +from io import BytesIO + +from . import Pdf # Provide os.fspath equivalent for Python <3.6 @@ -44,3 +50,66 @@ if sys.version_info[0:2] <= (3, 5): # pragma: no cover else: fspath = os.fspath + + +def _single_page_pdf(page): + """ + Construct a single page PDF from the provided page in memory + """ + pdf = Pdf.new() + pdf.pages.append(page) + bio = BytesIO() + pdf.save(bio) + bio.seek(0) + return bio.read() + + +def _mudraw(buffer, fmt): + """ + Use mupdf draw to rasterize the PDF in the memory buffer + """ + with NamedTemporaryFile(suffix='.pdf') as tmp_in: + tmp_in.write(buffer) + tmp_in.seek(0) + tmp_in.flush() + + proc = run( + ['mudraw', '-F', fmt, '-o', '-', tmp_in.name], + stdout=PIPE, stderr=PIPE + ) + if proc.stderr: + raise RuntimeError(proc.stderr.decode()) + return proc.stdout + + +def object_repr_mimebundle(obj, **kwargs): + """ + Present options to IPython for rich display of this object + + See https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display + """ + + include = kwargs['include'] + exclude = kwargs['exclude'] + include = set() if include else include + exclude = set() if exclude is None else exclude + + data = {} + if '/Type' not in obj: + return data + + if obj.Type == '/Page': + bundle = {'application/pdf', 'image/png'} + if include: + bundle = bundle & include + bundle = bundle - exclude + pagedata = _single_page_pdf(obj) + if 'application/pdf' in bundle: + data['application/pdf'] = pagedata + if 'image/png' in bundle: + try: + data['image/png'] = _mudraw(pagedata, 'png') + except (FileNotFoundError, RuntimeError): + pass + + return data diff --git a/src/pikepdf/models/image.py b/src/pikepdf/models/image.py index afd2226..b099f40 100644 --- a/src/pikepdf/models/image.py +++ b/src/pikepdf/models/image.py @@ -479,24 +479,3 @@ class PdfInlineImage(PdfImage): def get_stream_buffer(self): raise NotImplementedError("qpdf returns compressed") #return memoryview(self._data.inline_image_bytes()) - - -def page_to_svg(page): - pdf = Pdf.new() - pdf.pages.append(page) - with NamedTemporaryFile(suffix='.pdf') as tmp_in, \ - NamedTemporaryFile(mode='w+b', suffix='.svg') as tmp_out: - pdf.save(tmp_in) - tmp_in.seek(0) - - try: - proc = run(['mudraw', '-F', 'svg', '-o', tmp_out.name, tmp_in.name], stderr=PIPE) - except FileNotFoundError: - raise DependencyError("Could not find the required executable 'mutool'") - - if proc.stderr: - print(proc.stderr.decode()) - tmp_out.flush() - tmp_out2 = open(tmp_out.name, 'rb') # Not sure why re-opening is need, but it is - svg = tmp_out2.read() - return svg.decode() diff --git a/src/qpdf/object.cpp b/src/qpdf/object.cpp index d641fdf..9b1d462 100644 --- a/src/qpdf/object.cpp +++ b/src/qpdf/object.cpp @@ -729,34 +729,10 @@ void init_object(py::module& m) }, "Convert PDF objects into PostScript, and resolve referenced objects when possible." ) - .def("_repr_pdf_singlepage", - [](QPDFObjectHandle &page) -> py::object { - if (!page.isPageObject()) - return py::none(); - QPDF q; - q.emptyPDF(); - q.setSuppressWarnings(true); - - QPDFObjectHandle page_copy = q.copyForeignObject(page); - q.addPage(page_copy, true); - - QPDFWriter w(q); - w.setOutputMemory(); - w.write(); - std::unique_ptr<Buffer> output_buffer(w.getBuffer()); - auto output = py::bytes( - (const char*)output_buffer->getBuffer(), - output_buffer->getSize()); - return output; - }, - "Render as PDF - for Jupyter/IPython" - ) - .def("_repr_svg_", - [](QPDFObjectHandle &page) -> py::object { - if (!page.isPageObject()) - return py::none(); - auto page_to_svg = py::module::import("pikepdf._pdfimage").attr("page_to_svg"); - return page_to_svg(page); + .def("_repr_mimebundle_", + [](QPDFObjectHandle &h, py::kwargs kwargs) { + auto repr_mimebundle = py::module::import("pikepdf._cpphelpers").attr("object_repr_mimebundle"); + return repr_mimebundle(h, **kwargs); } ) ; // end of QPDFObjectHandle bindings diff --git a/tests/test_ipython.py b/tests/test_ipython.py new file mode 100644 index 0000000..4f616c8 --- /dev/null +++ b/tests/test_ipython.py @@ -0,0 +1,24 @@ +""" +Test IPython/Jupyter display hooks +""" + +import pikepdf +import pytest + + +@pytest.fixture +def graph(resources): + return pikepdf.open(resources / 'graph.pdf') + + +def test_display_page(graph): + page0 = graph.pages[0] + mimebundle = page0._repr_mimebundle_(include=None, exclude=None) + assert 'application/pdf' in mimebundle + + +def test_display_image(graph): + im0 = graph.pages[0].Resources.XObject['/Im0'] + pim = pikepdf.PdfImage(im0) + result = pim._repr_png_() + assert result[1:4] == b'PNG' |