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