latex writer fixes
[docutils.git] / docutils / writers / latex2e / __init__.py
blob020e6c2ae26ed3fc46ec1e0131bcd3078bddddc7
1 # .. coding: utf8
2 # $Id$
3 # Author: Engelbert Gruber <grubert@users.sourceforge.net>
4 # Copyright: This module has been placed in the public domain.
6 """LaTeX2e document tree Writer."""
8 __docformat__ = 'reStructuredText'
10 # code contributions from several people included, thanks to all.
11 # some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
13 # convention deactivate code by two # i.e. ##.
15 import sys
16 import os
17 import time
18 import re
19 import string
20 import urllib
21 from docutils import frontend, nodes, languages, writers, utils, io
22 from docutils.transforms import writer_aux
24 # compatibility module for Python 2.3
25 if not hasattr(string, 'Template'):
26 import docutils._string_template_compat
27 string.Template = docutils._string_template_compat.Template
29 class Writer(writers.Writer):
31 supported = ('latex','latex2e')
32 """Formats this writer supports."""
34 default_template = 'default.tex'
35 default_template_path = os.path.dirname(__file__)
37 default_preamble = '\n'.join([r'% PDF Standard Fonts',
38 r'\usepackage{mathptmx} % Times',
39 r'\usepackage[scaled=.90]{helvet}',
40 r'\usepackage{courier}'])
41 settings_spec = (
42 'LaTeX-Specific Options',
43 None,
44 (('Specify documentclass. Default is "article".',
45 ['--documentclass'],
46 {'default': 'article', }),
47 ('Specify document options. Multiple options can be given, '
48 'separated by commas. Default is "a4paper".',
49 ['--documentoptions'],
50 {'default': 'a4paper', }),
51 ('Footnotes with numbers/symbols by Docutils. (default)',
52 ['--docutils-footnotes'],
53 {'default': True, 'action': 'store_true',
54 'validator': frontend.validate_boolean}),
55 ('Alias for --docutils-footnotes (deprecated)',
56 ['--use-latex-footnotes'],
57 {'action': 'store_true',
58 'validator': frontend.validate_boolean}),
59 ('Use figure floats for footnote text (deprecated)',
60 ['--figure-footnotes'],
61 {'action': 'store_true',
62 'validator': frontend.validate_boolean}),
63 ('Format for footnote references: one of "superscript" or '
64 '"brackets". Default is "superscript".',
65 ['--footnote-references'],
66 {'choices': ['superscript', 'brackets'], 'default': 'superscript',
67 'metavar': '<format>',
68 'overrides': 'trim_footnote_reference_space'}),
69 ('Use \\cite command for citations. ',
70 ['--use-latex-citations'],
71 {'default': 0, 'action': 'store_true',
72 'validator': frontend.validate_boolean}),
73 ('Use figure floats for citations '
74 '(might get mixed with real figures). (default)',
75 ['--figure-citations'],
76 {'dest': 'use_latex_citations', 'action': 'store_false',
77 'validator': frontend.validate_boolean}),
78 ('Format for block quote attributions: one of "dash" (em-dash '
79 'prefix), "parentheses"/"parens", or "none". Default is "dash".',
80 ['--attribution'],
81 {'choices': ['dash', 'parentheses', 'parens', 'none'],
82 'default': 'dash', 'metavar': '<format>'}),
83 ('Specify LaTeX packages/stylesheets. '
84 ' A style is referenced with \\usepackage if extension is '
85 '".sty" or omitted and with \\input else. '
86 ' Overrides previous --stylesheet and --stylesheet-path settings.',
87 ['--stylesheet'],
88 {'default': '', 'metavar': '<file>',
89 'overrides': 'stylesheet_path'}),
90 ('Like --stylesheet, but the path is rewritten '
91 'relative to the output file. ',
92 ['--stylesheet-path'],
93 {'metavar': '<file>', 'overrides': 'stylesheet'}),
94 ('Link to the stylesheet(s) in the output file. (default)',
95 ['--link-stylesheet'],
96 {'dest': 'embed_stylesheet', 'action': 'store_false'}),
97 ('Embed the stylesheet(s) in the output file. '
98 'Stylesheets must be accessible during processing. ',
99 ['--embed-stylesheet'],
100 {'default': 0, 'action': 'store_true',
101 'validator': frontend.validate_boolean}),
102 ('Customization by LaTeX code in the preamble. '
103 'Default: select PDF standard fonts (Times, Helvetica, Courier).',
104 ['--latex-preamble'],
105 {'default': default_preamble}),
106 ('Specify the template file. Default: "%s".' % default_template,
107 ['--template'],
108 {'default': default_template, 'metavar': '<file>'}),
109 ('Table of contents by LaTeX. (default) ',
110 ['--use-latex-toc'],
111 {'default': 1, 'action': 'store_true',
112 'validator': frontend.validate_boolean}),
113 ('Table of contents by Docutils (without page numbers). ',
114 ['--use-docutils-toc'],
115 {'dest': 'use_latex_toc', 'action': 'store_false',
116 'validator': frontend.validate_boolean}),
117 ('Add parts on top of the section hierarchy.',
118 ['--use-part-section'],
119 {'default': 0, 'action': 'store_true',
120 'validator': frontend.validate_boolean}),
121 ('Attach author and date to the document info table. (default) ',
122 ['--use-docutils-docinfo'],
123 {'dest': 'use_latex_docinfo', 'action': 'store_false',
124 'validator': frontend.validate_boolean}),
125 ('Attach author and date to the document title.',
126 ['--use-latex-docinfo'],
127 {'default': 0, 'action': 'store_true',
128 'validator': frontend.validate_boolean}),
129 ("Typeset abstract as topic. (default)",
130 ['--topic-abstract'],
131 {'dest': 'use_latex_abstract', 'action': 'store_false',
132 'validator': frontend.validate_boolean}),
133 ("Use LaTeX abstract environment for the document's abstract. ",
134 ['--use-latex-abstract'],
135 {'default': 0, 'action': 'store_true',
136 'validator': frontend.validate_boolean}),
137 ('Color of any hyperlinks embedded in text '
138 '(default: "blue", "false" to disable).',
139 ['--hyperlink-color'], {'default': 'blue'}),
140 ('Additional options to the "hyperref" package '
141 '(default: "").',
142 ['--hyperref-options'], {'default': ''}),
143 ('Enable compound enumerators for nested enumerated lists '
144 '(e.g. "1.2.a.ii"). Default: disabled.',
145 ['--compound-enumerators'],
146 {'default': None, 'action': 'store_true',
147 'validator': frontend.validate_boolean}),
148 ('Disable compound enumerators for nested enumerated lists. '
149 'This is the default.',
150 ['--no-compound-enumerators'],
151 {'action': 'store_false', 'dest': 'compound_enumerators'}),
152 ('Enable section ("." subsection ...) prefixes for compound '
153 'enumerators. This has no effect without --compound-enumerators.'
154 'Default: disabled.',
155 ['--section-prefix-for-enumerators'],
156 {'default': None, 'action': 'store_true',
157 'validator': frontend.validate_boolean}),
158 ('Disable section prefixes for compound enumerators. '
159 'This is the default.',
160 ['--no-section-prefix-for-enumerators'],
161 {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
162 ('Set the separator between section number and enumerator '
163 'for compound enumerated lists. Default is "-".',
164 ['--section-enumerator-separator'],
165 {'default': '-', 'metavar': '<char>'}),
166 ('When possibile, use the specified environment for literal-blocks. '
167 'Default is quoting of whitespace and special chars.',
168 ['--literal-block-env'],
169 {'default': ''}),
170 ('When possibile, use verbatim for literal-blocks. '
171 'Compatibility alias for "--literal-block-env=verbatim".',
172 ['--use-verbatim-when-possible'],
173 {'default': 0, 'action': 'store_true',
174 'validator': frontend.validate_boolean}),
175 ('Table style. "standard" with horizontal and vertical lines, '
176 '"booktabs" (LaTeX booktabs style) only horizontal lines '
177 'above and below the table and below the header or "borderless". '
178 'Default: "standard"',
179 ['--table-style'],
180 {'choices': ['standard', 'booktabs','nolines', 'borderless'],
181 'default': 'standard',
182 'metavar': '<format>'}),
183 ('LaTeX graphicx package option. '
184 'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
185 'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
186 'Default is no option.',
187 ['--graphicx-option'],
188 {'default': ''}),
189 ('LaTeX font encoding. '
190 'Possible values are "", "T1" (default), "OT1", "LGR,T1" or '
191 'any other combination of options to the `fontenc` package. ',
192 ['--font-encoding'],
193 {'default': 'T1'}),
194 ('Per default the latex-writer puts the reference title into '
195 'hyperreferences. Specify "ref*" or "pageref*" to get the section '
196 'number or the page number.',
197 ['--reference-label'],
198 {'default': None, }),
199 ('Specify style and database for bibtex, for example '
200 '"--use-bibtex=mystyle,mydb1,mydb2".',
201 ['--use-bibtex'],
202 {'default': None, }),
205 settings_defaults = {'sectnum_depth': 0 # updated by SectNum transform
207 relative_path_settings = ('stylesheet_path',)
209 config_section = 'latex2e writer'
210 config_section_dependencies = ('writers',)
212 head_parts = ('head_prefix', 'requirements', 'latex_preamble',
213 'stylesheet', 'fallbacks', 'pdfsetup',
214 'title', 'subtitle', 'titledata')
215 visitor_attributes = head_parts + ('body_pre_docinfo', 'docinfo',
216 'dedication', 'abstract', 'body')
218 output = None
219 """Final translated form of `document`."""
221 def __init__(self):
222 writers.Writer.__init__(self)
223 self.translator_class = LaTeXTranslator
225 # Override parent method to add latex-specific transforms
226 def get_transforms(self):
227 # call the parent class' method
228 transform_list = writers.Writer.get_transforms(self)
229 # print transform_list
230 # Convert specific admonitions to generic one
231 transform_list.append(writer_aux.Admonitions)
232 # TODO: footnote collection transform
233 # transform_list.append(footnotes.collect)
234 return transform_list
236 def translate(self):
237 visitor = self.translator_class(self.document)
238 self.document.walkabout(visitor)
239 # copy parts
240 for part in self.visitor_attributes:
241 setattr(self, part, getattr(visitor, part))
242 # get template string from file
243 try:
244 file = open(self.document.settings.template, 'rb')
245 except IOError:
246 file = open(os.path.join(self.default_template_path,
247 self.document.settings.template), 'rb')
248 template = string.Template(unicode(file.read(), 'utf-8'))
249 file.close()
250 # fill template
251 self.assemble_parts() # create dictionary of parts
252 self.output = template.substitute(self.parts)
254 def assemble_parts(self):
255 """Assemble the `self.parts` dictionary of output fragments."""
256 writers.Writer.assemble_parts(self)
257 for part in self.visitor_attributes:
258 lines = getattr(self, part)
259 if part in self.head_parts:
260 if lines:
261 lines.append('') # to get a trailing newline
262 self.parts[part] = '\n'.join(lines)
263 else:
264 # body contains inline elements, so join without newline
265 self.parts[part] = ''.join(lines)
268 class Babel(object):
269 """Language specifics for LaTeX."""
271 # TeX (babel) language names:
272 # ! not all of these are supported by Docutils!
274 # based on LyX' languages file with adaptions to `BCP 47`_
275 # (http://www.rfc-editor.org/rfc/bcp/bcp47.txt) and
276 # http://www.tug.org/TUGboat/Articles/tb29-3/tb93miklavec.pdf
277 # * the key without subtags is the default
278 # * case is ignored
279 # cf. http://docutils.sourceforge.net/docs/howto/i18n.html
280 # http://www.w3.org/International/articles/language-tags/
281 # and http://www.iana.org/assignments/language-subtag-registry
282 language_codes = {
283 # code TeX/Babel-name comment
284 'af': 'afrikaans',
285 'ar': 'arabic',
286 # 'be': 'belarusian',
287 'bg': 'bulgarian',
288 'br': 'breton',
289 'ca': 'catalan',
290 # 'cop': 'coptic',
291 'cs': 'czech',
292 'cy': 'welsh',
293 'da': 'danish',
294 'de': 'ngerman', # new spelling (de_1996)
295 'de_1901': 'german', # old spelling
296 'de_at': 'naustrian',
297 'de_at_1901': 'austrian',
298 'dsb': 'lowersorbian',
299 'el': 'greek', # monotonic (el-monoton)
300 'el_polyton': 'polutonikogreek',
301 'en': 'english', # TeX' default language
302 'en_au': 'australian',
303 'en_ca': 'canadian',
304 'en_gb': 'british',
305 'en_nz': 'newzealand',
306 'en_us': 'american',
307 'eo': 'esperanto', # '^' is active
308 'es': 'spanish',
309 'et': 'estonian',
310 'eu': 'basque',
311 # 'fa': 'farsi',
312 'fi': 'finnish',
313 'fr': 'french',
314 'fr_ca': 'canadien',
315 'ga': 'irish', # Irish Gaelic
316 # 'grc': # Ancient Greek
317 'grc_ibycus': 'ibycus', # Ibycus encoding
318 'gl': 'galician',
319 'he': 'hebrew',
320 'hr': 'croatian',
321 'hsb': 'uppersorbian',
322 'hu': 'magyar',
323 'ia': 'interlingua',
324 'id': 'bahasai', # Bahasa (Indonesian)
325 'is': 'icelandic',
326 'it': 'italian',
327 'ja': 'japanese',
328 'kk': 'kazakh',
329 'la': 'latin',
330 'lt': 'lithuanian',
331 'lv': 'latvian',
332 'mn': 'mongolian', # Mongolian, Cyrillic script (mn-cyrl)
333 'ms': 'bahasam', # Bahasa (Malay)
334 'nb': 'norsk', # Norwegian Bokmal
335 'nl': 'dutch',
336 'nn': 'nynorsk', # Norwegian Nynorsk
337 'no': 'norsk', # Norwegian Bokmal
338 'pl': 'polish',
339 'pt': 'portuges',
340 'pt_br': 'brazil',
341 'ro': 'romanian',
342 'ru': 'russian', # '"' is active
343 'se': 'samin', # North Sami
344 # sh-cyrl: Serbo-Croatian, Cyrillic script
345 'sh-latn': 'serbian', # Serbo-Croatian, Latin script
346 'sk': 'slovak',
347 'sl': 'slovene',
348 'sq': 'albanian',
349 # 'sr-cyrl': Serbian, Cyrillic script (sr-cyrl)
350 'sr-latn': 'serbian', # Serbian, Latin script, " active.
351 'sv': 'swedish',
352 # 'th': 'thai',
353 'tr': 'turkish',
354 'uk': 'ukrainian',
355 'vi': 'vietnam',
356 # zh-latn: Chinese Pinyin
358 warn_msg = 'Language "%s" not supported by LaTeX (babel)'
360 def __init__(self, language_code, reporter):
361 self.language_code = language_code
362 self.reporter = reporter
363 self.language = self.get_language(language_code)
364 self.otherlanguages = {}
365 self.quote_index = 0
366 self.quotes = ('``', "''")
367 # language dependent configuration:
368 # double quotes are "active" in some languages (e.g. German).
369 # TODO: use \textquotedbl in T1 font encoding?
370 self.literal_double_quote = u'"'
371 if self.language in ('ngerman', 'german', 'austrian', 'naustrian',
372 'russian'):
373 self.quotes = (r'\glqq{}', r'\grqq{}')
374 self.literal_double_quote = ur'\dq{}'
375 if self.language == 'french':
376 self.quotes = (r'\og{}', r'\fg{}')
377 if self.language == 'italian':
378 self.literal_double_quote = ur'{\char`\"}'
380 def __call__(self):
381 """Return the babel call with correct options and settings"""
382 languages = self.otherlanguages.keys()
383 languages.append(self.language or 'english')
384 self.setup = [r'\usepackage[%s]{babel}' % ','.join(languages)]
385 if 'spanish' in languages:
386 # reset active chars to the original meaning:
387 self.setup.append(
388 r'\addto\shorthandsspanish{\spanishdeactivate{."~<>}}')
389 # or prepend r'\def\spanishoptions{es-noshorthands}'
390 if (languages[-1] is 'english' and
391 'french' in self.otherlanguages.keys()):
392 self.setup += ['% Prevent side-effects if French hyphenation '
393 'patterns are not loaded:',
394 r'\frenchbsetup{StandardLayout}',
395 r'\AtBeginDocument{\selectlanguage{%s}'
396 r'\noextrasfrench}' % self.language]
397 return '\n'.join(self.setup)
399 def next_quote(self):
400 q = self.quotes[self.quote_index]
401 self.quote_index = (self.quote_index+1) % 2
402 return q
404 def quote_quotes(self,text):
405 t = None
406 for part in text.split('"'):
407 if t == None:
408 t = part
409 else:
410 t += self.next_quote() + part
411 return t
413 def get_language(self, language_code):
414 """Return TeX language name for `language_code`"""
415 for tag in utils.normalize_language_tag(language_code):
416 try:
417 language = self.language_codes[tag]
418 break
419 except KeyError:
420 continue
421 else:
422 self.reporter.warning(self.warn_msg % self.language_code)
423 language = ''
424 return language
427 # Building blocks for the latex preamble
428 # --------------------------------------
430 class SortableDict(dict):
431 """Dictionary with additional sorting methods
433 Tip: use key starting with with '_' for sorting before small letters
434 and with '~' for sorting after small letters.
436 def sortedkeys(self):
437 """Return sorted list of keys"""
438 keys = self.keys()
439 keys.sort()
440 return keys
442 def sortedvalues(self):
443 """Return list of values sorted by keys"""
444 return [self[key] for key in self.sortedkeys()]
447 # PreambleCmds
448 # `````````````
449 # A container for LaTeX code snippets that can be
450 # inserted into the preamble if required in the document.
452 # .. The package 'makecmds' would enable shorter definitions using the
453 # \providelength and \provideenvironment commands.
454 # However, it is pretty non-standard (texlive-latex-extra).
456 class PreambleCmds(object):
457 """Building blocks for the latex preamble."""
459 PreambleCmds.abstract = r"""
460 % abstract title
461 \providecommand*{\DUtitleabstract}[1]{\centerline{\textbf{#1}}}"""
463 PreambleCmds.admonition = r"""
464 % admonition (specially marked topic)
465 \providecommand{\DUadmonition}[2][class-arg]{%
466 % try \DUadmonition#1{#2}:
467 \ifcsname DUadmonition#1\endcsname%
468 \csname DUadmonition#1\endcsname{#2}%
469 \else
470 \begin{center}
471 \fbox{\parbox{0.9\textwidth}{#2}}
472 \end{center}
474 }"""
476 PreambleCmds.align_center = r"""
477 \makeatletter
478 \@namedef{DUrolealign-center}{\centering}
479 \makeatother
482 ## PreambleCmds.caption = r"""% configure caption layout
483 ## \usepackage{caption}
484 ## \captionsetup{singlelinecheck=false}% no exceptions for one-liners"""
486 PreambleCmds.color = r"""\usepackage{color}"""
488 PreambleCmds.docinfo = r"""
489 % docinfo (width of docinfo table)
490 \DUprovidelength{\DUdocinfowidth}{0.9\textwidth}"""
491 # PreambleCmds.docinfo._depends = 'providelength'
493 PreambleCmds.embedded_package_wrapper = r"""\makeatletter
494 %% embedded stylesheet: %s
496 \makeatother"""
498 PreambleCmds.dedication = r"""
499 % dedication topic
500 \providecommand{\DUtopicdedication}[1]{\begin{center}#1\end{center}}"""
502 PreambleCmds.error = r"""
503 % error admonition title
504 \providecommand*{\DUtitleerror}[1]{\DUtitle{\color{red}#1}}"""
505 # PreambleCmds.errortitle._depends = 'color'
507 PreambleCmds.fieldlist = r"""
508 % fieldlist environment
509 \ifthenelse{\isundefined{\DUfieldlist}}{
510 \newenvironment{DUfieldlist}%
511 {\quote\description}
512 {\enddescription\endquote}
513 }{}"""
515 PreambleCmds.float_settings = r"""\usepackage{float} % float configuration
516 \floatplacement{figure}{H} % place figures here definitely"""
518 PreambleCmds.footnotes = r"""% numeric or symbol footnotes with hyperlinks
519 \providecommand*{\DUfootnotemark}[3]{%
520 \raisebox{1em}{\hypertarget{#1}{}}%
521 \hyperlink{#2}{\textsuperscript{#3}}%
523 \providecommand{\DUfootnotetext}[4]{%
524 \begingroup%
525 \renewcommand{\thefootnote}{%
526 \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
527 \protect\hyperlink{#2}{#3}}%
528 \footnotetext{#4}%
529 \endgroup%
530 }"""
532 PreambleCmds.footnote_floats = r"""% settings for footnotes as floats:
533 \setlength{\floatsep}{0.5em}
534 \setlength{\textfloatsep}{\fill}
535 \addtolength{\textfloatsep}{3em}
536 \renewcommand{\textfraction}{0.5}
537 \renewcommand{\topfraction}{0.5}
538 \renewcommand{\bottomfraction}{0.5}
539 \setcounter{totalnumber}{50}
540 \setcounter{topnumber}{50}
541 \setcounter{bottomnumber}{50}"""
543 PreambleCmds.graphicx_auto = r"""% Check output format
544 \ifx\pdftexversion\undefined
545 \usepackage{graphicx}
546 \else
547 \usepackage[pdftex]{graphicx}
548 \fi'))"""
550 PreambleCmds.inline = r"""
551 % inline markup (custom roles)
552 % \DUrole{#1}{#2} tries \DUrole#1{#2}
553 \providecommand*{\DUrole}[2]{%
554 \ifcsname DUrole#1\endcsname%
555 \csname DUrole#1\endcsname{#2}%
556 \else% backwards compatibility: try \docutilsrole#1{#2}
557 \ifcsname docutilsrole#1\endcsname%
558 \csname docutilsrole#1\endcsname{#2}%
559 \else%
561 \fi%
562 \fi%
563 }"""
565 PreambleCmds.legend = r"""
566 % legend environment
567 \ifthenelse{\isundefined{\DUlegend}}{
568 \newenvironment{DUlegend}{\small}{}
569 }{}"""
571 PreambleCmds.lineblock = r"""
572 % lineblock environment
573 \DUprovidelength{\DUlineblockindent}{2.5em}
574 \ifthenelse{\isundefined{\DUlineblock}}{
575 \newenvironment{DUlineblock}[1]{%
576 \list{}{\setlength{\partopsep}{\parskip}
577 \addtolength{\partopsep}{\baselineskip}
578 \setlength{\topsep}{0pt}
579 \setlength{\itemsep}{0.15\baselineskip}
580 \setlength{\parsep}{0pt}
581 \setlength{\leftmargin}{#1}}
582 \raggedright
584 {\endlist}
585 }{}"""
586 # PreambleCmds.lineblock._depends = 'providelength'
588 PreambleCmds.linking = r"""
589 %% hyperlinks:
590 \ifthenelse{\isundefined{\hypersetup}}{
591 \usepackage[%s]{hyperref}
592 \urlstyle{same} %% normal text font (alternatives: tt, rm, sf)
593 }{}"""
595 PreambleCmds.minitoc = r"""%% local table of contents
596 \usepackage{minitoc}"""
598 PreambleCmds.optionlist = r"""
599 % optionlist environment
600 \providecommand*{\DUoptionlistlabel}[1]{\bf #1 \hfill}
601 \DUprovidelength{\DUoptionlistindent}{3cm}
602 \ifthenelse{\isundefined{\DUoptionlist}}{
603 \newenvironment{DUoptionlist}{%
604 \list{}{\setlength{\labelwidth}{\DUoptionlistindent}
605 \setlength{\rightmargin}{1cm}
606 \setlength{\leftmargin}{\rightmargin}
607 \addtolength{\leftmargin}{\labelwidth}
608 \addtolength{\leftmargin}{\labelsep}
609 \renewcommand{\makelabel}{\DUoptionlistlabel}}
611 {\endlist}
612 }{}"""
613 # PreambleCmds.optionlist._depends = 'providelength'
615 PreambleCmds.providelength = r"""
616 % providelength (provide a length variable and set default, if it is new)
617 \providecommand*{\DUprovidelength}[2]{
618 \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{}
619 }"""
621 PreambleCmds.rubric = r"""
622 % rubric (informal heading)
623 \providecommand*{\DUrubric}[2][class-arg]{%
624 \subsubsection*{\centering\textit{\textmd{#2}}}}"""
626 PreambleCmds.sidebar = r"""
627 % sidebar (text outside the main text flow)
628 \providecommand{\DUsidebar}[2][class-arg]{%
629 \begin{center}
630 \colorbox[gray]{0.80}{\parbox{0.9\textwidth}{#2}}
631 \end{center}
632 }"""
634 PreambleCmds.subtitle = r"""
635 % subtitle (for topic/sidebar)
636 \providecommand*{\DUsubtitle}[2][class-arg]{\par\emph{#2}\smallskip}"""
638 PreambleCmds.table = r"""\usepackage{longtable,ltcaption,array}
639 \setlength{\extrarowheight}{2pt}
640 \newlength{\DUtablewidth} % internal use in tables"""
642 # Options [force,almostfull] prevent spurious error messages, see
643 # de.comp.text.tex/2005-12/msg01855
644 PreambleCmds.textcomp = """\
645 \\usepackage{textcomp} % text symbol macros"""
647 PreambleCmds.titlereference = r"""
648 % titlereference role
649 \providecommand*{\DUroletitlereference}[1]{\textsl{#1}}"""
651 PreambleCmds.title = r"""
652 % title for topics, admonitions and sidebar
653 \providecommand*{\DUtitle}[2][class-arg]{%
654 % call \DUtitle#1{#2} if it exists:
655 \ifcsname DUtitle#1\endcsname%
656 \csname DUtitle#1\endcsname{#2}%
657 \else
658 \smallskip\noindent\textbf{#2}\smallskip%
660 }"""
662 PreambleCmds.topic = r"""
663 % topic (quote with heading)
664 \providecommand{\DUtopic}[2][class-arg]{%
665 \ifcsname DUtopic#1\endcsname%
666 \csname DUtopic#1\endcsname{#2}%
667 \else
668 \begin{quote}#2\end{quote}
670 }"""
672 PreambleCmds.transition = r"""
673 % transition (break, fancybreak, anonymous section)
674 \providecommand*{\DUtransition}[1][class-arg]{%
675 \hspace*{\fill}\hrulefill\hspace*{\fill}
676 \vskip 0.5\baselineskip
677 }"""
680 class DocumentClass(object):
681 """Details of a LaTeX document class."""
683 def __init__(self, document_class, with_part=False):
684 self.document_class = document_class
685 self._with_part = with_part
686 self.sections = ['section', 'subsection', 'subsubsection',
687 'paragraph', 'subparagraph']
688 if self.document_class in ('book', 'memoir', 'report',
689 'scrbook', 'scrreprt'):
690 self.sections.insert(0, 'chapter')
691 if self._with_part:
692 self.sections.insert(0, 'part')
694 def section(self, level):
695 """Return the LaTeX section name for section `level`.
697 The name depends on the specific document class.
698 Level is 1,2,3..., as level 0 is the title.
701 if level <= len(self.sections):
702 return self.sections[level-1]
703 else:
704 return self.sections[-1]
707 class Table(object):
708 """Manage a table while traversing.
710 Maybe change to a mixin defining the visit/departs, but then
711 class Table internal variables are in the Translator.
713 Table style might be
715 :standard: horizontal and vertical lines
716 :booktabs: only horizontal lines (requires "booktabs" LaTeX package)
717 :borderless: no borders around table cells
718 :nolines: alias for borderless
720 def __init__(self,translator,latex_type,table_style):
721 self._translator = translator
722 self._latex_type = latex_type
723 self._table_style = table_style
724 self._open = 0
725 # miscellaneous attributes
726 self._attrs = {}
727 self._col_width = []
728 self._rowspan = []
729 self.stubs = []
730 self._in_thead = 0
732 def open(self):
733 self._open = True
734 self._col_specs = []
735 self.caption = []
736 self._attrs = {}
737 self._in_head = False # maybe context with search
738 def close(self):
739 self._open = False
740 self._col_specs = None
741 self.caption = []
742 self._attrs = {}
743 self.stubs = []
744 def is_open(self):
745 return self._open
747 def set_table_style(self, table_style):
748 if not table_style in ('standard','booktabs','borderless','nolines'):
749 return
750 self._table_style = table_style
752 def get_latex_type(self):
753 if self._latex_type == 'longtable' and not self.caption:
754 # do not advance the "table" counter (requires "ltcaption" package)
755 return('longtable*')
756 return self._latex_type
758 def set(self,attr,value):
759 self._attrs[attr] = value
760 def get(self,attr):
761 if attr in self._attrs:
762 return self._attrs[attr]
763 return None
765 def get_vertical_bar(self):
766 if self._table_style == 'standard':
767 return '|'
768 return ''
770 # horizontal lines are drawn below a row,
771 def get_opening(self):
772 return '\n'.join([r'\setlength{\DUtablewidth}{\linewidth}',
773 r'\begin{%s}[c]' % self.get_latex_type()])
775 def get_closing(self):
776 closing = []
777 if self._table_style == 'booktabs':
778 closing.append(r'\bottomrule')
779 # elif self._table_style == 'standard':
780 # closing.append(r'\hline')
781 closing.append(r'\end{%s}' % self.get_latex_type())
782 return '\n'.join(closing)
784 def visit_colspec(self, node):
785 self._col_specs.append(node)
786 # "stubs" list is an attribute of the tgroup element:
787 self.stubs.append(node.attributes.get('stub'))
789 def get_colspecs(self):
790 """Return column specification for longtable.
792 Assumes reST line length being 80 characters.
793 Table width is hairy.
795 === ===
796 ABC DEF
797 === ===
799 usually gets to narrow, therefore we add 1 (fiddlefactor).
801 width = 80
803 total_width = 0.0
804 # first see if we get too wide.
805 for node in self._col_specs:
806 colwidth = float(node['colwidth']+1) / width
807 total_width += colwidth
808 self._col_width = []
809 self._rowspan = []
810 # donot make it full linewidth
811 factor = 0.93
812 if total_width > 1.0:
813 factor /= total_width
814 bar = self.get_vertical_bar()
815 latex_table_spec = ''
816 for node in self._col_specs:
817 colwidth = factor * float(node['colwidth']+1) / width
818 self._col_width.append(colwidth+0.005)
819 self._rowspan.append(0)
820 latex_table_spec += '%sp{%.3f\\DUtablewidth}' % (bar, colwidth+0.005)
821 return latex_table_spec+bar
823 def get_column_width(self):
824 """Return columnwidth for current cell (not multicell)."""
825 return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row-1]
827 def get_multicolumn_width(self, start, len_):
828 """Return sum of columnwidths for multicell."""
829 mc_width = sum(width
830 for width in (self._col_width[start + co - 1]
831 for co in range (len_)))
832 return '{0:.2f}\\DUtablewidth'.format(mc_width)
834 def get_caption(self):
835 if not self.caption:
836 return ''
837 caption = ''.join(self.caption)
838 if 1 == self._translator.thead_depth():
839 return r'\caption{%s}\\' '\n' % caption
840 return r'\caption[]{%s (... continued)}\\' '\n' % caption
842 def need_recurse(self):
843 if self._latex_type == 'longtable':
844 return 1 == self._translator.thead_depth()
845 return 0
847 def visit_thead(self):
848 self._in_thead += 1
849 if self._table_style == 'standard':
850 return ['\\hline\n']
851 elif self._table_style == 'booktabs':
852 return ['\\toprule\n']
853 return []
854 def depart_thead(self):
855 a = []
856 #if self._table_style == 'standard':
857 # a.append('\\hline\n')
858 if self._table_style == 'booktabs':
859 a.append('\\midrule\n')
860 if self._latex_type == 'longtable':
861 if 1 == self._translator.thead_depth():
862 a.append('\\endfirsthead\n')
863 else:
864 a.append('\\endhead\n')
865 a.append(r'\multicolumn{%d}{c}' % len(self._col_specs) +
866 r'{\hfill ... continued on next page} \\')
867 a.append('\n\\endfoot\n\\endlastfoot\n')
868 # for longtable one could add firsthead, foot and lastfoot
869 self._in_thead -= 1
870 return a
871 def visit_row(self):
872 self._cell_in_row = 0
873 def depart_row(self):
874 res = [' \\\\\n']
875 self._cell_in_row = None # remove cell counter
876 for i in range(len(self._rowspan)):
877 if (self._rowspan[i]>0):
878 self._rowspan[i] -= 1
880 if self._table_style == 'standard':
881 rowspans = [i+1 for i in range(len(self._rowspan))
882 if (self._rowspan[i]<=0)]
883 if len(rowspans)==len(self._rowspan):
884 res.append('\\hline\n')
885 else:
886 cline = ''
887 rowspans.reverse()
888 # TODO merge clines
889 while 1:
890 try:
891 c_start = rowspans.pop()
892 except:
893 break
894 cline += '\\cline{%d-%d}\n' % (c_start,c_start)
895 res.append(cline)
896 return res
898 def set_rowspan(self,cell,value):
899 try:
900 self._rowspan[cell] = value
901 except:
902 pass
903 def get_rowspan(self,cell):
904 try:
905 return self._rowspan[cell]
906 except:
907 return 0
908 def get_entry_number(self):
909 return self._cell_in_row
910 def visit_entry(self):
911 self._cell_in_row += 1
912 def is_stub_column(self):
913 if len(self.stubs) >= self._cell_in_row:
914 return self.stubs[self._cell_in_row-1]
915 return False
918 class LaTeXTranslator(nodes.NodeVisitor):
920 # When options are given to the documentclass, latex will pass them
921 # to other packages, as done with babel.
922 # Dummy settings might be taken from document settings
924 # Config setting defaults
925 # -----------------------
927 # TODO: use mixins for different implementations.
928 # list environment for docinfo. else tabularx
929 ## use_optionlist_for_docinfo = False # TODO: NOT YET IN USE
931 # Use compound enumerations (1.A.1.)
932 compound_enumerators = 0
934 # If using compound enumerations, include section information.
935 section_prefix_for_enumerators = 0
937 # This is the character that separates the section ("." subsection ...)
938 # prefix from the regular list enumerator.
939 section_enumerator_separator = '-'
941 # Auxiliary variables
942 # -------------------
944 has_latex_toc = False # is there a toc in the doc? (needed by minitoc)
945 is_toc_list = False # is the current bullet_list a ToC?
946 section_level = 0
948 # Flags to encode():
949 # inside citation reference labels underscores dont need to be escaped
950 inside_citation_reference_label = False
951 verbatim = False # do not encode
952 insert_non_breaking_blanks = False # replace blanks by "~"
953 insert_newline = False # add latex newline commands
954 literal = False # literal text (block or inline)
957 def __init__(self, document, babel_class=Babel):
958 nodes.NodeVisitor.__init__(self, document)
959 # Reporter
960 # ~~~~~~~~
961 self.warn = self.document.reporter.warning
962 self.error = self.document.reporter.error
964 # Settings
965 # ~~~~~~~~
966 self.settings = settings = document.settings
967 self.latex_encoding = self.to_latex_encoding(settings.output_encoding)
968 self.use_latex_toc = settings.use_latex_toc
969 self.use_latex_docinfo = settings.use_latex_docinfo
970 self._use_latex_citations = settings.use_latex_citations
971 self.embed_stylesheet = settings.embed_stylesheet
972 self._reference_label = settings.reference_label
973 self.hyperlink_color = settings.hyperlink_color
974 self.compound_enumerators = settings.compound_enumerators
975 self.font_encoding = getattr(settings, 'font_encoding', '')
976 self.section_prefix_for_enumerators = (
977 settings.section_prefix_for_enumerators)
978 self.section_enumerator_separator = (
979 settings.section_enumerator_separator.replace('_', r'\_'))
980 # literal blocks:
981 self.literal_block_env = ''
982 self.literal_block_options = ''
983 if settings.literal_block_env != '':
984 (none,
985 self.literal_block_env,
986 self.literal_block_options,
987 none ) = re.split('(\w+)(.*)', settings.literal_block_env)
988 elif settings.use_verbatim_when_possible:
989 self.literal_block_env = 'verbatim'
991 if self.settings.use_bibtex:
992 self.bibtex = self.settings.use_bibtex.split(',',1)
993 # TODO avoid errors on not declared citations.
994 else:
995 self.bibtex = None
996 # language module for Docutils-generated text
997 # (labels, bibliographic_fields, and author_separators)
998 self.language_module = languages.get_language(settings.language_code,
999 document.reporter)
1000 self.babel = babel_class(settings.language_code, document.reporter)
1001 self.author_separator = self.language_module.author_separators[0]
1002 d_options = [self.settings.documentoptions]
1003 if self.babel.language not in ('english', ''):
1004 d_options.append(self.babel.language)
1005 self.documentoptions = ','.join(filter(None, d_options))
1006 self.d_class = DocumentClass(settings.documentclass,
1007 settings.use_part_section)
1008 # graphic package options:
1009 if self.settings.graphicx_option == '':
1010 self.graphicx_package = r'\usepackage{graphicx}'
1011 elif self.settings.graphicx_option.lower() == 'auto':
1012 self.graphicx_package = PreambleCmds.graphicx_auto
1013 else:
1014 self.graphicx_package = (r'\usepackage[%s]{graphicx}' %
1015 self.settings.graphicx_option)
1016 # footnotes:
1017 self.docutils_footnotes = settings.docutils_footnotes
1018 if settings.use_latex_footnotes:
1019 self.docutils_footnotes = True
1020 self.warn('`use_latex_footnotes` is deprecated. '
1021 'The setting has been renamed to `docutils_footnotes` '
1022 'and the alias will be removed in a future version.')
1023 self.figure_footnotes = settings.figure_footnotes
1024 if self.figure_footnotes:
1025 self.docutils_footnotes = True
1026 self.warn('The "figure footnotes" workaround/setting is strongly '
1027 'deprecated and will be removed in a future version.')
1029 # Output collection stacks
1030 # ~~~~~~~~~~~~~~~~~~~~~~~~
1032 # Document parts
1033 self.head_prefix = [r'\documentclass[%s]{%s}' %
1034 (self.documentoptions, self.settings.documentclass)]
1035 self.requirements = SortableDict() # made a list in depart_document()
1036 self.requirements['__static'] = r'\usepackage{ifthen}'
1037 self.latex_preamble = [settings.latex_preamble]
1038 self.stylesheet = []
1039 self.fallbacks = SortableDict() # made a list in depart_document()
1040 self.pdfsetup = [] # PDF properties (hyperref package)
1041 self.title = []
1042 self.subtitle = []
1043 self.titledata = [] # \title, \author, \date
1044 ## self.body_prefix = ['\\begin{document}\n']
1045 self.body_pre_docinfo = [] # \maketitle
1046 self.docinfo = []
1047 self.dedication = []
1048 self.abstract = []
1049 self.body = []
1050 ## self.body_suffix = ['\\end{document}\n']
1052 # A heterogenous stack used in conjunction with the tree traversal.
1053 # Make sure that the pops correspond to the pushes:
1054 self.context = []
1056 # Title metadata:
1057 self.title_labels = []
1058 self.subtitle_labels = []
1059 # (if use_latex_docinfo: collects lists of
1060 # author/organization/contact/address lines)
1061 self.author_stack = []
1062 self.date = []
1064 # PDF properties: pdftitle, pdfauthor
1065 # TODO?: pdfcreator, pdfproducer, pdfsubject, pdfkeywords
1066 self.pdfinfo = []
1067 self.pdfauthor = []
1069 # Stack of section counters so that we don't have to use_latex_toc.
1070 # This will grow and shrink as processing occurs.
1071 # Initialized for potential first-level sections.
1072 self._section_number = [0]
1074 # The current stack of enumerations so that we can expand
1075 # them into a compound enumeration.
1076 self._enumeration_counters = []
1077 # The maximum number of enumeration counters we've used.
1078 # If we go beyond this number, we need to create a new
1079 # counter; otherwise, just reuse an old one.
1080 self._max_enumeration_counters = 0
1082 self._bibitems = []
1084 # object for a table while proccessing.
1085 self.table_stack = []
1086 self.active_table = Table(self, 'longtable', settings.table_style)
1088 # Where to collect the output of visitor methods (default: body)
1089 self.out = self.body
1090 self.out_stack = [] # stack of output collectors
1092 # Process settings
1093 # ~~~~~~~~~~~~~~~~
1094 # Encodings:
1095 # Docutils' output-encoding => TeX input encoding
1096 if self.latex_encoding != 'ascii':
1097 self.requirements['_inputenc'] = (r'\usepackage[%s]{inputenc}'
1098 % self.latex_encoding)
1099 # TeX font encoding
1100 if self.font_encoding:
1101 self.requirements['_fontenc'] = (r'\usepackage[%s]{fontenc}' %
1102 self.font_encoding)
1103 # page layout with typearea (if there are relevant document options)
1104 if (settings.documentclass.find('scr') == -1 and
1105 (self.documentoptions.find('DIV') != -1 or
1106 self.documentoptions.find('BCOR') != -1)):
1107 self.requirements['typearea'] = r'\usepackage{typearea}'
1109 # Stylesheets
1110 # get list of style sheets from settings
1111 styles = utils.get_stylesheet_list(settings)
1112 # adapt path if --stylesheet_path is used
1113 if settings.stylesheet_path and not(self.embed_stylesheet):
1114 styles = [utils.relative_path(settings._destination, sheet)
1115 for sheet in styles]
1116 for sheet in styles:
1117 (base, ext) = os.path.splitext(sheet)
1118 is_package = ext in ['.sty', '']
1119 if self.embed_stylesheet:
1120 if is_package:
1121 sheet = base + '.sty' # adapt package name
1122 # wrap in \makeatletter, \makeatother
1123 wrapper = PreambleCmds.embedded_package_wrapper
1124 else:
1125 wrapper = '%% embedded stylesheet: %s\n%s'
1126 settings.record_dependencies.add(sheet)
1127 self.stylesheet.append(wrapper %
1128 (sheet, io.FileInput(source_path=sheet, encoding='utf-8').read()))
1129 else: # link to style sheet
1130 if is_package:
1131 self.stylesheet.append(r'\usepackage{%s}' % base)
1132 else:
1133 self.stylesheet.append(r'\input{%s}' % sheet)
1135 # PDF setup
1136 if self.hyperlink_color in ('0', 'false', 'False', ''):
1137 self.hyperref_options = ''
1138 else:
1139 self.hyperref_options = 'colorlinks=true,linkcolor=%s,urlcolor=%s' % (
1140 self.hyperlink_color, self.hyperlink_color)
1141 if settings.hyperref_options:
1142 self.hyperref_options += ',' + settings.hyperref_options
1144 # LaTeX Toc
1145 # include all supported sections in toc and PDF bookmarks
1146 # (or use documentclass-default (as currently))?
1147 ## if self.use_latex_toc:
1148 ## self.requirements['tocdepth'] = (r'\setcounter{tocdepth}{%d}' %
1149 ## len(self.d_class.sections))
1151 # LaTeX section numbering
1152 if not self.settings.sectnum_xform: # section numbering by LaTeX:
1153 # sectnum_depth:
1154 # None "sectnum" directive without depth arg -> LaTeX default
1155 # 0 no "sectnum" directive -> no section numbers
1156 # else value of the "depth" argument: translate to LaTeX level
1157 # -1 part (0 with "article" document class)
1158 # 0 chapter (missing in "article" document class)
1159 # 1 section
1160 # 2 subsection
1161 # 3 subsubsection
1162 # 4 paragraph
1163 # 5 subparagraph
1164 if settings.sectnum_depth is not None:
1165 # limit to supported levels
1166 sectnum_depth = min(settings.sectnum_depth,
1167 len(self.d_class.sections))
1168 # adjust to document class and use_part_section settings
1169 if 'chapter' in self.d_class.sections:
1170 sectnum_depth -= 1
1171 if self.d_class.sections[0] == 'part':
1172 sectnum_depth -= 1
1173 self.requirements['sectnum_depth'] = (
1174 r'\setcounter{secnumdepth}{%d}' % sectnum_depth)
1175 # start with specified number:
1176 if (hasattr(settings, 'sectnum_start') and
1177 settings.sectnum_start != 1):
1178 self.requirements['sectnum_start'] = (
1179 r'\setcounter{%s}{%d}' % (self.d_class.sections[0],
1180 settings.sectnum_start-1))
1181 # currently ignored (configure in a stylesheet):
1182 ## settings.sectnum_prefix
1183 ## settings.sectnum_suffix
1186 # Auxiliary Methods
1187 # -----------------
1189 def to_latex_encoding(self,docutils_encoding):
1190 """Translate docutils encoding name into LaTeX's.
1192 Default method is remove "-" and "_" chars from docutils_encoding.
1194 tr = { 'iso-8859-1': 'latin1', # west european
1195 'iso-8859-2': 'latin2', # east european
1196 'iso-8859-3': 'latin3', # esperanto, maltese
1197 'iso-8859-4': 'latin4', # north european, scandinavian, baltic
1198 'iso-8859-5': 'iso88595', # cyrillic (ISO)
1199 'iso-8859-9': 'latin5', # turkish
1200 'iso-8859-15': 'latin9', # latin9, update to latin1.
1201 'mac_cyrillic': 'maccyr', # cyrillic (on Mac)
1202 'windows-1251': 'cp1251', # cyrillic (on Windows)
1203 'koi8-r': 'koi8-r', # cyrillic (Russian)
1204 'koi8-u': 'koi8-u', # cyrillic (Ukrainian)
1205 'windows-1250': 'cp1250', #
1206 'windows-1252': 'cp1252', #
1207 'us-ascii': 'ascii', # ASCII (US)
1208 # unmatched encodings
1209 #'': 'applemac',
1210 #'': 'ansinew', # windows 3.1 ansi
1211 #'': 'ascii', # ASCII encoding for the range 32--127.
1212 #'': 'cp437', # dos latin us
1213 #'': 'cp850', # dos latin 1
1214 #'': 'cp852', # dos latin 2
1215 #'': 'decmulti',
1216 #'': 'latin10',
1217 #'iso-8859-6': '' # arabic
1218 #'iso-8859-7': '' # greek
1219 #'iso-8859-8': '' # hebrew
1220 #'iso-8859-10': '' # latin6, more complete iso-8859-4
1222 encoding = docutils_encoding.lower()
1223 if encoding in tr:
1224 return tr[encoding]
1225 # convert: latin-1, latin_1, utf-8 and similar things
1226 encoding = encoding.replace('_', '').replace('-', '')
1227 # strip the error handler
1228 return encoding.split(':')[0]
1230 def language_label(self, docutil_label):
1231 return self.language_module.labels[docutil_label]
1233 def ensure_math(self, text):
1234 if not hasattr(self, 'ensure_math_re'):
1235 chars = { # lnot,pm,twosuperior,threesuperior,mu,onesuperior,times,div
1236 'latin1' : '\xac\xb1\xb2\xb3\xb5\xb9\xd7\xf7' , # ¬±²³µ¹×÷
1237 # TODO?: use texcomp instead.
1239 self.ensure_math_re = re.compile('([%s])' % chars['latin1'])
1240 text = self.ensure_math_re.sub(r'\\ensuremath{\1}', text)
1241 return text
1243 def encode(self, text):
1244 """Return text with 'problematic' characters escaped.
1246 Escape the ten special printing characters ``# $ % & ~ _ ^ \ { }``,
1247 square brackets ``[ ]``, double quotes and (in OT1) ``< | >``.
1249 Separate ``-`` (and more in literal text) to prevent input ligatures.
1251 Translate non-supported Unicode characters.
1253 if self.verbatim:
1254 return text
1255 # Separate compound characters, e.g. '--' to '-{}-'.
1256 separate_chars = '-'
1257 # In monospace-font, we also separate ',,', '``' and "''" and some
1258 # other characters which can't occur in non-literal text.
1259 if self.literal:
1260 separate_chars += ',`\'"<>'
1261 # LaTeX encoding maps:
1262 special_chars = {
1263 ord('#'): ur'\#',
1264 ord('$'): ur'\$',
1265 ord('%'): ur'\%',
1266 ord('&'): ur'\&',
1267 ord('~'): ur'\textasciitilde{}',
1268 ord('_'): ur'\_',
1269 ord('^'): ur'\textasciicircum{}',
1270 ord('\\'): ur'\textbackslash{}',
1271 ord('{'): ur'\{',
1272 ord('}'): ur'\}',
1273 # Square brackets are ordinary chars and cannot be escaped with '\',
1274 # so we put them in a group '{[}'. (Alternative: ensure that all
1275 # macros with optional arguments are terminated with {} and text
1276 # inside any optional argument is put in a group ``[{text}]``).
1277 # Commands with optional args inside an optional arg must be put
1278 # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
1279 ord('['): ur'{[}',
1280 ord(']'): ur'{]}'
1282 # Unicode chars that are not recognized by LaTeX's utf8 encoding
1283 unsupported_unicode_chars = {
1284 0x00A0: ur'~', # NO-BREAK SPACE
1285 0x00AD: ur'\-', # SOFT HYPHEN
1287 0x2008: ur'\,', # PUNCTUATION SPACE   
1288 0x2011: ur'\hbox{-}', # NON-BREAKING HYPHEN
1289 0x202F: ur'\,', # NARROW NO-BREAK SPACE
1290 0x21d4: ur'$\Leftrightarrow$',
1291 # Docutils footnote symbols:
1292 0x2660: ur'$\spadesuit$',
1293 0x2663: ur'$\clubsuit$',
1295 # Unicode chars that are recognized by LaTeX's utf8 encoding
1296 unicode_chars = {
1297 0x200C: ur'\textcompwordmark', # ZERO WIDTH NON-JOINER
1298 0x2013: ur'\textendash{}',
1299 0x2014: ur'\textemdash{}',
1300 0x2018: ur'\textquoteleft{}',
1301 0x2019: ur'\textquoteright{}',
1302 0x201A: ur'\quotesinglbase{}', # SINGLE LOW-9 QUOTATION MARK
1303 0x201C: ur'\textquotedblleft{}',
1304 0x201D: ur'\textquotedblright{}',
1305 0x201E: ur'\quotedblbase{}', # DOUBLE LOW-9 QUOTATION MARK
1306 0x2030: ur'\textperthousand{}', # PER MILLE SIGN
1307 0x2031: ur'\textpertenthousand{}', # PER TEN THOUSAND SIGN
1308 0x2039: ur'\guilsinglleft{}',
1309 0x203A: ur'\guilsinglright{}',
1310 0x2423: ur'\textvisiblespace{}', # OPEN BOX
1311 0x2020: ur'\dag{}',
1312 0x2021: ur'\ddag{}',
1313 0x2026: ur'\dots{}',
1314 0x2122: ur'\texttrademark{}',
1316 # Unicode chars that require a feature/package to render
1317 pifont_chars = {
1318 0x2665: ur'\ding{170}', # black heartsuit
1319 0x2666: ur'\ding{169}', # black diamondsuit
1320 0x2713: ur'\ding{51}', # check mark
1321 0x2717: ur'\ding{55}', # check mark
1323 # recognized with 'utf8', if textcomp is loaded
1324 textcomp_chars = {
1325 # Latin-1 Supplement
1326 0x00a2: ur'\textcent{}', # ¢ CENT SIGN
1327 0x00a4: ur'\textcurrency{}', # ¤ CURRENCY SYMBOL
1328 0x00a5: ur'\textyen{}', # ¥ YEN SIGN
1329 0x00a6: ur'\textbrokenbar{}', # ¦ BROKEN BAR
1330 0x00a7: ur'\textsection{}', # § SECTION SIGN
1331 0x00a8: ur'\textasciidieresis{}', # ¨ DIAERESIS
1332 0x00a9: ur'\textcopyright{}', # © COPYRIGHT SIGN
1333 0x00aa: ur'\textordfeminine{}', # ª FEMININE ORDINAL INDICATOR
1334 0x00ac: ur'\textlnot{}', # ¬ NOT SIGN
1335 0x00ae: ur'\textregistered{}', # ® REGISTERED SIGN
1336 0x00af: ur'\textasciimacron{}', # ¯ MACRON
1337 0x00b0: ur'\textdegree{}', # ° DEGREE SIGN
1338 0x00b1: ur'\textpm{}', # ± PLUS-MINUS SIGN
1339 0x00b2: ur'\texttwosuperior{}', # ² SUPERSCRIPT TWO
1340 0x00b3: ur'\textthreesuperior{}', # ³ SUPERSCRIPT THREE
1341 0x00b4: ur'\textasciiacute{}', # ´ ACUTE ACCENT
1342 0x00b5: ur'\textmu{}', # µ MICRO SIGN
1343 0x00b6: ur'\textparagraph{}', # ¶ PILCROW SIGN # not equal to \textpilcrow
1344 0x00b9: ur'\textonesuperior{}', # ¹ SUPERSCRIPT ONE
1345 0x00ba: ur'\textordmasculine{}', # º MASCULINE ORDINAL INDICATOR
1346 0x00bc: ur'\textonequarter{}', # 1/4 FRACTION
1347 0x00bd: ur'\textonehalf{}', # 1/2 FRACTION
1348 0x00be: ur'\textthreequarters{}', # 3/4 FRACTION
1349 0x00d7: ur'\texttimes{}', # × MULTIPLICATION SIGN
1350 0x00f7: ur'\textdiv{}', # ÷ DIVISION SIGN
1352 0x0192: ur'\textflorin{}', # LATIN SMALL LETTER F WITH HOOK
1353 0x02b9: ur'\textasciiacute{}', # MODIFIER LETTER PRIME
1354 0x02ba: ur'\textacutedbl{}', # MODIFIER LETTER DOUBLE PRIME
1355 0x2016: ur'\textbardbl{}', # DOUBLE VERTICAL LINE
1356 0x2022: ur'\textbullet{}', # BULLET
1357 0x2032: ur'\textasciiacute{}', # PRIME
1358 0x2033: ur'\textacutedbl{}', # DOUBLE PRIME
1359 0x2035: ur'\textasciigrave{}', # REVERSED PRIME
1360 0x2036: ur'\textgravedbl{}', # REVERSED DOUBLE PRIME
1361 0x203b: ur'\textreferencemark{}', # REFERENCE MARK
1362 0x203d: ur'\textinterrobang{}', # INTERROBANG
1363 0x2044: ur'\textfractionsolidus{}', # FRACTION SLASH
1364 0x2045: ur'\textlquill{}', # LEFT SQUARE BRACKET WITH QUILL
1365 0x2046: ur'\textrquill{}', # RIGHT SQUARE BRACKET WITH QUILL
1366 0x2052: ur'\textdiscount{}', # COMMERCIAL MINUS SIGN
1367 0x20a1: ur'\textcolonmonetary{}', # COLON SIGN
1368 0x20a3: ur'\textfrenchfranc{}', # FRENCH FRANC SIGN
1369 0x20a4: ur'\textlira{}', # LIRA SIGN
1370 0x20a6: ur'\textnaira{}', # NAIRA SIGN
1371 0x20a9: ur'\textwon{}', # WON SIGN
1372 0x20ab: ur'\textdong{}', # DONG SIGN
1373 0x20ac: ur'\texteuro{}', # EURO SIGN
1374 0x20b1: ur'\textpeso{}', # PESO SIGN
1375 0x20b2: ur'\textguarani{}', # GUARANI SIGN
1376 0x2103: ur'\textcelsius{}', # DEGREE CELSIUS
1377 0x2116: ur'\textnumero{}', # NUMERO SIGN
1378 0x2117: ur'\textcircledP{}', # SOUND RECORDING COYRIGHT
1379 0x211e: ur'\textrecipe{}', # PRESCRIPTION TAKE
1380 0x2120: ur'\textservicemark{}', # SERVICE MARK
1381 0x2122: ur'\texttrademark{}', # TRADE MARK SIGN
1382 0x2126: ur'\textohm{}', # OHM SIGN
1383 0x2127: ur'\textmho{}', # INVERTED OHM SIGN
1384 0x212e: ur'\textestimated{}', # ESTIMATED SYMBOL
1385 0x2190: ur'\textleftarrow{}', # LEFTWARDS ARROW
1386 0x2191: ur'\textuparrow{}', # UPWARDS ARROW
1387 0x2192: ur'\textrightarrow{}', # RIGHTWARDS ARROW
1388 0x2193: ur'\textdownarrow{}', # DOWNWARDS ARROW
1389 0x2212: ur'\textminus{}', # MINUS SIGN
1390 0x2217: ur'\textasteriskcentered{}', # ASTERISK OPERATOR
1391 0x221a: ur'\textsurd{}', # SQUARE ROOT
1392 0x2422: ur'\textblank{}', # BLANK SYMBOL
1393 0x25e6: ur'\textopenbullet{}', # WHITE BULLET
1394 0x25ef: ur'\textbigcircle{}', # LARGE CIRCLE
1395 0x266a: ur'\textmusicalnote{}', # EIGHTH NOTE
1396 0x26ad: ur'\textmarried{}', # MARRIAGE SYMBOL
1397 0x26ae: ur'\textdivorced{}', # DIVORCE SYMBOL
1398 0x27e8: ur'\textlangle{}', # MATHEMATICAL LEFT ANGLE BRACKET
1399 0x27e9: ur'\textrangle{}', # MATHEMATICAL RIGHT ANGLE BRACKET
1401 # TODO: greek alphabet ... ?
1402 # see also LaTeX codec
1403 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
1404 # and unimap.py from TeXML
1406 # set up the translation table:
1407 table = special_chars
1408 # keep the underscore in citation references
1409 if self.inside_citation_reference_label:
1410 del(table[ord('_')])
1411 # Workarounds for OT1 font-encoding
1412 if self.font_encoding in ['OT1', '']:
1413 # * out-of-order characters in cmtt
1414 if self.literal:
1415 # replace underscore by underlined blank,
1416 # because this has correct width.
1417 table[ord('_')] = u'\\underline{~}'
1418 # the backslash doesn't work, so we use a mirrored slash.
1419 # \reflectbox is provided by graphicx:
1420 self.requirements['graphicx'] = self.graphicx_package
1421 table[ord('\\')] = ur'\reflectbox{/}'
1422 # * ``< | >`` come out as different chars (except for cmtt):
1423 else:
1424 table[ord('|')] = ur'\textbar{}'
1425 table[ord('<')] = ur'\textless{}'
1426 table[ord('>')] = ur'\textgreater{}'
1427 if self.insert_non_breaking_blanks:
1428 table[ord(' ')] = ur'~'
1429 if self.literal:
1430 # double quotes are 'active' in some languages
1431 table[ord('"')] = self.babel.literal_double_quote
1432 # Unicode chars:
1433 table.update(unsupported_unicode_chars)
1434 table.update(pifont_chars)
1435 if not self.latex_encoding.startswith('utf8'):
1436 table.update(unicode_chars)
1437 table.update(textcomp_chars)
1438 # Characters that require a feature/package to render
1439 for ch in text:
1440 if ord(ch) in pifont_chars:
1441 self.requirements['pifont'] = '\\usepackage{pifont}'
1442 if ord(ch) in textcomp_chars:
1443 self.requirements['textcomp'] = PreambleCmds.textcomp
1445 text = text.translate(table)
1447 # Break up input ligatures
1448 for char in separate_chars * 2:
1449 # Do it twice ("* 2") because otherwise we would replace
1450 # '---' by '-{}--'.
1451 text = text.replace(char + char, char + '{}' + char)
1452 # Literal line breaks (in address or literal blocks):
1453 if self.insert_newline:
1454 lines = text.split('\n')
1455 # for blank lines, insert a protected space, to avoid
1456 # ! LaTeX Error: There's no line here to end.
1457 lines = [line + '~'*(not line.lstrip())
1458 for line in lines[:-1]] + lines[-1:]
1459 text = '\\\\\n'.join(lines)
1460 if not self.literal:
1461 text = self.babel.quote_quotes(text)
1462 if self.literal and not self.insert_non_breaking_blanks:
1463 # preserve runs of spaces but allow wrapping
1464 text = text.replace(' ', ' ~')
1465 if not self.latex_encoding.startswith('utf8'):
1466 text = self.ensure_math(text)
1467 return text
1469 def attval(self, text,
1470 whitespace=re.compile('[\n\r\t\v\f]')):
1471 """Cleanse, encode, and return attribute value text."""
1472 return self.encode(whitespace.sub(' ', text))
1474 # TODO: is this used anywhere? -> update (use template) or delete
1475 ## def astext(self):
1476 ## """Assemble document parts and return as string."""
1477 ## head = '\n'.join(self.head_prefix + self.stylesheet + self.head)
1478 ## body = ''.join(self.body_prefix + self.body + self.body_suffix)
1479 ## return head + '\n' + body
1481 def is_inline(self, node):
1482 """Check whether a node represents an inline element"""
1483 return isinstance(node.parent, nodes.TextElement)
1485 def append_hypertargets(self, node):
1486 """Append hypertargets for all ids of `node`"""
1487 # hypertarget places the anchor at the target's baseline,
1488 # so we raise it explicitely
1489 self.out.append('%\n'.join(['\\raisebox{1em}{\\hypertarget{%s}{}}' %
1490 id for id in node['ids']]))
1492 def ids_to_labels(self, node, set_anchor=True):
1493 """Return list of label definitions for all ids of `node`
1495 If `set_anchor` is True, an anchor is set with \phantomsection.
1497 labels = ['\\label{%s}' % id for id in node.get('ids', [])]
1498 if set_anchor and labels:
1499 labels.insert(0, '\\phantomsection')
1500 return labels
1502 def push_output_collector(self, new_out):
1503 self.out_stack.append(self.out)
1504 self.out = new_out
1506 def pop_output_collector(self):
1507 self.out = self.out_stack.pop()
1509 # Visitor methods
1510 # ---------------
1512 def visit_Text(self, node):
1513 self.out.append(self.encode(node.astext()))
1515 def depart_Text(self, node):
1516 pass
1518 def visit_address(self, node):
1519 self.visit_docinfo_item(node, 'address')
1521 def depart_address(self, node):
1522 self.depart_docinfo_item(node)
1524 def visit_admonition(self, node):
1525 self.fallbacks['admonition'] = PreambleCmds.admonition
1526 if 'error' in node['classes']:
1527 self.fallbacks['error'] = PreambleCmds.error
1528 # strip the generic 'admonition' from the list of classes
1529 node['classes'] = [cls for cls in node['classes']
1530 if cls != 'admonition']
1531 self.out.append('\n\\DUadmonition[%s]{\n' % ','.join(node['classes']))
1533 def depart_admonition(self, node=None):
1534 self.out.append('}\n')
1536 def visit_author(self, node):
1537 self.visit_docinfo_item(node, 'author')
1539 def depart_author(self, node):
1540 self.depart_docinfo_item(node)
1542 def visit_authors(self, node):
1543 # not used: visit_author is called anyway for each author.
1544 pass
1546 def depart_authors(self, node):
1547 pass
1549 def visit_block_quote(self, node):
1550 self.out.append( '%\n\\begin{quote}\n')
1551 if node['classes']:
1552 self.visit_inline(node)
1554 def depart_block_quote(self, node):
1555 if node['classes']:
1556 self.depart_inline(node)
1557 self.out.append( '\n\\end{quote}\n')
1559 def visit_bullet_list(self, node):
1560 if self.is_toc_list:
1561 self.out.append( '%\n\\begin{list}{}{}\n' )
1562 else:
1563 self.out.append( '%\n\\begin{itemize}\n' )
1565 def depart_bullet_list(self, node):
1566 if self.is_toc_list:
1567 self.out.append( '\n\\end{list}\n' )
1568 else:
1569 self.out.append( '\n\\end{itemize}\n' )
1571 def visit_superscript(self, node):
1572 self.out.append(r'\textsuperscript{')
1573 if node['classes']:
1574 self.visit_inline(node)
1576 def depart_superscript(self, node):
1577 if node['classes']:
1578 self.depart_inline(node)
1579 self.out.append('}')
1581 def visit_subscript(self, node):
1582 self.out.append(r'\textsubscript{') # requires `fixltx2e`
1583 if node['classes']:
1584 self.visit_inline(node)
1586 def depart_subscript(self, node):
1587 if node['classes']:
1588 self.depart_inline(node)
1589 self.out.append('}')
1591 def visit_caption(self, node):
1592 self.out.append( '\\caption{' )
1594 def depart_caption(self, node):
1595 self.out.append('}\n')
1597 def visit_title_reference(self, node):
1598 self.fallbacks['titlereference'] = PreambleCmds.titlereference
1599 self.out.append(r'\DUroletitlereference{')
1600 if node['classes']:
1601 self.visit_inline(node)
1603 def depart_title_reference(self, node):
1604 if node['classes']:
1605 self.depart_inline(node)
1606 self.out.append( '}' )
1608 def visit_citation(self, node):
1609 # TODO maybe use cite bibitems
1610 if self._use_latex_citations:
1611 self.push_output_collector([])
1612 else:
1613 # TODO: do we need these?
1614 ## self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats
1615 self.out.append(r'\begin{figure}[b]')
1616 self.append_hypertargets(node)
1618 def depart_citation(self, node):
1619 if self._use_latex_citations:
1620 label = self.out[0]
1621 text = ''.join(self.out[1:])
1622 self._bibitems.append([label, text])
1623 self.pop_output_collector()
1624 else:
1625 self.out.append('\\end{figure}\n')
1627 def visit_citation_reference(self, node):
1628 if self._use_latex_citations:
1629 if not self.inside_citation_reference_label:
1630 self.out.append(r'\cite{')
1631 self.inside_citation_reference_label = 1
1632 else:
1633 assert self.body[-1] in (' ', '\n'),\
1634 'unexpected non-whitespace while in reference label'
1635 del self.body[-1]
1636 else:
1637 href = ''
1638 if 'refid' in node:
1639 href = node['refid']
1640 elif 'refname' in node:
1641 href = self.document.nameids[node['refname']]
1642 self.out.append('\\hyperlink{%s}{[' % href)
1644 def depart_citation_reference(self, node):
1645 if self._use_latex_citations:
1646 followup_citation = False
1647 # check for a following citation separated by a space or newline
1648 next_siblings = node.traverse(descend=0, siblings=1,
1649 include_self=0)
1650 if len(next_siblings) > 1:
1651 next = next_siblings[0]
1652 if (isinstance(next, nodes.Text) and
1653 next.astext() in (' ', '\n')):
1654 if next_siblings[1].__class__ == node.__class__:
1655 followup_citation = True
1656 if followup_citation:
1657 self.out.append(',')
1658 else:
1659 self.out.append('}')
1660 self.inside_citation_reference_label = False
1661 else:
1662 self.out.append(']}')
1664 def visit_classifier(self, node):
1665 self.out.append( '(\\textbf{' )
1667 def depart_classifier(self, node):
1668 self.out.append( '})\n' )
1670 def visit_colspec(self, node):
1671 self.active_table.visit_colspec(node)
1673 def depart_colspec(self, node):
1674 pass
1676 def visit_comment(self, node):
1677 # Precede every line with a comment sign, wrap in newlines
1678 self.out.append('\n%% %s\n' % node.astext().replace('\n', '\n% '))
1679 raise nodes.SkipNode
1681 def depart_comment(self, node):
1682 pass
1684 def visit_compound(self, node):
1685 pass
1687 def depart_compound(self, node):
1688 pass
1690 def visit_contact(self, node):
1691 self.visit_docinfo_item(node, 'contact')
1693 def depart_contact(self, node):
1694 self.depart_docinfo_item(node)
1696 def visit_container(self, node):
1697 pass
1699 def depart_container(self, node):
1700 pass
1702 def visit_copyright(self, node):
1703 self.visit_docinfo_item(node, 'copyright')
1705 def depart_copyright(self, node):
1706 self.depart_docinfo_item(node)
1708 def visit_date(self, node):
1709 self.visit_docinfo_item(node, 'date')
1711 def depart_date(self, node):
1712 self.depart_docinfo_item(node)
1714 def visit_decoration(self, node):
1715 # header and footer
1716 pass
1718 def depart_decoration(self, node):
1719 pass
1721 def visit_definition(self, node):
1722 pass
1724 def depart_definition(self, node):
1725 self.out.append('\n')
1727 def visit_definition_list(self, node):
1728 self.out.append( '%\n\\begin{description}\n' )
1730 def depart_definition_list(self, node):
1731 self.out.append( '\\end{description}\n' )
1733 def visit_definition_list_item(self, node):
1734 pass
1736 def depart_definition_list_item(self, node):
1737 pass
1739 def visit_description(self, node):
1740 self.out.append(' ')
1742 def depart_description(self, node):
1743 pass
1745 def visit_docinfo(self, node):
1746 self.push_output_collector(self.docinfo)
1748 def depart_docinfo(self, node):
1749 self.pop_output_collector()
1750 # Some itmes (e.g. author) end up at other places
1751 if self.docinfo:
1752 # tabularx: automatic width of columns, no page breaks allowed.
1753 self.requirements['tabularx'] = r'\usepackage{tabularx}'
1754 self.fallbacks['_providelength'] = PreambleCmds.providelength
1755 self.fallbacks['docinfo'] = PreambleCmds.docinfo
1757 self.docinfo.insert(0, '\n% Docinfo\n'
1758 '\\begin{center}\n'
1759 '\\begin{tabularx}{\\DUdocinfowidth}{lX}\n')
1760 self.docinfo.append('\\end{tabularx}\n'
1761 '\\end{center}\n')
1763 def visit_docinfo_item(self, node, name):
1764 if name == 'author':
1765 self.pdfauthor.append(self.attval(node.astext()))
1766 if self.use_latex_docinfo:
1767 if name in ('author', 'organization', 'contact', 'address'):
1768 # We attach these to the last author. If any of them precedes
1769 # the first author, put them in a separate "author" group
1770 # (in lack of better semantics).
1771 if name == 'author' or not self.author_stack:
1772 self.author_stack.append([])
1773 if name == 'address': # newlines are meaningful
1774 self.insert_newline = True
1775 text = self.encode(node.astext())
1776 self.insert_newline = False
1777 else:
1778 text = self.attval(node.astext())
1779 self.author_stack[-1].append(text)
1780 raise nodes.SkipNode
1781 elif name == 'date':
1782 self.date.append(self.attval(node.astext()))
1783 raise nodes.SkipNode
1784 self.out.append('\\textbf{%s}: &\n\t' % self.language_label(name))
1785 if name == 'address':
1786 self.insert_newline = 1
1787 self.out.append('{\\raggedright\n')
1788 self.context.append(' } \\\\\n')
1789 else:
1790 self.context.append(' \\\\\n')
1792 def depart_docinfo_item(self, node):
1793 self.out.append(self.context.pop())
1794 # for address we did set insert_newline
1795 self.insert_newline = False
1797 def visit_doctest_block(self, node):
1798 self.visit_literal_block(node)
1800 def depart_doctest_block(self, node):
1801 self.depart_literal_block(node)
1803 def visit_document(self, node):
1804 # titled document?
1805 if (self.use_latex_docinfo or len(node) and
1806 isinstance(node[0], nodes.title)):
1807 self.title_labels += self.ids_to_labels(node, set_anchor=False)
1809 def depart_document(self, node):
1810 # Complete header with information gained from walkabout
1811 # * language setup
1812 if (self.babel.otherlanguages or
1813 self.babel.language not in ('', 'english')):
1814 self.requirements['babel'] = self.babel()
1815 # * conditional requirements (before style sheet)
1816 self.requirements = self.requirements.sortedvalues()
1817 # * coditional fallback definitions (after style sheet)
1818 self.fallbacks = self.fallbacks.sortedvalues()
1819 # * PDF properties
1820 self.pdfsetup.append(PreambleCmds.linking % self.hyperref_options)
1821 if self.pdfauthor:
1822 authors = self.author_separator.join(self.pdfauthor)
1823 self.pdfinfo.append(' pdfauthor={%s}' % authors)
1824 if self.pdfinfo:
1825 self.pdfsetup += [r'\hypersetup{'] + self.pdfinfo + ['}']
1826 # Complete body
1827 # * document title (with "use_latex_docinfo" also
1828 # 'author', 'organization', 'contact', 'address' and 'date')
1829 if self.title or (
1830 self.use_latex_docinfo and (self.author_stack or self.date)):
1831 # with the default template, titledata is written to the preamble
1832 self.titledata.append('%%% Title Data')
1833 # \title (empty \title prevents error with \maketitle)
1834 if self.title:
1835 self.title.insert(0, '\phantomsection%\n ')
1836 title = [''.join(self.title)] + self.title_labels
1837 if self.subtitle:
1838 title += [r'\\ % subtitle',
1839 r'\large{%s}' % ''.join(self.subtitle)
1840 ] + self.subtitle_labels
1841 self.titledata.append(r'\title{%s}' % '%\n '.join(title))
1842 # \author (empty \author prevents warning with \maketitle)
1843 authors = ['\\\\\n'.join(author_entry)
1844 for author_entry in self.author_stack]
1845 self.titledata.append(r'\author{%s}' %
1846 ' \\and\n'.join(authors))
1847 # \date (empty \date prevents defaulting to \today)
1848 self.titledata.append(r'\date{%s}' % ', '.join(self.date))
1849 # \maketitle in the body formats title with LaTeX
1850 self.body_pre_docinfo.append('\\maketitle\n')
1852 # * bibliography
1853 # TODO insertion point of bibliography should be configurable.
1854 if self._use_latex_citations and len(self._bibitems)>0:
1855 if not self.bibtex:
1856 widest_label = ''
1857 for bi in self._bibitems:
1858 if len(widest_label)<len(bi[0]):
1859 widest_label = bi[0]
1860 self.out.append('\n\\begin{thebibliography}{%s}\n' %
1861 widest_label)
1862 for bi in self._bibitems:
1863 # cite_key: underscores must not be escaped
1864 cite_key = bi[0].replace(r'\_','_')
1865 self.out.append('\\bibitem[%s]{%s}{%s}\n' %
1866 (bi[0], cite_key, bi[1]))
1867 self.out.append('\\end{thebibliography}\n')
1868 else:
1869 self.out.append('\n\\bibliographystyle{%s}\n' %
1870 self.bibtex[0])
1871 self.out.append('\\bibliography{%s}\n' % self.bibtex[1])
1872 # * make sure to generate a toc file if needed for local contents:
1873 if 'minitoc' in self.requirements and not self.has_latex_toc:
1874 self.out.append('\n\\faketableofcontents % for local ToCs\n')
1876 def visit_emphasis(self, node):
1877 self.out.append('\\emph{')
1878 if node['classes']:
1879 self.visit_inline(node)
1881 def depart_emphasis(self, node):
1882 if node['classes']:
1883 self.depart_inline(node)
1884 self.out.append('}')
1886 def visit_entry(self, node):
1887 self.active_table.visit_entry()
1888 # cell separation
1889 # BUG: the following fails, with more than one multirow
1890 # starting in the second column (or later) see
1891 # ../../../test/functional/input/data/latex.txt
1892 if self.active_table.get_entry_number() == 1:
1893 # if the first row is a multirow, this actually is the second row.
1894 # this gets hairy if rowspans follow each other.
1895 if self.active_table.get_rowspan(0):
1896 count = 0
1897 while self.active_table.get_rowspan(count):
1898 count += 1
1899 self.out.append(' & ')
1900 self.active_table.visit_entry() # increment cell count
1901 else:
1902 self.out.append(' & ')
1903 # multirow, multicolumn
1904 # IN WORK BUG TODO HACK continues here
1905 # multirow in LaTeX simply will enlarge the cell over several rows
1906 # (the following n if n is positive, the former if negative).
1907 if 'morerows' in node and 'morecols' in node:
1908 raise NotImplementedError('Cells that '
1909 'span multiple rows *and* columns are not supported, sorry.')
1910 if 'morerows' in node:
1911 self.requirements['multirow'] = r'\usepackage{multirow}'
1912 count = node['morerows'] + 1
1913 self.active_table.set_rowspan(
1914 self.active_table.get_entry_number()-1,count)
1915 self.out.append('\\multirow{%d}{%s}{%%' %
1916 (count,self.active_table.get_column_width()))
1917 self.context.append('}')
1918 elif 'morecols' in node:
1919 # the vertical bar before column is missing if it is the first
1920 # column. the one after always.
1921 if self.active_table.get_entry_number() == 1:
1922 bar1 = self.active_table.get_vertical_bar()
1923 else:
1924 bar1 = ''
1925 count = node['morecols'] + 1
1926 self.out.append('\\multicolumn{%d}{%sp{%s}%s}{' %
1927 (count, bar1,
1928 self.active_table.get_multicolumn_width(
1929 self.active_table.get_entry_number(),
1930 count),
1931 self.active_table.get_vertical_bar()))
1932 self.context.append('}')
1933 else:
1934 self.context.append('')
1936 # header / not header
1937 if isinstance(node.parent.parent, nodes.thead):
1938 self.out.append('\\textbf{%')
1939 self.context.append('}')
1940 elif self.active_table.is_stub_column():
1941 self.out.append('\\textbf{')
1942 self.context.append('}')
1943 else:
1944 self.context.append('')
1946 def depart_entry(self, node):
1947 self.out.append(self.context.pop()) # header / not header
1948 self.out.append(self.context.pop()) # multirow/column
1949 # if following row is spanned from above.
1950 if self.active_table.get_rowspan(self.active_table.get_entry_number()):
1951 self.out.append(' & ')
1952 self.active_table.visit_entry() # increment cell count
1954 def visit_row(self, node):
1955 self.active_table.visit_row()
1957 def depart_row(self, node):
1958 self.out.extend(self.active_table.depart_row())
1960 def visit_enumerated_list(self, node):
1961 # We create our own enumeration list environment.
1962 # This allows to set the style and starting value
1963 # and unlimited nesting.
1964 enum_style = {'arabic':'arabic',
1965 'loweralpha':'alph',
1966 'upperalpha':'Alph',
1967 'lowerroman':'roman',
1968 'upperroman':'Roman' }
1969 enum_suffix = ''
1970 if 'suffix' in node:
1971 enum_suffix = node['suffix']
1972 enum_prefix = ''
1973 if 'prefix' in node:
1974 enum_prefix = node['prefix']
1975 if self.compound_enumerators:
1976 pref = ''
1977 if self.section_prefix_for_enumerators and self.section_level:
1978 for i in range(self.section_level):
1979 pref += '%d.' % self._section_number[i]
1980 pref = pref[:-1] + self.section_enumerator_separator
1981 enum_prefix += pref
1982 for ctype, cname in self._enumeration_counters:
1983 enum_prefix += '\\%s{%s}.' % (ctype, cname)
1984 enum_type = 'arabic'
1985 if 'enumtype' in node:
1986 enum_type = node['enumtype']
1987 if enum_type in enum_style:
1988 enum_type = enum_style[enum_type]
1990 counter_name = 'listcnt%d' % len(self._enumeration_counters)
1991 self._enumeration_counters.append((enum_type, counter_name))
1992 # If we haven't used this counter name before, then create a
1993 # new counter; otherwise, reset & reuse the old counter.
1994 if len(self._enumeration_counters) > self._max_enumeration_counters:
1995 self._max_enumeration_counters = len(self._enumeration_counters)
1996 self.out.append('\\newcounter{%s}\n' % counter_name)
1997 else:
1998 self.out.append('\\setcounter{%s}{0}\n' % counter_name)
2000 self.out.append('\\begin{list}{%s\\%s{%s}%s}\n' %
2001 (enum_prefix,enum_type,counter_name,enum_suffix))
2002 self.out.append('{\n')
2003 self.out.append('\\usecounter{%s}\n' % counter_name)
2004 # set start after usecounter, because it initializes to zero.
2005 if 'start' in node:
2006 self.out.append('\\addtocounter{%s}{%d}\n' %
2007 (counter_name,node['start']-1))
2008 ## set rightmargin equal to leftmargin
2009 self.out.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
2010 self.out.append('}\n')
2012 def depart_enumerated_list(self, node):
2013 self.out.append('\\end{list}\n')
2014 self._enumeration_counters.pop()
2016 def visit_field(self, node):
2017 # real output is done in siblings: _argument, _body, _name
2018 pass
2020 def depart_field(self, node):
2021 self.out.append('\n')
2022 ##self.out.append('%[depart_field]\n')
2024 def visit_field_argument(self, node):
2025 self.out.append('%[visit_field_argument]\n')
2027 def depart_field_argument(self, node):
2028 self.out.append('%[depart_field_argument]\n')
2030 def visit_field_body(self, node):
2031 pass
2033 def depart_field_body(self, node):
2034 if self.out is self.docinfo:
2035 self.out.append(r'\\')
2037 def visit_field_list(self, node):
2038 if self.out is not self.docinfo:
2039 self.fallbacks['fieldlist'] = PreambleCmds.fieldlist
2040 self.out.append('%\n\\begin{DUfieldlist}\n')
2042 def depart_field_list(self, node):
2043 if self.out is not self.docinfo:
2044 self.out.append('\\end{DUfieldlist}\n')
2046 def visit_field_name(self, node):
2047 if self.out is self.docinfo:
2048 self.out.append('\\textbf{')
2049 else:
2050 # Commands with optional args inside an optional arg must be put
2051 # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
2052 self.out.append('\\item[{')
2054 def depart_field_name(self, node):
2055 if self.out is self.docinfo:
2056 self.out.append('}: &')
2057 else:
2058 self.out.append(':}]')
2060 def visit_figure(self, node):
2061 self.requirements['float_settings'] = PreambleCmds.float_settings
2062 # ! the 'align' attribute should set "outer alignment" !
2063 # For "inner alignment" use LaTeX default alignment (similar to HTML)
2064 ## if ('align' not in node.attributes or
2065 ## node.attributes['align'] == 'center'):
2066 ## align = '\n\\centering'
2067 ## align_end = ''
2068 ## else:
2069 ## # TODO non vertical space for other alignments.
2070 ## align = '\\begin{flush%s}' % node.attributes['align']
2071 ## align_end = '\\end{flush%s}' % node.attributes['align']
2072 ## self.out.append( '\\begin{figure}%s\n' % align )
2073 ## self.context.append( '%s\\end{figure}\n' % align_end )
2074 self.out.append('\\begin{figure}')
2075 if node.get('ids'):
2076 self.out += ['\n'] + self.ids_to_labels(node)
2078 def depart_figure(self, node):
2079 self.out.append('\\end{figure}\n')
2081 def visit_footer(self, node):
2082 self.push_output_collector([])
2083 self.out.append(r'\newcommand{\DUfooter}{')
2085 def depart_footer(self, node):
2086 self.out.append('}')
2087 self.requirements['~footer'] = ''.join(self.out)
2088 self.pop_output_collector()
2090 def visit_footnote(self, node):
2091 try:
2092 backref = node['backrefs'][0]
2093 except IndexError:
2094 backref = node['ids'][0] # no backref, use self-ref instead
2095 if self.settings.figure_footnotes:
2096 self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats
2097 self.out.append('\\begin{figure}[b]')
2098 self.append_hypertargets(node)
2099 if node.get('id') == node.get('name'): # explicite label
2100 self.out += self.ids_to_labels(node)
2101 elif self.docutils_footnotes:
2102 self.fallbacks['footnotes'] = PreambleCmds.footnotes
2103 num,text = node.astext().split(None,1)
2104 if self.settings.footnote_references == 'brackets':
2105 num = '[%s]' % num
2106 self.out.append('%%\n\\DUfootnotetext{%s}{%s}{%s}{' %
2107 (node['ids'][0], backref, self.encode(num)))
2108 if node['ids'] == node['names']:
2109 self.out += self.ids_to_labels(node)
2110 # mask newline to prevent spurious whitespace:
2111 self.out.append('%')
2112 ## else: # TODO: "real" LaTeX \footnote{}s
2114 def depart_footnote(self, node):
2115 if self.figure_footnotes:
2116 self.out.append('\\end{figure}\n')
2117 else:
2118 self.out.append('}\n')
2120 def visit_footnote_reference(self, node):
2121 href = ''
2122 if 'refid' in node:
2123 href = node['refid']
2124 elif 'refname' in node:
2125 href = self.document.nameids[node['refname']]
2126 # if not self.docutils_footnotes:
2127 # TODO: insert footnote content at (or near) this place
2128 # print "footnote-ref to", node['refid']
2129 # footnotes = (self.document.footnotes +
2130 # self.document.autofootnotes +
2131 # self.document.symbol_footnotes)
2132 # for footnote in footnotes:
2133 # # print footnote['ids']
2134 # if node.get('refid', '') in footnote['ids']:
2135 # print 'matches', footnote['ids']
2136 format = self.settings.footnote_references
2137 if format == 'brackets':
2138 self.append_hypertargets(node)
2139 self.out.append('\\hyperlink{%s}{[' % href)
2140 self.context.append(']}')
2141 else:
2142 self.fallbacks['footnotes'] = PreambleCmds.footnotes
2143 self.out.append(r'\DUfootnotemark{%s}{%s}{' %
2144 (node['ids'][0], href))
2145 self.context.append('}')
2147 def depart_footnote_reference(self, node):
2148 self.out.append(self.context.pop())
2150 # footnote/citation label
2151 def label_delim(self, node, bracket, superscript):
2152 if isinstance(node.parent, nodes.footnote):
2153 if not self.figure_footnotes:
2154 raise nodes.SkipNode
2155 if self.settings.footnote_references == 'brackets':
2156 self.out.append(bracket)
2157 else:
2158 self.out.append(superscript)
2159 else:
2160 assert isinstance(node.parent, nodes.citation)
2161 if not self._use_latex_citations:
2162 self.out.append(bracket)
2164 def visit_label(self, node):
2165 """footnote or citation label: in brackets or as superscript"""
2166 self.label_delim(node, '[', '\\textsuperscript{')
2168 def depart_label(self, node):
2169 self.label_delim(node, ']', '}')
2171 # elements generated by the framework e.g. section numbers.
2172 def visit_generated(self, node):
2173 pass
2175 def depart_generated(self, node):
2176 pass
2178 def visit_header(self, node):
2179 self.push_output_collector([])
2180 self.out.append(r'\newcommand{\DUheader}{')
2182 def depart_header(self, node):
2183 self.out.append('}')
2184 self.requirements['~header'] = ''.join(self.out)
2185 self.pop_output_collector()
2187 def to_latex_length(self, length_str, pxunit='px'):
2188 """Convert `length_str` with rst lenght to LaTeX length
2190 match = re.match('(\d*\.?\d*)\s*(\S*)', length_str)
2191 if not match:
2192 return length_str
2193 value, unit = match.groups()[:2]
2194 # no unit or "DTP" points (called 'bp' in TeX):
2195 if unit in ('', 'pt'):
2196 length_str = '%sbp' % value
2197 # percentage: relate to current line width
2198 elif unit == '%':
2199 length_str = '%.3f\\linewidth' % (float(value)/100.0)
2200 elif (unit == 'px') and (pxunit != 'px'):
2201 # length unit px not defined in some tex variants (e.g. XeTeX)
2202 self.fallbacks['_providelength'] = PreambleCmds.providelength
2203 self.fallbacks['px'] = '\n\\DUprovidelength{%s}{1bp}\n' % pxunit
2204 length_str = '%s%s' % (value, pxunit)
2206 return length_str
2208 def visit_image(self, node):
2209 self.requirements['graphicx'] = self.graphicx_package
2210 attrs = node.attributes
2211 # Convert image URI to a local file path and add to dependency list
2212 imagepath = urllib.url2pathname(attrs['uri'])
2213 self.settings.record_dependencies.add(imagepath)
2214 # alignment defaults:
2215 if not 'align' in attrs:
2216 # Set default align of image in a figure to 'center'
2217 if isinstance(node.parent, nodes.figure):
2218 attrs['align'] = 'center'
2219 # query 'align-*' class argument
2220 for cls in node['classes']:
2221 if cls.startswith('align-'):
2222 attrs['align'] = cls.split('-')[1]
2223 # pre- and postfix (prefix inserted in reverse order)
2224 pre = []
2225 post = []
2226 include_graphics_options = []
2227 display_style = ('block-', 'inline-')[self.is_inline(node)]
2228 align_codes = {
2229 # inline images: by default latex aligns the bottom.
2230 'bottom': ('', ''),
2231 'middle': (r'\raisebox{-0.5\height}{', '}'),
2232 'top': (r'\raisebox{-\height}{', '}'),
2233 # block level images:
2234 'center': (r'\noindent\makebox[\textwidth][c]{', '}'),
2235 'left': (r'\noindent{', r'\hfill}'),
2236 'right': (r'\noindent{\hfill', '}'),}
2237 if 'align' in attrs:
2238 try:
2239 align_code = align_codes[attrs['align']]
2240 pre.append(align_code[0])
2241 post.append(align_code[1])
2242 except KeyError:
2243 pass # TODO: warn?
2244 if 'height' in attrs:
2245 include_graphics_options.append('height=%s' %
2246 self.to_latex_length(attrs['height']))
2247 if 'scale' in attrs:
2248 include_graphics_options.append('scale=%f' %
2249 (attrs['scale'] / 100.0))
2250 if 'width' in attrs:
2251 include_graphics_options.append('width=%s' %
2252 self.to_latex_length(attrs['width']))
2253 if not self.is_inline(node):
2254 pre.append('\n')
2255 post.append('\n')
2256 pre.reverse()
2257 self.out.extend(pre)
2258 options = ''
2259 if include_graphics_options:
2260 options = '[%s]' % (','.join(include_graphics_options))
2261 self.out.append('\\includegraphics%s{%s}' % (options, imagepath))
2262 self.out.extend(post)
2264 def depart_image(self, node):
2265 if node.get('ids'):
2266 self.out += self.ids_to_labels(node) + ['\n']
2268 def visit_inline(self, node): # <span>, i.e. custom roles
2269 # Make a copy to keep ``node['classes']`` True if a
2270 # language argument is popped (used in conditional calls of
2271 # depart_inline()):
2272 classes = node['classes'][:]
2273 self.context.append('}' * len(classes))
2274 # handle language specification:
2275 language_tags = [cls for cls in classes
2276 if cls.startswith('language-')]
2277 if language_tags:
2278 language = self.babel.get_language(language_tags[0][9:])
2279 if language:
2280 self.babel.otherlanguages[language] = True
2281 self.out.append(r'\otherlanguage{%s}{' % language)
2282 classes.pop(classes.index(language_tags[0]))
2283 if not classes:
2284 return
2285 # mark up for styling with custom macros
2286 if 'align-center' in classes:
2287 self.fallbacks['align-center'] = PreambleCmds.align_center
2288 self.fallbacks['inline'] = PreambleCmds.inline
2289 self.out += [r'\DUrole{%s}{' % cls for cls in classes]
2291 def depart_inline(self, node):
2292 self.out.append(self.context.pop())
2294 def visit_interpreted(self, node):
2295 # @@@ Incomplete, pending a proper implementation on the
2296 # Parser/Reader end.
2297 self.visit_literal(node)
2299 def depart_interpreted(self, node):
2300 self.depart_literal(node)
2302 def visit_legend(self, node):
2303 self.fallbacks['legend'] = PreambleCmds.legend
2304 self.out.append('\\begin{DUlegend}')
2306 def depart_legend(self, node):
2307 self.out.append('\\end{DUlegend}\n')
2309 def visit_line(self, node):
2310 self.out.append('\item[] ')
2312 def depart_line(self, node):
2313 self.out.append('\n')
2315 def visit_line_block(self, node):
2316 self.fallbacks['_providelength'] = PreambleCmds.providelength
2317 self.fallbacks['lineblock'] = PreambleCmds.lineblock
2318 if isinstance(node.parent, nodes.line_block):
2319 self.out.append('\\item[]\n'
2320 '\\begin{DUlineblock}{\\DUlineblockindent}\n')
2321 else:
2322 self.out.append('\n\\begin{DUlineblock}{0em}\n')
2323 if node['classes']:
2324 self.visit_inline(node)
2325 self.out.append('\n')
2327 def depart_line_block(self, node):
2328 if node['classes']:
2329 self.depart_inline(node)
2330 self.out.append('\n')
2331 self.out.append('\\end{DUlineblock}\n')
2333 def visit_list_item(self, node):
2334 self.out.append('\n\\item ')
2336 def depart_list_item(self, node):
2337 pass
2339 def visit_literal(self, node):
2340 self.literal = True
2341 self.out.append('\\texttt{')
2342 if node['classes']:
2343 self.visit_inline(node)
2345 def depart_literal(self, node):
2346 self.literal = False
2347 if node['classes']:
2348 self.depart_inline(node)
2349 self.out.append('}')
2351 # Literal blocks are used for '::'-prefixed literal-indented
2352 # blocks of text, where the inline markup is not recognized,
2353 # but are also the product of the "parsed-literal" directive,
2354 # where the markup is respected.
2356 # In both cases, we want to use a typewriter/monospaced typeface.
2357 # For "real" literal-blocks, we can use \verbatim, while for all
2358 # the others we must use \mbox or \alltt.
2360 # We can distinguish between the two kinds by the number of
2361 # siblings that compose this node: if it is composed by a
2362 # single element, it's either
2363 # * a real one,
2364 # * a parsed-literal that does not contain any markup, or
2365 # * a parsed-literal containing just one markup construct.
2366 def is_plaintext(self, node):
2367 """Check whether a node can be typeset verbatim"""
2368 return (len(node) == 1) and isinstance(node[0], nodes.Text)
2370 def visit_literal_block(self, node):
2371 """Render a literal block."""
2372 # environments and packages to typeset literal blocks
2373 packages = {'listing': r'\usepackage{moreverb}',
2374 'lstlisting': r'\usepackage{listings}',
2375 'Verbatim': r'\usepackage{fancyvrb}',
2376 # 'verbatim': '',
2377 'verbatimtab': r'\usepackage{moreverb}'}
2379 if not self.active_table.is_open():
2380 # no quote inside tables, to avoid vertical space between
2381 # table border and literal block.
2382 # BUG: fails if normal text preceeds the literal block.
2383 self.out.append('%\n\\begin{quote}')
2384 self.context.append('\n\\end{quote}\n')
2385 else:
2386 self.out.append('\n')
2387 self.context.append('\n')
2388 if self.literal_block_env != '' and self.is_plaintext(node):
2389 self.requirements['literal_block'] = packages.get(
2390 self.literal_block_env, '')
2391 self.verbatim = True
2392 self.out.append('\\begin{%s}%s\n' % (self.literal_block_env,
2393 self.literal_block_options))
2394 else:
2395 self.literal = True
2396 self.insert_newline = True
2397 self.insert_non_breaking_blanks = True
2398 self.out.append('{\\ttfamily \\raggedright \\noindent\n')
2400 def depart_literal_block(self, node):
2401 if self.verbatim:
2402 self.out.append('\n\\end{%s}\n' % self.literal_block_env)
2403 self.verbatim = False
2404 else:
2405 self.out.append('\n}')
2406 self.insert_non_breaking_blanks = False
2407 self.insert_newline = False
2408 self.literal = False
2409 self.out.append(self.context.pop())
2411 ## def visit_meta(self, node):
2412 ## self.out.append('[visit_meta]\n')
2413 # TODO: set keywords for pdf?
2414 # But:
2415 # The reStructuredText "meta" directive creates a "pending" node,
2416 # which contains knowledge that the embedded "meta" node can only
2417 # be handled by HTML-compatible writers. The "pending" node is
2418 # resolved by the docutils.transforms.components.Filter transform,
2419 # which checks that the calling writer supports HTML; if it doesn't,
2420 # the "pending" node (and enclosed "meta" node) is removed from the
2421 # document.
2422 # --- docutils/docs/peps/pep-0258.html#transformer
2424 ## def depart_meta(self, node):
2425 ## self.out.append('[depart_meta]\n')
2427 def visit_math(self, node):
2428 """math role"""
2429 self.requirements['amsmath'] = r'\usepackage{amsmath}'
2430 if node['classes']:
2431 self.visit_inline(node)
2432 self.verbatim = True
2433 self.out.append('$')
2435 def depart_math(self, node):
2436 self.verbatim = False
2437 self.out.append('$')
2438 if node['classes']:
2439 self.depart_inline(node)
2441 def multiline_math(self, code):
2442 """find out whether `code` is a multi-line equation
2444 This is a very simplified test, looking
2445 for line-breaks (``\\``) outside environments.
2447 # cut out environment content:
2448 chunks = code.split(r'\begin{')
2449 toplevel_code = ''.join([chunk.split(r'\end{')[-1]
2450 for chunk in chunks])
2451 return toplevel_code.find(r'\\') >= 0
2453 def visit_math_block(self, node):
2454 self.requirements['amsmath'] = r'\usepackage{amsmath}'
2455 if node['classes']:
2456 self.visit_inline(node)
2457 math_code = node.astext()
2458 if self.multiline_math(math_code):
2459 environment = 'align*'
2460 else:
2461 environment = 'equation*'
2462 self.out.append ('%%\n\\begin{%s}\n' % environment)
2463 self.out.append(math_code)
2464 self.out.append('\n\\end{%s}' % environment)
2465 # Content already processed:
2466 raise nodes.SkipNode
2468 def depart_math_block(self, node):
2469 if node['classes']:
2470 self.depart_inline(node)
2472 def visit_option(self, node):
2473 if self.context[-1]:
2474 # this is not the first option
2475 self.out.append(', ')
2477 def depart_option(self, node):
2478 # flag that the first option is done.
2479 self.context[-1] += 1
2481 def visit_option_argument(self, node):
2482 """Append the delimiter betweeen an option and its argument to body."""
2483 self.out.append(node.get('delimiter', ' '))
2485 def depart_option_argument(self, node):
2486 pass
2488 def visit_option_group(self, node):
2489 self.out.append('\n\\item[')
2490 # flag for first option
2491 self.context.append(0)
2493 def depart_option_group(self, node):
2494 self.context.pop() # the flag
2495 self.out.append('] ')
2497 def visit_option_list(self, node):
2498 self.fallbacks['_providelength'] = PreambleCmds.providelength
2499 self.fallbacks['optionlist'] = PreambleCmds.optionlist
2500 self.out.append('%\n\\begin{DUoptionlist}\n')
2502 def depart_option_list(self, node):
2503 self.out.append('\n\\end{DUoptionlist}\n')
2505 def visit_option_list_item(self, node):
2506 pass
2508 def depart_option_list_item(self, node):
2509 pass
2511 def visit_option_string(self, node):
2512 ##self.out.append(self.starttag(node, 'span', '', CLASS='option'))
2513 pass
2515 def depart_option_string(self, node):
2516 ##self.out.append('</span>')
2517 pass
2519 def visit_organization(self, node):
2520 self.visit_docinfo_item(node, 'organization')
2522 def depart_organization(self, node):
2523 self.depart_docinfo_item(node)
2525 def visit_paragraph(self, node):
2526 # insert blank line, if the paragraph is not first in a list item
2527 # nor follows a non-paragraph node in a compound
2528 index = node.parent.index(node)
2529 if (index == 0 and (isinstance(node.parent, nodes.list_item) or
2530 isinstance(node.parent, nodes.description))):
2531 pass
2532 elif (index > 0 and isinstance(node.parent, nodes.compound) and
2533 not isinstance(node.parent[index - 1], nodes.paragraph) and
2534 not isinstance(node.parent[index - 1], nodes.compound)):
2535 pass
2536 else:
2537 self.out.append('\n')
2538 if node.get('ids'):
2539 self.out += self.ids_to_labels(node) + ['\n']
2540 if node['classes']:
2541 self.visit_inline(node)
2543 def depart_paragraph(self, node):
2544 if node['classes']:
2545 self.depart_inline(node)
2546 self.out.append('\n')
2548 def visit_problematic(self, node):
2549 self.requirements['color'] = PreambleCmds.color
2550 self.out.append('%\n')
2551 self.append_hypertargets(node)
2552 self.out.append(r'\hyperlink{%s}{\textbf{\color{red}' % node['refid'])
2554 def depart_problematic(self, node):
2555 self.out.append('}}')
2557 def visit_raw(self, node):
2558 if not 'latex' in node.get('format', '').split():
2559 raise nodes.SkipNode
2560 if not self.is_inline(node):
2561 self.out.append('\n')
2562 if node['classes']:
2563 self.visit_inline(node)
2564 # append "as-is" skipping any LaTeX-encoding
2565 self.verbatim = True
2567 def depart_raw(self, node):
2568 self.verbatim = False
2569 if node['classes']:
2570 self.depart_inline(node)
2571 if not self.is_inline(node):
2572 self.out.append('\n')
2574 def has_unbalanced_braces(self, string):
2575 """Test whether there are unmatched '{' or '}' characters."""
2576 level = 0
2577 for ch in string:
2578 if ch == '{':
2579 level += 1
2580 if ch == '}':
2581 level -= 1
2582 if level < 0:
2583 return True
2584 return level != 0
2586 def visit_reference(self, node):
2587 # We need to escape #, \, and % if we use the URL in a command.
2588 special_chars = {ord('#'): ur'\#',
2589 ord('%'): ur'\%',
2590 ord('\\'): ur'\\',
2592 # external reference (URL)
2593 if 'refuri' in node:
2594 href = unicode(node['refuri']).translate(special_chars)
2595 # problematic chars double caret and unbalanced braces:
2596 if href.find('^^') != -1 or self.has_unbalanced_braces(href):
2597 self.error(
2598 'External link "%s" not supported by LaTeX.\n'
2599 ' (Must not contain "^^" or unbalanced braces.)' % href)
2600 if node['refuri'] == node.astext():
2601 self.out.append(r'\url{%s}' % href)
2602 raise nodes.SkipNode
2603 self.out.append(r'\href{%s}{' % href)
2604 return
2605 # internal reference
2606 if 'refid' in node:
2607 href = node['refid']
2608 elif 'refname' in node:
2609 href = self.document.nameids[node['refname']]
2610 else:
2611 raise AssertionError('Unknown reference.')
2612 if not self.is_inline(node):
2613 self.out.append('\n')
2614 self.out.append('\\hyperref[%s]{' % href)
2615 if self._reference_label:
2616 self.out.append('\\%s{%s}}' %
2617 (self._reference_label, href.replace('#', '')))
2618 raise nodes.SkipNode
2620 def depart_reference(self, node):
2621 self.out.append('}')
2622 if not self.is_inline(node):
2623 self.out.append('\n')
2625 def visit_revision(self, node):
2626 self.visit_docinfo_item(node, 'revision')
2628 def depart_revision(self, node):
2629 self.depart_docinfo_item(node)
2631 def visit_section(self, node):
2632 self.section_level += 1
2633 # Initialize counter for potential subsections:
2634 self._section_number.append(0)
2635 # Counter for this section's level (initialized by parent section):
2636 self._section_number[self.section_level - 1] += 1
2638 def depart_section(self, node):
2639 # Remove counter for potential subsections:
2640 self._section_number.pop()
2641 self.section_level -= 1
2643 def visit_sidebar(self, node):
2644 self.requirements['color'] = PreambleCmds.color
2645 self.fallbacks['sidebar'] = PreambleCmds.sidebar
2646 self.out.append('\n\\DUsidebar{\n')
2648 def depart_sidebar(self, node):
2649 self.out.append('}\n')
2651 attribution_formats = {'dash': (u'—', ''), # EM DASH
2652 'parentheses': ('(', ')'),
2653 'parens': ('(', ')'),
2654 'none': ('', '')}
2656 def visit_attribution(self, node):
2657 prefix, suffix = self.attribution_formats[self.settings.attribution]
2658 self.out.append('\\nopagebreak\n\n\\raggedleft ')
2659 self.out.append(prefix)
2660 self.context.append(suffix)
2662 def depart_attribution(self, node):
2663 self.out.append(self.context.pop() + '\n')
2665 def visit_status(self, node):
2666 self.visit_docinfo_item(node, 'status')
2668 def depart_status(self, node):
2669 self.depart_docinfo_item(node)
2671 def visit_strong(self, node):
2672 self.out.append('\\textbf{')
2673 if node['classes']:
2674 self.visit_inline(node)
2676 def depart_strong(self, node):
2677 if node['classes']:
2678 self.depart_inline(node)
2679 self.out.append('}')
2681 def visit_substitution_definition(self, node):
2682 raise nodes.SkipNode
2684 def visit_substitution_reference(self, node):
2685 self.unimplemented_visit(node)
2687 def visit_subtitle(self, node):
2688 if isinstance(node.parent, nodes.document):
2689 self.push_output_collector(self.subtitle)
2690 self.subtitle_labels += self.ids_to_labels(node, set_anchor=False)
2691 # section subtitle: "starred" (no number, not in ToC)
2692 elif isinstance(node.parent, nodes.section):
2693 self.out.append(r'\%s*{' %
2694 self.d_class.section(self.section_level + 1))
2695 else:
2696 self.fallbacks['subtitle'] = PreambleCmds.subtitle
2697 self.out.append('\n\\DUsubtitle[%s]{' % node.parent.tagname)
2699 def depart_subtitle(self, node):
2700 if isinstance(node.parent, nodes.document):
2701 self.pop_output_collector()
2702 else:
2703 self.out.append('}\n')
2705 def visit_system_message(self, node):
2706 self.requirements['color'] = PreambleCmds.color
2707 self.fallbacks['title'] = PreambleCmds.title
2708 node['classes'] = ['system-message']
2709 self.visit_admonition(node)
2710 self.out.append('\\DUtitle[system-message]{system-message}\n')
2711 self.append_hypertargets(node)
2712 try:
2713 line = ', line~%s' % node['line']
2714 except KeyError:
2715 line = ''
2716 self.out.append('\n\n{\color{red}%s/%s} in \\texttt{%s}%s\n' %
2717 (node['type'], node['level'],
2718 self.encode(node['source']), line))
2719 if len(node['backrefs']) == 1:
2720 self.out.append('\n\\hyperlink{%s}{' % node['backrefs'][0])
2721 self.context.append('}')
2722 else:
2723 backrefs = ['\\hyperlink{%s}{%d}' % (href, i+1)
2724 for (i, href) in enumerate(node['backrefs'])]
2725 self.context.append('backrefs: ' + ' '.join(backrefs))
2727 def depart_system_message(self, node):
2728 self.out.append(self.context.pop())
2729 self.depart_admonition()
2731 def visit_table(self, node):
2732 self.requirements['table'] = PreambleCmds.table
2733 if self.active_table.is_open():
2734 self.table_stack.append(self.active_table)
2735 # nesting longtable does not work (e.g. 2007-04-18)
2736 self.active_table = Table(self,'tabular',self.settings.table_style)
2737 # A longtable moves before \paragraph and \subparagraph
2738 # section titles if it immediately follows them:
2739 if (self.active_table._latex_type == 'longtable' and
2740 isinstance(node.parent, nodes.section) and
2741 node.parent.index(node) == 1 and
2742 self.d_class.section(self.section_level).find('paragraph') != -1):
2743 self.out.append('\\leavevmode')
2744 self.active_table.open()
2745 for cls in node['classes']:
2746 self.active_table.set_table_style(cls)
2747 if self.active_table._table_style == 'booktabs':
2748 self.requirements['booktabs'] = r'\usepackage{booktabs}'
2749 self.push_output_collector([])
2751 def depart_table(self, node):
2752 # wrap content in the right environment:
2753 content = self.out
2754 self.pop_output_collector()
2755 self.out.append('\n' + self.active_table.get_opening())
2756 self.out += content
2757 self.out.append(self.active_table.get_closing() + '\n')
2758 self.active_table.close()
2759 if len(self.table_stack)>0:
2760 self.active_table = self.table_stack.pop()
2761 else:
2762 self.active_table.set_table_style(self.settings.table_style)
2763 # Insert hyperlabel after (long)table, as
2764 # other places (beginning, caption) result in LaTeX errors.
2765 if node.get('ids'):
2766 self.out += self.ids_to_labels(node, set_anchor=False) + ['\n']
2768 def visit_target(self, node):
2769 # Skip indirect targets:
2770 if ('refuri' in node # external hyperlink
2771 or 'refid' in node # resolved internal link
2772 or 'refname' in node): # unresolved internal link
2773 ## self.out.append('%% %s\n' % node) # for debugging
2774 return
2775 self.out.append('%\n')
2776 # do we need an anchor (\phantomsection)?
2777 set_anchor = not(isinstance(node.parent, nodes.caption) or
2778 isinstance(node.parent, nodes.title))
2779 # TODO: where else can/must we omit the \phantomsection?
2780 self.out += self.ids_to_labels(node, set_anchor)
2782 def depart_target(self, node):
2783 pass
2785 def visit_tbody(self, node):
2786 # BUG write preamble if not yet done (colspecs not [])
2787 # for tables without heads.
2788 if not self.active_table.get('preamble written'):
2789 self.visit_thead(None)
2790 self.depart_thead(None)
2792 def depart_tbody(self, node):
2793 pass
2795 def visit_term(self, node):
2796 """definition list term"""
2797 # Commands with optional args inside an optional arg must be put
2798 # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
2799 self.out.append('\\item[{')
2801 def depart_term(self, node):
2802 # \leavevmode results in a line break if the
2803 # term is followed by an item list.
2804 self.out.append('}] \leavevmode ')
2806 def visit_tgroup(self, node):
2807 #self.out.append(self.starttag(node, 'colgroup'))
2808 #self.context.append('</colgroup>\n')
2809 pass
2811 def depart_tgroup(self, node):
2812 pass
2814 _thead_depth = 0
2815 def thead_depth (self):
2816 return self._thead_depth
2818 def visit_thead(self, node):
2819 self._thead_depth += 1
2820 if 1 == self.thead_depth():
2821 self.out.append('{%s}\n' % self.active_table.get_colspecs())
2822 self.active_table.set('preamble written',1)
2823 self.out.append(self.active_table.get_caption())
2824 self.out.extend(self.active_table.visit_thead())
2826 def depart_thead(self, node):
2827 if node is not None:
2828 self.out.extend(self.active_table.depart_thead())
2829 if self.active_table.need_recurse():
2830 node.walkabout(self)
2831 self._thead_depth -= 1
2833 def bookmark(self, node):
2834 """Return label and pdfbookmark string for titles."""
2835 result = ['']
2836 if self.settings.sectnum_xform: # "starred" section cmd
2837 # add to the toc and pdfbookmarks
2838 section_name = self.d_class.section(max(self.section_level, 1))
2839 section_title = self.encode(node.astext())
2840 result.append(r'\addcontentsline{toc}{%s}{%s}' %
2841 (section_name, section_title))
2842 result += self.ids_to_labels(node.parent, set_anchor=False)
2843 return '%\n '.join(result) + '%\n'
2845 def visit_title(self, node):
2846 """Append section and other titles."""
2847 # Document title
2848 if node.parent.tagname == 'document':
2849 self.push_output_collector(self.title)
2850 self.context.append('')
2851 self.pdfinfo.append(' pdftitle={%s},' %
2852 self.encode(node.astext()))
2853 # Topic titles (topic, admonition, sidebar)
2854 elif (isinstance(node.parent, nodes.topic) or
2855 isinstance(node.parent, nodes.admonition) or
2856 isinstance(node.parent, nodes.sidebar)):
2857 self.fallbacks['title'] = PreambleCmds.title
2858 classes = ','.join(node.parent['classes'])
2859 if not classes:
2860 classes = node.tagname
2861 self.out.append('\\DUtitle[%s]{' % classes)
2862 self.context.append('}\n')
2863 # Table caption
2864 elif isinstance(node.parent, nodes.table):
2865 self.push_output_collector(self.active_table.caption)
2866 self.context.append('')
2867 # Section title
2868 else:
2869 self.out.append('\n\n')
2870 self.out.append('%' + '_' * 75)
2871 self.out.append('\n\n')
2873 section_name = self.d_class.section(self.section_level)
2874 section_star = ''
2875 pdfanchor = ''
2876 # number sections?
2877 if (self.settings.sectnum_xform # numbering by Docutils
2878 or (self.section_level > len(self.d_class.sections))):
2879 section_star = '*'
2880 pdfanchor = '\\phantomsection%\n '
2881 self.out.append(r'\%s%s{%s' %
2882 (section_name, section_star, pdfanchor))
2883 # System messages heading in red:
2884 if ('system-messages' in node.parent['classes']):
2885 self.requirements['color'] = PreambleCmds.color
2886 self.out.append('\color{red}')
2887 # label and ToC entry:
2888 self.context.append(self.bookmark(node) + '}\n')
2889 # MAYBE postfix paragraph and subparagraph with \leavemode to
2890 # ensure floats stay in the section and text starts on a new line.
2892 def depart_title(self, node):
2893 self.out.append(self.context.pop())
2894 if (isinstance(node.parent, nodes.table) or
2895 node.parent.tagname == 'document'):
2896 self.pop_output_collector()
2898 def minitoc(self, node, title, depth):
2899 """Generate a local table of contents with LaTeX package minitoc"""
2900 section_name = self.d_class.section(self.section_level)
2901 # name-prefix for current section level
2902 minitoc_names = {'part': 'part', 'chapter': 'mini'}
2903 if 'chapter' not in self.d_class.sections:
2904 minitoc_names['section'] = 'sect'
2905 try:
2906 minitoc_name = minitoc_names[section_name]
2907 except KeyError: # minitoc only supports part- and toplevel
2908 self.warn('Skipping local ToC at %s level.\n' % section_name +
2909 ' Feature not supported with option "use-latex-toc"',
2910 base_node=node)
2911 return
2912 # Requirements/Setup
2913 self.requirements['minitoc'] = PreambleCmds.minitoc
2914 self.requirements['minitoc-'+minitoc_name] = (r'\do%stoc' %
2915 minitoc_name)
2916 # depth: (Docutils defaults to unlimited depth)
2917 maxdepth = len(self.d_class.sections)
2918 self.requirements['minitoc-%s-depth' % minitoc_name] = (
2919 r'\mtcsetdepth{%stoc}{%d}' % (minitoc_name, maxdepth))
2920 # Process 'depth' argument (!Docutils stores a relative depth while
2921 # minitoc expects an absolute depth!):
2922 offset = {'sect': 1, 'mini': 0, 'part': 0}
2923 if 'chapter' in self.d_class.sections:
2924 offset['part'] = -1
2925 if depth:
2926 self.out.append('\\setcounter{%stocdepth}{%d}' %
2927 (minitoc_name, depth + offset[minitoc_name]))
2928 # title:
2929 self.out.append('\\mtcsettitle{%stoc}{%s}\n' % (minitoc_name, title))
2930 # the toc-generating command:
2931 self.out.append('\\%stoc\n' % minitoc_name)
2933 def visit_topic(self, node):
2934 # Topic nodes can be generic topic, abstract, dedication, or ToC.
2935 # table of contents:
2936 if 'contents' in node['classes']:
2937 self.out.append('\n')
2938 self.out += self.ids_to_labels(node)
2939 # add contents to PDF bookmarks sidebar
2940 if isinstance(node.next_node(), nodes.title):
2941 self.out.append('\n\\pdfbookmark[%d]{%s}{%s}\n' %
2942 (self.section_level+1,
2943 node.next_node().astext(),
2944 node.get('ids', ['contents'])[0]
2946 if self.use_latex_toc:
2947 title = ''
2948 if isinstance(node.next_node(), nodes.title):
2949 title = self.encode(node.pop(0).astext())
2950 depth = node.get('depth', 0)
2951 if 'local' in node['classes']:
2952 self.minitoc(node, title, depth)
2953 self.context.append('')
2954 return
2955 if depth:
2956 self.out.append('\\setcounter{tocdepth}{%d}\n' % depth)
2957 if title != 'Contents':
2958 self.out.append('\\renewcommand{\\contentsname}{%s}\n' %
2959 title)
2960 self.out.append('\\tableofcontents\n\n')
2961 self.has_latex_toc = True
2962 else: # Docutils generated contents list
2963 # set flag for visit_bullet_list() and visit_title()
2964 self.is_toc_list = True
2965 self.context.append('')
2966 elif ('abstract' in node['classes'] and
2967 self.settings.use_latex_abstract):
2968 self.push_output_collector(self.abstract)
2969 self.out.append('\\begin{abstract}')
2970 self.context.append('\\end{abstract}\n')
2971 if isinstance(node.next_node(), nodes.title):
2972 node.pop(0) # LaTeX provides its own title
2973 else:
2974 self.fallbacks['topic'] = PreambleCmds.topic
2975 # special topics:
2976 if 'abstract' in node['classes']:
2977 self.fallbacks['abstract'] = PreambleCmds.abstract
2978 self.push_output_collector(self.abstract)
2979 if 'dedication' in node['classes']:
2980 self.fallbacks['dedication'] = PreambleCmds.dedication
2981 self.push_output_collector(self.dedication)
2982 self.out.append('\n\\DUtopic[%s]{\n' % ','.join(node['classes']))
2983 self.context.append('}\n')
2985 def depart_topic(self, node):
2986 self.out.append(self.context.pop())
2987 self.is_toc_list = False
2988 if ('abstract' in node['classes'] or
2989 'dedication' in node['classes']):
2990 self.pop_output_collector()
2992 def visit_rubric(self, node):
2993 self.fallbacks['rubric'] = PreambleCmds.rubric
2994 self.out.append('\n\\DUrubric{')
2995 self.context.append('}\n')
2997 def depart_rubric(self, node):
2998 self.out.append(self.context.pop())
3000 def visit_transition(self, node):
3001 self.fallbacks['transition'] = PreambleCmds.transition
3002 self.out.append('\n\n')
3003 self.out.append('%' + '_' * 75 + '\n')
3004 self.out.append(r'\DUtransition')
3005 self.out.append('\n\n')
3007 def depart_transition(self, node):
3008 pass
3010 def visit_version(self, node):
3011 self.visit_docinfo_item(node, 'version')
3013 def depart_version(self, node):
3014 self.depart_docinfo_item(node)
3016 def unimplemented_visit(self, node):
3017 raise NotImplementedError('visiting unimplemented node type: %s' %
3018 node.__class__.__name__)
3020 # def unknown_visit(self, node):
3021 # def default_visit(self, node):
3023 # vim: set ts=4 et ai :