1010import shutil
1111import subprocess
1212import sys
13- from tempfile import TemporaryFile
13+ from tempfile import TemporaryDirectory , TemporaryFile
1414
1515import numpy as np
1616import PIL
@@ -158,53 +158,56 @@ def encode_and_escape(name):
158158
159159class _SVGConverter (_Converter ):
160160 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 ()
161165 if (not self ._proc # First run.
162166 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.
176182 stderr = TemporaryFile ()
177183 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 )
181188 # Slight abuse, but makes shutdown handling easier.
182189 self ._proc .stderr = stderr
183190 try :
184- self ._read_until (b" \n >" )
191+ self ._read_until (terminator )
185192 except _ConverterError as err :
186193 raise OSError ("Failed to start Inkscape in interactive "
187194 "mode" ) from err
188195
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 " )
205208 self ._proc .stdin .flush ()
206209 try :
207- self ._read_until (b" \n >" )
210+ self ._read_until (terminator )
208211 except _ConverterError as err :
209212 # Inkscape's output is not localized but gtk's is, so the output
210213 # stream probably has a mixed encoding. Using the filesystem
@@ -213,6 +216,13 @@ def __call__(self, orig, dest):
213216 raise ImageComparisonFailure (
214217 self ._proc .stderr .read ().decode (
215218 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 ()
216226
217227
218228def _update_converter ():
0 commit comments