Take care of too wide tables (dave abrahams).
[docutils.git] / docutils / writers / latex2e.py
blobe283347b9be5c647c96536faaf23ae33c52890b2
1 """
2 :Author: Engelbert Gruber
3 :Contact: grubert@users.sourceforge.net
4 :Revision: $Revision$
5 :Date: $Date$
6 :Copyright: This module has been placed in the public domain.
8 LaTeX2e document tree Writer.
9 """
11 __docformat__ = 'reStructuredText'
13 # code contributions from several people included, thanks too all.
14 # some named: David Abrahams, Julien Letessier, who is missing.
16 # convention deactivate code by two # e.g. ##.
18 import sys
19 import time
20 import re
21 import string
22 from types import ListType
23 from docutils import writers, nodes, languages
25 class Writer(writers.Writer):
27 supported = ('latex','latex2e')
28 """Formats this writer supports."""
30 settings_spec = (
31 'LaTeX-Specific Options',
32 'The LaTeX "--output-encoding" default is "latin-1:strict".',
33 (('Specify documentclass. Default is "article".',
34 ['--documentclass'],
35 {'default': 'article', }),
36 ('Format for footnote references: one of "superscript" or '
37 '"brackets". Default is "brackets".',
38 ['--footnote-references'],
39 {'choices': ['superscript', 'brackets'], 'default': 'brackets',
40 'metavar': '<format>'}),
41 ('Format for block quote attributions: one of "dash" (em-dash '
42 'prefix), "parentheses"/"parens", or "none". Default is "dash".',
43 ['--attribution'],
44 {'choices': ['dash', 'parentheses', 'parens', 'none'],
45 'default': 'dash', 'metavar': '<format>'}),
46 ('Specify a stylesheet file. The file will be "input" by latex '
47 'in the document header. Default is "style.tex". '
48 'If this is set to "" disables input.'
49 'Overridden by --stylesheet-path.',
50 ['--stylesheet'],
51 {'default': 'style.tex', 'metavar': '<file>'}),
52 ('Specify a stylesheet file, relative to the current working '
53 'directory.'
54 'Overrides --stylesheet.',
55 ['--stylesheet-path'],
56 {'metavar': '<file>'}),
57 ('Link to the stylesheet in the output LaTeX file. This is the '
58 'default.',
59 ['--link-stylesheet'],
60 {'dest': 'embed_stylesheet', 'action': 'store_false'}),
61 ('Embed the stylesheet in the output LaTeX file. The stylesheet '
62 'file must be accessible during processing (--stylesheet-path is '
63 'recommended).',
64 ['--embed-stylesheet'],
65 {'action': 'store_true'}),
66 ('Table of contents by docutils (default) or latex. Latex(writer) '
67 'supports only one ToC per document, but docutils does not write '
68 'pagenumbers.',
69 ['--use-latex-toc'], {'default': 0}),
70 ('Color of any hyperlinks embedded in text '
71 '(default: "blue", "0" to disable).',
72 ['--hyperlink-color'], {'default': 'blue'}),))
74 settings_defaults = {'output_encoding': 'latin-1'}
76 output = None
77 """Final translated form of `document`."""
79 def translate(self):
80 visitor = LaTeXTranslator(self.document)
81 self.document.walkabout(visitor)
82 self.output = visitor.astext()
83 self.head_prefix = visitor.head_prefix
84 self.head = visitor.head
85 self.body_prefix = visitor.body_prefix
86 self.body = visitor.body
87 self.body_suffix = visitor.body_suffix
89 """
90 Notes on LaTeX
91 --------------
93 * latex does not support multiple tocs in one document.
94 (might be no limitation except for docutils documentation)
96 * width
98 * linewidth - width of a line in the local environment
99 * textwidth - the width of text on the page
101 Maybe always use linewidth ?
102 """
104 class Babel:
105 """Language specifics for LaTeX."""
106 # country code by a.schlock.
107 # partly manually converted from iso and babel stuff, dialects and some
108 _ISO639_TO_BABEL = {
109 'no': 'norsk', #XXX added by hand ( forget about nynorsk?)
110 'gd': 'scottish', #XXX added by hand
111 'hu': 'magyar', #XXX added by hand
112 'pt': 'portuguese',#XXX added by hand
113 'sl': 'slovenian',
114 'af': 'afrikaans',
115 'bg': 'bulgarian',
116 'br': 'breton',
117 'ca': 'catalan',
118 'cs': 'czech',
119 'cy': 'welsh',
120 'da': 'danish',
121 'fr': 'french',
122 # french, francais, canadien, acadian
123 'de': 'ngerman', #XXX rather than german
124 # ngerman, naustrian, german, germanb, austrian
125 'el': 'greek',
126 'en': 'english',
127 # english, USenglish, american, UKenglish, british, canadian
128 'eo': 'esperanto',
129 'es': 'spanish',
130 'et': 'estonian',
131 'eu': 'basque',
132 'fi': 'finnish',
133 'ga': 'irish',
134 'gl': 'galician',
135 'he': 'hebrew',
136 'hr': 'croatian',
137 'hu': 'hungarian',
138 'is': 'icelandic',
139 'it': 'italian',
140 'la': 'latin',
141 'nl': 'dutch',
142 'pl': 'polish',
143 'pt': 'portuguese',
144 'ro': 'romanian',
145 'ru': 'russian',
146 'sk': 'slovak',
147 'sr': 'serbian',
148 'sv': 'swedish',
149 'tr': 'turkish',
150 'uk': 'ukrainian'
153 def __init__(self,lang):
154 self.language = lang
155 # pdflatex does not produce double quotes for ngerman in tt.
156 self.double_quote_replacment = None
157 if re.search('^de',self.language):
158 # maybe use: {\glqq} {\grqq}.
159 self.quotes = ("\"`", "\"'")
160 self.double_quote_replacment = "{\\dq}"
161 else:
162 self.quotes = ("``", "''")
163 self.quote_index = 0
165 def next_quote(self):
166 q = self.quotes[self.quote_index]
167 self.quote_index = (self.quote_index+1)%2
168 return q
170 def quote_quotes(self,text):
171 t = None
172 for part in text.split('"'):
173 if t == None:
174 t = part
175 else:
176 t += self.next_quote() + part
177 return t
179 def double_quotes_in_tt (self,text):
180 if not self.double_quote_replacment:
181 return text
182 return text.replace('"', self.double_quote_replacment)
184 def get_language(self):
185 if self._ISO639_TO_BABEL.has_key(self.language):
186 return self._ISO639_TO_BABEL[self.language]
187 else:
188 # support dialects.
189 l = self.language.split("_")[0]
190 if self._ISO639_TO_BABEL.has_key(l):
191 return self._ISO639_TO_BABEL[l]
192 return None
195 latex_headings = {
196 'optionlist_environment' : [
197 '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
198 '\\newenvironment{optionlist}[1]\n'
199 '{\\begin{list}{}\n'
200 ' {\\setlength{\\labelwidth}{#1}\n'
201 ' \\setlength{\\rightmargin}{1cm}\n'
202 ' \\setlength{\\leftmargin}{\\rightmargin}\n'
203 ' \\addtolength{\\leftmargin}{\\labelwidth}\n'
204 ' \\addtolength{\\leftmargin}{\\labelsep}\n'
205 ' \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
206 '}{\\end{list}}\n',
208 'footnote_floats' : [
209 '% begin: floats for footnotes tweaking.\n',
210 '\\setlength{\\floatsep}{0.5em}\n',
211 '\\setlength{\\textfloatsep}{\\fill}\n',
212 '\\addtolength{\\textfloatsep}{3em}\n',
213 '\\renewcommand{\\textfraction}{0.5}\n',
214 '\\renewcommand{\\topfraction}{0.5}\n',
215 '\\renewcommand{\\bottomfraction}{0.5}\n',
216 '\\setcounter{totalnumber}{50}\n',
217 '\\setcounter{topnumber}{50}\n',
218 '\\setcounter{bottomnumber}{50}\n',
219 '% end floats for footnotes\n',
221 'some_commands' : [
222 '% some commands, that could be overwritten in the style file.\n'
223 '\\newcommand{\\rubric}[1]'
224 '{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n'
225 '% end of "some commands"\n',
230 class LaTeXTranslator(nodes.NodeVisitor):
231 # When options are given to the documentclass, latex will pass them
232 # to other packages, as done with babel.
233 # Dummy settings might be taken from document settings
235 d_options = '10pt' # papersize, fontsize
236 d_paper = 'a4paper'
237 d_margins = '2cm'
239 latex_head = '\\documentclass[%s]{%s}\n'
240 encoding = '\\usepackage[latin1]{inputenc}\n'
241 linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
242 geometry = '\\usepackage[%s,margin=%s,nohead]{geometry}\n'
243 stylesheet = '\\input{%s}\n'
244 # add a generated on day , machine by user using docutils version.
245 generator = '%% generator Docutils: http://docutils.sourceforge.net/\n'
247 # use latex tableofcontents or let docutils do it.
248 use_latex_toc = 0
249 # table kind: if 0 tabularx (single page), 1 longtable
250 # maybe should be decided on row count.
251 use_longtable = 1
252 # TODO: use mixins for different implementations.
253 # list environment for option-list. else tabularx
254 use_optionlist_for_option_list = 1
255 # list environment for docinfo. else tabularx
256 use_optionlist_for_docinfo = 0 # NOT YET IN USE
258 # default link color
259 hyperlink_color = "blue"
261 def __init__(self, document):
262 nodes.NodeVisitor.__init__(self, document)
263 self.settings = settings = document.settings
264 self.use_latex_toc = settings.use_latex_toc
265 self.hyperlink_color = settings.hyperlink_color
266 if self.hyperlink_color == '0':
267 self.hyperlink_color = 'black'
268 self.colorlinks = 'false'
269 else:
270 self.colorlinks = 'true'
272 # language: labels, bibliographic_fields, and author_separators.
273 # to allow writing labes for specific languages.
274 self.language = languages.get_language(settings.language_code)
275 self.babel = Babel(settings.language_code)
276 self.author_separator = self.language.author_separators[0]
277 if self.babel.get_language():
278 self.d_options += ',%s' % \
279 self.babel.get_language()
280 self.head_prefix = [
281 self.latex_head % (self.d_options,self.settings.documentclass),
282 '\\usepackage{babel}\n', # language is in documents settings.
283 '\\usepackage{shortvrb}\n', # allows verb in footnotes.
284 self.encoding,
285 # * tabularx: for docinfo, automatic width of columns, always on one page.
286 '\\usepackage{tabularx}\n',
287 '\\usepackage{longtable}\n',
288 # possible other packages.
289 # * fancyhdr
290 # * ltxtable is a combination of tabularx and longtable (pagebreaks).
291 # but ??
293 # extra space between text in tables and the line above them
294 '\\setlength{\\extrarowheight}{2pt}\n',
295 '\\usepackage{amsmath}\n', # what fore amsmath.
296 '\\usepackage{graphicx}\n',
297 '\\usepackage{color}\n',
298 '\\usepackage{multirow}\n',
299 self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
300 # geometry and fonts might go into style.tex.
301 self.geometry % (self.d_paper, self.d_margins),
303 self.generator,
304 # latex lengths
305 '\\newlength{\\admonitionwidth}\n',
306 '\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
307 # width for docinfo tablewidth
308 '\\newlength{\\docinfowidth}\n',
309 '\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
311 self.head_prefix.extend( latex_headings['optionlist_environment'] )
312 self.head_prefix.extend( latex_headings['footnote_floats'] )
313 self.head_prefix.extend( latex_headings['some_commands'] )
314 ## stylesheet is last: so it might be possible to overwrite defaults.
315 stylesheet = self.get_stylesheet_reference()
316 if stylesheet:
317 self.head_prefix.append(self.stylesheet % (stylesheet))
319 if self.linking: # and maybe check for pdf
320 self.pdfinfo = [ ]
321 self.pdfauthor = None
322 # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
323 else:
324 self.pdfinfo = None
325 # NOTE: Latex wants a date and an author, rst puts this into
326 # docinfo, so normally we donot want latex author/date handling.
327 # latex article has its own handling of date and author, deactivate.
328 self.latex_docinfo = 0
329 self.head = [ ]
330 if not self.latex_docinfo:
331 self.head.extend( [ '\\author{}\n', '\\date{}\n' ] )
332 self.body_prefix = ['\\raggedbottom\n']
333 # separate title, so we can appen subtitle.
334 self.title = ""
335 self.body = []
336 self.body_suffix = ['\n']
337 self.section_level = 0
338 self.context = []
339 self.topic_class = ''
340 # column specification for tables
341 self.colspecs = []
342 # Flags to encode
343 # ---------------
344 # verbatim: to tell encode not to encode.
345 self.verbatim = 0
346 # insert_newline: to tell encode to replace blanks by "~".
347 self.insert_none_breaking_blanks = 0
348 # insert_newline: to tell encode to add latex newline.
349 self.insert_newline = 0
350 # mbox_newline: to tell encode to add mbox and newline.
351 self.mbox_newline = 0
353 # enumeration is done by list environment.
354 self._enum_cnt = 0
355 # docinfo.
356 self.docinfo = None
357 # inside literal block: no quote mangling.
358 self.literal_block = 0
359 self.literal = 0
361 def get_stylesheet_reference(self):
362 if self.settings.stylesheet_path:
363 return self.settings.stylesheet_path
364 else:
365 return self.settings.stylesheet
367 def language_label(self, docutil_label):
368 return self.language.labels[docutil_label]
370 def encode(self, text):
372 Encode special characters in `text` & return.
373 # $ % & ~ _ ^ \ { }
374 Escaping with a backslash does not help with backslashes, ~ and ^.
376 < > are only available in math-mode (really ?)
377 $ starts math- mode.
378 AND quotes:
381 if self.verbatim:
382 return text
383 # compile the regexps once. do it here so one can see them.
385 # first the braces.
386 if not self.__dict__.has_key('encode_re_braces'):
387 self.encode_re_braces = re.compile(r'([{}])')
388 text = self.encode_re_braces.sub(r'{\\\1}',text)
389 if not self.__dict__.has_key('encode_re_bslash'):
390 # find backslash: except in the form '{\{}' or '{\}}'.
391 self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
392 # then the backslash: except in the form from line above:
393 # either '{\{}' or '{\}}'.
394 text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)
396 # then dollar
397 text = text.replace("$", '{\\$}')
398 # then all that needs math mode
399 text = text.replace("<", '{$<$}')
400 text = text.replace(">", '{$>$}')
401 # then
402 text = text.replace("&", '{\\&}')
403 text = text.replace("_", '{\\_}')
404 # the ^:
405 # * verb|^| does not work in mbox.
406 # * mathmode has wedge. hat{~} would also work.
407 text = text.replace("^", '{\\ensuremath{^\\wedge}}')
408 text = text.replace("%", '{\\%}')
409 text = text.replace("#", '{\\#}')
410 text = text.replace("~", '{\\~{ }}')
411 if self.literal_block or self.literal:
412 # pdflatex does not produce doublequotes for ngerman.
413 text = self.babel.double_quotes_in_tt(text)
414 else:
415 text = self.babel.quote_quotes(text)
416 if self.insert_newline:
417 # HACK: insert a blank before the newline, to avoid
418 # ! LaTeX Error: There's no line here to end.
419 text = text.replace("\n", '~\\\\\n')
420 elif self.mbox_newline:
421 text = text.replace("\n", '}\\\\\n\\mbox{')
422 if self.insert_none_breaking_blanks:
423 text = text.replace(' ', '~')
424 # unicode !!!
425 text = text.replace(u'\u2020', '{$\\dagger$}')
426 return text
428 def attval(self, text,
429 whitespace=re.compile('[\n\r\t\v\f]')):
430 """Cleanse, encode, and return attribute value text."""
431 return self.encode(whitespace.sub(' ', text))
433 def astext(self):
434 if self.pdfinfo:
435 if self.pdfauthor:
436 self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
437 pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
438 else:
439 pdfinfo = ''
440 title = '\\title{%s}\n' % self.title
441 return ''.join(self.head_prefix + [title]
442 + self.head + [pdfinfo]
443 + self.body_prefix + self.body + self.body_suffix)
445 def visit_Text(self, node):
446 self.body.append(self.encode(node.astext()))
448 def depart_Text(self, node):
449 pass
451 def visit_address(self, node):
452 self.visit_docinfo_item(node, 'address')
454 def depart_address(self, node):
455 self.depart_docinfo_item(node)
457 def visit_admonition(self, node, name):
458 self.body.append('\\begin{center}\\begin{sffamily}\n')
459 self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
460 self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
461 self.body.append('\\vspace{2mm}\n')
464 def depart_admonition(self):
465 self.body.append('}}\n') # end parbox fbox
466 self.body.append('\\end{sffamily}\n\\end{center}\n');
468 def visit_attention(self, node):
469 self.visit_admonition(node, 'attention')
471 def depart_attention(self, node):
472 self.depart_admonition()
474 def visit_author(self, node):
475 self.visit_docinfo_item(node, 'author')
477 def depart_author(self, node):
478 self.depart_docinfo_item(node)
480 def visit_authors(self, node):
481 # ignore. visit_author is called for each one
482 # self.visit_docinfo_item(node, 'author')
483 pass
485 def depart_authors(self, node):
486 # self.depart_docinfo_item(node)
487 pass
489 def visit_block_quote(self, node):
490 self.body.append( '\\begin{quote}\n')
492 def depart_block_quote(self, node):
493 self.body.append( '\\end{quote}\n')
495 def visit_bullet_list(self, node):
496 if not self.use_latex_toc and self.topic_class == 'contents':
497 self.body.append( '\\begin{list}{}{}\n' )
498 else:
499 self.body.append( '\\begin{itemize}\n' )
501 def depart_bullet_list(self, node):
502 if not self.use_latex_toc and self.topic_class == 'contents':
503 self.body.append( '\\end{list}\n' )
504 else:
505 self.body.append( '\\end{itemize}\n' )
507 def visit_caption(self, node):
508 self.body.append( '\\caption{' )
510 def depart_caption(self, node):
511 self.body.append('}')
513 def visit_caution(self, node):
514 self.visit_admonition(node, 'caution')
516 def depart_caution(self, node):
517 self.depart_admonition()
519 def visit_citation(self, node):
520 self.visit_footnote(node)
522 def depart_citation(self, node):
523 self.depart_footnote(node)
525 def visit_title_reference(self, node):
526 # BUG title-references are what?
527 pass
529 def depart_title_reference(self, node):
530 pass
532 def visit_citation_reference(self, node):
533 href = ''
534 if node.has_key('refid'):
535 href = node['refid']
536 elif node.has_key('refname'):
537 href = self.document.nameids[node['refname']]
538 self.body.append('[\\hyperlink{%s}{' % href)
540 def depart_citation_reference(self, node):
541 self.body.append('}]')
543 def visit_classifier(self, node):
544 self.body.append( '(\\textbf{' )
546 def depart_classifier(self, node):
547 self.body.append( '})\n' )
549 def visit_colspec(self, node):
550 if self.use_longtable:
551 self.colspecs.append(node)
552 else:
553 self.context[-1] += 1
555 def depart_colspec(self, node):
556 pass
558 def visit_comment(self, node,
559 sub=re.compile('\n').sub):
560 """Escape end of line by a ne comment start in comment text."""
561 self.body.append('%% %s \n' % sub('\n% ', node.astext()))
562 raise nodes.SkipNode
564 def visit_contact(self, node):
565 self.visit_docinfo_item(node, 'contact')
567 def depart_contact(self, node):
568 self.depart_docinfo_item(node)
570 def visit_copyright(self, node):
571 self.visit_docinfo_item(node, 'copyright')
573 def depart_copyright(self, node):
574 self.depart_docinfo_item(node)
576 def visit_danger(self, node):
577 self.visit_admonition(node, 'danger')
579 def depart_danger(self, node):
580 self.depart_admonition()
582 def visit_date(self, node):
583 self.visit_docinfo_item(node, 'date')
585 def depart_date(self, node):
586 self.depart_docinfo_item(node)
588 def visit_decoration(self, node):
589 pass
591 def depart_decoration(self, node):
592 pass
594 def visit_definition(self, node):
595 self.body.append('%[visit_definition]\n')
597 def depart_definition(self, node):
598 self.body.append('\n')
599 self.body.append('%[depart_definition]\n')
601 def visit_definition_list(self, node):
602 self.body.append( '\\begin{description}\n' )
604 def depart_definition_list(self, node):
605 self.body.append( '\\end{description}\n' )
607 def visit_definition_list_item(self, node):
608 self.body.append('%[visit_definition_list_item]\n')
610 def depart_definition_list_item(self, node):
611 self.body.append('%[depart_definition_list_item]\n')
613 def visit_description(self, node):
614 if self.use_optionlist_for_option_list:
615 self.body.append( ' ' )
616 else:
617 self.body.append( ' & ' )
619 def depart_description(self, node):
620 pass
622 def visit_docinfo(self, node):
623 self.docinfo = []
624 self.docinfo.append('%' + '_'*75 + '\n')
625 self.docinfo.append('\\begin{center}\n')
626 self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')
628 def depart_docinfo(self, node):
629 self.docinfo.append('\\end{tabularx}\n')
630 self.docinfo.append('\\end{center}\n')
631 self.body = self.docinfo + self.body
632 # clear docinfo, so field names are no longer appended.
633 self.docinfo = None
634 if self.use_latex_toc:
635 self.body.append('\\tableofcontents\n\n\\bigskip\n')
637 def visit_docinfo_item(self, node, name):
638 if not self.latex_docinfo:
639 self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
640 if name == 'author':
641 if not self.pdfinfo == None:
642 if not self.pdfauthor:
643 self.pdfauthor = self.attval(node.astext())
644 else:
645 self.pdfauthor += self.author_separator + self.attval(node.astext())
646 if self.latex_docinfo:
647 self.head.append('\\author{%s}\n' % self.attval(node.astext()))
648 raise nodes.SkipNode
649 elif name == 'date':
650 if self.latex_docinfo:
651 self.head.append('\\date{%s}\n' % self.attval(node.astext()))
652 raise nodes.SkipNode
653 if name == 'address':
654 # BUG will fail if latex_docinfo is set.
655 self.insert_newline = 1
656 self.docinfo.append('{\\raggedright\n')
657 self.context.append(' } \\\\\n')
658 else:
659 self.context.append(' \\\\\n')
660 self.context.append(self.docinfo)
661 self.context.append(len(self.body))
663 def depart_docinfo_item(self, node):
664 size = self.context.pop()
665 dest = self.context.pop()
666 tail = self.context.pop()
667 tail = self.body[size:] + [tail]
668 del self.body[size:]
669 dest.extend(tail)
670 # for address we did set insert_newline
671 self.insert_newline = 0
673 def visit_doctest_block(self, node):
674 self.body.append( '\\begin{verbatim}' )
675 self.verbatim = 1
677 def depart_doctest_block(self, node):
678 self.body.append( '\\end{verbatim}\n' )
679 self.verbatim = 0
681 def visit_document(self, node):
682 self.body_prefix.append('\\begin{document}\n')
683 self.body_prefix.append('\\maketitle\n\n')
684 # alternative use titlepage environment.
685 # \begin{titlepage}
687 def depart_document(self, node):
688 self.body_suffix.append('\\end{document}\n')
690 def visit_emphasis(self, node):
691 self.body.append('\\emph{')
693 def depart_emphasis(self, node):
694 self.body.append('}')
696 def visit_entry(self, node):
697 # cell separation
698 column_one = 1
699 if self.context[-1] > 0:
700 column_one = 0
701 if not column_one:
702 self.body.append(' & ')
704 # multi{row,column}
705 if node.has_key('morerows') and node.has_key('morecols'):
706 raise NotImplementedError('LaTeX can\'t handle cells that'
707 'span multiple rows *and* columns, sorry.')
708 atts = {}
709 if node.has_key('morerows'):
710 count = node['morerows'] + 1
711 self.body.append('\\multirow{%d}*{' % count)
712 self.context.append('}')
713 elif node.has_key('morecols'):
714 # the vertical bar before column is missing if it is the first column.
715 # the one after always.
716 if column_one:
717 bar = '|'
718 else:
719 bar = ''
720 count = node['morecols'] + 1
721 self.body.append('\\multicolumn{%d}{%sl|}{' % (count, bar))
722 self.context.append('}')
723 else:
724 self.context.append('')
726 # header / not header
727 if isinstance(node.parent.parent, nodes.thead):
728 self.body.append('\\textbf{')
729 self.context.append('}')
730 else:
731 self.context.append('')
733 def depart_entry(self, node):
734 self.body.append(self.context.pop()) # header / not header
735 self.body.append(self.context.pop()) # multirow/column
736 self.context[-1] += 1
738 def visit_enumerated_list(self, node):
739 # We create our own enumeration list environment.
740 # This allows to set the style and starting value
741 # and unlimited nesting.
742 self._enum_cnt += 1
744 enum_style = {'arabic':'arabic',
745 'loweralpha':'alph',
746 'upperalpha':'Alph',
747 'lowerroman':'roman',
748 'upperroman':'Roman' }
749 enum_suffix = ""
750 if node.has_key('suffix'):
751 enum_suffix = node['suffix']
752 enum_prefix = ""
753 if node.has_key('prefix'):
754 enum_prefix = node['prefix']
756 enum_type = "arabic"
757 if node.has_key('enumtype'):
758 enum_type = node['enumtype']
759 if enum_style.has_key(enum_type):
760 enum_type = enum_style[enum_type]
761 counter_name = "listcnt%d" % self._enum_cnt;
762 self.body.append('\\newcounter{%s}\n' % counter_name)
763 self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
764 (enum_prefix,enum_type,counter_name,enum_suffix))
765 self.body.append('{\n')
766 self.body.append('\\usecounter{%s}\n' % counter_name)
767 # set start after usecounter, because it initializes to zero.
768 if node.has_key('start'):
769 self.body.append('\\addtocounter{%s}{%d}\n' \
770 % (counter_name,node['start']-1))
771 ## set rightmargin equal to leftmargin
772 self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
773 self.body.append('}\n')
775 def depart_enumerated_list(self, node):
776 self.body.append('\\end{list}\n')
778 def visit_error(self, node):
779 self.visit_admonition(node, 'error')
781 def depart_error(self, node):
782 self.depart_admonition()
784 def visit_field(self, node):
785 # real output is done in siblings: _argument, _body, _name
786 pass
788 def depart_field(self, node):
789 self.body.append('\n')
790 ##self.body.append('%[depart_field]\n')
792 def visit_field_argument(self, node):
793 self.body.append('%[visit_field_argument]\n')
795 def depart_field_argument(self, node):
796 self.body.append('%[depart_field_argument]\n')
798 def visit_field_body(self, node):
799 # BUG by attach as text we loose references.
800 if self.docinfo:
801 self.docinfo.append('%s \\\\\n' % node.astext())
802 raise nodes.SkipNode
803 # BUG: what happens if not docinfo
805 def depart_field_body(self, node):
806 self.body.append( '\n' )
808 def visit_field_list(self, node):
809 if not self.docinfo:
810 self.body.append('\\begin{quote}\n')
811 self.body.append('\\begin{description}\n')
813 def depart_field_list(self, node):
814 if not self.docinfo:
815 self.body.append('\\end{description}\n')
816 self.body.append('\\end{quote}\n')
818 def visit_field_name(self, node):
819 # BUG this duplicates docinfo_item
820 if self.docinfo:
821 self.docinfo.append('\\textbf{%s}: &\n\t' % node.astext())
822 raise nodes.SkipNode
823 else:
824 self.body.append('\\item [')
826 def depart_field_name(self, node):
827 if not self.docinfo:
828 self.body.append(':]')
830 def visit_figure(self, node):
831 self.body.append( '\\begin{figure}\n' )
833 def depart_figure(self, node):
834 self.body.append( '\\end{figure}\n' )
836 def visit_footer(self, node):
837 self.context.append(len(self.body))
839 def depart_footer(self, node):
840 start = self.context.pop()
841 footer = (['\n\\begin{center}\small\n']
842 + self.body[start:] + ['\n\\end{center}\n'])
843 self.body_suffix[:0] = footer
844 del self.body[start:]
846 def visit_footnote(self, node):
847 notename = node['id']
848 self.body.append('\\begin{figure}[b]')
849 self.body.append('\\hypertarget{%s}' % notename)
851 def depart_footnote(self, node):
852 self.body.append('\\end{figure}\n')
854 def visit_footnote_reference(self, node):
855 href = ''
856 if node.has_key('refid'):
857 href = node['refid']
858 elif node.has_key('refname'):
859 href = self.document.nameids[node['refname']]
860 format = self.settings.footnote_references
861 if format == 'brackets':
862 suffix = '['
863 self.context.append(']')
864 elif format == 'superscript':
865 suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
866 self.context.append('}')
867 else: # shouldn't happen
868 raise AssertionError('Illegal footnote reference format.')
869 self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
871 def depart_footnote_reference(self, node):
872 self.body.append('}%s' % self.context.pop())
874 def visit_generated(self, node):
875 pass
877 def depart_generated(self, node):
878 pass
880 def visit_header(self, node):
881 self.context.append(len(self.body))
883 def depart_header(self, node):
884 start = self.context.pop()
885 self.body_prefix.append('\n\\verb|begin_header|\n')
886 self.body_prefix.extend(self.body[start:])
887 self.body_prefix.append('\n\\verb|end_header|\n')
888 del self.body[start:]
890 def visit_hint(self, node):
891 self.visit_admonition(node, 'hint')
893 def depart_hint(self, node):
894 self.depart_admonition()
896 def visit_image(self, node):
897 atts = node.attributes.copy()
898 href = atts['uri']
899 ##self.body.append('\\begin{center}\n')
900 self.body.append('\n\\includegraphics{%s}\n' % href)
901 ##self.body.append('\\end{center}\n')
903 def depart_image(self, node):
904 pass
906 def visit_important(self, node):
907 self.visit_admonition(node, 'important')
909 def depart_important(self, node):
910 self.depart_admonition()
912 def visit_interpreted(self, node):
913 # @@@ Incomplete, pending a proper implementation on the
914 # Parser/Reader end.
915 self.visit_literal(node)
917 def depart_interpreted(self, node):
918 self.depart_literal(node)
920 def visit_label(self, node):
921 # footnote/citation label
922 self.body.append('[')
924 def depart_label(self, node):
925 self.body.append(']')
927 def visit_legend(self, node):
928 self.body.append('{\\small ')
930 def depart_legend(self, node):
931 self.body.append('}')
933 def visit_line_block(self, node):
934 """line-block:
935 * whitespace (including linebreaks) is significant
936 * inline markup is supported.
937 * serif typeface
939 self.body.append('\\begin{flushleft}\n')
940 self.insert_none_breaking_blanks = 1
941 self.line_block_without_mbox = 1
942 if self.line_block_without_mbox:
943 self.insert_newline = 1
944 else:
945 self.mbox_newline = 1
946 self.body.append('\\mbox{')
948 def depart_line_block(self, node):
949 if self.line_block_without_mbox:
950 self.insert_newline = 0
951 else:
952 self.body.append('}')
953 self.mbox_newline = 0
954 self.insert_none_breaking_blanks = 0
955 self.body.append('\n\\end{flushleft}\n')
957 def visit_list_item(self, node):
958 self.body.append('\\item ')
960 def depart_list_item(self, node):
961 self.body.append('\n')
963 def visit_literal(self, node):
964 self.literal = 1
965 self.body.append('\\texttt{')
967 def depart_literal(self, node):
968 self.body.append('}')
969 self.literal = 0
971 def visit_literal_block(self, node):
973 .. parsed-literal::
975 # typically in a typewriter/monospaced typeface.
976 # care must be taken with the text, because inline markup is recognized.
978 # possibilities:
979 # * verbatim: is no possibility, as inline markup does not work.
980 # * obey..: is from julien and never worked for me (grubert).
981 self.use_for_literal_block = "mbox"
982 self.literal_block = 1
983 if (self.use_for_literal_block == "mbox"):
984 self.mbox_newline = 1
985 self.insert_none_breaking_blanks = 1
986 self.body.append('\\begin{ttfamily}\\begin{flushleft}\n\\mbox{')
987 else:
988 self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')
990 def depart_literal_block(self, node):
991 if (self.use_for_literal_block == "mbox"):
992 self.body.append('}\n\\end{flushleft}\\end{ttfamily}\n')
993 self.insert_none_breaking_blanks = 0
994 self.mbox_newline = 0
995 else:
996 self.body.append('}\n')
997 self.literal_block = 0
999 def visit_meta(self, node):
1000 self.body.append('[visit_meta]\n')
1001 # BUG maybe set keywords for pdf
1002 ##self.head.append(self.starttag(node, 'meta', **node.attributes))
1004 def depart_meta(self, node):
1005 self.body.append('[depart_meta]\n')
1007 def visit_note(self, node):
1008 self.visit_admonition(node, 'note')
1010 def depart_note(self, node):
1011 self.depart_admonition()
1013 def visit_option(self, node):
1014 if self.context[-1]:
1015 # this is not the first option
1016 self.body.append(', ')
1018 def depart_option(self, node):
1019 # flag tha the first option is done.
1020 self.context[-1] += 1
1022 def visit_option_argument(self, node):
1023 """The delimiter betweeen an option and its argument."""
1024 self.body.append(node.get('delimiter', ' '))
1026 def depart_option_argument(self, node):
1027 pass
1029 def visit_option_group(self, node):
1030 if self.use_optionlist_for_option_list:
1031 self.body.append('\\item [')
1032 else:
1033 atts = {}
1034 if len(node.astext()) > 14:
1035 self.body.append('\\multicolumn{2}{l}{')
1036 self.context.append('} \\\\\n ')
1037 else:
1038 self.context.append('')
1039 self.body.append('\\texttt{')
1040 # flag for first option
1041 self.context.append(0)
1043 def depart_option_group(self, node):
1044 self.context.pop() # the flag
1045 if self.use_optionlist_for_option_list:
1046 self.body.append('] ')
1047 else:
1048 self.body.append('}')
1049 self.body.append(self.context.pop())
1051 def visit_option_list(self, node):
1052 self.body.append('% [option list]\n')
1053 if self.use_optionlist_for_option_list:
1054 self.body.append('\\begin{optionlist}{3cm}\n')
1055 else:
1056 self.body.append('\\begin{center}\n')
1057 # BUG: use admwidth or make it relative to textwidth ?
1058 self.body.append('\\begin{tabularx}{.9\\linewidth}{lX}\n')
1060 def depart_option_list(self, node):
1061 if self.use_optionlist_for_option_list:
1062 self.body.append('\\end{optionlist}\n')
1063 else:
1064 self.body.append('\\end{tabularx}\n')
1065 self.body.append('\\end{center}\n')
1067 def visit_option_list_item(self, node):
1068 pass
1070 def depart_option_list_item(self, node):
1071 if not self.use_optionlist_for_option_list:
1072 self.body.append('\\\\\n')
1074 def visit_option_string(self, node):
1075 ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
1076 pass
1078 def depart_option_string(self, node):
1079 ##self.body.append('</span>')
1080 pass
1082 def visit_organization(self, node):
1083 self.visit_docinfo_item(node, 'organization')
1085 def depart_organization(self, node):
1086 self.depart_docinfo_item(node)
1088 def visit_paragraph(self, node):
1089 if not self.topic_class == 'contents':
1090 self.body.append('\n')
1092 def depart_paragraph(self, node):
1093 if self.topic_class == 'contents':
1094 self.body.append('\n')
1095 else:
1096 self.body.append('\n')
1098 def visit_problematic(self, node):
1099 self.body.append('{\\color{red}\\bfseries{}')
1101 def depart_problematic(self, node):
1102 self.body.append('}')
1104 def visit_raw(self, node):
1105 if node.has_key('format') and node['format'].lower() == 'latex':
1106 self.body.append(node.astext())
1107 raise nodes.SkipNode
1109 def visit_reference(self, node):
1110 # for pdflatex hyperrefs might be supported
1111 if node.has_key('refuri'):
1112 href = node['refuri']
1113 elif node.has_key('refid'):
1114 href = '#' + node['refid']
1115 elif node.has_key('refname'):
1116 href = '#' + self.document.nameids[node['refname']]
1117 ##self.body.append('[visit_reference]')
1118 self.body.append('\\href{%s}{' % href)
1120 def depart_reference(self, node):
1121 self.body.append('}')
1122 ##self.body.append('[depart_reference]')
1124 def visit_revision(self, node):
1125 self.visit_docinfo_item(node, 'revision')
1127 def depart_revision(self, node):
1128 self.depart_docinfo_item(node)
1130 def visit_row(self, node):
1131 self.context.append(0)
1133 def depart_row(self, node):
1134 self.context.pop() # remove cell counter
1135 self.body.append(' \\\\ \\hline\n')
1137 def visit_section(self, node):
1138 self.section_level += 1
1140 def depart_section(self, node):
1141 self.section_level -= 1
1143 def visit_sidebar(self, node):
1144 # BUG: this is just a hack to make sidebars render something
1145 self.body.append('\\begin{center}\\begin{sffamily}\n')
1146 self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')
1148 def depart_sidebar(self, node):
1149 self.body.append('}}}\n') # end parbox colorbox fbox
1150 self.body.append('\\end{sffamily}\n\\end{center}\n');
1153 attribution_formats = {'dash': ('---', ''),
1154 'parentheses': ('(', ')'),
1155 'parens': ('(', ')'),
1156 'none': ('', '')}
1158 def visit_attribution(self, node):
1159 prefix, suffix = self.attribution_formats[self.settings.attribution]
1160 self.body.append('\n\\begin{flushright}\n')
1161 self.body.append(prefix)
1162 self.context.append(suffix)
1164 def depart_attribution(self, node):
1165 self.body.append(self.context.pop() + '\n')
1166 self.body.append('\\end{flushright}\n')
1168 def visit_status(self, node):
1169 self.visit_docinfo_item(node, 'status')
1171 def depart_status(self, node):
1172 self.depart_docinfo_item(node)
1174 def visit_strong(self, node):
1175 self.body.append('\\textbf{')
1177 def depart_strong(self, node):
1178 self.body.append('}')
1180 def visit_substitution_definition(self, node):
1181 raise nodes.SkipNode
1183 def visit_substitution_reference(self, node):
1184 self.unimplemented_visit(node)
1186 def visit_subtitle(self, node):
1187 if isinstance(node.parent, nodes.sidebar):
1188 self.body.append('~\\\\\n\\textbf{')
1189 self.context.append('}\n\\smallskip\n')
1190 else:
1191 self.title = self.title + \
1192 '\\\\\n\\large{%s}\n' % self.encode(node.astext())
1193 raise nodes.SkipNode
1195 def depart_subtitle(self, node):
1196 if isinstance(node.parent, nodes.sidebar):
1197 self.body.append(self.context.pop())
1199 def visit_system_message(self, node):
1200 if node['level'] < self.document.reporter['writer'].report_level:
1201 raise nodes.SkipNode
1204 def depart_system_message(self, node):
1205 self.body.append('\n')
1207 def get_colspecs(self):
1209 Return column specification for longtable.
1211 Assumes reST line length being 80 characters.
1213 width = 80
1215 total_width = 0.0
1216 # donot make it full linewidth
1217 factor = 0.93
1218 # first see if we get too wide.
1219 for node in self.colspecs:
1220 colwidth = factor * float(node['colwidth']) / width
1221 total_width += colwidth
1222 if total_width > 1.0:
1223 factor /= total_width
1225 latex_table_spec = ""
1226 for node in self.colspecs:
1227 colwidth = factor * float(node['colwidth']) / width
1228 latex_table_spec += "|p{%.2f\\linewidth}" % colwidth
1229 self.colspecs = []
1230 return latex_table_spec+"|"
1232 def visit_table(self, node):
1233 if self.use_longtable:
1234 self.body.append('\n\\begin{longtable}[c]')
1235 else:
1236 self.body.append('\n\\begin{tabularx}{\\linewidth}')
1237 self.context.append('table_sentinel') # sentinel
1238 self.context.append(0) # column counter
1240 def depart_table(self, node):
1241 if self.use_longtable:
1242 self.body.append('\\end{longtable}\n')
1243 else:
1244 self.body.append('\\end{tabularx}\n')
1245 sentinel = self.context.pop()
1246 if sentinel != 'table_sentinel':
1247 print 'context:', self.context + [sentinel]
1248 raise AssertionError
1250 def table_preamble(self):
1251 if self.use_longtable:
1252 self.body.append('{%s}\n' % self.get_colspecs())
1253 else:
1254 if self.context[-1] != 'table_sentinel':
1255 self.body.append('{%s}' % ('|X' * self.context.pop() + '|'))
1256 self.body.append('\n\\hline')
1258 def visit_target(self, node):
1259 if not (node.has_key('refuri') or node.has_key('refid')
1260 or node.has_key('refname')):
1261 self.body.append('\\hypertarget{%s}{' % node['name'])
1262 self.context.append('}')
1263 else:
1264 self.context.append('')
1266 def depart_target(self, node):
1267 self.body.append(self.context.pop())
1269 def visit_tbody(self, node):
1270 # BUG write preamble if not yet done (colspecs not [])
1271 # for tables without heads.
1272 if self.colspecs:
1273 self.visit_thead(None)
1274 self.depart_thead(None)
1275 self.body.append('%[visit_tbody]\n')
1277 def depart_tbody(self, node):
1278 self.body.append('%[depart_tbody]\n')
1280 def visit_term(self, node):
1281 self.body.append('\\item[')
1283 def depart_term(self, node):
1284 # definition list term.
1285 self.body.append(':]\n')
1287 def visit_tgroup(self, node):
1288 #self.body.append(self.starttag(node, 'colgroup'))
1289 #self.context.append('</colgroup>\n')
1290 pass
1292 def depart_tgroup(self, node):
1293 pass
1295 def visit_thead(self, node):
1296 # number_of_columns will be zero after get_colspecs.
1297 # BUG ! push onto context for depart to pop it.
1298 number_of_columns = len(self.colspecs)
1299 self.table_preamble()
1300 #BUG longtable needs firstpage and lastfooter too.
1301 self.body.append('\\hline\n')
1303 def depart_thead(self, node):
1304 if self.use_longtable:
1305 # the table header written should be on every page
1306 # => \endhead
1307 self.body.append('\\endhead\n')
1308 # and the firsthead => \endfirsthead
1309 # BUG i want a "continued from previous page" on every not
1310 # firsthead, but then we need the header twice.
1312 # there is a \endfoot and \endlastfoot too.
1313 # but we need the number of columns to
1314 # self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
1315 # self.body.append('\\hline\n\\endfoot\n')
1316 # self.body.append('\\hline\n')
1317 # self.body.append('\\endlastfoot\n')
1320 def visit_tip(self, node):
1321 self.visit_admonition(node, 'tip')
1323 def depart_tip(self, node):
1324 self.depart_admonition()
1326 def visit_title(self, node):
1327 """Only 3 section levels are supported by LaTeX article (AFAIR)."""
1328 if isinstance(node.parent, nodes.topic):
1329 # section titles before the table of contents.
1330 if node.parent.hasattr('id'):
1331 self.body.append('\\hypertarget{%s}{}' % node.parent['id'])
1332 # BUG: latex chokes on center environment with "perhaps a missing item".
1333 # so we use hfill.
1334 self.body.append('\\subsection*{~\\hfill ')
1335 # the closing brace for subsection.
1336 self.context.append('\\hfill ~}\n')
1337 elif isinstance(node.parent, nodes.sidebar):
1338 self.body.append('\\textbf{\\large ')
1339 self.context.append('}\n\\smallskip\n')
1340 elif self.section_level == 0:
1341 # document title
1342 self.title = self.encode(node.astext())
1343 if not self.pdfinfo == None:
1344 self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
1345 raise nodes.SkipNode
1346 else:
1347 self.body.append('\n\n')
1348 self.body.append('%' + '_' * 75)
1349 self.body.append('\n\n')
1350 if node.parent.hasattr('id'):
1351 self.body.append('\\hypertarget{%s}{}\n' % node.parent['id'])
1352 # section_level 0 is title and handled above.
1353 # BUG: latex has no deeper sections (actually paragrah is no section either).
1354 if self.use_latex_toc:
1355 section_star = ""
1356 else:
1357 section_star = "*"
1358 if (self.section_level<=3): # 1,2,3
1359 self.body.append('\\%ssection%s{' % ('sub'*(self.section_level-1),section_star))
1360 elif (self.section_level==4):
1361 #self.body.append('\\paragraph*{')
1362 self.body.append('\\subsubsection%s{' % (section_star))
1363 else:
1364 #self.body.append('\\subparagraph*{')
1365 self.body.append('\\subsubsection%s{' % (section_star))
1366 # BUG: self.body.append( '\\label{%s}\n' % name)
1367 self.context.append('}\n')
1369 def depart_title(self, node):
1370 self.body.append(self.context.pop())
1371 if isinstance(node.parent, nodes.sidebar):
1372 return
1373 # BUG level depends on style.
1374 elif node.parent.hasattr('id') and not self.use_latex_toc:
1375 # pdflatex allows level 0 to 3
1376 # ToC would be the only on level 0 so i choose to decrement the rest.
1377 # "Table of contents" bookmark to see the ToC. To avoid this
1378 # we set all zeroes to one.
1379 l = self.section_level
1380 if l>0:
1381 l = l-1
1382 self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
1383 (l,node.astext(),node.parent['id']))
1385 def visit_topic(self, node):
1386 self.topic_class = node.get('class')
1387 if self.use_latex_toc:
1388 self.topic_class = ''
1389 raise nodes.SkipNode
1391 def depart_topic(self, node):
1392 self.topic_class = ''
1393 self.body.append('\n')
1395 def visit_rubric(self, node):
1396 # self.body.append('\\hfill {\\color{red}\\bfseries{}')
1397 # self.context.append('} \\hfill ~\n')
1398 self.body.append('\\rubric{')
1399 self.context.append('}\n')
1401 def depart_rubric(self, node):
1402 self.body.append(self.context.pop())
1404 def visit_transition(self, node):
1405 self.body.append('\n\n')
1406 self.body.append('%' + '_' * 75)
1407 self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
1408 self.body.append('\n\n')
1410 def depart_transition(self, node):
1411 #self.body.append('[depart_transition]')
1412 pass
1414 def visit_version(self, node):
1415 self.visit_docinfo_item(node, 'version')
1417 def depart_version(self, node):
1418 self.depart_docinfo_item(node)
1420 def visit_warning(self, node):
1421 self.visit_admonition(node, 'warning')
1423 def depart_warning(self, node):
1424 self.depart_admonition()
1426 def unimplemented_visit(self, node):
1427 raise NotImplementedError('visiting unimplemented node type: %s'
1428 % node.__class__.__name__)
1430 # def unknown_visit(self, node):
1431 # def default_visit(self, node):
1433 # vim: set ts=4 et ai :