summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pikepdf/_cpphelpers.py71
-rw-r--r--src/pikepdf/models/image.py21
-rw-r--r--src/qpdf/object.cpp32
-rw-r--r--tests/test_ipython.py24
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'