Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Overwrites latex writer as Sphinx's version is 

5bugged in version 1.8.0. 

6""" 

7import os 

8from docutils import nodes 

9from docutils.frontend import OptionParser 

10from sphinx.locale import __ 

11from sphinx.builders.latex import LaTeXBuilder 

12from sphinx.writers.latex import ( 

13 LaTeXWriter, LaTeXTranslator, rstdim_to_latexdim) 

14from sphinx.util import logging 

15from sphinx import addnodes 

16from sphinx.writers.latex import toRoman, ENUMERATE_LIST_STYLE 

17from sphinx.util.docutils import SphinxFileOutput 

18from sphinx.util.template import LaTeXRenderer 

19 

20 

21class CustomizedSphinxFileOutput(SphinxFileOutput): 

22 """Customized FileOutput class for :epkg:`Sphinx`.""" 

23 

24 def __init__(self, **kwargs): 

25 SphinxFileOutput.__init__(self, **kwargs) 

26 

27 def write(self, data): 

28 res = SphinxFileOutput.write(self, data) 

29 return res 

30 

31 

32class EnhancedLaTeXTranslator(LaTeXTranslator): 

33 """ 

34 Overwrites `LaTeXTranslator <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/writers/latex.py#L451>`_ 

35 and modifies a few functions. 

36 """ 

37 

38 def __init__(self, document, builder): 

39 if not hasattr(builder, 'config'): 

40 raise TypeError( 

41 "Unexpected type for builder {0}".format(type(builder))) 

42 LaTeXTranslator.__init__(self, document, builder) 

43 

44 newlines = builder.config.text_newlines 

45 if newlines == 'windows': 

46 self.nl = '\r\n' 

47 elif newlines == 'native': 

48 self.nl = os.linesep 

49 else: 

50 self.nl = '\n' 

51 

52 def add_text(self, line): 

53 self.body.append(line) 

54 

55 def visit_document(self, node): 

56 LaTeXTranslator.visit_document(self, node) 

57 

58 def visit_enumerated_list(self, node): 

59 

60 def get_enumtype(node): 

61 enumtype = node.get('enumtype', 'arabic') 

62 if 'alpha' in enumtype and 26 < node.get('start', 0) + len(node): # pylint: disable=C0122 

63 # fallback to arabic if alphabet counter overflows 

64 enumtype = 'arabic' 

65 

66 return enumtype 

67 

68 def get_nested_level(node): 

69 if node is None: 

70 return 0 

71 elif isinstance(node, nodes.enumerated_list): 

72 return get_nested_level(node.parent) + 1 

73 else: 

74 return get_nested_level(node.parent) 

75 

76 enum = "enum%s" % toRoman(get_nested_level(node)).lower() 

77 enumnext = "enum%s" % toRoman(get_nested_level(node) + 1).lower() 

78 style = ENUMERATE_LIST_STYLE.get(get_enumtype(node)) 

79 

80 self.body.append('\\begin{enumerate}\n') 

81 self.body.append('\\def\\the%s{%s{%s}}\n' % (enum, style, enum)) 

82 prefix = node['prefix'] if 'prefix' in node else '' 

83 suffix = node['suffix'] if 'suffix' in node else '' 

84 self.body.append('\\def\\label%s{%s\\the%s %s}\n' % 

85 (enum, prefix, enum, suffix)) 

86 self.body.append('\\makeatletter\\def\\p@%s{\\p@%s %s\\the%s %s}\\makeatother\n' % 

87 (enumnext, enum, prefix, enum, suffix)) 

88 if 'start' in node: 

89 self.body.append('\\setcounter{%s}{%d}\n' % 

90 (enum, node['start'] - 1)) 

91 if self.table: 

92 self.table.has_problematic = True 

93 

94 def eval_expr(self, expr): 

95 md = False 

96 rst = True 

97 html = False 

98 latex = False 

99 if not(rst or html or latex or md): 

100 raise ValueError("One of them should be True") 

101 try: 

102 ev = eval(expr) 

103 except Exception: 

104 raise ValueError( 

105 "Unable to interpret expression '{0}'".format(expr)) 

106 return ev 

107 

108 def visit_only(self, node): 

109 ev = self.eval_expr(node.attributes['expr']) 

110 if ev: 

111 pass 

112 else: 

113 raise nodes.SkipNode 

114 

115 def depart_only(self, node): 

116 ev = self.eval_expr(node.attributes['expr']) 

117 if ev: 

118 pass 

119 else: 

120 # The program should not necessarily be here. 

121 pass 

122 

123 def latex_image_length(self, width_str, scale=100): 

124 try: 

125 return rstdim_to_latexdim(width_str, scale) 

126 except ValueError: 

127 if width_str == 'auto': 

128 pass 

129 else: 

130 self.builder.logger.warning( 

131 __('[EnhancedLaTeXTranslator] dimension unit ' 

132 '%s is invalid. Ignored.'), width_str) 

133 return None 

134 except TypeError: 

135 # Sphinx <= 1.7 

136 try: 

137 return rstdim_to_latexdim(width_str) 

138 except ValueError: 

139 if width_str == 'auto': 

140 pass 

141 else: 

142 self.builder.logger.warning( 

143 __('[EnhancedLaTeXTranslator] dimension unit ' 

144 '%s is invalid. Ignored.'), width_str) 

145 return None 

146 

147 def visit_inheritance_diagram(self, node): 

148 pass 

149 

150 def depart_inheritance_diagram(self, node): 

151 pass 

152 

153 def render(self, template_name, variables): 

154 renderer = LaTeXRenderer(latex_engine=self.config.latex_engine) 

155 if self.builder.config.templates_path is None: 

156 tpls = [os.path.join(os.path.abspath(os.path.dirname(__file__)), 

157 "templates")] 

158 else: 

159 tpls = self.builder.config.templates_path 

160 for template_dir in tpls: 

161 if os.path.exists(template_dir): 

162 template = os.path.join(template_dir, template_name) 

163 else: 

164 template = os.path.join(self.builder.confdir, template_dir, 

165 template_name) 

166 if os.path.exists(template): 

167 return renderer.render(template, variables) 

168 

169 return renderer.render(template_name, variables) 

170 

171 def visit_imgsgnode(self, node): 

172 pass 

173 

174 def depart_imgsgnode(self, node): 

175 pass 

176 

177 def unknown_visit(self, node): 

178 logger = logging.getLogger("MdBuilder") 

179 logger.warning("[latex] unknown visit node: '{0}' - '{1}'".format( 

180 node.__class__.__name__, node)) 

181 

182 

183class EnhancedLaTeXWriter(LaTeXWriter): 

184 """ 

185 Overwrites `LatexWriter <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/writers/latex.py#L189>`_. 

186 """ 

187 translator_class = EnhancedLaTeXTranslator 

188 

189 def __init__(self, builder): 

190 if not hasattr(builder, "config"): 

191 raise TypeError("Builder has no config: {}".format(type(builder))) 

192 LaTeXWriter.__init__(self, builder) 

193 

194 def translate(self): 

195 visitor = self.builder.create_translator(self.document, self.builder) 

196 self.document.walkabout(visitor) 

197 self.output = visitor.astext() 

198 

199 

200class EnhancedLaTeXBuilder(LaTeXBuilder): 

201 """ 

202 Overwrites `LaTeXBuilder <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/builders/latex/__init__.py>`_. 

203 """ 

204 name = 'elatex' 

205 format = 'latex' 

206 file_suffix = '.tex' 

207 epilog = __('The EnhancedTexinfo files are in %(outdir)s.') 

208 if os.name == 'posix': 

209 epilog += __("\nRun 'make' in that directory to run these through " 

210 "makeinfo\n" 

211 "(use 'make info' here to do that automatically).") 

212 

213 supported_image_types = ['image/png', 'image/jpeg', 'image/gif'] 

214 default_translator_class = EnhancedLaTeXTranslator 

215 

216 def __init__(self, *args, **kwargs): 

217 """ 

218 Constructor, add a logger. 

219 """ 

220 LaTeXBuilder.__init__(self, *args, **kwargs) 

221 self.logger = logging.getLogger("EnhancedLatexBuilder") 

222 self._memo_pages = {} 

223 self._skip_finish = self.app.config.elatex_bypass_finish 

224 

225 def finish(self): 

226 if self._skip_finish: 

227 return None 

228 else: 

229 return LaTeXBuilder.finish(self) 

230 

231 def get_outfilename(self, pagename): 

232 """ 

233 Overwrites *get_target_uri* to control file names. 

234 """ 

235 return "{0}/{1}.tex".format(self.outdir, pagename).replace("\\", "/") 

236 

237 def _get_filename(self, targetname, encoding='utf-8', overwrite_if_changed=True): 

238 return CustomizedSphinxFileOutput(destination_path=os.path.join(self.outdir, targetname), 

239 encoding=encoding, overwrite_if_changed=overwrite_if_changed) 

240 

241 def write(self, *ignored): 

242 docwriter = EnhancedLaTeXWriter(self) 

243 docsettings = OptionParser( 

244 defaults=self.env.settings, 

245 components=(docwriter,), 

246 read_config_files=True).get_default_values() 

247 

248 self.init_document_data() 

249 self.write_stylesheet() 

250 

251 for entry in self.document_data: 

252 docname, targetname, title, author, docclass = entry[:5] 

253 toctree_only = False 

254 if len(entry) > 5: 

255 toctree_only = entry[5] 

256 destination = self._get_filename(targetname, encoding='utf-8', 

257 overwrite_if_changed=True) 

258 self.logger.info(__("processing %s..."), targetname, nonl=1) 

259 toctrees = self.env.get_doctree(docname).traverse(addnodes.toctree) 

260 if toctrees: 

261 if toctrees[0].get('maxdepth') > 0: 

262 tocdepth = toctrees[0].get('maxdepth') 

263 else: 

264 tocdepth = None 

265 else: 

266 tocdepth = None 

267 doctree = self.assemble_doctree( 

268 docname, toctree_only, 

269 appendices=((docclass != 'howto') and self.config.latex_appendices or [])) 

270 doctree['tocdepth'] = tocdepth 

271 self.post_process_images(doctree) 

272 

273 self.logger.info(__("writing... "), nonl=1) 

274 doctree.settings = docsettings 

275 doctree.settings.author = author 

276 doctree.settings.title = title 

277 doctree.settings.contentsname = self.get_contentsname(docname) 

278 doctree.settings.docname = docname 

279 doctree.settings.docclass = docclass 

280 docwriter.write(doctree, destination) 

281 self.logger.info( 

282 "[EnhancedLaTeXBuilder] done in '{}'".format(targetname)) 

283 

284 

285def setup(app): 

286 """ 

287 Initializes builder @see cl EnhancedLaTeXBuilder. 

288 """ 

289 app.add_config_value('elatex_bypass_finish', False, 'elatex') 

290 app.add_builder(EnhancedLaTeXBuilder)