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