-
Notifications
You must be signed in to change notification settings - Fork 555
/
svg2pdf.py
158 lines (130 loc) · 5.46 KB
/
svg2pdf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""Module containing a preprocessor that converts outputs in the notebook from
one format to another.
"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import base64
import os
import subprocess
import sys
from shutil import which
from tempfile import TemporaryDirectory
from traitlets import List, Unicode, Union, default
from nbconvert.utils.io import FormatSafeDict
from .convertfigures import ConvertFiguresPreprocessor
# inkscape path for darwin (macOS)
INKSCAPE_APP = "/Applications/Inkscape.app/Contents/Resources/bin/inkscape"
# Recent versions of Inkscape (v1.0) moved the executable from
# Resources/bin/inkscape to MacOS/inkscape
INKSCAPE_APP_v1 = "/Applications/Inkscape.app/Contents/MacOS/inkscape"
if sys.platform == "win32":
try:
import winreg
except ImportError:
import _winreg as winreg
class SVG2PDFPreprocessor(ConvertFiguresPreprocessor):
"""
Converts all of the outputs in a notebook from SVG to PDF.
"""
@default("from_format")
def _from_format_default(self):
return "image/svg+xml"
@default("to_format")
def _to_format_default(self):
return "application/pdf"
inkscape_version = Unicode(
help="""The version of inkscape 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"], # noqa:S603
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
output, _ = p.communicate()
if p.returncode != 0:
msg = "Unable to find inkscape executable --version"
raise RuntimeError(msg)
return output.decode("utf-8").split(" ")[1]
# FIXME: Deprecate passing a string here
command = Union(
[Unicode(), List()],
help="""
The command to use for converting SVG to PDF
This traitlet 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}.
It could be a List (recommended) or a String. If string, it will
be passed to a shell for execution.
""",
).tag(config=True)
@default("command")
def _command_default(self):
major_version = self.inkscape_version.split(".")[0]
command = [self.inkscape]
if int(major_version) < 1:
# --without-gui is only needed for inkscape 0.x
command.append("--without-gui")
# --export-pdf is old name for --export-filename
command.append("--export-pdf={to_filename}")
else:
command.append("--export-filename={to_filename}")
command.append("{from_filename}")
return command
inkscape = Unicode(help="The path to Inkscape, if necessary").tag(config=True)
@default("inkscape")
def _inkscape_default(self):
inkscape_path = which("inkscape")
if inkscape_path is not None:
return inkscape_path
if sys.platform == "darwin":
if os.path.isfile(INKSCAPE_APP_v1):
return INKSCAPE_APP_v1
# Order is important. If INKSCAPE_APP exists, prefer it over
# the executable in the MacOS directory.
if os.path.isfile(INKSCAPE_APP):
return INKSCAPE_APP
if sys.platform == "win32":
wr_handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
rkey = winreg.OpenKey(wr_handle, "SOFTWARE\\Classes\\inkscape.svg\\DefaultIcon")
inkscape = winreg.QueryValueEx(rkey, "")[0]
except FileNotFoundError:
msg = "Inkscape executable not found"
raise FileNotFoundError(msg) from None
return inkscape
return "inkscape"
def convert_figure(self, data_format, data):
"""
Convert a single SVG figure to PDF. Returns converted data.
"""
# Work in a temporary directory
with TemporaryDirectory() as tmpdir:
# Write fig to temp file
input_filename = os.path.join(tmpdir, "figure.svg")
# SVG data is unicode text
with open(input_filename, "w", encoding="utf8") as f:
f.write(data)
# Call conversion application
output_filename = os.path.join(tmpdir, "figure.pdf")
template_vars = {"from_filename": input_filename, "to_filename": output_filename}
if isinstance(self.command, list):
full_cmd = [s.format_map(FormatSafeDict(**template_vars)) for s in self.command]
else:
# For backwards compatibility with specifying strings
# Okay-ish, since the string is trusted
full_cmd = self.command.format(*template_vars)
subprocess.call(full_cmd, shell=isinstance(full_cmd, str)) # noqa: S603
# Read output from drive
# return value expects a filename
if os.path.isfile(output_filename):
with open(output_filename, "rb") as f:
# PDF is a nb supported binary, data type, so base64 encode.
return base64.encodebytes(f.read()).decode("utf-8")
else:
msg = "Inkscape svg to pdf conversion failed"
raise TypeError(msg)