diff --git a/nbconvert/preprocessors/svg2pdf.py b/nbconvert/preprocessors/svg2pdf.py index aff14d9f..de51f3b8 100644 --- a/nbconvert/preprocessors/svg2pdf.py +++ b/nbconvert/preprocessors/svg2pdf.py @@ -1,4 +1,4 @@ -"""Module containing a preprocessor that converts outputs in the notebook from +"""Module containing a preprocessor that converts outputs in the notebook from one format to another. """ @@ -17,11 +17,7 @@ from traitlets import Unicode, default from .convertfigures import ConvertFiguresPreprocessor -if sys.version_info >= (3,3): - from shutil import which - get_inkscape_path = which('inkscape') -else: - get_inkscape_path = None +from shutil import which INKSCAPE_APP = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape' @@ -46,26 +42,49 @@ class SVG2PDFPreprocessor(ConvertFiguresPreprocessor): def _to_format_default(self): return 'application/pdf' + inkscape_version = Unicode( + help="""The version of inkscpae being used. + + This affects how the conversion command is run. + """ + ).tag(config=True) + + @default('inkscape_version') + def _inkscape_version_default(self): + p = subprocess.Popen([self.inkscape, '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output, _ = p.communicate() + if p.returncode != 0: + raise RuntimeError("Unable to find inkscape executable --version") + return output.decode('utf-8').split(' ')[1] + command = Unicode( help="""The command to use for converting SVG to PDF - + This string is a template, which will be formatted with the keys to_filename and from_filename. - + The conversion call must read the SVG from {from_filename}, and write a PDF to {to_filename}. """).tag(config=True) @default('command') def _command_default(self): - return self.inkscape + \ - ' --without-gui --export-pdf="{to_filename}" "{from_filename}"' - + major_verison = self.inkscape_version.split('.')[0] + export_option = ' --export-filename' if int(major_verison) > 0 else ' --export-pdf' + gui_option = '' if int(major_verison) > 0 else ' --without-gui' + + return '{inkscape}{gui_option}{export_option}='.format( + inkscape=self.inkscape, export_option=export_option, gui_option=gui_option + ) + '"{to_filename}" "{from_filename}"' + inkscape = Unicode(help="The path to Inkscape, if necessary").tag(config=True) @default('inkscape') def _inkscape_default(self): - if get_inkscape_path is not None: - return get_inkscape_path + inkscape_path = which('inkscape') + if inkscape_path is not None: + return inkscape_path if sys.platform == "darwin": if os.path.isfile(INKSCAPE_APP): return INKSCAPE_APP @@ -85,22 +104,22 @@ class SVG2PDFPreprocessor(ConvertFiguresPreprocessor): Convert a single SVG figure to PDF. Returns converted data. """ - #Work in a temporary directory + # Work in a temporary directory with TemporaryDirectory() as tmpdir: - - #Write fig to temp file + + # Write fig to temp file input_filename = os.path.join(tmpdir, 'figure.svg') # SVG data is unicode text with io.open(input_filename, 'w', encoding='utf8') as f: f.write(cast_unicode_py2(data)) - #Call conversion application + # Call conversion application output_filename = os.path.join(tmpdir, 'figure.pdf') - shell = self.command.format(from_filename=input_filename, + shell = self.command.format(from_filename=input_filename, to_filename=output_filename) - subprocess.call(shell, shell=True) #Shell=True okay since input is trusted. + subprocess.call(shell, shell=True) # Shell=True okay since input is trusted. - #Read output from drive + # Read output from drive # return value expects a filename if os.path.isfile(output_filename): with open(output_filename, 'rb') as f: diff --git a/nbconvert/preprocessors/tests/test_svg2pdf.py b/nbconvert/preprocessors/tests/test_svg2pdf.py index c42222c7..d9ccec8c 100644 --- a/nbconvert/preprocessors/tests/test_svg2pdf.py +++ b/nbconvert/preprocessors/tests/test_svg2pdf.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. from nbformat import v4 as nbformat +from unittest.mock import patch, Mock from .base import PreprocessorTestsBase from ..svg2pdf import SVG2PDFPreprocessor @@ -51,9 +52,9 @@ class Testsvg2pdf(PreprocessorTestsBase): return nbformat.new_notebook(cells=cells) - def build_preprocessor(self): + def build_preprocessor(self, **kwargs): """Make an instance of a preprocessor""" - preprocessor = SVG2PDFPreprocessor() + preprocessor = SVG2PDFPreprocessor(**kwargs) preprocessor.enabled = True return preprocessor @@ -71,4 +72,23 @@ class Testsvg2pdf(PreprocessorTestsBase): preprocessor = self.build_preprocessor() nb, res = preprocessor(nb, res) self.assertIn('application/pdf', nb.cells[0].outputs[0].data) - + + @patch('subprocess.Popen') + def test_inkscape_version_default(self, mock_popen): + mock_popen().communicate.return_value = (b'Inkscape 0.92.3 (2405546, 2018-03-11)', b'') + mock_popen().returncode = 0 + + preprocessor = self.build_preprocessor() + self.assertEquals(preprocessor.inkscape_version, '0.92.3') + + def test_inkscape_pre_v1_command(self): + preprocessor = self.build_preprocessor(inkscape_version='0.92.3') + self.assertEquals(preprocessor.command, '0.92.3') + + def test_inkscape_pre_v1_command(self): + preprocessor = self.build_preprocessor(inkscape='fake-inkscape', inkscape_version='0.92.3') + self.assertEquals(preprocessor.command, 'fake-inkscape --without-gui --export-pdf="{to_filename}" "{from_filename}"') + + def test_inkscape_v1_command(self): + preprocessor = self.build_preprocessor(inkscape='fake-inkscape', inkscape_version='1.0beta2') + self.assertEquals(preprocessor.command, 'fake-inkscape --export-filename="{to_filename}" "{from_filename}"')