Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/nbconvert/preprocessors/svg2pdf.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

81 statements  

1"""Module containing a preprocessor that converts outputs in the notebook from 

2one format to another. 

3""" 

4 

5# Copyright (c) Jupyter Development Team. 

6# Distributed under the terms of the Modified BSD License. 

7 

8import base64 

9import os 

10import subprocess 

11import sys 

12from shutil import which 

13from tempfile import TemporaryDirectory 

14 

15from traitlets import List, Unicode, Union, default 

16 

17from nbconvert.utils.io import FormatSafeDict 

18 

19from .convertfigures import ConvertFiguresPreprocessor 

20 

21# inkscape path for darwin (macOS) 

22INKSCAPE_APP = "/Applications/Inkscape.app/Contents/Resources/bin/inkscape" 

23# Recent versions of Inkscape (v1.0) moved the executable from 

24# Resources/bin/inkscape to MacOS/inkscape 

25INKSCAPE_APP_v1 = "/Applications/Inkscape.app/Contents/MacOS/inkscape" 

26 

27if sys.platform == "win32": 

28 try: 

29 import winreg 

30 except ImportError: 

31 import _winreg as winreg 

32 

33 

34class SVG2PDFPreprocessor(ConvertFiguresPreprocessor): 

35 """ 

36 Converts all of the outputs in a notebook from SVG to PDF. 

37 """ 

38 

39 @default("from_format") 

40 def _from_format_default(self): 

41 return "image/svg+xml" 

42 

43 @default("to_format") 

44 def _to_format_default(self): 

45 return "application/pdf" 

46 

47 inkscape_version = Unicode( 

48 help="""The version of inkscape being used. 

49 

50 This affects how the conversion command is run. 

51 """ 

52 ).tag(config=True) 

53 

54 @default("inkscape_version") 

55 def _inkscape_version_default(self): 

56 p = subprocess.Popen( 

57 [self.inkscape, "--version"], # noqa:S603 

58 stdout=subprocess.PIPE, 

59 stderr=subprocess.PIPE, 

60 ) 

61 output, _ = p.communicate() 

62 if p.returncode != 0: 

63 msg = "Unable to find inkscape executable --version" 

64 raise RuntimeError(msg) 

65 return output.decode("utf-8").split(" ")[1] 

66 

67 # FIXME: Deprecate passing a string here 

68 command = Union( 

69 [Unicode(), List()], 

70 help=""" 

71 The command to use for converting SVG to PDF 

72 

73 This traitlet is a template, which will be formatted with the keys 

74 to_filename and from_filename. 

75 

76 The conversion call must read the SVG from {from_filename}, 

77 and write a PDF to {to_filename}. 

78 

79 It could be a List (recommended) or a String. If string, it will 

80 be passed to a shell for execution. 

81 """, 

82 ).tag(config=True) 

83 

84 @default("command") 

85 def _command_default(self): 

86 major_version = self.inkscape_version.split(".")[0] 

87 command = [self.inkscape] 

88 

89 if int(major_version) < 1: 

90 # --without-gui is only needed for inkscape 0.x 

91 command.append("--without-gui") 

92 # --export-pdf is old name for --export-filename 

93 command.append("--export-pdf={to_filename}") 

94 else: 

95 command.append("--export-filename={to_filename}") 

96 

97 command.append("{from_filename}") 

98 return command 

99 

100 inkscape = Unicode(help="The path to Inkscape, if necessary").tag(config=True) 

101 

102 @default("inkscape") 

103 def _inkscape_default(self): 

104 inkscape_path = which("inkscape") 

105 if inkscape_path is not None: 

106 return inkscape_path 

107 if sys.platform == "darwin": 

108 if os.path.isfile(INKSCAPE_APP_v1): 

109 return INKSCAPE_APP_v1 

110 # Order is important. If INKSCAPE_APP exists, prefer it over 

111 # the executable in the MacOS directory. 

112 if os.path.isfile(INKSCAPE_APP): 

113 return INKSCAPE_APP 

114 if sys.platform == "win32": 

115 wr_handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) 

116 try: 

117 rkey = winreg.OpenKey(wr_handle, "SOFTWARE\\Classes\\inkscape.svg\\DefaultIcon") 

118 inkscape = winreg.QueryValueEx(rkey, "")[0] 

119 except FileNotFoundError: 

120 msg = "Inkscape executable not found" 

121 raise FileNotFoundError(msg) from None 

122 return inkscape 

123 return "inkscape" 

124 

125 def convert_figure(self, data_format, data): 

126 """ 

127 Convert a single SVG figure to PDF. Returns converted data. 

128 """ 

129 

130 # Work in a temporary directory 

131 with TemporaryDirectory() as tmpdir: 

132 # Write fig to temp file 

133 input_filename = os.path.join(tmpdir, "figure.svg") 

134 # SVG data is unicode text 

135 with open(input_filename, "w", encoding="utf8") as f: 

136 f.write(data) 

137 

138 # Call conversion application 

139 output_filename = os.path.join(tmpdir, "figure.pdf") 

140 

141 template_vars = {"from_filename": input_filename, "to_filename": output_filename} 

142 if isinstance(self.command, list): 

143 full_cmd = [s.format_map(FormatSafeDict(**template_vars)) for s in self.command] 

144 else: 

145 # For backwards compatibility with specifying strings 

146 # Okay-ish, since the string is trusted 

147 full_cmd = self.command.format(*template_vars) 

148 subprocess.call(full_cmd, shell=isinstance(full_cmd, str)) # noqa: S603 

149 

150 # Read output from drive 

151 # return value expects a filename 

152 if os.path.isfile(output_filename): 

153 with open(output_filename, "rb") as f: 

154 # PDF is a nb supported binary, data type, so base64 encode. 

155 return base64.encodebytes(f.read()).decode("utf-8") 

156 else: 

157 msg = "Inkscape svg to pdf conversion failed" 

158 raise TypeError(msg)