Merge smaller differences from latex2e_adaptive_preamble.
[docutils.git] / docutils / writers / latex2e / __init__.py
bloba4916d77158c7664a3ea87ba19663d7008e1a362
1 # $Id$
2 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
3 # Copyright: This module has been placed in the public domain.
5 """
6 LaTeX2e document tree Writer.
7 """
9 __docformat__ = 'reStructuredText'
11 # code contributions from several people included, thanks to all.
12 # some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
14 # convention deactivate code by two # e.g. ##.
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
24 from docutils.transforms.references import DanglingReferencesVisitor
26 class Writer(writers.Writer):
28 supported = ('latex','latex2e')
29 """Formats this writer supports."""
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, }),
155 settings_defaults = {'output_encoding': 'latin-1'}
157 relative_path_settings = ('stylesheet_path',)
159 config_section = 'latex2e writer'
160 config_section_dependencies = ('writers',)
162 visitor_attributes = ("head_prefix", "head",
163 "body_prefix", "body", "body_suffix")
165 output = None
166 """Final translated form of `document`."""
168 def __init__(self):
169 writers.Writer.__init__(self)
170 self.translator_class = LaTeXTranslator
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))
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))
187 Notes on LaTeX
188 --------------
190 * LaTeX does not support multiple tocs in one document.
191 (might be no limitation except for docutils documentation)
193 The "minitoc" latex package can produce per-chapter tocs in
194 book and report document classes.
196 * width
198 * linewidth - width of a line in the local environment
199 * textwidth - the width of text on the page
201 Maybe always use linewidth ?
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.
207 So we add locallinewidth set it initially and
208 on entering sidebar and reset on exit.
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'
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
275 def next_quote(self):
276 q = self.quotes[self.quote_index]
277 self.quote_index = (self.quote_index+1)%2
278 return q
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
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)
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
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',
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'
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',
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',
352 class DocumentClass:
353 """Details of a LaTeX document class."""
355 def __init__(self, document_class, with_part=False):
356 self.document_class = document_class
357 self._with_part = with_part
359 def section(self, level):
360 """ Return the section name at the given level for the specific
361 document class.
363 Level is 1,2,3..., as level 0 is the title."""
365 sections = [ 'section', 'subsection', 'subsubsection',
366 'paragraph', 'subparagraph' ]
367 if self.document_class in ('book', 'report', 'scrartcl', '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]
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.
381 Table style might be
383 * standard: horizontal and vertical lines
384 * booktabs (requires booktabs latex package): only horizontal lines
385 * nolines, borderless : no lines
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 = []
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
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
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
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)
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'))
455 def get_colspecs(self):
457 Return column specification for longtable.
459 Assumes reST line length being 80 characters.
460 Table width is hairy.
462 === ===
463 ABC DEF
464 === ===
466 usually gets to narrow, therefore we add 1 (fiddlefactor).
468 width = 80
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
490 def get_column_width(self):
491 """ return columnwidth for current cell (not multicell)
493 return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1]
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
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
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
562 class LaTeXTranslator(nodes.NodeVisitor):
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
568 # Templates
569 # ---------
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 # -----------------------
579 # use latex tableofcontents or let docutils do it.
580 use_latex_toc = 0
582 # TODO: use mixins for different implementations.
583 # list environment for docinfo. else tabularx
584 use_optionlist_for_docinfo = 0 # NOT YET IN USE
586 # Use compound enumerations (1.A.1.)
587 compound_enumerators = 0
589 # If using compound enumerations, include section information.
590 section_prefix_for_enumerators = 0
592 # This is the character that separates the section ("." subsection ...)
593 # prefix from the regular list enumerator.
594 section_enumerator_separator = '-'
596 # default link color
597 hyperlink_color = "blue"
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'
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()
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)
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 = ''
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)
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 ??
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.
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))
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 = ''
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
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
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]
767 # The current stack of enumerations so that we can expand
768 # them into a compound enumeration.
769 self._enumeration_counters = []
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
776 self._bibitems = []
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
787 def to_latex_encoding(self,docutils_encoding):
789 Translate docutils encoding name into latex's.
791 Default fallback method is remove "-" and "_" chars from docutils_encoding.
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
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()
827 def language_label(self, docutil_label):
828 return self.language.labels[docutil_label]
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 ?
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
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
864 self.ensure_math_re = re.compile('([%s])' % chars['latin1'])
865 text = self.ensure_math_re.sub(r'\\ensuremath{\1}', text)
866 return text
868 def encode(self, text):
870 Encode special characters (``# $ % & ~ _ ^ \ { }``) in `text` & return
872 # Escaping with a backslash does not help with backslashes, ~ and ^.
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.
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)
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
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))
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)
978 def visit_Text(self, node):
979 self.body.append(self.encode(node.astext()))
981 def depart_Text(self, node):
982 pass
984 def visit_address(self, node):
985 self.visit_docinfo_item(node, 'address')
987 def depart_address(self, node):
988 self.depart_docinfo_item(node)
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')
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');
1002 def visit_attention(self, node):
1003 self.visit_admonition(node, 'attention')
1005 def depart_attention(self, node):
1006 self.depart_admonition()
1008 def visit_author(self, node):
1009 self.visit_docinfo_item(node, 'author')
1011 def depart_author(self, node):
1012 self.depart_docinfo_item(node)
1014 def visit_authors(self, node):
1015 # not used: visit_author is called anyway for each author.
1016 pass
1018 def depart_authors(self, node):
1019 pass
1021 def visit_block_quote(self, node):
1022 self.body.append( '\\begin{quote}\n')
1024 def depart_block_quote(self, node):
1025 self.body.append( '\\end{quote}\n')
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' )
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' )
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
1047 def depart_superscript(self, node):
1048 self.body.append('}$')
1049 self.mathmode = 0
1051 def visit_subscript(self, node):
1052 self.body.append('$_{')
1053 self.mathmode = 1
1055 def depart_subscript(self, node):
1056 self.body.append('}$')
1057 self.mathmode = 0
1059 def visit_caption(self, node):
1060 self.body.append( '\\caption{' )
1062 def depart_caption(self, node):
1063 self.body.append('}')
1065 def visit_caution(self, node):
1066 self.visit_admonition(node, 'caution')
1068 def depart_caution(self, node):
1069 self.depart_admonition()
1071 def visit_title_reference(self, node):
1072 self.body.append( '\\titlereference{' )
1074 def depart_title_reference(self, node):
1075 self.body.append( '}' )
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)
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')
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)
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('}]')
1115 def visit_classifier(self, node):
1116 self.body.append( '(\\textbf{' )
1118 def depart_classifier(self, node):
1119 self.body.append( '})\n' )
1121 def visit_colspec(self, node):
1122 self.active_table.visit_colspec(node)
1124 def depart_colspec(self, node):
1125 pass
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
1132 def visit_compound(self, node):
1133 pass
1135 def depart_compound(self, node):
1136 pass
1138 def visit_contact(self, node):
1139 self.visit_docinfo_item(node, 'contact')
1141 def depart_contact(self, node):
1142 self.depart_docinfo_item(node)
1144 def visit_container(self, node):
1145 pass
1147 def depart_container(self, node):
1148 pass
1150 def visit_copyright(self, node):
1151 self.visit_docinfo_item(node, 'copyright')
1153 def depart_copyright(self, node):
1154 self.depart_docinfo_item(node)
1156 def visit_danger(self, node):
1157 self.visit_admonition(node, 'danger')
1159 def depart_danger(self, node):
1160 self.depart_admonition()
1162 def visit_date(self, node):
1163 self.visit_docinfo_item(node, 'date')
1165 def depart_date(self, node):
1166 self.depart_docinfo_item(node)
1168 def visit_decoration(self, node):
1169 pass
1171 def depart_decoration(self, node):
1172 pass
1174 def visit_definition(self, node):
1175 pass
1177 def depart_definition(self, node):
1178 self.body.append('\n')
1180 def visit_definition_list(self, node):
1181 self.body.append( '\\begin{description}\n' )
1183 def depart_definition_list(self, node):
1184 self.body.append( '\\end{description}\n' )
1186 def visit_definition_list_item(self, node):
1187 pass
1189 def depart_definition_list_item(self, node):
1190 pass
1192 def visit_description(self, node):
1193 self.body.append( ' ' )
1195 def depart_description(self, node):
1196 pass
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')
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
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))
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
1256 def visit_doctest_block(self, node):
1257 self.body.append( '\\begin{verbatim}' )
1258 self.verbatim = 1
1260 def depart_doctest_block(self, node):
1261 self.body.append( '\\end{verbatim}\n' )
1262 self.verbatim = 0
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')
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])
1292 self.body_suffix.append('\\end{document}\n')
1294 def visit_emphasis(self, node):
1295 self.body.append('\\emph{')
1296 self.literal_block_stack.append('\\emph{')
1298 def depart_emphasis(self, node):
1299 self.body.append('}')
1300 self.literal_block_stack.pop()
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(' & ')
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('')
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('')
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
1363 def visit_row(self, node):
1364 self.active_table.visit_row()
1366 def depart_row(self, node):
1367 self.body.extend(self.active_table.depart_row())
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]
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)
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')
1421 def depart_enumerated_list(self, node):
1422 self.body.append('\\end{list}\n')
1423 self._enumeration_counters.pop()
1425 def visit_error(self, node):
1426 self.visit_admonition(node, 'error')
1428 def depart_error(self, node):
1429 self.depart_admonition()
1431 def visit_field(self, node):
1432 # real output is done in siblings: _argument, _body, _name
1433 pass
1435 def depart_field(self, node):
1436 self.body.append('\n')
1437 ##self.body.append('%[depart_field]\n')
1439 def visit_field_argument(self, node):
1440 self.body.append('%[visit_field_argument]\n')
1442 def depart_field_argument(self, node):
1443 self.body.append('%[depart_field_argument]\n')
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
1452 def depart_field_body(self, node):
1453 self.body.append( '\n' )
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')
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')
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 [')
1473 def depart_field_name(self, node):
1474 if not self.docinfo:
1475 self.body.append(':]')
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 )
1490 def depart_figure(self, node):
1491 self.body.append( self.context.pop() )
1493 def visit_footer(self, node):
1494 self.context.append(len(self.body))
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:]
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)
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')
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))
1540 def depart_footnote_reference(self, node):
1541 if self.use_latex_footnotes:
1542 return
1543 self.body.append('}%s' % self.context.pop())
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)
1559 def visit_label(self, node):
1560 self.label_delim(node, '[', '$^{')
1562 def depart_label(self, node):
1563 self.label_delim(node, ']', '}$')
1565 # elements generated by the framework e.g. section numbers.
1566 def visit_generated(self, node):
1567 pass
1569 def depart_generated(self, node):
1570 pass
1572 def visit_header(self, node):
1573 self.context.append(len(self.body))
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:]
1582 def visit_hint(self, node):
1583 self.visit_admonition(node, 'hint')
1585 def depart_hint(self, node):
1586 self.depart_admonition()
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
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 )
1651 def depart_image(self, node):
1652 pass
1654 def visit_important(self, node):
1655 self.visit_admonition(node, 'important')
1657 def depart_important(self, node):
1658 self.depart_admonition()
1660 def visit_interpreted(self, node):
1661 # @@@ Incomplete, pending a proper implementation on the
1662 # Parser/Reader end.
1663 self.visit_literal(node)
1665 def depart_interpreted(self, node):
1666 self.depart_literal(node)
1668 def visit_legend(self, node):
1669 self.body.append('{\\small ')
1671 def depart_legend(self, node):
1672 self.body.append('}')
1674 def visit_line(self, node):
1675 self.body.append('\item[] ')
1677 def depart_line(self, node):
1678 self.body.append('\n')
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')
1687 def depart_line_block(self, node):
1688 self.body.append('\\end{lineblock}\n')
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 {} ')
1695 def depart_list_item(self, node):
1696 self.body.append('\n')
1698 def visit_literal(self, node):
1699 self.literal = 1
1700 self.body.append('\\texttt{')
1702 def depart_literal(self, node):
1703 self.body.append('}')
1704 self.literal = 0
1706 def visit_literal_block(self, node):
1708 Render a literal-block.
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.
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.
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.
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')
1745 def depart_literal_block(self, node):
1746 if self.verbatim:
1747 self.body.append('\n\\end{verbatim}')
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())
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))
1761 def depart_meta(self, node):
1762 self.body.append('[depart_meta]\n')
1764 def visit_note(self, node):
1765 self.visit_admonition(node, 'note')
1767 def depart_note(self, node):
1768 self.depart_admonition()
1770 def visit_option(self, node):
1771 if self.context[-1]:
1772 # this is not the first option
1773 self.body.append(', ')
1775 def depart_option(self, node):
1776 # flag tha the first option is done.
1777 self.context[-1] += 1
1779 def visit_option_argument(self, node):
1780 """The delimiter betweeen an option and its argument."""
1781 self.body.append(node.get('delimiter', ' '))
1783 def depart_option_argument(self, node):
1784 pass
1786 def visit_option_group(self, node):
1787 self.body.append('\\item [')
1788 # flag for first option
1789 self.context.append(0)
1791 def depart_option_group(self, node):
1792 self.context.pop() # the flag
1793 self.body.append('] ')
1795 def visit_option_list(self, node):
1796 self.body.append('\\begin{optionlist}{3cm}\n')
1798 def depart_option_list(self, node):
1799 self.body.append('\\end{optionlist}\n')
1801 def visit_option_list_item(self, node):
1802 pass
1804 def depart_option_list_item(self, node):
1805 pass
1807 def visit_option_string(self, node):
1808 ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
1809 pass
1811 def depart_option_string(self, node):
1812 ##self.body.append('</span>')
1813 pass
1815 def visit_organization(self, node):
1816 self.visit_docinfo_item(node, 'organization')
1818 def depart_organization(self, node):
1819 self.depart_docinfo_item(node)
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')
1830 def depart_paragraph(self, node):
1831 self.body.append('\n')
1833 def visit_problematic(self, node):
1834 self.body.append('{\\color{red}\\bfseries{}')
1836 def depart_problematic(self, node):
1837 self.body.append('}')
1839 def visit_raw(self, node):
1840 if 'latex' in node.get('format', '').split():
1841 self.body.append(node.astext())
1842 raise nodes.SkipNode
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
1862 def depart_reference(self, node):
1863 self.body.append('}')
1865 def visit_revision(self, node):
1866 self.visit_docinfo_item(node, 'revision')
1868 def depart_revision(self, node):
1869 self.depart_docinfo_item(node)
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
1878 def depart_section(self, node):
1879 # Remove counter for potential subsections:
1880 self._section_number.pop()
1881 self.section_level -= 1
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')
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')
1895 attribution_formats = {'dash': ('---', ''),
1896 'parentheses': ('(', ')'),
1897 'parens': ('(', ')'),
1898 'none': ('', '')}
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)
1906 def depart_attribution(self, node):
1907 self.body.append(self.context.pop() + '\n')
1908 self.body.append('\\end{flushright}\n')
1910 def visit_status(self, node):
1911 self.visit_docinfo_item(node, 'status')
1913 def depart_status(self, node):
1914 self.depart_docinfo_item(node)
1916 def visit_strong(self, node):
1917 self.body.append('\\textbf{')
1918 self.literal_block_stack.append('\\textbf{')
1920 def depart_strong(self, node):
1921 self.body.append('}')
1922 self.literal_block_stack.pop()
1924 def visit_substitution_definition(self, node):
1925 raise nodes.SkipNode
1927 def visit_substitution_reference(self, node):
1928 self.unimplemented_visit(node)
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 ')
1942 def depart_subtitle(self, node):
1943 self.body.append(self.context.pop())
1945 def visit_system_message(self, node):
1946 pass
1948 def depart_system_message(self, node):
1949 self.body.append('\n')
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())
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)
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('')
1982 def depart_target(self, node):
1983 self.body.append(self.context.pop())
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)
1992 def depart_tbody(self, node):
1993 pass
1995 def visit_term(self, node):
1996 self.body.append('\\item[{')
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 ')
2003 def visit_tgroup(self, node):
2004 #self.body.append(self.starttag(node, 'colgroup'))
2005 #self.context.append('</colgroup>\n')
2006 pass
2008 def depart_tgroup(self, node):
2009 pass
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())
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.
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')
2034 def visit_tip(self, node):
2035 self.visit_admonition(node, 'tip')
2037 def depart_tip(self, node):
2038 self.depart_admonition()
2040 def bookmark(self, node):
2041 """Append latex href and pdfbookmarks for titles.
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))
2060 def visit_title(self, node):
2061 """Section and other titles."""
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)
2101 if self.use_latex_toc:
2102 section_star = ""
2103 else:
2104 section_star = "*"
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')
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)
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')
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')
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))
2139 def depart_inline(self, node):
2140 self.body.append(self.context.pop())
2142 def visit_rubric(self, node):
2143 self.body.append('\\rubric{')
2144 self.context.append('}\n')
2146 def depart_rubric(self, node):
2147 self.body.append(self.context.pop())
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')
2155 def depart_transition(self, node):
2156 pass
2158 def visit_version(self, node):
2159 self.visit_docinfo_item(node, 'version')
2161 def depart_version(self, node):
2162 self.depart_docinfo_item(node)
2164 def visit_warning(self, node):
2165 self.visit_admonition(node, 'warning')
2167 def depart_warning(self, node):
2168 self.depart_admonition()
2170 def unimplemented_visit(self, node):
2171 raise NotImplementedError('visiting unimplemented node type: %s'
2172 % node.__class__.__name__)
2174 # def unknown_visit(self, node):
2175 # def default_visit(self, node):
2177 # vim: set ts=4 et ai :