|
1 # $Id: __init__.py 5333 2007-07-10 17:31:28Z grubert $ |
|
2 # Author: Engelbert Gruber <grubert@users.sourceforge.net> |
|
3 # Copyright: This module has been placed in the public domain. |
|
4 |
|
5 """ |
|
6 LaTeX2e document tree Writer. |
|
7 """ |
|
8 |
|
9 __docformat__ = 'reStructuredText' |
|
10 |
|
11 # code contributions from several people included, thanks to all. |
|
12 # some named: David Abrahams, Julien Letessier, Lele Gaifax, and others. |
|
13 # |
|
14 # convention deactivate code by two # e.g. ##. |
|
15 |
|
16 import sys |
|
17 import time |
|
18 import re |
|
19 import string |
|
20 from types import ListType |
|
21 from docutils import frontend, nodes, languages, writers, utils |
|
22 from docutils.writers.newlatex2e import unicode_map |
|
23 |
|
24 from docutils.transforms.references import DanglingReferencesVisitor |
|
25 |
|
26 class Writer(writers.Writer): |
|
27 |
|
28 supported = ('latex','latex2e') |
|
29 """Formats this writer supports.""" |
|
30 |
|
31 settings_spec = ( |
|
32 'LaTeX-Specific Options', |
|
33 'The LaTeX "--output-encoding" default is "latin-1:strict".', |
|
34 (('Specify documentclass. Default is "article".', |
|
35 ['--documentclass'], |
|
36 {'default': 'article', }), |
|
37 ('Specify document options. Multiple options can be given, ' |
|
38 'separated by commas. Default is "10pt,a4paper".', |
|
39 ['--documentoptions'], |
|
40 {'default': '10pt,a4paper', }), |
|
41 ('Use LaTeX footnotes. LaTeX supports only numbered footnotes (does it?). ' |
|
42 'Default: no, uses figures.', |
|
43 ['--use-latex-footnotes'], |
|
44 {'default': 0, 'action': 'store_true', |
|
45 'validator': frontend.validate_boolean}), |
|
46 ('Format for footnote references: one of "superscript" or ' |
|
47 '"brackets". Default is "superscript".', |
|
48 ['--footnote-references'], |
|
49 {'choices': ['superscript', 'brackets'], 'default': 'superscript', |
|
50 'metavar': '<format>', |
|
51 'overrides': 'trim_footnote_reference_space'}), |
|
52 ('Use LaTeX citations. ' |
|
53 'Default: no, uses figures which might get mixed with images.', |
|
54 ['--use-latex-citations'], |
|
55 {'default': 0, 'action': 'store_true', |
|
56 'validator': frontend.validate_boolean}), |
|
57 ('Format for block quote attributions: one of "dash" (em-dash ' |
|
58 'prefix), "parentheses"/"parens", or "none". Default is "dash".', |
|
59 ['--attribution'], |
|
60 {'choices': ['dash', 'parentheses', 'parens', 'none'], |
|
61 'default': 'dash', 'metavar': '<format>'}), |
|
62 ('Specify a stylesheet file. The file will be "input" by latex in ' |
|
63 'the document header. Default is no stylesheet (""). ' |
|
64 'Overrides --stylesheet-path.', |
|
65 ['--stylesheet'], |
|
66 {'default': '', 'metavar': '<file>', |
|
67 'overrides': 'stylesheet_path'}), |
|
68 ('Specify a stylesheet file, relative to the current working ' |
|
69 'directory. Overrides --stylesheet.', |
|
70 ['--stylesheet-path'], |
|
71 {'metavar': '<file>', 'overrides': 'stylesheet'}), |
|
72 ('Table of contents by docutils (default) or LaTeX. LaTeX (writer) ' |
|
73 'supports only one ToC per document, but docutils does not know of ' |
|
74 'pagenumbers. LaTeX table of contents also means LaTeX generates ' |
|
75 'sectionnumbers.', |
|
76 ['--use-latex-toc'], |
|
77 {'default': 0, 'action': 'store_true', |
|
78 'validator': frontend.validate_boolean}), |
|
79 ('Add parts on top of the section hierarchy.', |
|
80 ['--use-part-section'], |
|
81 {'default': 0, 'action': 'store_true', |
|
82 'validator': frontend.validate_boolean}), |
|
83 ('Let LaTeX print author and date, do not show it in docutils ' |
|
84 'document info.', |
|
85 ['--use-latex-docinfo'], |
|
86 {'default': 0, 'action': 'store_true', |
|
87 'validator': frontend.validate_boolean}), |
|
88 ('Use LaTeX abstract environment for the documents abstract.' |
|
89 'Per default the abstract is an unnumbered section.', |
|
90 ['--use-latex-abstract'], |
|
91 {'default': 0, 'action': 'store_true', |
|
92 'validator': frontend.validate_boolean}), |
|
93 ('Color of any hyperlinks embedded in text ' |
|
94 '(default: "blue", "0" to disable).', |
|
95 ['--hyperlink-color'], {'default': 'blue'}), |
|
96 ('Enable compound enumerators for nested enumerated lists ' |
|
97 '(e.g. "1.2.a.ii"). Default: disabled.', |
|
98 ['--compound-enumerators'], |
|
99 {'default': None, 'action': 'store_true', |
|
100 'validator': frontend.validate_boolean}), |
|
101 ('Disable compound enumerators for nested enumerated lists. This is ' |
|
102 'the default.', |
|
103 ['--no-compound-enumerators'], |
|
104 {'action': 'store_false', 'dest': 'compound_enumerators'}), |
|
105 ('Enable section ("." subsection ...) prefixes for compound ' |
|
106 'enumerators. This has no effect without --compound-enumerators. ' |
|
107 'Default: disabled.', |
|
108 ['--section-prefix-for-enumerators'], |
|
109 {'default': None, 'action': 'store_true', |
|
110 'validator': frontend.validate_boolean}), |
|
111 ('Disable section prefixes for compound enumerators. ' |
|
112 'This is the default.', |
|
113 ['--no-section-prefix-for-enumerators'], |
|
114 {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}), |
|
115 ('Set the separator between section number and enumerator ' |
|
116 'for compound enumerated lists. Default is "-".', |
|
117 ['--section-enumerator-separator'], |
|
118 {'default': '-', 'metavar': '<char>'}), |
|
119 ('When possibile, use verbatim for literal-blocks. ' |
|
120 'Default is to always use the mbox environment.', |
|
121 ['--use-verbatim-when-possible'], |
|
122 {'default': 0, 'action': 'store_true', |
|
123 'validator': frontend.validate_boolean}), |
|
124 ('Table style. "standard" with horizontal and vertical lines, ' |
|
125 '"booktabs" (LaTeX booktabs style) only horizontal lines ' |
|
126 'above and below the table and below the header or "nolines". ' |
|
127 'Default: "standard"', |
|
128 ['--table-style'], |
|
129 {'choices': ['standard', 'booktabs','nolines'], 'default': 'standard', |
|
130 'metavar': '<format>'}), |
|
131 ('LaTeX graphicx package option. ' |
|
132 'Possible values are "dvips", "pdftex". "auto" includes LaTeX code ' |
|
133 'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. ' |
|
134 'Default is no option.', |
|
135 ['--graphicx-option'], |
|
136 {'default': ''}), |
|
137 ('LaTeX font encoding. ' |
|
138 'Possible values are "T1", "OT1", "" or some other fontenc option. ' |
|
139 'The font encoding influences available symbols, e.g. "<<" as one ' |
|
140 'character. Default is "" which leads to package "ae" (a T1 ' |
|
141 'emulation using CM fonts).', |
|
142 ['--font-encoding'], |
|
143 {'default': ''}), |
|
144 ('Per default the latex-writer puts the reference title into ' |
|
145 'hyperreferences. Specify "ref*" or "pageref*" to get the section ' |
|
146 'number or the page number.', |
|
147 ['--reference-label'], |
|
148 {'default': None, }), |
|
149 ('Specify style and database for bibtex, for example ' |
|
150 '"--use-bibtex=mystyle,mydb1,mydb2".', |
|
151 ['--use-bibtex'], |
|
152 {'default': None, }), |
|
153 ),) |
|
154 |
|
155 settings_defaults = {'output_encoding': 'latin-1'} |
|
156 |
|
157 relative_path_settings = ('stylesheet_path',) |
|
158 |
|
159 config_section = 'latex2e writer' |
|
160 config_section_dependencies = ('writers',) |
|
161 |
|
162 visitor_attributes = ("head_prefix", "head", |
|
163 "body_prefix", "body", "body_suffix") |
|
164 |
|
165 output = None |
|
166 """Final translated form of `document`.""" |
|
167 |
|
168 def __init__(self): |
|
169 writers.Writer.__init__(self) |
|
170 self.translator_class = LaTeXTranslator |
|
171 |
|
172 def translate(self): |
|
173 visitor = self.translator_class(self.document) |
|
174 self.document.walkabout(visitor) |
|
175 self.output = visitor.astext() |
|
176 # copy parts |
|
177 for attr in self.visitor_attributes: |
|
178 setattr(self, attr, getattr(visitor, attr)) |
|
179 |
|
180 def assemble_parts(self): |
|
181 writers.Writer.assemble_parts(self) |
|
182 for part in self.visitor_attributes: |
|
183 self.parts[part] = ''.join(getattr(self, part)) |
|
184 |
|
185 |
|
186 """ |
|
187 Notes on LaTeX |
|
188 -------------- |
|
189 |
|
190 * LaTeX does not support multiple tocs in one document. |
|
191 (might be no limitation except for docutils documentation) |
|
192 |
|
193 The "minitoc" latex package can produce per-chapter tocs in |
|
194 book and report document classes. |
|
195 |
|
196 * width |
|
197 |
|
198 * linewidth - width of a line in the local environment |
|
199 * textwidth - the width of text on the page |
|
200 |
|
201 Maybe always use linewidth ? |
|
202 |
|
203 *Bug* inside a minipage a (e.g. Sidebar) the linewidth is |
|
204 not changed, needs fix in docutils so that tables |
|
205 are not too wide. |
|
206 |
|
207 So we add locallinewidth set it initially and |
|
208 on entering sidebar and reset on exit. |
|
209 """ |
|
210 |
|
211 class Babel: |
|
212 """Language specifics for LaTeX.""" |
|
213 # country code by a.schlock. |
|
214 # partly manually converted from iso and babel stuff, dialects and some |
|
215 _ISO639_TO_BABEL = { |
|
216 'no': 'norsk', #XXX added by hand ( forget about nynorsk?) |
|
217 'gd': 'scottish', #XXX added by hand |
|
218 'hu': 'magyar', #XXX added by hand |
|
219 'pt': 'portuguese',#XXX added by hand |
|
220 'sl': 'slovenian', |
|
221 'af': 'afrikaans', |
|
222 'bg': 'bulgarian', |
|
223 'br': 'breton', |
|
224 'ca': 'catalan', |
|
225 'cs': 'czech', |
|
226 'cy': 'welsh', |
|
227 'da': 'danish', |
|
228 'fr': 'french', |
|
229 # french, francais, canadien, acadian |
|
230 'de': 'ngerman', #XXX rather than german |
|
231 # ngerman, naustrian, german, germanb, austrian |
|
232 'el': 'greek', |
|
233 'en': 'english', |
|
234 # english, USenglish, american, UKenglish, british, canadian |
|
235 'eo': 'esperanto', |
|
236 'es': 'spanish', |
|
237 'et': 'estonian', |
|
238 'eu': 'basque', |
|
239 'fi': 'finnish', |
|
240 'ga': 'irish', |
|
241 'gl': 'galician', |
|
242 'he': 'hebrew', |
|
243 'hr': 'croatian', |
|
244 'hu': 'hungarian', |
|
245 'is': 'icelandic', |
|
246 'it': 'italian', |
|
247 'la': 'latin', |
|
248 'nl': 'dutch', |
|
249 'pl': 'polish', |
|
250 'pt': 'portuguese', |
|
251 'ro': 'romanian', |
|
252 'ru': 'russian', |
|
253 'sk': 'slovak', |
|
254 'sr': 'serbian', |
|
255 'sv': 'swedish', |
|
256 'tr': 'turkish', |
|
257 'uk': 'ukrainian' |
|
258 } |
|
259 |
|
260 def __init__(self,lang): |
|
261 self.language = lang |
|
262 # pdflatex does not produce double quotes for ngerman in tt. |
|
263 self.double_quote_replacment = None |
|
264 if re.search('^de',self.language): |
|
265 #self.quotes = ("\"`", "\"'") |
|
266 self.quotes = ('{\\glqq}', '{\\grqq}') |
|
267 self.double_quote_replacment = "{\\dq}" |
|
268 elif re.search('^it',self.language): |
|
269 self.quotes = ("``", "''") |
|
270 self.double_quote_replacment = r'{\char`\"}' |
|
271 else: |
|
272 self.quotes = ("``", "''") |
|
273 self.quote_index = 0 |
|
274 |
|
275 def next_quote(self): |
|
276 q = self.quotes[self.quote_index] |
|
277 self.quote_index = (self.quote_index+1)%2 |
|
278 return q |
|
279 |
|
280 def quote_quotes(self,text): |
|
281 t = None |
|
282 for part in text.split('"'): |
|
283 if t == None: |
|
284 t = part |
|
285 else: |
|
286 t += self.next_quote() + part |
|
287 return t |
|
288 |
|
289 def double_quotes_in_tt (self,text): |
|
290 if not self.double_quote_replacment: |
|
291 return text |
|
292 return text.replace('"', self.double_quote_replacment) |
|
293 |
|
294 def get_language(self): |
|
295 if self._ISO639_TO_BABEL.has_key(self.language): |
|
296 return self._ISO639_TO_BABEL[self.language] |
|
297 else: |
|
298 # support dialects. |
|
299 l = self.language.split("_")[0] |
|
300 if self._ISO639_TO_BABEL.has_key(l): |
|
301 return self._ISO639_TO_BABEL[l] |
|
302 return None |
|
303 |
|
304 |
|
305 latex_headings = { |
|
306 'optionlist_environment' : [ |
|
307 '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n' |
|
308 '\\newenvironment{optionlist}[1]\n' |
|
309 '{\\begin{list}{}\n' |
|
310 ' {\\setlength{\\labelwidth}{#1}\n' |
|
311 ' \\setlength{\\rightmargin}{1cm}\n' |
|
312 ' \\setlength{\\leftmargin}{\\rightmargin}\n' |
|
313 ' \\addtolength{\\leftmargin}{\\labelwidth}\n' |
|
314 ' \\addtolength{\\leftmargin}{\\labelsep}\n' |
|
315 ' \\renewcommand{\\makelabel}{\\optionlistlabel}}\n' |
|
316 '}{\\end{list}}\n', |
|
317 ], |
|
318 'lineblock_environment' : [ |
|
319 '\\newlength{\\lineblockindentation}\n' |
|
320 '\\setlength{\\lineblockindentation}{2.5em}\n' |
|
321 '\\newenvironment{lineblock}[1]\n' |
|
322 '{\\begin{list}{}\n' |
|
323 ' {\\setlength{\\partopsep}{\\parskip}\n' |
|
324 ' \\addtolength{\\partopsep}{\\baselineskip}\n' |
|
325 ' \\topsep0pt\\itemsep0.15\\baselineskip\\parsep0pt\n' |
|
326 ' \\leftmargin#1}\n' |
|
327 ' \\raggedright}\n' |
|
328 '{\\end{list}}\n' |
|
329 ], |
|
330 'footnote_floats' : [ |
|
331 '% begin: floats for footnotes tweaking.\n', |
|
332 '\\setlength{\\floatsep}{0.5em}\n', |
|
333 '\\setlength{\\textfloatsep}{\\fill}\n', |
|
334 '\\addtolength{\\textfloatsep}{3em}\n', |
|
335 '\\renewcommand{\\textfraction}{0.5}\n', |
|
336 '\\renewcommand{\\topfraction}{0.5}\n', |
|
337 '\\renewcommand{\\bottomfraction}{0.5}\n', |
|
338 '\\setcounter{totalnumber}{50}\n', |
|
339 '\\setcounter{topnumber}{50}\n', |
|
340 '\\setcounter{bottomnumber}{50}\n', |
|
341 '% end floats for footnotes\n', |
|
342 ], |
|
343 'some_commands' : [ |
|
344 '% some commands, that could be overwritten in the style file.\n' |
|
345 '\\newcommand{\\rubric}[1]' |
|
346 '{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n' |
|
347 '\\newcommand{\\titlereference}[1]{\\textsl{#1}}\n' |
|
348 '% end of "some commands"\n', |
|
349 ] |
|
350 } |
|
351 |
|
352 class DocumentClass: |
|
353 """Details of a LaTeX document class.""" |
|
354 |
|
355 def __init__(self, document_class, with_part=False): |
|
356 self.document_class = document_class |
|
357 self._with_part = with_part |
|
358 |
|
359 def section(self, level): |
|
360 """ Return the section name at the given level for the specific |
|
361 document class. |
|
362 |
|
363 Level is 1,2,3..., as level 0 is the title.""" |
|
364 |
|
365 sections = [ 'section', 'subsection', 'subsubsection', |
|
366 'paragraph', 'subparagraph' ] |
|
367 if self.document_class in ('book', 'report', 'scrreprt', 'scrbook'): |
|
368 sections.insert(0, 'chapter') |
|
369 if self._with_part: |
|
370 sections.insert(0, 'part') |
|
371 if level <= len(sections): |
|
372 return sections[level-1] |
|
373 else: |
|
374 return sections[-1] |
|
375 |
|
376 class Table: |
|
377 """ Manage a table while traversing. |
|
378 Maybe change to a mixin defining the visit/departs, but then |
|
379 class Table internal variables are in the Translator. |
|
380 |
|
381 Table style might be |
|
382 |
|
383 * standard: horizontal and vertical lines |
|
384 * booktabs (requires booktabs latex package): only horizontal lines |
|
385 * nolines, borderless : no lines |
|
386 """ |
|
387 def __init__(self,latex_type,table_style): |
|
388 self._latex_type = latex_type |
|
389 self._table_style = table_style |
|
390 self._open = 0 |
|
391 # miscellaneous attributes |
|
392 self._attrs = {} |
|
393 self._col_width = [] |
|
394 self._rowspan = [] |
|
395 self.stubs = [] |
|
396 |
|
397 def open(self): |
|
398 self._open = 1 |
|
399 self._col_specs = [] |
|
400 self.caption = None |
|
401 self._attrs = {} |
|
402 self._in_head = 0 # maybe context with search |
|
403 def close(self): |
|
404 self._open = 0 |
|
405 self._col_specs = None |
|
406 self.caption = None |
|
407 self._attrs = {} |
|
408 self.stubs = [] |
|
409 def is_open(self): |
|
410 return self._open |
|
411 |
|
412 def set_table_style(self, table_style): |
|
413 if not table_style in ('standard','booktabs','borderless','nolines'): |
|
414 return |
|
415 self._table_style = table_style |
|
416 |
|
417 def used_packages(self): |
|
418 if self._table_style == 'booktabs': |
|
419 return '\\usepackage{booktabs}\n' |
|
420 return '' |
|
421 def get_latex_type(self): |
|
422 return self._latex_type |
|
423 |
|
424 def set(self,attr,value): |
|
425 self._attrs[attr] = value |
|
426 def get(self,attr): |
|
427 if self._attrs.has_key(attr): |
|
428 return self._attrs[attr] |
|
429 return None |
|
430 def get_vertical_bar(self): |
|
431 if self._table_style == 'standard': |
|
432 return '|' |
|
433 return '' |
|
434 # horizontal lines are drawn below a row, because we. |
|
435 def get_opening(self): |
|
436 if self._latex_type == 'longtable': |
|
437 # otherwise longtable might move before paragraph and subparagraph |
|
438 prefix = '\\leavevmode\n' |
|
439 else: |
|
440 prefix = '' |
|
441 return '%s\\begin{%s}[c]' % (prefix, self._latex_type) |
|
442 def get_closing(self): |
|
443 line = "" |
|
444 if self._table_style == 'booktabs': |
|
445 line = '\\bottomrule\n' |
|
446 elif self._table_style == 'standard': |
|
447 lines = '\\hline\n' |
|
448 return '%s\\end{%s}' % (line,self._latex_type) |
|
449 |
|
450 def visit_colspec(self, node): |
|
451 self._col_specs.append(node) |
|
452 # "stubs" list is an attribute of the tgroup element: |
|
453 self.stubs.append(node.attributes.get('stub')) |
|
454 |
|
455 def get_colspecs(self): |
|
456 """ |
|
457 Return column specification for longtable. |
|
458 |
|
459 Assumes reST line length being 80 characters. |
|
460 Table width is hairy. |
|
461 |
|
462 === === |
|
463 ABC DEF |
|
464 === === |
|
465 |
|
466 usually gets to narrow, therefore we add 1 (fiddlefactor). |
|
467 """ |
|
468 width = 80 |
|
469 |
|
470 total_width = 0.0 |
|
471 # first see if we get too wide. |
|
472 for node in self._col_specs: |
|
473 colwidth = float(node['colwidth']+1) / width |
|
474 total_width += colwidth |
|
475 self._col_width = [] |
|
476 self._rowspan = [] |
|
477 # donot make it full linewidth |
|
478 factor = 0.93 |
|
479 if total_width > 1.0: |
|
480 factor /= total_width |
|
481 bar = self.get_vertical_bar() |
|
482 latex_table_spec = "" |
|
483 for node in self._col_specs: |
|
484 colwidth = factor * float(node['colwidth']+1) / width |
|
485 self._col_width.append(colwidth+0.005) |
|
486 self._rowspan.append(0) |
|
487 latex_table_spec += "%sp{%.3f\\locallinewidth}" % (bar,colwidth+0.005) |
|
488 return latex_table_spec+bar |
|
489 |
|
490 def get_column_width(self): |
|
491 """ return columnwidth for current cell (not multicell) |
|
492 """ |
|
493 return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1] |
|
494 |
|
495 def visit_thead(self): |
|
496 self._in_thead = 1 |
|
497 if self._table_style == 'standard': |
|
498 return ['\\hline\n'] |
|
499 elif self._table_style == 'booktabs': |
|
500 return ['\\toprule\n'] |
|
501 return [] |
|
502 def depart_thead(self): |
|
503 a = [] |
|
504 #if self._table_style == 'standard': |
|
505 # a.append('\\hline\n') |
|
506 if self._table_style == 'booktabs': |
|
507 a.append('\\midrule\n') |
|
508 if self._latex_type == 'longtable': |
|
509 a.append('\\endhead\n') |
|
510 # for longtable one could add firsthead, foot and lastfoot |
|
511 self._in_thead = 0 |
|
512 return a |
|
513 def visit_row(self): |
|
514 self._cell_in_row = 0 |
|
515 def depart_row(self): |
|
516 res = [' \\\\\n'] |
|
517 self._cell_in_row = None # remove cell counter |
|
518 for i in range(len(self._rowspan)): |
|
519 if (self._rowspan[i]>0): |
|
520 self._rowspan[i] -= 1 |
|
521 |
|
522 if self._table_style == 'standard': |
|
523 rowspans = [] |
|
524 for i in range(len(self._rowspan)): |
|
525 if (self._rowspan[i]<=0): |
|
526 rowspans.append(i+1) |
|
527 if len(rowspans)==len(self._rowspan): |
|
528 res.append('\\hline\n') |
|
529 else: |
|
530 cline = '' |
|
531 rowspans.reverse() |
|
532 # TODO merge clines |
|
533 while 1: |
|
534 try: |
|
535 c_start = rowspans.pop() |
|
536 except: |
|
537 break |
|
538 cline += '\\cline{%d-%d}\n' % (c_start,c_start) |
|
539 res.append(cline) |
|
540 return res |
|
541 |
|
542 def set_rowspan(self,cell,value): |
|
543 try: |
|
544 self._rowspan[cell] = value |
|
545 except: |
|
546 pass |
|
547 def get_rowspan(self,cell): |
|
548 try: |
|
549 return self._rowspan[cell] |
|
550 except: |
|
551 return 0 |
|
552 def get_entry_number(self): |
|
553 return self._cell_in_row |
|
554 def visit_entry(self): |
|
555 self._cell_in_row += 1 |
|
556 def is_stub_column(self): |
|
557 if len(self.stubs) >= self._cell_in_row: |
|
558 return self.stubs[self._cell_in_row-1] |
|
559 return False |
|
560 |
|
561 |
|
562 class LaTeXTranslator(nodes.NodeVisitor): |
|
563 |
|
564 # When options are given to the documentclass, latex will pass them |
|
565 # to other packages, as done with babel. |
|
566 # Dummy settings might be taken from document settings |
|
567 |
|
568 # Templates |
|
569 # --------- |
|
570 |
|
571 latex_head = '\\documentclass[%s]{%s}\n' |
|
572 linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n' |
|
573 stylesheet = '\\input{%s}\n' |
|
574 # add a generated on day , machine by user using docutils version. |
|
575 generator = '% generated by Docutils <http://docutils.sourceforge.net/>\n' |
|
576 # Config setting defaults |
|
577 # ----------------------- |
|
578 |
|
579 # use latex tableofcontents or let docutils do it. |
|
580 use_latex_toc = 0 |
|
581 |
|
582 # TODO: use mixins for different implementations. |
|
583 # list environment for docinfo. else tabularx |
|
584 use_optionlist_for_docinfo = 0 # NOT YET IN USE |
|
585 |
|
586 # Use compound enumerations (1.A.1.) |
|
587 compound_enumerators = 0 |
|
588 |
|
589 # If using compound enumerations, include section information. |
|
590 section_prefix_for_enumerators = 0 |
|
591 |
|
592 # This is the character that separates the section ("." subsection ...) |
|
593 # prefix from the regular list enumerator. |
|
594 section_enumerator_separator = '-' |
|
595 |
|
596 # default link color |
|
597 hyperlink_color = "blue" |
|
598 |
|
599 def __init__(self, document): |
|
600 nodes.NodeVisitor.__init__(self, document) |
|
601 self.settings = settings = document.settings |
|
602 self.latex_encoding = self.to_latex_encoding(settings.output_encoding) |
|
603 self.use_latex_toc = settings.use_latex_toc |
|
604 self.use_latex_docinfo = settings.use_latex_docinfo |
|
605 self.use_latex_footnotes = settings.use_latex_footnotes |
|
606 self._use_latex_citations = settings.use_latex_citations |
|
607 self._reference_label = settings.reference_label |
|
608 self.hyperlink_color = settings.hyperlink_color |
|
609 self.compound_enumerators = settings.compound_enumerators |
|
610 self.font_encoding = settings.font_encoding |
|
611 self.section_prefix_for_enumerators = ( |
|
612 settings.section_prefix_for_enumerators) |
|
613 self.section_enumerator_separator = ( |
|
614 settings.section_enumerator_separator.replace('_', '\\_')) |
|
615 if self.hyperlink_color == '0': |
|
616 self.hyperlink_color = 'black' |
|
617 self.colorlinks = 'false' |
|
618 else: |
|
619 self.colorlinks = 'true' |
|
620 |
|
621 if self.settings.use_bibtex: |
|
622 self.bibtex = self.settings.use_bibtex.split(",",1) |
|
623 # TODO avoid errors on not declared citations. |
|
624 else: |
|
625 self.bibtex = None |
|
626 # language: labels, bibliographic_fields, and author_separators. |
|
627 # to allow writing labes for specific languages. |
|
628 self.language = languages.get_language(settings.language_code) |
|
629 self.babel = Babel(settings.language_code) |
|
630 self.author_separator = self.language.author_separators[0] |
|
631 self.d_options = self.settings.documentoptions |
|
632 if self.babel.get_language(): |
|
633 self.d_options += ',%s' % self.babel.get_language() |
|
634 |
|
635 self.d_class = DocumentClass(settings.documentclass, |
|
636 settings.use_part_section) |
|
637 # object for a table while proccessing. |
|
638 self.table_stack = [] |
|
639 self.active_table = Table('longtable',settings.table_style) |
|
640 |
|
641 # HACK. Should have more sophisticated typearea handling. |
|
642 if settings.documentclass.find('scr') == -1: |
|
643 self.typearea = '\\usepackage[DIV12]{typearea}\n' |
|
644 else: |
|
645 if self.d_options.find('DIV') == -1 and self.d_options.find('BCOR') == -1: |
|
646 self.typearea = '\\typearea{12}\n' |
|
647 else: |
|
648 self.typearea = '' |
|
649 |
|
650 if self.font_encoding == 'OT1': |
|
651 fontenc_header = '' |
|
652 elif self.font_encoding == '': |
|
653 fontenc_header = '\\usepackage{ae}\n\\usepackage{aeguill}\n' |
|
654 else: |
|
655 fontenc_header = '\\usepackage[%s]{fontenc}\n' % (self.font_encoding,) |
|
656 if self.latex_encoding.startswith('utf8'): |
|
657 input_encoding = '\\usepackage{ucs}\n\\usepackage[utf8x]{inputenc}\n' |
|
658 else: |
|
659 input_encoding = '\\usepackage[%s]{inputenc}\n' % self.latex_encoding |
|
660 if self.settings.graphicx_option == '': |
|
661 self.graphicx_package = '\\usepackage{graphicx}\n' |
|
662 elif self.settings.graphicx_option.lower() == 'auto': |
|
663 self.graphicx_package = '\n'.join( |
|
664 ('%Check if we are compiling under latex or pdflatex', |
|
665 '\\ifx\\pdftexversion\\undefined', |
|
666 ' \\usepackage{graphicx}', |
|
667 '\\else', |
|
668 ' \\usepackage[pdftex]{graphicx}', |
|
669 '\\fi\n')) |
|
670 else: |
|
671 self.graphicx_package = ( |
|
672 '\\usepackage[%s]{graphicx}\n' % self.settings.graphicx_option) |
|
673 |
|
674 self.head_prefix = [ |
|
675 self.latex_head % (self.d_options,self.settings.documentclass), |
|
676 '\\usepackage{babel}\n', # language is in documents settings. |
|
677 fontenc_header, |
|
678 '\\usepackage{shortvrb}\n', # allows verb in footnotes. |
|
679 input_encoding, |
|
680 # * tabularx: for docinfo, automatic width of columns, always on one page. |
|
681 '\\usepackage{tabularx}\n', |
|
682 '\\usepackage{longtable}\n', |
|
683 self.active_table.used_packages(), |
|
684 # possible other packages. |
|
685 # * fancyhdr |
|
686 # * ltxtable is a combination of tabularx and longtable (pagebreaks). |
|
687 # but ?? |
|
688 # |
|
689 # extra space between text in tables and the line above them |
|
690 '\\setlength{\\extrarowheight}{2pt}\n', |
|
691 '\\usepackage{amsmath}\n', # what fore amsmath. |
|
692 self.graphicx_package, |
|
693 '\\usepackage{color}\n', |
|
694 '\\usepackage{multirow}\n', |
|
695 '\\usepackage{ifthen}\n', # before hyperref! |
|
696 self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color), |
|
697 self.typearea, |
|
698 self.generator, |
|
699 # latex lengths |
|
700 '\\newlength{\\admonitionwidth}\n', |
|
701 '\\setlength{\\admonitionwidth}{0.9\\textwidth}\n' |
|
702 # width for docinfo tablewidth |
|
703 '\\newlength{\\docinfowidth}\n', |
|
704 '\\setlength{\\docinfowidth}{0.9\\textwidth}\n' |
|
705 # linewidth of current environment, so tables are not wider |
|
706 # than the sidebar: using locallinewidth seems to defer evaluation |
|
707 # of linewidth, this is fixing it. |
|
708 '\\newlength{\\locallinewidth}\n', |
|
709 # will be set later. |
|
710 ] |
|
711 self.head_prefix.extend( latex_headings['optionlist_environment'] ) |
|
712 self.head_prefix.extend( latex_headings['lineblock_environment'] ) |
|
713 self.head_prefix.extend( latex_headings['footnote_floats'] ) |
|
714 self.head_prefix.extend( latex_headings['some_commands'] ) |
|
715 ## stylesheet is last: so it might be possible to overwrite defaults. |
|
716 stylesheet = utils.get_stylesheet_reference(settings) |
|
717 if stylesheet: |
|
718 settings.record_dependencies.add(stylesheet) |
|
719 self.head_prefix.append(self.stylesheet % (stylesheet)) |
|
720 |
|
721 if self.linking: # and maybe check for pdf |
|
722 self.pdfinfo = [ ] |
|
723 self.pdfauthor = None |
|
724 # pdftitle, pdfsubject, pdfauthor, pdfkeywords, |
|
725 # pdfcreator, pdfproducer |
|
726 else: |
|
727 self.pdfinfo = None |
|
728 # NOTE: Latex wants a date and an author, rst puts this into |
|
729 # docinfo, so normally we do not want latex author/date handling. |
|
730 # latex article has its own handling of date and author, deactivate. |
|
731 # self.astext() adds \title{...} \author{...} \date{...}, even if the |
|
732 # "..." are empty strings. |
|
733 self.head = [ ] |
|
734 # separate title, so we can appen subtitle. |
|
735 self.title = '' |
|
736 # if use_latex_docinfo: collects lists of author/organization/contact/address lines |
|
737 self.author_stack = [] |
|
738 self.date = '' |
|
739 |
|
740 self.body_prefix = ['\\raggedbottom\n'] |
|
741 self.body = [] |
|
742 self.body_suffix = ['\n'] |
|
743 self.section_level = 0 |
|
744 self.context = [] |
|
745 self.topic_classes = [] |
|
746 # column specification for tables |
|
747 self.table_caption = None |
|
748 |
|
749 # Flags to encode |
|
750 # --------------- |
|
751 # verbatim: to tell encode not to encode. |
|
752 self.verbatim = 0 |
|
753 # insert_newline: to tell encode to replace blanks by "~". |
|
754 self.insert_none_breaking_blanks = 0 |
|
755 # insert_newline: to tell encode to add latex newline. |
|
756 self.insert_newline = 0 |
|
757 # mbox_newline: to tell encode to add mbox and newline. |
|
758 self.mbox_newline = 0 |
|
759 # inside citation reference labels underscores dont need to be escaped. |
|
760 self.inside_citation_reference_label = 0 |
|
761 |
|
762 # Stack of section counters so that we don't have to use_latex_toc. |
|
763 # This will grow and shrink as processing occurs. |
|
764 # Initialized for potential first-level sections. |
|
765 self._section_number = [0] |
|
766 |
|
767 # The current stack of enumerations so that we can expand |
|
768 # them into a compound enumeration. |
|
769 self._enumeration_counters = [] |
|
770 |
|
771 # The maximum number of enumeration counters we've used. |
|
772 # If we go beyond this number, we need to create a new |
|
773 # counter; otherwise, just reuse an old one. |
|
774 self._max_enumeration_counters = 0 |
|
775 |
|
776 self._bibitems = [] |
|
777 |
|
778 # docinfo. |
|
779 self.docinfo = None |
|
780 # inside literal block: no quote mangling. |
|
781 self.literal_block = 0 |
|
782 self.literal_block_stack = [] |
|
783 self.literal = 0 |
|
784 # true when encoding in math mode |
|
785 self.mathmode = 0 |
|
786 |
|
787 def to_latex_encoding(self,docutils_encoding): |
|
788 """ |
|
789 Translate docutils encoding name into latex's. |
|
790 |
|
791 Default fallback method is remove "-" and "_" chars from docutils_encoding. |
|
792 |
|
793 """ |
|
794 tr = { "iso-8859-1": "latin1", # west european |
|
795 "iso-8859-2": "latin2", # east european |
|
796 "iso-8859-3": "latin3", # esperanto, maltese |
|
797 "iso-8859-4": "latin4", # north european,scandinavian, baltic |
|
798 "iso-8859-5": "iso88595", # cyrillic (ISO) |
|
799 "iso-8859-9": "latin5", # turkish |
|
800 "iso-8859-15": "latin9", # latin9, update to latin1. |
|
801 "mac_cyrillic": "maccyr", # cyrillic (on Mac) |
|
802 "windows-1251": "cp1251", # cyrillic (on Windows) |
|
803 "koi8-r": "koi8-r", # cyrillic (Russian) |
|
804 "koi8-u": "koi8-u", # cyrillic (Ukrainian) |
|
805 "windows-1250": "cp1250", # |
|
806 "windows-1252": "cp1252", # |
|
807 "us-ascii": "ascii", # ASCII (US) |
|
808 # unmatched encodings |
|
809 #"": "applemac", |
|
810 #"": "ansinew", # windows 3.1 ansi |
|
811 #"": "ascii", # ASCII encoding for the range 32--127. |
|
812 #"": "cp437", # dos latine us |
|
813 #"": "cp850", # dos latin 1 |
|
814 #"": "cp852", # dos latin 2 |
|
815 #"": "decmulti", |
|
816 #"": "latin10", |
|
817 #"iso-8859-6": "" # arabic |
|
818 #"iso-8859-7": "" # greek |
|
819 #"iso-8859-8": "" # hebrew |
|
820 #"iso-8859-10": "" # latin6, more complete iso-8859-4 |
|
821 } |
|
822 if tr.has_key(docutils_encoding.lower()): |
|
823 return tr[docutils_encoding.lower()] |
|
824 # convert: latin-1 and utf-8 and similar things |
|
825 return docutils_encoding.replace("_", "").replace("-", "").lower() |
|
826 |
|
827 def language_label(self, docutil_label): |
|
828 return self.language.labels[docutil_label] |
|
829 |
|
830 latex_equivalents = { |
|
831 u'\u00A0' : '~', |
|
832 u'\u2013' : '{--}', |
|
833 u'\u2014' : '{---}', |
|
834 u'\u2018' : '`', |
|
835 u'\u2019' : '\'', |
|
836 u'\u201A' : ',', |
|
837 u'\u201C' : '``', |
|
838 u'\u201D' : '\'\'', |
|
839 u'\u201E' : ',,', |
|
840 u'\u2020' : '{\\dag}', |
|
841 u'\u2021' : '{\\ddag}', |
|
842 u'\u2026' : '{\\dots}', |
|
843 u'\u2122' : '{\\texttrademark}', |
|
844 u'\u21d4' : '{$\\Leftrightarrow$}', |
|
845 # greek alphabet ? |
|
846 } |
|
847 |
|
848 def unicode_to_latex(self,text): |
|
849 # see LaTeX codec |
|
850 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124 |
|
851 # Only some special chracters are translated, for documents with many |
|
852 # utf-8 chars one should use the LaTeX unicode package. |
|
853 for uchar in self.latex_equivalents.keys(): |
|
854 text = text.replace(uchar,self.latex_equivalents[uchar]) |
|
855 return text |
|
856 |
|
857 def ensure_math(self, text): |
|
858 if not self.__dict__.has_key('ensure_math_re'): |
|
859 chars = { |
|
860 # lnot,pm,twosuperior,threesuperior,mu,onesuperior,times,div |
|
861 'latin1' : '\xac\xb1\xb2\xb3\xb5\xb9\xd7\xf7' , |
|
862 # also latin5 and latin9 |
|
863 } |
|
864 self.ensure_math_re = re.compile('([%s])' % chars['latin1']) |
|
865 text = self.ensure_math_re.sub(r'\\ensuremath{\1}', text) |
|
866 return text |
|
867 |
|
868 def encode(self, text): |
|
869 """ |
|
870 Encode special characters (``# $ % & ~ _ ^ \ { }``) in `text` & return |
|
871 """ |
|
872 # Escaping with a backslash does not help with backslashes, ~ and ^. |
|
873 |
|
874 # < > are only available in math-mode or tt font. (really ?) |
|
875 # $ starts math- mode. |
|
876 # AND quotes |
|
877 if self.verbatim: |
|
878 return text |
|
879 # compile the regexps once. do it here so one can see them. |
|
880 # |
|
881 # first the braces. |
|
882 if not self.__dict__.has_key('encode_re_braces'): |
|
883 self.encode_re_braces = re.compile(r'([{}])') |
|
884 text = self.encode_re_braces.sub(r'{\\\1}',text) |
|
885 if not self.__dict__.has_key('encode_re_bslash'): |
|
886 # find backslash: except in the form '{\{}' or '{\}}'. |
|
887 self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})') |
|
888 # then the backslash: except in the form from line above: |
|
889 # either '{\{}' or '{\}}'. |
|
890 text = self.encode_re_bslash.sub(r'{\\textbackslash}', text) |
|
891 |
|
892 # then dollar |
|
893 text = text.replace("$", '{\\$}') |
|
894 if not ( self.literal_block or self.literal or self.mathmode ): |
|
895 # the vertical bar: in mathmode |,\vert or \mid |
|
896 # in textmode \textbar |
|
897 text = text.replace("|", '{\\textbar}') |
|
898 text = text.replace("<", '{\\textless}') |
|
899 text = text.replace(">", '{\\textgreater}') |
|
900 # then |
|
901 text = text.replace("&", '{\\&}') |
|
902 # the ^: |
|
903 # * verb|^| does not work in mbox. |
|
904 # * mathmode has wedge. hat{~} would also work. |
|
905 # text = text.replace("^", '{\\ensuremath{^\\wedge}}') |
|
906 text = text.replace("^", '{\\textasciicircum}') |
|
907 text = text.replace("%", '{\\%}') |
|
908 text = text.replace("#", '{\\#}') |
|
909 text = text.replace("~", '{\\textasciitilde}') |
|
910 # Separate compound characters, e.g. "--" to "-{}-". (The |
|
911 # actual separation is done later; see below.) |
|
912 separate_chars = '-' |
|
913 if self.literal_block or self.literal: |
|
914 # In monospace-font, we also separate ",,", "``" and "''" |
|
915 # and some other characters which can't occur in |
|
916 # non-literal text. |
|
917 separate_chars += ',`\'"<>' |
|
918 # pdflatex does not produce doublequotes for ngerman. |
|
919 text = self.babel.double_quotes_in_tt(text) |
|
920 if self.font_encoding == 'OT1': |
|
921 # We're using OT1 font-encoding and have to replace |
|
922 # underscore by underlined blank, because this has |
|
923 # correct width. |
|
924 text = text.replace('_', '{\\underline{ }}') |
|
925 # And the tt-backslash doesn't work in OT1, so we use |
|
926 # a mirrored slash. |
|
927 text = text.replace('\\textbackslash', '\\reflectbox{/}') |
|
928 else: |
|
929 text = text.replace('_', '{\\_}') |
|
930 else: |
|
931 text = self.babel.quote_quotes(text) |
|
932 if not self.inside_citation_reference_label: |
|
933 text = text.replace("_", '{\\_}') |
|
934 for char in separate_chars * 2: |
|
935 # Do it twice ("* 2") becaues otherwise we would replace |
|
936 # "---" by "-{}--". |
|
937 text = text.replace(char + char, char + '{}' + char) |
|
938 if self.insert_newline or self.literal_block: |
|
939 # Insert a blank before the newline, to avoid |
|
940 # ! LaTeX Error: There's no line here to end. |
|
941 text = text.replace("\n", '~\\\\\n') |
|
942 elif self.mbox_newline: |
|
943 if self.literal_block: |
|
944 closings = "}" * len(self.literal_block_stack) |
|
945 openings = "".join(self.literal_block_stack) |
|
946 else: |
|
947 closings = "" |
|
948 openings = "" |
|
949 text = text.replace("\n", "%s}\\\\\n\\mbox{%s" % (closings,openings)) |
|
950 text = text.replace('[', '{[}').replace(']', '{]}') |
|
951 if self.insert_none_breaking_blanks: |
|
952 text = text.replace(' ', '~') |
|
953 if self.latex_encoding != 'utf8': |
|
954 text = self.unicode_to_latex(text) |
|
955 text = self.ensure_math(text) |
|
956 return text |
|
957 |
|
958 def attval(self, text, |
|
959 whitespace=re.compile('[\n\r\t\v\f]')): |
|
960 """Cleanse, encode, and return attribute value text.""" |
|
961 return self.encode(whitespace.sub(' ', text)) |
|
962 |
|
963 def astext(self): |
|
964 if self.pdfinfo is not None and self.pdfauthor: |
|
965 self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor) |
|
966 if self.pdfinfo: |
|
967 pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n' |
|
968 else: |
|
969 pdfinfo = '' |
|
970 head = '\\title{%s}\n\\author{%s}\n\\date{%s}\n' % \ |
|
971 (self.title, |
|
972 ' \\and\n'.join(['~\\\\\n'.join(author_lines) |
|
973 for author_lines in self.author_stack]), |
|
974 self.date) |
|
975 return ''.join(self.head_prefix + [head] + self.head + [pdfinfo] |
|
976 + self.body_prefix + self.body + self.body_suffix) |
|
977 |
|
978 def visit_Text(self, node): |
|
979 self.body.append(self.encode(node.astext())) |
|
980 |
|
981 def depart_Text(self, node): |
|
982 pass |
|
983 |
|
984 def visit_address(self, node): |
|
985 self.visit_docinfo_item(node, 'address') |
|
986 |
|
987 def depart_address(self, node): |
|
988 self.depart_docinfo_item(node) |
|
989 |
|
990 def visit_admonition(self, node, name=''): |
|
991 self.body.append('\\begin{center}\\begin{sffamily}\n') |
|
992 self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n') |
|
993 if name: |
|
994 self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n'); |
|
995 self.body.append('\\vspace{2mm}\n') |
|
996 |
|
997 |
|
998 def depart_admonition(self, node=None): |
|
999 self.body.append('}}\n') # end parbox fbox |
|
1000 self.body.append('\\end{sffamily}\n\\end{center}\n'); |
|
1001 |
|
1002 def visit_attention(self, node): |
|
1003 self.visit_admonition(node, 'attention') |
|
1004 |
|
1005 def depart_attention(self, node): |
|
1006 self.depart_admonition() |
|
1007 |
|
1008 def visit_author(self, node): |
|
1009 self.visit_docinfo_item(node, 'author') |
|
1010 |
|
1011 def depart_author(self, node): |
|
1012 self.depart_docinfo_item(node) |
|
1013 |
|
1014 def visit_authors(self, node): |
|
1015 # not used: visit_author is called anyway for each author. |
|
1016 pass |
|
1017 |
|
1018 def depart_authors(self, node): |
|
1019 pass |
|
1020 |
|
1021 def visit_block_quote(self, node): |
|
1022 self.body.append( '\\begin{quote}\n') |
|
1023 |
|
1024 def depart_block_quote(self, node): |
|
1025 self.body.append( '\\end{quote}\n') |
|
1026 |
|
1027 def visit_bullet_list(self, node): |
|
1028 if 'contents' in self.topic_classes: |
|
1029 if self.use_latex_toc: |
|
1030 raise nodes.SkipNode |
|
1031 self.body.append( '\\begin{list}{}{}\n' ) |
|
1032 else: |
|
1033 self.body.append( '\\begin{itemize}\n' ) |
|
1034 |
|
1035 def depart_bullet_list(self, node): |
|
1036 if 'contents' in self.topic_classes: |
|
1037 self.body.append( '\\end{list}\n' ) |
|
1038 else: |
|
1039 self.body.append( '\\end{itemize}\n' ) |
|
1040 |
|
1041 # Imperfect superscript/subscript handling: mathmode italicizes |
|
1042 # all letters by default. |
|
1043 def visit_superscript(self, node): |
|
1044 self.body.append('$^{') |
|
1045 self.mathmode = 1 |
|
1046 |
|
1047 def depart_superscript(self, node): |
|
1048 self.body.append('}$') |
|
1049 self.mathmode = 0 |
|
1050 |
|
1051 def visit_subscript(self, node): |
|
1052 self.body.append('$_{') |
|
1053 self.mathmode = 1 |
|
1054 |
|
1055 def depart_subscript(self, node): |
|
1056 self.body.append('}$') |
|
1057 self.mathmode = 0 |
|
1058 |
|
1059 def visit_caption(self, node): |
|
1060 self.body.append( '\\caption{' ) |
|
1061 |
|
1062 def depart_caption(self, node): |
|
1063 self.body.append('}') |
|
1064 |
|
1065 def visit_caution(self, node): |
|
1066 self.visit_admonition(node, 'caution') |
|
1067 |
|
1068 def depart_caution(self, node): |
|
1069 self.depart_admonition() |
|
1070 |
|
1071 def visit_title_reference(self, node): |
|
1072 self.body.append( '\\titlereference{' ) |
|
1073 |
|
1074 def depart_title_reference(self, node): |
|
1075 self.body.append( '}' ) |
|
1076 |
|
1077 def visit_citation(self, node): |
|
1078 # TODO maybe use cite bibitems |
|
1079 if self._use_latex_citations: |
|
1080 self.context.append(len(self.body)) |
|
1081 else: |
|
1082 self.body.append('\\begin{figure}[b]') |
|
1083 for id in node['ids']: |
|
1084 self.body.append('\\hypertarget{%s}' % id) |
|
1085 |
|
1086 def depart_citation(self, node): |
|
1087 if self._use_latex_citations: |
|
1088 size = self.context.pop() |
|
1089 label = self.body[size] |
|
1090 text = ''.join(self.body[size+1:]) |
|
1091 del self.body[size:] |
|
1092 self._bibitems.append([label, text]) |
|
1093 else: |
|
1094 self.body.append('\\end{figure}\n') |
|
1095 |
|
1096 def visit_citation_reference(self, node): |
|
1097 if self._use_latex_citations: |
|
1098 self.body.append('\\cite{') |
|
1099 self.inside_citation_reference_label = 1 |
|
1100 else: |
|
1101 href = '' |
|
1102 if node.has_key('refid'): |
|
1103 href = node['refid'] |
|
1104 elif node.has_key('refname'): |
|
1105 href = self.document.nameids[node['refname']] |
|
1106 self.body.append('[\\hyperlink{%s}{' % href) |
|
1107 |
|
1108 def depart_citation_reference(self, node): |
|
1109 if self._use_latex_citations: |
|
1110 self.body.append('}') |
|
1111 self.inside_citation_reference_label = 0 |
|
1112 else: |
|
1113 self.body.append('}]') |
|
1114 |
|
1115 def visit_classifier(self, node): |
|
1116 self.body.append( '(\\textbf{' ) |
|
1117 |
|
1118 def depart_classifier(self, node): |
|
1119 self.body.append( '})\n' ) |
|
1120 |
|
1121 def visit_colspec(self, node): |
|
1122 self.active_table.visit_colspec(node) |
|
1123 |
|
1124 def depart_colspec(self, node): |
|
1125 pass |
|
1126 |
|
1127 def visit_comment(self, node): |
|
1128 # Escape end of line by a new comment start in comment text. |
|
1129 self.body.append('%% %s \n' % node.astext().replace('\n', '\n% ')) |
|
1130 raise nodes.SkipNode |
|
1131 |
|
1132 def visit_compound(self, node): |
|
1133 pass |
|
1134 |
|
1135 def depart_compound(self, node): |
|
1136 pass |
|
1137 |
|
1138 def visit_contact(self, node): |
|
1139 self.visit_docinfo_item(node, 'contact') |
|
1140 |
|
1141 def depart_contact(self, node): |
|
1142 self.depart_docinfo_item(node) |
|
1143 |
|
1144 def visit_container(self, node): |
|
1145 pass |
|
1146 |
|
1147 def depart_container(self, node): |
|
1148 pass |
|
1149 |
|
1150 def visit_copyright(self, node): |
|
1151 self.visit_docinfo_item(node, 'copyright') |
|
1152 |
|
1153 def depart_copyright(self, node): |
|
1154 self.depart_docinfo_item(node) |
|
1155 |
|
1156 def visit_danger(self, node): |
|
1157 self.visit_admonition(node, 'danger') |
|
1158 |
|
1159 def depart_danger(self, node): |
|
1160 self.depart_admonition() |
|
1161 |
|
1162 def visit_date(self, node): |
|
1163 self.visit_docinfo_item(node, 'date') |
|
1164 |
|
1165 def depart_date(self, node): |
|
1166 self.depart_docinfo_item(node) |
|
1167 |
|
1168 def visit_decoration(self, node): |
|
1169 pass |
|
1170 |
|
1171 def depart_decoration(self, node): |
|
1172 pass |
|
1173 |
|
1174 def visit_definition(self, node): |
|
1175 pass |
|
1176 |
|
1177 def depart_definition(self, node): |
|
1178 self.body.append('\n') |
|
1179 |
|
1180 def visit_definition_list(self, node): |
|
1181 self.body.append( '\\begin{description}\n' ) |
|
1182 |
|
1183 def depart_definition_list(self, node): |
|
1184 self.body.append( '\\end{description}\n' ) |
|
1185 |
|
1186 def visit_definition_list_item(self, node): |
|
1187 pass |
|
1188 |
|
1189 def depart_definition_list_item(self, node): |
|
1190 pass |
|
1191 |
|
1192 def visit_description(self, node): |
|
1193 self.body.append( ' ' ) |
|
1194 |
|
1195 def depart_description(self, node): |
|
1196 pass |
|
1197 |
|
1198 def visit_docinfo(self, node): |
|
1199 self.docinfo = [] |
|
1200 self.docinfo.append('%' + '_'*75 + '\n') |
|
1201 self.docinfo.append('\\begin{center}\n') |
|
1202 self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n') |
|
1203 |
|
1204 def depart_docinfo(self, node): |
|
1205 self.docinfo.append('\\end{tabularx}\n') |
|
1206 self.docinfo.append('\\end{center}\n') |
|
1207 self.body = self.docinfo + self.body |
|
1208 # clear docinfo, so field names are no longer appended. |
|
1209 self.docinfo = None |
|
1210 |
|
1211 def visit_docinfo_item(self, node, name): |
|
1212 if name == 'author': |
|
1213 if not self.pdfinfo == None: |
|
1214 if not self.pdfauthor: |
|
1215 self.pdfauthor = self.attval(node.astext()) |
|
1216 else: |
|
1217 self.pdfauthor += self.author_separator + self.attval(node.astext()) |
|
1218 if self.use_latex_docinfo: |
|
1219 if name in ('author', 'organization', 'contact', 'address'): |
|
1220 # We attach these to the last author. If any of them precedes |
|
1221 # the first author, put them in a separate "author" group (for |
|
1222 # no better semantics). |
|
1223 if name == 'author' or not self.author_stack: |
|
1224 self.author_stack.append([]) |
|
1225 if name == 'address': # newlines are meaningful |
|
1226 self.insert_newline = 1 |
|
1227 text = self.encode(node.astext()) |
|
1228 self.insert_newline = 0 |
|
1229 else: |
|
1230 text = self.attval(node.astext()) |
|
1231 self.author_stack[-1].append(text) |
|
1232 raise nodes.SkipNode |
|
1233 elif name == 'date': |
|
1234 self.date = self.attval(node.astext()) |
|
1235 raise nodes.SkipNode |
|
1236 self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name)) |
|
1237 if name == 'address': |
|
1238 self.insert_newline = 1 |
|
1239 self.docinfo.append('{\\raggedright\n') |
|
1240 self.context.append(' } \\\\\n') |
|
1241 else: |
|
1242 self.context.append(' \\\\\n') |
|
1243 self.context.append(self.docinfo) |
|
1244 self.context.append(len(self.body)) |
|
1245 |
|
1246 def depart_docinfo_item(self, node): |
|
1247 size = self.context.pop() |
|
1248 dest = self.context.pop() |
|
1249 tail = self.context.pop() |
|
1250 tail = self.body[size:] + [tail] |
|
1251 del self.body[size:] |
|
1252 dest.extend(tail) |
|
1253 # for address we did set insert_newline |
|
1254 self.insert_newline = 0 |
|
1255 |
|
1256 def visit_doctest_block(self, node): |
|
1257 self.body.append( '\\begin{verbatim}' ) |
|
1258 self.verbatim = 1 |
|
1259 |
|
1260 def depart_doctest_block(self, node): |
|
1261 self.body.append( '\\end{verbatim}\n' ) |
|
1262 self.verbatim = 0 |
|
1263 |
|
1264 def visit_document(self, node): |
|
1265 self.body_prefix.append('\\begin{document}\n') |
|
1266 # titled document? |
|
1267 if self.use_latex_docinfo or len(node) and isinstance(node[0], nodes.title): |
|
1268 self.body_prefix.append('\\maketitle\n') |
|
1269 # alternative use titlepage environment. |
|
1270 # \begin{titlepage} |
|
1271 # ... |
|
1272 self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n') |
|
1273 |
|
1274 def depart_document(self, node): |
|
1275 # TODO insertion point of bibliography should none automatic. |
|
1276 if self._use_latex_citations and len(self._bibitems)>0: |
|
1277 if not self.bibtex: |
|
1278 widest_label = "" |
|
1279 for bi in self._bibitems: |
|
1280 if len(widest_label)<len(bi[0]): |
|
1281 widest_label = bi[0] |
|
1282 self.body.append('\n\\begin{thebibliography}{%s}\n'%widest_label) |
|
1283 for bi in self._bibitems: |
|
1284 # cite_key: underscores must not be escaped |
|
1285 cite_key = bi[0].replace(r"{\_}","_") |
|
1286 self.body.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], cite_key, bi[1])) |
|
1287 self.body.append('\\end{thebibliography}\n') |
|
1288 else: |
|
1289 self.body.append('\n\\bibliographystyle{%s}\n' % self.bibtex[0]) |
|
1290 self.body.append('\\bibliography{%s}\n' % self.bibtex[1]) |
|
1291 |
|
1292 self.body_suffix.append('\\end{document}\n') |
|
1293 |
|
1294 def visit_emphasis(self, node): |
|
1295 self.body.append('\\emph{') |
|
1296 self.literal_block_stack.append('\\emph{') |
|
1297 |
|
1298 def depart_emphasis(self, node): |
|
1299 self.body.append('}') |
|
1300 self.literal_block_stack.pop() |
|
1301 |
|
1302 def visit_entry(self, node): |
|
1303 self.active_table.visit_entry() |
|
1304 # cell separation |
|
1305 if self.active_table.get_entry_number() == 1: |
|
1306 # if the firstrow is a multirow, this actually is the second row. |
|
1307 # this gets hairy if rowspans follow each other. |
|
1308 if self.active_table.get_rowspan(0): |
|
1309 count = 0 |
|
1310 while self.active_table.get_rowspan(count): |
|
1311 count += 1 |
|
1312 self.body.append(' & ') |
|
1313 self.active_table.visit_entry() # increment cell count |
|
1314 else: |
|
1315 self.body.append(' & ') |
|
1316 |
|
1317 # multi{row,column} |
|
1318 # IN WORK BUG TODO HACK continues here |
|
1319 # multirow in LaTeX simply will enlarge the cell over several rows |
|
1320 # (the following n if n is positive, the former if negative). |
|
1321 if node.has_key('morerows') and node.has_key('morecols'): |
|
1322 raise NotImplementedError('Cells that ' |
|
1323 'span multiple rows *and* columns are not supported, sorry.') |
|
1324 if node.has_key('morerows'): |
|
1325 count = node['morerows'] + 1 |
|
1326 self.active_table.set_rowspan(self.active_table.get_entry_number()-1,count) |
|
1327 self.body.append('\\multirow{%d}{%s}{' % \ |
|
1328 (count,self.active_table.get_column_width())) |
|
1329 self.context.append('}') |
|
1330 # BUG following rows must have empty cells. |
|
1331 elif node.has_key('morecols'): |
|
1332 # the vertical bar before column is missing if it is the first column. |
|
1333 # the one after always. |
|
1334 if self.active_table.get_entry_number() == 1: |
|
1335 bar1 = self.active_table.get_vertical_bar() |
|
1336 else: |
|
1337 bar1 = '' |
|
1338 count = node['morecols'] + 1 |
|
1339 self.body.append('\\multicolumn{%d}{%sl%s}{' % \ |
|
1340 (count, bar1, self.active_table.get_vertical_bar())) |
|
1341 self.context.append('}') |
|
1342 else: |
|
1343 self.context.append('') |
|
1344 |
|
1345 # header / not header |
|
1346 if isinstance(node.parent.parent, nodes.thead): |
|
1347 self.body.append('\\textbf{') |
|
1348 self.context.append('}') |
|
1349 elif self.active_table.is_stub_column(): |
|
1350 self.body.append('\\textbf{') |
|
1351 self.context.append('}') |
|
1352 else: |
|
1353 self.context.append('') |
|
1354 |
|
1355 def depart_entry(self, node): |
|
1356 self.body.append(self.context.pop()) # header / not header |
|
1357 self.body.append(self.context.pop()) # multirow/column |
|
1358 # if following row is spanned from above. |
|
1359 if self.active_table.get_rowspan(self.active_table.get_entry_number()): |
|
1360 self.body.append(' & ') |
|
1361 self.active_table.visit_entry() # increment cell count |
|
1362 |
|
1363 def visit_row(self, node): |
|
1364 self.active_table.visit_row() |
|
1365 |
|
1366 def depart_row(self, node): |
|
1367 self.body.extend(self.active_table.depart_row()) |
|
1368 |
|
1369 def visit_enumerated_list(self, node): |
|
1370 # We create our own enumeration list environment. |
|
1371 # This allows to set the style and starting value |
|
1372 # and unlimited nesting. |
|
1373 enum_style = {'arabic':'arabic', |
|
1374 'loweralpha':'alph', |
|
1375 'upperalpha':'Alph', |
|
1376 'lowerroman':'roman', |
|
1377 'upperroman':'Roman' } |
|
1378 enum_suffix = "" |
|
1379 if node.has_key('suffix'): |
|
1380 enum_suffix = node['suffix'] |
|
1381 enum_prefix = "" |
|
1382 if node.has_key('prefix'): |
|
1383 enum_prefix = node['prefix'] |
|
1384 if self.compound_enumerators: |
|
1385 pref = "" |
|
1386 if self.section_prefix_for_enumerators and self.section_level: |
|
1387 for i in range(self.section_level): |
|
1388 pref += '%d.' % self._section_number[i] |
|
1389 pref = pref[:-1] + self.section_enumerator_separator |
|
1390 enum_prefix += pref |
|
1391 for ctype, cname in self._enumeration_counters: |
|
1392 enum_prefix += '\\%s{%s}.' % (ctype, cname) |
|
1393 enum_type = "arabic" |
|
1394 if node.has_key('enumtype'): |
|
1395 enum_type = node['enumtype'] |
|
1396 if enum_style.has_key(enum_type): |
|
1397 enum_type = enum_style[enum_type] |
|
1398 |
|
1399 counter_name = "listcnt%d" % len(self._enumeration_counters) |
|
1400 self._enumeration_counters.append((enum_type, counter_name)) |
|
1401 # If we haven't used this counter name before, then create a |
|
1402 # new counter; otherwise, reset & reuse the old counter. |
|
1403 if len(self._enumeration_counters) > self._max_enumeration_counters: |
|
1404 self._max_enumeration_counters = len(self._enumeration_counters) |
|
1405 self.body.append('\\newcounter{%s}\n' % counter_name) |
|
1406 else: |
|
1407 self.body.append('\\setcounter{%s}{0}\n' % counter_name) |
|
1408 |
|
1409 self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \ |
|
1410 (enum_prefix,enum_type,counter_name,enum_suffix)) |
|
1411 self.body.append('{\n') |
|
1412 self.body.append('\\usecounter{%s}\n' % counter_name) |
|
1413 # set start after usecounter, because it initializes to zero. |
|
1414 if node.has_key('start'): |
|
1415 self.body.append('\\addtocounter{%s}{%d}\n' \ |
|
1416 % (counter_name,node['start']-1)) |
|
1417 ## set rightmargin equal to leftmargin |
|
1418 self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n') |
|
1419 self.body.append('}\n') |
|
1420 |
|
1421 def depart_enumerated_list(self, node): |
|
1422 self.body.append('\\end{list}\n') |
|
1423 self._enumeration_counters.pop() |
|
1424 |
|
1425 def visit_error(self, node): |
|
1426 self.visit_admonition(node, 'error') |
|
1427 |
|
1428 def depart_error(self, node): |
|
1429 self.depart_admonition() |
|
1430 |
|
1431 def visit_field(self, node): |
|
1432 # real output is done in siblings: _argument, _body, _name |
|
1433 pass |
|
1434 |
|
1435 def depart_field(self, node): |
|
1436 self.body.append('\n') |
|
1437 ##self.body.append('%[depart_field]\n') |
|
1438 |
|
1439 def visit_field_argument(self, node): |
|
1440 self.body.append('%[visit_field_argument]\n') |
|
1441 |
|
1442 def depart_field_argument(self, node): |
|
1443 self.body.append('%[depart_field_argument]\n') |
|
1444 |
|
1445 def visit_field_body(self, node): |
|
1446 # BUG by attach as text we loose references. |
|
1447 if self.docinfo: |
|
1448 self.docinfo.append('%s \\\\\n' % self.encode(node.astext())) |
|
1449 raise nodes.SkipNode |
|
1450 # BUG: what happens if not docinfo |
|
1451 |
|
1452 def depart_field_body(self, node): |
|
1453 self.body.append( '\n' ) |
|
1454 |
|
1455 def visit_field_list(self, node): |
|
1456 if not self.docinfo: |
|
1457 self.body.append('\\begin{quote}\n') |
|
1458 self.body.append('\\begin{description}\n') |
|
1459 |
|
1460 def depart_field_list(self, node): |
|
1461 if not self.docinfo: |
|
1462 self.body.append('\\end{description}\n') |
|
1463 self.body.append('\\end{quote}\n') |
|
1464 |
|
1465 def visit_field_name(self, node): |
|
1466 # BUG this duplicates docinfo_item |
|
1467 if self.docinfo: |
|
1468 self.docinfo.append('\\textbf{%s}: &\n\t' % self.encode(node.astext())) |
|
1469 raise nodes.SkipNode |
|
1470 else: |
|
1471 self.body.append('\\item [') |
|
1472 |
|
1473 def depart_field_name(self, node): |
|
1474 if not self.docinfo: |
|
1475 self.body.append(':]') |
|
1476 |
|
1477 def visit_figure(self, node): |
|
1478 if (not node.attributes.has_key('align') or |
|
1479 node.attributes['align'] == 'center'): |
|
1480 # centering does not add vertical space like center. |
|
1481 align = '\n\\centering' |
|
1482 align_end = '' |
|
1483 else: |
|
1484 # TODO non vertical space for other alignments. |
|
1485 align = '\\begin{flush%s}' % node.attributes['align'] |
|
1486 align_end = '\\end{flush%s}' % node.attributes['align'] |
|
1487 self.body.append( '\\begin{figure}[htbp]%s\n' % align ) |
|
1488 self.context.append( '%s\\end{figure}\n' % align_end ) |
|
1489 |
|
1490 def depart_figure(self, node): |
|
1491 self.body.append( self.context.pop() ) |
|
1492 |
|
1493 def visit_footer(self, node): |
|
1494 self.context.append(len(self.body)) |
|
1495 |
|
1496 def depart_footer(self, node): |
|
1497 start = self.context.pop() |
|
1498 footer = (['\n\\begin{center}\small\n'] |
|
1499 + self.body[start:] + ['\n\\end{center}\n']) |
|
1500 self.body_suffix[:0] = footer |
|
1501 del self.body[start:] |
|
1502 |
|
1503 def visit_footnote(self, node): |
|
1504 if self.use_latex_footnotes: |
|
1505 num,text = node.astext().split(None,1) |
|
1506 num = self.encode(num.strip()) |
|
1507 self.body.append('\\footnotetext['+num+']') |
|
1508 self.body.append('{') |
|
1509 else: |
|
1510 self.body.append('\\begin{figure}[b]') |
|
1511 for id in node['ids']: |
|
1512 self.body.append('\\hypertarget{%s}' % id) |
|
1513 |
|
1514 def depart_footnote(self, node): |
|
1515 if self.use_latex_footnotes: |
|
1516 self.body.append('}\n') |
|
1517 else: |
|
1518 self.body.append('\\end{figure}\n') |
|
1519 |
|
1520 def visit_footnote_reference(self, node): |
|
1521 if self.use_latex_footnotes: |
|
1522 self.body.append("\\footnotemark["+self.encode(node.astext())+"]") |
|
1523 raise nodes.SkipNode |
|
1524 href = '' |
|
1525 if node.has_key('refid'): |
|
1526 href = node['refid'] |
|
1527 elif node.has_key('refname'): |
|
1528 href = self.document.nameids[node['refname']] |
|
1529 format = self.settings.footnote_references |
|
1530 if format == 'brackets': |
|
1531 suffix = '[' |
|
1532 self.context.append(']') |
|
1533 elif format == 'superscript': |
|
1534 suffix = '\\raisebox{.5em}[0em]{\\scriptsize' |
|
1535 self.context.append('}') |
|
1536 else: # shouldn't happen |
|
1537 raise AssertionError('Illegal footnote reference format.') |
|
1538 self.body.append('%s\\hyperlink{%s}{' % (suffix,href)) |
|
1539 |
|
1540 def depart_footnote_reference(self, node): |
|
1541 if self.use_latex_footnotes: |
|
1542 return |
|
1543 self.body.append('}%s' % self.context.pop()) |
|
1544 |
|
1545 # footnote/citation label |
|
1546 def label_delim(self, node, bracket, superscript): |
|
1547 if isinstance(node.parent, nodes.footnote): |
|
1548 if self.use_latex_footnotes: |
|
1549 raise nodes.SkipNode |
|
1550 if self.settings.footnote_references == 'brackets': |
|
1551 self.body.append(bracket) |
|
1552 else: |
|
1553 self.body.append(superscript) |
|
1554 else: |
|
1555 assert isinstance(node.parent, nodes.citation) |
|
1556 if not self._use_latex_citations: |
|
1557 self.body.append(bracket) |
|
1558 |
|
1559 def visit_label(self, node): |
|
1560 self.label_delim(node, '[', '$^{') |
|
1561 |
|
1562 def depart_label(self, node): |
|
1563 self.label_delim(node, ']', '}$') |
|
1564 |
|
1565 # elements generated by the framework e.g. section numbers. |
|
1566 def visit_generated(self, node): |
|
1567 pass |
|
1568 |
|
1569 def depart_generated(self, node): |
|
1570 pass |
|
1571 |
|
1572 def visit_header(self, node): |
|
1573 self.context.append(len(self.body)) |
|
1574 |
|
1575 def depart_header(self, node): |
|
1576 start = self.context.pop() |
|
1577 self.body_prefix.append('\n\\verb|begin_header|\n') |
|
1578 self.body_prefix.extend(self.body[start:]) |
|
1579 self.body_prefix.append('\n\\verb|end_header|\n') |
|
1580 del self.body[start:] |
|
1581 |
|
1582 def visit_hint(self, node): |
|
1583 self.visit_admonition(node, 'hint') |
|
1584 |
|
1585 def depart_hint(self, node): |
|
1586 self.depart_admonition() |
|
1587 |
|
1588 def latex_image_length(self, width_str): |
|
1589 match = re.match('(\d*\.?\d*)\s*(\S*)', width_str) |
|
1590 if not match: |
|
1591 # fallback |
|
1592 return width_str |
|
1593 res = width_str |
|
1594 amount, unit = match.groups()[:2] |
|
1595 if unit == "px": |
|
1596 # LaTeX does not know pixels but points |
|
1597 res = "%spt" % amount |
|
1598 elif unit == "%": |
|
1599 res = "%.3f\\linewidth" % (float(amount)/100.0) |
|
1600 return res |
|
1601 |
|
1602 def visit_image(self, node): |
|
1603 attrs = node.attributes |
|
1604 # Add image URI to dependency list, assuming that it's |
|
1605 # referring to a local file. |
|
1606 self.settings.record_dependencies.add(attrs['uri']) |
|
1607 pre = [] # in reverse order |
|
1608 post = [] |
|
1609 include_graphics_options = [] |
|
1610 inline = isinstance(node.parent, nodes.TextElement) |
|
1611 if attrs.has_key('scale'): |
|
1612 # Could also be done with ``scale`` option to |
|
1613 # ``\includegraphics``; doing it this way for consistency. |
|
1614 pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,)) |
|
1615 post.append('}') |
|
1616 if attrs.has_key('width'): |
|
1617 include_graphics_options.append('width=%s' % ( |
|
1618 self.latex_image_length(attrs['width']), )) |
|
1619 if attrs.has_key('height'): |
|
1620 include_graphics_options.append('height=%s' % ( |
|
1621 self.latex_image_length(attrs['height']), )) |
|
1622 if attrs.has_key('align'): |
|
1623 align_prepost = { |
|
1624 # By default latex aligns the top of an image. |
|
1625 (1, 'top'): ('', ''), |
|
1626 (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'), |
|
1627 (1, 'bottom'): ('\\raisebox{-\\height}{', '}'), |
|
1628 (0, 'center'): ('{\\hfill', '\\hfill}'), |
|
1629 # These 2 don't exactly do the right thing. The image should |
|
1630 # be floated alongside the paragraph. See |
|
1631 # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG |
|
1632 (0, 'left'): ('{', '\\hfill}'), |
|
1633 (0, 'right'): ('{\\hfill', '}'),} |
|
1634 try: |
|
1635 pre.append(align_prepost[inline, attrs['align']][0]) |
|
1636 post.append(align_prepost[inline, attrs['align']][1]) |
|
1637 except KeyError: |
|
1638 pass # XXX complain here? |
|
1639 if not inline: |
|
1640 pre.append('\n') |
|
1641 post.append('\n') |
|
1642 pre.reverse() |
|
1643 self.body.extend( pre ) |
|
1644 options = '' |
|
1645 if len(include_graphics_options)>0: |
|
1646 options = '[%s]' % (','.join(include_graphics_options)) |
|
1647 self.body.append( '\\includegraphics%s{%s}' % ( |
|
1648 options, attrs['uri'] ) ) |
|
1649 self.body.extend( post ) |
|
1650 |
|
1651 def depart_image(self, node): |
|
1652 pass |
|
1653 |
|
1654 def visit_important(self, node): |
|
1655 self.visit_admonition(node, 'important') |
|
1656 |
|
1657 def depart_important(self, node): |
|
1658 self.depart_admonition() |
|
1659 |
|
1660 def visit_interpreted(self, node): |
|
1661 # @@@ Incomplete, pending a proper implementation on the |
|
1662 # Parser/Reader end. |
|
1663 self.visit_literal(node) |
|
1664 |
|
1665 def depart_interpreted(self, node): |
|
1666 self.depart_literal(node) |
|
1667 |
|
1668 def visit_legend(self, node): |
|
1669 self.body.append('{\\small ') |
|
1670 |
|
1671 def depart_legend(self, node): |
|
1672 self.body.append('}') |
|
1673 |
|
1674 def visit_line(self, node): |
|
1675 self.body.append('\item[] ') |
|
1676 |
|
1677 def depart_line(self, node): |
|
1678 self.body.append('\n') |
|
1679 |
|
1680 def visit_line_block(self, node): |
|
1681 if isinstance(node.parent, nodes.line_block): |
|
1682 self.body.append('\\item[] \n' |
|
1683 '\\begin{lineblock}{\\lineblockindentation}\n') |
|
1684 else: |
|
1685 self.body.append('\n\\begin{lineblock}{0em}\n') |
|
1686 |
|
1687 def depart_line_block(self, node): |
|
1688 self.body.append('\\end{lineblock}\n') |
|
1689 |
|
1690 def visit_list_item(self, node): |
|
1691 # Append "{}" in case the next character is "[", which would break |
|
1692 # LaTeX's list environment (no numbering and the "[" is not printed). |
|
1693 self.body.append('\\item {} ') |
|
1694 |
|
1695 def depart_list_item(self, node): |
|
1696 self.body.append('\n') |
|
1697 |
|
1698 def visit_literal(self, node): |
|
1699 self.literal = 1 |
|
1700 self.body.append('\\texttt{') |
|
1701 |
|
1702 def depart_literal(self, node): |
|
1703 self.body.append('}') |
|
1704 self.literal = 0 |
|
1705 |
|
1706 def visit_literal_block(self, node): |
|
1707 """ |
|
1708 Render a literal-block. |
|
1709 |
|
1710 Literal blocks are used for "::"-prefixed literal-indented |
|
1711 blocks of text, where the inline markup is not recognized, |
|
1712 but are also the product of the parsed-literal directive, |
|
1713 where the markup is respected. |
|
1714 """ |
|
1715 # In both cases, we want to use a typewriter/monospaced typeface. |
|
1716 # For "real" literal-blocks, we can use \verbatim, while for all |
|
1717 # the others we must use \mbox. |
|
1718 # |
|
1719 # We can distinguish between the two kinds by the number of |
|
1720 # siblings that compose this node: if it is composed by a |
|
1721 # single element, it's surely either a real one or a |
|
1722 # parsed-literal that does not contain any markup. |
|
1723 # |
|
1724 if not self.active_table.is_open(): |
|
1725 # no quote inside tables, to avoid vertical space between |
|
1726 # table border and literal block. |
|
1727 # BUG: fails if normal text preceeds the literal block. |
|
1728 self.body.append('\\begin{quote}') |
|
1729 self.context.append('\\end{quote}\n') |
|
1730 else: |
|
1731 self.body.append('\n') |
|
1732 self.context.append('\n') |
|
1733 if (self.settings.use_verbatim_when_possible and (len(node) == 1) |
|
1734 # in case of a parsed-literal containing just a "**bold**" word: |
|
1735 and isinstance(node[0], nodes.Text)): |
|
1736 self.verbatim = 1 |
|
1737 self.body.append('\\begin{verbatim}\n') |
|
1738 else: |
|
1739 self.literal_block = 1 |
|
1740 self.insert_none_breaking_blanks = 1 |
|
1741 self.body.append('{\\ttfamily \\raggedright \\noindent\n') |
|
1742 # * obey..: is from julien and never worked for me (grubert). |
|
1743 # self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n') |
|
1744 |
|
1745 def depart_literal_block(self, node): |
|
1746 if self.verbatim: |
|
1747 self.body.append('\n\\end{verbatim}\n') |
|
1748 self.verbatim = 0 |
|
1749 else: |
|
1750 self.body.append('\n}') |
|
1751 self.insert_none_breaking_blanks = 0 |
|
1752 self.literal_block = 0 |
|
1753 # obey end: self.body.append('}\n') |
|
1754 self.body.append(self.context.pop()) |
|
1755 |
|
1756 def visit_meta(self, node): |
|
1757 self.body.append('[visit_meta]\n') |
|
1758 # BUG maybe set keywords for pdf |
|
1759 ##self.head.append(self.starttag(node, 'meta', **node.attributes)) |
|
1760 |
|
1761 def depart_meta(self, node): |
|
1762 self.body.append('[depart_meta]\n') |
|
1763 |
|
1764 def visit_note(self, node): |
|
1765 self.visit_admonition(node, 'note') |
|
1766 |
|
1767 def depart_note(self, node): |
|
1768 self.depart_admonition() |
|
1769 |
|
1770 def visit_option(self, node): |
|
1771 if self.context[-1]: |
|
1772 # this is not the first option |
|
1773 self.body.append(', ') |
|
1774 |
|
1775 def depart_option(self, node): |
|
1776 # flag tha the first option is done. |
|
1777 self.context[-1] += 1 |
|
1778 |
|
1779 def visit_option_argument(self, node): |
|
1780 """The delimiter betweeen an option and its argument.""" |
|
1781 self.body.append(node.get('delimiter', ' ')) |
|
1782 |
|
1783 def depart_option_argument(self, node): |
|
1784 pass |
|
1785 |
|
1786 def visit_option_group(self, node): |
|
1787 self.body.append('\\item [') |
|
1788 # flag for first option |
|
1789 self.context.append(0) |
|
1790 |
|
1791 def depart_option_group(self, node): |
|
1792 self.context.pop() # the flag |
|
1793 self.body.append('] ') |
|
1794 |
|
1795 def visit_option_list(self, node): |
|
1796 self.body.append('\\begin{optionlist}{3cm}\n') |
|
1797 |
|
1798 def depart_option_list(self, node): |
|
1799 self.body.append('\\end{optionlist}\n') |
|
1800 |
|
1801 def visit_option_list_item(self, node): |
|
1802 pass |
|
1803 |
|
1804 def depart_option_list_item(self, node): |
|
1805 pass |
|
1806 |
|
1807 def visit_option_string(self, node): |
|
1808 ##self.body.append(self.starttag(node, 'span', '', CLASS='option')) |
|
1809 pass |
|
1810 |
|
1811 def depart_option_string(self, node): |
|
1812 ##self.body.append('</span>') |
|
1813 pass |
|
1814 |
|
1815 def visit_organization(self, node): |
|
1816 self.visit_docinfo_item(node, 'organization') |
|
1817 |
|
1818 def depart_organization(self, node): |
|
1819 self.depart_docinfo_item(node) |
|
1820 |
|
1821 def visit_paragraph(self, node): |
|
1822 index = node.parent.index(node) |
|
1823 if not ('contents' in self.topic_classes or |
|
1824 (isinstance(node.parent, nodes.compound) and |
|
1825 index > 0 and |
|
1826 not isinstance(node.parent[index - 1], nodes.paragraph) and |
|
1827 not isinstance(node.parent[index - 1], nodes.compound))): |
|
1828 self.body.append('\n') |
|
1829 |
|
1830 def depart_paragraph(self, node): |
|
1831 self.body.append('\n') |
|
1832 |
|
1833 def visit_problematic(self, node): |
|
1834 self.body.append('{\\color{red}\\bfseries{}') |
|
1835 |
|
1836 def depart_problematic(self, node): |
|
1837 self.body.append('}') |
|
1838 |
|
1839 def visit_raw(self, node): |
|
1840 if 'latex' in node.get('format', '').split(): |
|
1841 self.body.append(node.astext()) |
|
1842 raise nodes.SkipNode |
|
1843 |
|
1844 def visit_reference(self, node): |
|
1845 # BUG: hash_char "#" is trouble some in LaTeX. |
|
1846 # mbox and other environment do not like the '#'. |
|
1847 hash_char = '\\#' |
|
1848 if node.has_key('refuri'): |
|
1849 href = node['refuri'].replace('#',hash_char) |
|
1850 elif node.has_key('refid'): |
|
1851 href = hash_char + node['refid'] |
|
1852 elif node.has_key('refname'): |
|
1853 href = hash_char + self.document.nameids[node['refname']] |
|
1854 else: |
|
1855 raise AssertionError('Unknown reference.') |
|
1856 self.body.append('\\href{%s}{' % href) |
|
1857 if self._reference_label and not node.has_key('refuri'): |
|
1858 self.body.append('\\%s{%s}}' % (self._reference_label, |
|
1859 href.replace(hash_char, ''))) |
|
1860 raise nodes.SkipNode |
|
1861 |
|
1862 def depart_reference(self, node): |
|
1863 self.body.append('}') |
|
1864 |
|
1865 def visit_revision(self, node): |
|
1866 self.visit_docinfo_item(node, 'revision') |
|
1867 |
|
1868 def depart_revision(self, node): |
|
1869 self.depart_docinfo_item(node) |
|
1870 |
|
1871 def visit_section(self, node): |
|
1872 self.section_level += 1 |
|
1873 # Initialize counter for potential subsections: |
|
1874 self._section_number.append(0) |
|
1875 # Counter for this section's level (initialized by parent section): |
|
1876 self._section_number[self.section_level - 1] += 1 |
|
1877 |
|
1878 def depart_section(self, node): |
|
1879 # Remove counter for potential subsections: |
|
1880 self._section_number.pop() |
|
1881 self.section_level -= 1 |
|
1882 |
|
1883 def visit_sidebar(self, node): |
|
1884 # BUG: this is just a hack to make sidebars render something |
|
1885 self.body.append('\n\\setlength{\\locallinewidth}{0.9\\admonitionwidth}\n') |
|
1886 self.body.append('\\begin{center}\\begin{sffamily}\n') |
|
1887 self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n') |
|
1888 |
|
1889 def depart_sidebar(self, node): |
|
1890 self.body.append('}}}\n') # end parbox colorbox fbox |
|
1891 self.body.append('\\end{sffamily}\n\\end{center}\n'); |
|
1892 self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n') |
|
1893 |
|
1894 |
|
1895 attribution_formats = {'dash': ('---', ''), |
|
1896 'parentheses': ('(', ')'), |
|
1897 'parens': ('(', ')'), |
|
1898 'none': ('', '')} |
|
1899 |
|
1900 def visit_attribution(self, node): |
|
1901 prefix, suffix = self.attribution_formats[self.settings.attribution] |
|
1902 self.body.append('\n\\begin{flushright}\n') |
|
1903 self.body.append(prefix) |
|
1904 self.context.append(suffix) |
|
1905 |
|
1906 def depart_attribution(self, node): |
|
1907 self.body.append(self.context.pop() + '\n') |
|
1908 self.body.append('\\end{flushright}\n') |
|
1909 |
|
1910 def visit_status(self, node): |
|
1911 self.visit_docinfo_item(node, 'status') |
|
1912 |
|
1913 def depart_status(self, node): |
|
1914 self.depart_docinfo_item(node) |
|
1915 |
|
1916 def visit_strong(self, node): |
|
1917 self.body.append('\\textbf{') |
|
1918 self.literal_block_stack.append('\\textbf{') |
|
1919 |
|
1920 def depart_strong(self, node): |
|
1921 self.body.append('}') |
|
1922 self.literal_block_stack.pop() |
|
1923 |
|
1924 def visit_substitution_definition(self, node): |
|
1925 raise nodes.SkipNode |
|
1926 |
|
1927 def visit_substitution_reference(self, node): |
|
1928 self.unimplemented_visit(node) |
|
1929 |
|
1930 def visit_subtitle(self, node): |
|
1931 if isinstance(node.parent, nodes.sidebar): |
|
1932 self.body.append('~\\\\\n\\textbf{') |
|
1933 self.context.append('}\n\\smallskip\n') |
|
1934 elif isinstance(node.parent, nodes.document): |
|
1935 self.title = self.title + \ |
|
1936 '\\\\\n\\large{%s}\n' % self.encode(node.astext()) |
|
1937 raise nodes.SkipNode |
|
1938 elif isinstance(node.parent, nodes.section): |
|
1939 self.body.append('\\textbf{') |
|
1940 self.context.append('}\\vspace{0.2cm}\n\n\\noindent ') |
|
1941 |
|
1942 def depart_subtitle(self, node): |
|
1943 self.body.append(self.context.pop()) |
|
1944 |
|
1945 def visit_system_message(self, node): |
|
1946 pass |
|
1947 |
|
1948 def depart_system_message(self, node): |
|
1949 self.body.append('\n') |
|
1950 |
|
1951 def visit_table(self, node): |
|
1952 if self.active_table.is_open(): |
|
1953 self.table_stack.append(self.active_table) |
|
1954 # nesting longtable does not work (e.g. 2007-04-18) |
|
1955 self.active_table = Table('tabular',self.settings.table_style) |
|
1956 self.active_table.open() |
|
1957 for cl in node['classes']: |
|
1958 self.active_table.set_table_style(cl) |
|
1959 self.body.append('\n' + self.active_table.get_opening()) |
|
1960 |
|
1961 def depart_table(self, node): |
|
1962 self.body.append(self.active_table.get_closing() + '\n') |
|
1963 self.active_table.close() |
|
1964 if len(self.table_stack)>0: |
|
1965 self.active_table = self.table_stack.pop() |
|
1966 else: |
|
1967 self.active_table.set_table_style(self.settings.table_style) |
|
1968 |
|
1969 def visit_target(self, node): |
|
1970 # BUG: why not (refuri or refid or refname) means not footnote ? |
|
1971 if not (node.has_key('refuri') or node.has_key('refid') |
|
1972 or node.has_key('refname')): |
|
1973 for id in node['ids']: |
|
1974 self.body.append('\\hypertarget{%s}{' % id) |
|
1975 self.context.append('}' * len(node['ids'])) |
|
1976 elif node.get("refid"): |
|
1977 self.body.append('\\hypertarget{%s}{' % node.get("refid")) |
|
1978 self.context.append('}') |
|
1979 else: |
|
1980 self.context.append('') |
|
1981 |
|
1982 def depart_target(self, node): |
|
1983 self.body.append(self.context.pop()) |
|
1984 |
|
1985 def visit_tbody(self, node): |
|
1986 # BUG write preamble if not yet done (colspecs not []) |
|
1987 # for tables without heads. |
|
1988 if not self.active_table.get('preamble written'): |
|
1989 self.visit_thead(None) |
|
1990 # self.depart_thead(None) |
|
1991 |
|
1992 def depart_tbody(self, node): |
|
1993 pass |
|
1994 |
|
1995 def visit_term(self, node): |
|
1996 self.body.append('\\item[{') |
|
1997 |
|
1998 def depart_term(self, node): |
|
1999 # definition list term. |
|
2000 # \leavevmode results in a line break if the term is followed by a item list. |
|
2001 self.body.append('}] \leavevmode ') |
|
2002 |
|
2003 def visit_tgroup(self, node): |
|
2004 #self.body.append(self.starttag(node, 'colgroup')) |
|
2005 #self.context.append('</colgroup>\n') |
|
2006 pass |
|
2007 |
|
2008 def depart_tgroup(self, node): |
|
2009 pass |
|
2010 |
|
2011 def visit_thead(self, node): |
|
2012 self.body.append('{%s}\n' % self.active_table.get_colspecs()) |
|
2013 if self.active_table.caption: |
|
2014 self.body.append('\\caption{%s}\\\\\n' % self.active_table.caption) |
|
2015 self.active_table.set('preamble written',1) |
|
2016 # TODO longtable supports firsthead and lastfoot too. |
|
2017 self.body.extend(self.active_table.visit_thead()) |
|
2018 |
|
2019 def depart_thead(self, node): |
|
2020 # the table header written should be on every page |
|
2021 # => \endhead |
|
2022 self.body.extend(self.active_table.depart_thead()) |
|
2023 # and the firsthead => \endfirsthead |
|
2024 # BUG i want a "continued from previous page" on every not |
|
2025 # firsthead, but then we need the header twice. |
|
2026 # |
|
2027 # there is a \endfoot and \endlastfoot too. |
|
2028 # but we need the number of columns to |
|
2029 # self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns) |
|
2030 # self.body.append('\\hline\n\\endfoot\n') |
|
2031 # self.body.append('\\hline\n') |
|
2032 # self.body.append('\\endlastfoot\n') |
|
2033 |
|
2034 def visit_tip(self, node): |
|
2035 self.visit_admonition(node, 'tip') |
|
2036 |
|
2037 def depart_tip(self, node): |
|
2038 self.depart_admonition() |
|
2039 |
|
2040 def bookmark(self, node): |
|
2041 """Append latex href and pdfbookmarks for titles. |
|
2042 """ |
|
2043 if node.parent['ids']: |
|
2044 for id in node.parent['ids']: |
|
2045 self.body.append('\\hypertarget{%s}{}\n' % id) |
|
2046 if not self.use_latex_toc: |
|
2047 # BUG level depends on style. pdflatex allows level 0 to 3 |
|
2048 # ToC would be the only on level 0 so i choose to decrement the rest. |
|
2049 # "Table of contents" bookmark to see the ToC. To avoid this |
|
2050 # we set all zeroes to one. |
|
2051 l = self.section_level |
|
2052 if l>0: |
|
2053 l = l-1 |
|
2054 # pdftex does not like "_" subscripts in titles |
|
2055 text = self.encode(node.astext()) |
|
2056 for id in node.parent['ids']: |
|
2057 self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \ |
|
2058 (l, text, id)) |
|
2059 |
|
2060 def visit_title(self, node): |
|
2061 """Section and other titles.""" |
|
2062 |
|
2063 if isinstance(node.parent, nodes.topic): |
|
2064 # the table of contents. |
|
2065 self.bookmark(node) |
|
2066 if ('contents' in self.topic_classes |
|
2067 and self.use_latex_toc): |
|
2068 self.body.append('\\renewcommand{\\contentsname}{') |
|
2069 self.context.append('}\n\\tableofcontents\n\n\\bigskip\n') |
|
2070 elif ('abstract' in self.topic_classes |
|
2071 and self.settings.use_latex_abstract): |
|
2072 raise nodes.SkipNode |
|
2073 else: # or section titles before the table of contents. |
|
2074 # BUG: latex chokes on center environment with |
|
2075 # "perhaps a missing item", therefore we use hfill. |
|
2076 self.body.append('\\subsubsection*{~\\hfill ') |
|
2077 # the closing brace for subsection. |
|
2078 self.context.append('\\hfill ~}\n') |
|
2079 # TODO: for admonition titles before the first section |
|
2080 # either specify every possible node or ... ? |
|
2081 elif isinstance(node.parent, nodes.sidebar) \ |
|
2082 or isinstance(node.parent, nodes.admonition): |
|
2083 self.body.append('\\textbf{\\large ') |
|
2084 self.context.append('}\n\\smallskip\n') |
|
2085 elif isinstance(node.parent, nodes.table): |
|
2086 # caption must be written after column spec |
|
2087 self.active_table.caption = self.encode(node.astext()) |
|
2088 raise nodes.SkipNode |
|
2089 elif self.section_level == 0: |
|
2090 # document title |
|
2091 self.title = self.encode(node.astext()) |
|
2092 if not self.pdfinfo == None: |
|
2093 self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) ) |
|
2094 raise nodes.SkipNode |
|
2095 else: |
|
2096 self.body.append('\n\n') |
|
2097 self.body.append('%' + '_' * 75) |
|
2098 self.body.append('\n\n') |
|
2099 self.bookmark(node) |
|
2100 |
|
2101 if self.use_latex_toc: |
|
2102 section_star = "" |
|
2103 else: |
|
2104 section_star = "*" |
|
2105 |
|
2106 section_name = self.d_class.section(self.section_level) |
|
2107 self.body.append('\\%s%s{' % (section_name, section_star)) |
|
2108 # MAYBE postfix paragraph and subparagraph with \leavemode to |
|
2109 # ensure floatables stay in the section and text starts on a new line. |
|
2110 self.context.append('}\n') |
|
2111 |
|
2112 def depart_title(self, node): |
|
2113 self.body.append(self.context.pop()) |
|
2114 for id in node.parent['ids']: |
|
2115 self.body.append('\\label{%s}\n' % id) |
|
2116 |
|
2117 def visit_topic(self, node): |
|
2118 self.topic_classes = node['classes'] |
|
2119 if ('abstract' in self.topic_classes |
|
2120 and self.settings.use_latex_abstract): |
|
2121 self.body.append('\\begin{abstract}\n') |
|
2122 |
|
2123 def depart_topic(self, node): |
|
2124 if ('abstract' in self.topic_classes |
|
2125 and self.settings.use_latex_abstract): |
|
2126 self.body.append('\\end{abstract}\n') |
|
2127 self.topic_classes = [] |
|
2128 if 'contents' in node['classes'] and self.use_latex_toc: |
|
2129 pass |
|
2130 else: |
|
2131 self.body.append('\n') |
|
2132 |
|
2133 def visit_inline(self, node): # titlereference |
|
2134 classes = node.get('classes', ['Unknown', ]) |
|
2135 for cls in classes: |
|
2136 self.body.append( '\\docutilsrole%s{' % cls) |
|
2137 self.context.append('}'*len(classes)) |
|
2138 |
|
2139 def depart_inline(self, node): |
|
2140 self.body.append(self.context.pop()) |
|
2141 |
|
2142 def visit_rubric(self, node): |
|
2143 self.body.append('\\rubric{') |
|
2144 self.context.append('}\n') |
|
2145 |
|
2146 def depart_rubric(self, node): |
|
2147 self.body.append(self.context.pop()) |
|
2148 |
|
2149 def visit_transition(self, node): |
|
2150 self.body.append('\n\n') |
|
2151 self.body.append('%' + '_' * 75) |
|
2152 self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}') |
|
2153 self.body.append('\n\n') |
|
2154 |
|
2155 def depart_transition(self, node): |
|
2156 pass |
|
2157 |
|
2158 def visit_version(self, node): |
|
2159 self.visit_docinfo_item(node, 'version') |
|
2160 |
|
2161 def depart_version(self, node): |
|
2162 self.depart_docinfo_item(node) |
|
2163 |
|
2164 def visit_warning(self, node): |
|
2165 self.visit_admonition(node, 'warning') |
|
2166 |
|
2167 def depart_warning(self, node): |
|
2168 self.depart_admonition() |
|
2169 |
|
2170 def unimplemented_visit(self, node): |
|
2171 raise NotImplementedError('visiting unimplemented node type: %s' |
|
2172 % node.__class__.__name__) |
|
2173 |
|
2174 # def unknown_visit(self, node): |
|
2175 # def default_visit(self, node): |
|
2176 |
|
2177 # vim: set ts=4 et ai : |