2 :Author: Engelbert Gruber
3 :Contact: grubert@users.sourceforge.net
6 :Copyright: This module has been placed in the public domain.
8 LaTeX2e document tree Writer.
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. ##.
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."""
31 'LaTeX-Specific Options',
32 'The LaTeX "--output-encoding" default is "latin-1:strict".',
33 (('Specify documentclass. Default is "article".',
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".',
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.',
51 {'default': 'style.tex', 'metavar': '<file>'}),
52 ('Specify a stylesheet file, relative to the current working '
54 'Overrides --stylesheet.',
55 ['--stylesheet-path'],
56 {'metavar': '<file>'}),
57 ('Link to the stylesheet in the output LaTeX file. This is the '
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 '
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 '
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'}
77 """Final translated form of `document`."""
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
93 * latex does not support multiple tocs in one document.
94 (might be no limitation except for docutils documentation)
98 * linewidth - width of a line in the local environment
99 * textwidth - the width of text on the page
101 Maybe always use linewidth ?
105 """Language specifics for LaTeX."""
106 # country code by a.schlock.
107 # partly manually converted from iso and babel stuff, dialects and some
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
122 # french, francais, canadien, acadian
123 'de': 'ngerman', #XXX rather than german
124 # ngerman, naustrian, german, germanb, austrian
127 # english, USenglish, american, UKenglish, british, canadian
153 def __init__(self
,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}"
162 self
.quotes
= ("``", "''")
165 def next_quote(self
):
166 q
= self
.quotes
[self
.quote_index
]
167 self
.quote_index
= (self
.quote_index
+1)%2
170 def quote_quotes(self
,text
):
172 for part
in text
.split('"'):
176 t
+= self
.next_quote() + part
179 def double_quotes_in_tt (self
,text
):
180 if not self
.double_quote_replacment
:
182 return text
.replace('"', self
.double_quote_replacment
)
184 def get_language(self
):
185 if self
._ISO
639_TO
_BABEL
.has_key(self
.language
):
186 return self
._ISO
639_TO
_BABEL
[self
.language
]
189 l
= self
.language
.split("_")[0]
190 if self
._ISO
639_TO
_BABEL
.has_key(l
):
191 return self
._ISO
639_TO
_BABEL
[l
]
196 'optionlist_environment' : [
197 '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
198 '\\newenvironment{optionlist}[1]\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'
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',
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
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.
249 # table kind: if 0 tabularx (single page), 1 longtable
250 # maybe should be decided on row count.
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
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'
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()
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.
285 # * tabularx: for docinfo, automatic width of columns, always on one page.
286 '\\usepackage{tabularx}\n',
287 '\\usepackage{longtable}\n',
288 # possible other packages.
290 # * ltxtable is a combination of tabularx and longtable (pagebreaks).
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
),
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()
317 self
.head_prefix
.append(self
.stylesheet
% (stylesheet
))
319 if self
.linking
: # and maybe check for pdf
321 self
.pdfauthor
= None
322 # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
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
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.
336 self
.body_suffix
= ['\n']
337 self
.section_level
= 0
339 self
.topic_class
= ''
340 # column specification for tables
344 # verbatim: to tell encode not to encode.
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.
357 # inside literal block: no quote mangling.
358 self
.literal_block
= 0
361 def get_stylesheet_reference(self
):
362 if self
.settings
.stylesheet_path
:
363 return self
.settings
.stylesheet_path
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.
374 Escaping with a backslash does not help with backslashes, ~ and ^.
376 < > are only available in math-mode (really ?)
383 # compile the regexps once. do it here so one can see them.
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
)
397 text
= text
.replace("$", '{\\$}')
398 # then all that needs math mode
399 text
= text
.replace("<", '{$<$}')
400 text
= text
.replace(">", '{$>$}')
402 text
= text
.replace("&", '{\\&}')
403 text
= text
.replace("_", '{\\_}')
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
)
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(' ', '~')
425 text
= text
.replace(u
'\u2020', '{$\\dagger$}')
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
))
436 self
.pdfinfo
.append('pdfauthor={%s}' % self
.pdfauthor
)
437 pdfinfo
= '\\hypersetup{\n' + ',\n'.join(self
.pdfinfo
) + '\n}\n'
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
):
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')
485 def depart_authors(self
, node
):
486 # self.depart_docinfo_item(node)
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' )
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' )
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?
529 def depart_title_reference(self
, node
):
532 def visit_citation_reference(self
, node
):
534 if node
.has_key('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
)
553 self
.context
[-1] += 1
555 def depart_colspec(self
, node
):
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()))
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
):
591 def depart_decoration(self
, node
):
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( ' ' )
617 self
.body
.append( ' & ' )
619 def depart_description(self
, node
):
622 def visit_docinfo(self
, node
):
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.
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
))
641 if not self
.pdfinfo
== None:
642 if not self
.pdfauthor
:
643 self
.pdfauthor
= self
.attval(node
.astext())
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()))
650 if self
.latex_docinfo
:
651 self
.head
.append('\\date{%s}\n' % self
.attval(node
.astext()))
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')
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
]
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}' )
677 def depart_doctest_block(self
, node
):
678 self
.body
.append( '\\end{verbatim}\n' )
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.
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
):
699 if self
.context
[-1] > 0:
702 self
.body
.append(' & ')
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.')
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.
720 count
= node
['morecols'] + 1
721 self
.body
.append('\\multicolumn{%d}{%sl|}{' % (count
, bar
))
722 self
.context
.append('}')
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('}')
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.
744 enum_style
= {'arabic':'arabic',
747 'lowerroman':'roman',
748 'upperroman':'Roman' }
750 if node
.has_key('suffix'):
751 enum_suffix
= node
['suffix']
753 if node
.has_key('prefix'):
754 enum_prefix
= node
['prefix']
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
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.
801 self
.docinfo
.append('%s \\\\\n' % node
.astext())
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
):
810 self
.body
.append('\\begin{quote}\n')
811 self
.body
.append('\\begin{description}\n')
813 def depart_field_list(self
, node
):
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
821 self
.docinfo
.append('\\textbf{%s}: &\n\t' % node
.astext())
824 self
.body
.append('\\item [')
826 def depart_field_name(self
, node
):
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
):
856 if node
.has_key('refid'):
858 elif node
.has_key('refname'):
859 href
= self
.document
.nameids
[node
['refname']]
860 format
= self
.settings
.footnote_references
861 if format
== 'brackets':
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
):
877 def depart_generated(self
, node
):
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()
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
):
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
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
):
935 * whitespace (including linebreaks) is significant
936 * inline markup is supported.
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
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
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
):
965 self
.body
.append('\\texttt{')
967 def depart_literal(self
, node
):
968 self
.body
.append('}')
971 def visit_literal_block(self
, node
):
975 # typically in a typewriter/monospaced typeface.
976 # care must be taken with the text, because inline markup is recognized.
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{')
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
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
):
1029 def visit_option_group(self
, node
):
1030 if self
.use_optionlist_for_option_list
:
1031 self
.body
.append('\\item [')
1034 if len(node
.astext()) > 14:
1035 self
.body
.append('\\multicolumn{2}{l}{')
1036 self
.context
.append('} \\\\\n ')
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('] ')
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')
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')
1064 self
.body
.append('\\end{tabularx}\n')
1065 self
.body
.append('\\end{center}\n')
1067 def visit_option_list_item(self
, node
):
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'))
1078 def depart_option_string(self
, node
):
1079 ##self.body.append('</span>')
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')
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': ('(', ')'),
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')
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.
1216 # donot make it full linewidth
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
1230 return latex_table_spec
+"|"
1232 def visit_table(self
, node
):
1233 if self
.use_longtable
:
1234 self
.body
.append('\n\\begin{longtable}[c]')
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')
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())
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('}')
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.
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')
1292 def depart_tgroup(self
, node
):
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
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".
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:
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
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
:
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
))
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
):
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
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]')
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 :