10
10
import shutil
11
11
import subprocess
12
12
import sys
13
- from tempfile import TemporaryFile
13
+ from tempfile import TemporaryDirectory , TemporaryFile
14
14
15
15
import numpy as np
16
16
import PIL
@@ -158,53 +158,56 @@ def encode_and_escape(name):
158
158
159
159
class _SVGConverter (_Converter ):
160
160
def __call__ (self , orig , dest ):
161
+ old_inkscape = mpl ._get_executable_info ("inkscape" ).version < "1"
162
+ terminator = b"\n >" if old_inkscape else b"> "
163
+ if not hasattr (self , "_tmpdir" ):
164
+ self ._tmpdir = TemporaryDirectory ()
161
165
if (not self ._proc # First run.
162
166
or self ._proc .poll () is not None ): # Inkscape terminated.
163
- env = os .environ .copy ()
164
- # If one passes e.g. a png file to Inkscape, it will try to
165
- # query the user for conversion options via a GUI (even with
166
- # `--without-gui`). Unsetting `DISPLAY` prevents this (and causes
167
- # GTK to crash and Inkscape to terminate, but that'll just be
168
- # reported as a regular exception below).
169
- env .pop ("DISPLAY" , None ) # May already be unset.
170
- # Do not load any user options.
171
- env ["INKSCAPE_PROFILE_DIR" ] = os .devnull
172
- # Old versions of Inkscape (0.48.3.1, used on Travis as of now)
173
- # seem to sometimes deadlock when stderr is redirected to a pipe,
174
- # so we redirect it to a temporary file instead. This is not
175
- # necessary anymore as of Inkscape 0.92.1.
167
+ env = {
168
+ ** os .environ ,
169
+ # If one passes e.g. a png file to Inkscape, it will try to
170
+ # query the user for conversion options via a GUI (even with
171
+ # `--without-gui`). Unsetting `DISPLAY` prevents this (and
172
+ # causes GTK to crash and Inkscape to terminate, but that'll
173
+ # just be reported as a regular exception below).
174
+ "DISPLAY" : "" ,
175
+ # Do not load any user options.
176
+ "INKSCAPE_PROFILE_DIR" : os .devnull ,
177
+ }
178
+ # Old versions of Inkscape (e.g. 0.48.3.1) seem to sometimes
179
+ # deadlock when stderr is redirected to a pipe, so we redirect it
180
+ # to a temporary file instead. This is not necessary anymore as of
181
+ # Inkscape 0.92.1.
176
182
stderr = TemporaryFile ()
177
183
self ._proc = subprocess .Popen (
178
- ["inkscape" , "--without-gui" , "--shell" ],
179
- stdin = subprocess .PIPE , stdout = subprocess .PIPE ,
180
- stderr = stderr , env = env )
184
+ ["inkscape" , "--without-gui" , "--shell" ] if old_inkscape else
185
+ ["inkscape" , "--shell" ],
186
+ stdin = subprocess .PIPE , stdout = subprocess .PIPE , stderr = stderr ,
187
+ env = env , cwd = self ._tmpdir .name )
181
188
# Slight abuse, but makes shutdown handling easier.
182
189
self ._proc .stderr = stderr
183
190
try :
184
- self ._read_until (b" \n >" )
191
+ self ._read_until (terminator )
185
192
except _ConverterError as err :
186
193
raise OSError ("Failed to start Inkscape in interactive "
187
194
"mode" ) from err
188
195
189
- # Inkscape uses glib's `g_shell_parse_argv`, which has a consistent
190
- # behavior across platforms, so we can just use `shlex.quote`.
191
- orig_b , dest_b = map (_shlex_quote_bytes ,
192
- map (os .fsencode , [orig , dest ]))
193
- if b"\n " in orig_b or b"\n " in dest_b :
194
- # Who knows whether the current folder name has a newline, or if
195
- # our encoding is even ASCII compatible... Just fall back on the
196
- # slow solution (Inkscape uses `fgets` so it will always stop at a
197
- # newline).
198
- cbook .warn_deprecated (
199
- "3.3" , message = "Support for converting files from paths "
200
- "containing a newline is deprecated since %(since)s and "
201
- "support will be removed %(removal)s" )
202
- return make_external_conversion_command (lambda old , new : [
203
- 'inkscape' , '-z' , old , '--export-png' , new ])(orig , dest )
204
- self ._proc .stdin .write (orig_b + b" --export-png=" + dest_b + b"\n " )
196
+ # Inkscape's shell mode does not support escaping metacharacters in the
197
+ # filename ("\n", and ":;" for inkscape>=1). Avoid any problems by
198
+ # running from a temporary directory and using fixed filenames.
199
+ inkscape_orig = Path (self ._tmpdir .name , os .fsdecode (b"f.svg" ))
200
+ inkscape_dest = Path (self ._tmpdir .name , os .fsdecode (b"f.png" ))
201
+ try :
202
+ inkscape_orig .symlink_to (Path (orig ).resolve ())
203
+ except OSError :
204
+ shutil .copyfile (orig , inkscape_orig )
205
+ self ._proc .stdin .write (
206
+ b"f.svg --export-png=f.png\n " if old_inkscape else
207
+ b"file-open:f.svg;export-filename:f.png;export-do;file-close\n " )
205
208
self ._proc .stdin .flush ()
206
209
try :
207
- self ._read_until (b" \n >" )
210
+ self ._read_until (terminator )
208
211
except _ConverterError as err :
209
212
# Inkscape's output is not localized but gtk's is, so the output
210
213
# stream probably has a mixed encoding. Using the filesystem
@@ -213,6 +216,13 @@ def __call__(self, orig, dest):
213
216
raise ImageComparisonFailure (
214
217
self ._proc .stderr .read ().decode (
215
218
sys .getfilesystemencoding (), "replace" )) from err
219
+ os .remove (inkscape_orig )
220
+ shutil .move (inkscape_dest , dest )
221
+
222
+ def __del__ (self ):
223
+ super ().__del__ ()
224
+ if hasattr (self , "_tmpdir" ):
225
+ self ._tmpdir .cleanup ()
216
226
217
227
218
228
def _update_converter ():
0 commit comments