Apply [ 3148141 ] fix multicolumn support.
[docutils.git] / docutils / writers / latex2e / __init__.py
blob099b36a23f0ccc045cd29645ff27a7bddea7661e
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}
639 \usepackage{array}
640 \setlength{\extrarowheight}{2pt}
641 \newlength{\DUtablewidth} % internal use in tables"""
643 # Options [force,almostfull] prevent spurious error messages, see
644 # de.comp.text.tex/2005-12/msg01855
645 PreambleCmds.textcomp = """\
646 \\usepackage{textcomp} % text symbol macros"""
648 PreambleCmds.titlereference = r"""
649 % titlereference role
650 \providecommand*{\DUroletitlereference}[1]{\textsl{#1}}"""
652 PreambleCmds.title = r"""
653 % title for topics, admonitions and sidebar
654 \providecommand*{\DUtitle}[2][class-arg]{%
655 % call \DUtitle#1{#2} if it exists:
656 \ifcsname DUtitle#1\endcsname%
657 \csname DUtitle#1\endcsname{#2}%
658 \else
659 \smallskip\noindent\textbf{#2}\smallskip%
661 }"""
663 PreambleCmds.topic = r"""
664 % topic (quote with heading)
665 \providecommand{\DUtopic}[2][class-arg]{%
666 \ifcsname DUtopic#1\endcsname%
667 \csname DUtopic#1\endcsname{#2}%
668 \else
669 \begin{quote}#2\end{quote}
671 }"""
673 PreambleCmds.transition = r"""
674 % transition (break, fancybreak, anonymous section)
675 \providecommand*{\DUtransition}[1][class-arg]{%
676 \hspace*{\fill}\hrulefill\hspace*{\fill}
677 \vskip 0.5\baselineskip
678 }"""
681 class DocumentClass(object):
682 """Details of a LaTeX document class."""
684 def __init__(self, document_class, with_part=False):
685 self.document_class = document_class
686 self._with_part = with_part
687 self.sections = ['section', 'subsection', 'subsubsection',
688 'paragraph', 'subparagraph']
689 if self.document_class in ('book', 'memoir', 'report',
690 'scrbook', 'scrreprt'):
691 self.sections.insert(0, 'chapter')
692 if self._with_part:
693 self.sections.insert(0, 'part')
695 def section(self, level):
696 """Return the LaTeX section name for section `level`.
698 The name depends on the specific document class.
699 Level is 1,2,3..., as level 0 is the title.
702 if level <= len(self.sections):
703 return self.sections[level-1]
704 else:
705 return self.sections[-1]
708 class Table(object):
709 """Manage a table while traversing.
711 Maybe change to a mixin defining the visit/departs, but then
712 class Table internal variables are in the Translator.
714 Table style might be
716 :standard: horizontal and vertical lines
717 :booktabs: only horizontal lines (requires "booktabs" LaTeX package)
718 :borderless: no borders around table cells
719 :nolines: alias for borderless
721 def __init__(self,translator,latex_type,table_style):
722 self._translator = translator
723 self._latex_type = latex_type
724 self._table_style = table_style
725 self._open = 0
726 # miscellaneous attributes
727 self._attrs = {}
728 self._col_width = []
729 self._rowspan = []
730 self.stubs = []
731 self._in_thead = 0
733 def open(self):
734 self._open = 1
735 self._col_specs = []
736 self.caption = []
737 self._attrs = {}
738 self._in_head = 0 # maybe context with search
739 def close(self):
740 self._open = 0
741 self._col_specs = None
742 self.caption = []
743 self._attrs = {}
744 self.stubs = []
745 def is_open(self):
746 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 return self._latex_type
755 def set(self,attr,value):
756 self._attrs[attr] = value
757 def get(self,attr):
758 if attr in self._attrs:
759 return self._attrs[attr]
760 return None
762 def get_vertical_bar(self):
763 if self._table_style == 'standard':
764 return '|'
765 return ''
767 # horizontal lines are drawn below a row,
768 def get_opening(self):
769 if self._latex_type == 'longtable':
770 # otherwise longtable might move before paragraph and subparagraph
771 prefix = '\\leavevmode\n'
772 else:
773 prefix = ''
774 prefix += '\setlength{\DUtablewidth}{\linewidth}'
775 return '%s\n\\begin{%s}[c]' % (prefix, self._latex_type)
777 def get_closing(self):
778 line = ''
779 if self._table_style == 'booktabs':
780 line = '\\bottomrule\n'
781 elif self._table_style == 'standard':
782 lines = '\\hline\n'
783 return '%s\\end{%s}' % (line,self._latex_type)
785 def visit_colspec(self, node):
786 self._col_specs.append(node)
787 # "stubs" list is an attribute of the tgroup element:
788 self.stubs.append(node.attributes.get('stub'))
790 def get_colspecs(self):
791 """Return column specification for longtable.
793 Assumes reST line length being 80 characters.
794 Table width is hairy.
796 === ===
797 ABC DEF
798 === ===
800 usually gets to narrow, therefore we add 1 (fiddlefactor).
802 width = 80
804 total_width = 0.0
805 # first see if we get too wide.
806 for node in self._col_specs:
807 colwidth = float(node['colwidth']+1) / width
808 total_width += colwidth
809 self._col_width = []
810 self._rowspan = []
811 # donot make it full linewidth
812 factor = 0.93
813 if total_width > 1.0:
814 factor /= total_width
815 bar = self.get_vertical_bar()
816 latex_table_spec = ''
817 for node in self._col_specs:
818 colwidth = factor * float(node['colwidth']+1) / width
819 self._col_width.append(colwidth+0.005)
820 self._rowspan.append(0)
821 latex_table_spec += '%sp{%.3f\\DUtablewidth}' % (bar, colwidth+0.005)
822 return latex_table_spec+bar
824 def get_column_width(self):
825 """Return columnwidth for current cell (not multicell)."""
826 return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row-1]
828 def get_multicolumn_width(self, start, len_):
829 """Return sum of columnwidths for multicell."""
830 mc_width = sum(width
831 for width in (self._col_width[start + co - 1]
832 for co in range (len_)))
833 return '{0:.2f}\\DUtablewidth'.format(mc_width)
835 def get_caption(self):
836 if not self.caption:
837 return ''
838 caption = ''.join(self.caption)
839 if 1 == self._translator.thead_depth():
840 return r'\caption{%s}\\' '\n' % caption
841 return r'\caption[]{%s (... continued)}\\' '\n' % caption
843 def need_recurse(self):
844 if self._latex_type == 'longtable':
845 return 1 == self._translator.thead_depth()
846 return 0
848 def visit_thead(self):
849 self._in_thead += 1
850 if self._table_style == 'standard':
851 return ['\\hline\n']
852 elif self._table_style == 'booktabs':
853 return ['\\toprule\n']
854 return []
855 def depart_thead(self):
856 a = []
857 #if self._table_style == 'standard':
858 # a.append('\\hline\n')
859 if self._table_style == 'booktabs':
860 a.append('\\midrule\n')
861 if self._latex_type == 'longtable':
862 if 1 == self._translator.thead_depth():
863 a.append('\\endfirsthead\n')
864 else:
865 a.append('\\endhead\n')
866 a.append(r'\multicolumn{%d}{c}' % len(self._col_specs) +
867 r'{\hfill ... continued on next page} \\')
868 a.append('\n\\endfoot\n\\endlastfoot\n')
869 # for longtable one could add firsthead, foot and lastfoot
870 self._in_thead -= 1
871 return a
872 def visit_row(self):
873 self._cell_in_row = 0
874 def depart_row(self):
875 res = [' \\\\\n']
876 self._cell_in_row = None # remove cell counter
877 for i in range(len(self._rowspan)):
878 if (self._rowspan[i]>0):
879 self._rowspan[i] -= 1
881 if self._table_style == 'standard':
882 rowspans = [i+1 for i in range(len(self._rowspan))
883 if (self._rowspan[i]<=0)]
884 if len(rowspans)==len(self._rowspan):
885 res.append('\\hline\n')
886 else:
887 cline = ''
888 rowspans.reverse()
889 # TODO merge clines
890 while 1:
891 try:
892 c_start = rowspans.pop()
893 except:
894 break
895 cline += '\\cline{%d-%d}\n' % (c_start,c_start)
896 res.append(cline)
897 return res
899 def set_rowspan(self,cell,value):
900 try:
901 self._rowspan[cell] = value
902 except:
903 pass
904 def get_rowspan(self,cell):
905 try:
906 return self._rowspan[cell]
907 except:
908 return 0
909 def get_entry_number(self):
910 return self._cell_in_row
911 def visit_entry(self):
912 self._cell_in_row += 1
913 def is_stub_column(self):
914 if len(self.stubs) >= self._cell_in_row:
915 return self.stubs[self._cell_in_row-1]
916 return False
919 class LaTeXTranslator(nodes.NodeVisitor):
921 # When options are given to the documentclass, latex will pass them
922 # to other packages, as done with babel.
923 # Dummy settings might be taken from document settings
925 # Config setting defaults
926 # -----------------------
928 # TODO: use mixins for different implementations.
929 # list environment for docinfo. else tabularx
930 ## use_optionlist_for_docinfo = False # TODO: NOT YET IN USE
932 # Use compound enumerations (1.A.1.)
933 compound_enumerators = 0
935 # If using compound enumerations, include section information.
936 section_prefix_for_enumerators = 0
938 # This is the character that separates the section ("." subsection ...)
939 # prefix from the regular list enumerator.
940 section_enumerator_separator = '-'
942 # Auxiliary variables
943 # -------------------
945 has_latex_toc = False # is there a toc in the doc? (needed by minitoc)
946 is_toc_list = False # is the current bullet_list a ToC?
947 section_level = 0
949 # Flags to encode():
950 # inside citation reference labels underscores dont need to be escaped
951 inside_citation_reference_label = False
952 verbatim = False # do not encode
953 insert_non_breaking_blanks = False # replace blanks by "~"
954 insert_newline = False # add latex newline commands
955 literal = False # literal text (block or inline)
958 def __init__(self, document, babel_class=Babel):
959 nodes.NodeVisitor.__init__(self, document)
960 # Reporter
961 # ~~~~~~~~
962 self.warn = self.document.reporter.warning
963 self.error = self.document.reporter.error
965 # Settings
966 # ~~~~~~~~
967 self.settings = settings = document.settings
968 self.latex_encoding = self.to_latex_encoding(settings.output_encoding)
969 self.use_latex_toc = settings.use_latex_toc
970 self.use_latex_docinfo = settings.use_latex_docinfo
971 self._use_latex_citations = settings.use_latex_citations
972 self.embed_stylesheet = settings.embed_stylesheet
973 self._reference_label = settings.reference_label
974 self.hyperlink_color = settings.hyperlink_color
975 self.compound_enumerators = settings.compound_enumerators
976 self.font_encoding = getattr(settings, 'font_encoding', '')
977 self.section_prefix_for_enumerators = (
978 settings.section_prefix_for_enumerators)
979 self.section_enumerator_separator = (
980 settings.section_enumerator_separator.replace('_', r'\_'))
981 # literal blocks:
982 self.literal_block_env = ''
983 self.literal_block_options = ''
984 if settings.literal_block_env != '':
985 (none,
986 self.literal_block_env,
987 self.literal_block_options,
988 none ) = re.split('(\w+)(.*)', settings.literal_block_env)
989 elif settings.use_verbatim_when_possible:
990 self.literal_block_env = 'verbatim'
992 if self.settings.use_bibtex:
993 self.bibtex = self.settings.use_bibtex.split(',',1)
994 # TODO avoid errors on not declared citations.
995 else:
996 self.bibtex = None
997 # language module for Docutils-generated text
998 # (labels, bibliographic_fields, and author_separators)
999 self.language_module = languages.get_language(settings.language_code,
1000 document.reporter)
1001 self.babel = babel_class(settings.language_code, document.reporter)
1002 self.author_separator = self.language_module.author_separators[0]
1003 d_options = [self.settings.documentoptions]
1004 if self.babel.language not in ('english', ''):
1005 d_options.append(self.babel.language)
1006 self.documentoptions = ','.join(filter(None, d_options))
1007 self.d_class = DocumentClass(settings.documentclass,
1008 settings.use_part_section)
1009 # graphic package options:
1010 if self.settings.graphicx_option == '':
1011 self.graphicx_package = r'\usepackage{graphicx}'
1012 elif self.settings.graphicx_option.lower() == 'auto':
1013 self.graphicx_package = PreambleCmds.graphicx_auto
1014 else:
1015 self.graphicx_package = (r'\usepackage[%s]{graphicx}' %
1016 self.settings.graphicx_option)
1017 # footnotes:
1018 self.docutils_footnotes = settings.docutils_footnotes
1019 if settings.use_latex_footnotes:
1020 self.docutils_footnotes = True
1021 self.warn('`use_latex_footnotes` is deprecated. '
1022 'The setting has been renamed to `docutils_footnotes` '
1023 'and the alias will be removed in a future version.')
1024 self.figure_footnotes = settings.figure_footnotes
1025 if self.figure_footnotes:
1026 self.docutils_footnotes = True
1027 self.warn('The "figure footnotes" workaround/setting is strongly '
1028 'deprecated and will be removed in a future version.')
1030 # Output collection stacks
1031 # ~~~~~~~~~~~~~~~~~~~~~~~~
1033 # Document parts
1034 self.head_prefix = [r'\documentclass[%s]{%s}' %
1035 (self.documentoptions, self.settings.documentclass)]
1036 self.requirements = SortableDict() # made a list in depart_document()
1037 self.requirements['__static'] = r'\usepackage{ifthen}'
1038 self.latex_preamble = [settings.latex_preamble]
1039 self.stylesheet = []
1040 self.fallbacks = SortableDict() # made a list in depart_document()
1041 self.pdfsetup = [] # PDF properties (hyperref package)
1042 self.title = []
1043 self.subtitle = []
1044 self.titledata = [] # \title, \author, \date
1045 ## self.body_prefix = ['\\begin{document}\n']
1046 self.body_pre_docinfo = [] # \maketitle
1047 self.docinfo = []
1048 self.dedication = []
1049 self.abstract = []
1050 self.body = []
1051 ## self.body_suffix = ['\\end{document}\n']
1053 # A heterogenous stack used in conjunction with the tree traversal.
1054 # Make sure that the pops correspond to the pushes:
1055 self.context = []
1057 # Title metadata:
1058 self.title_labels = []
1059 self.subtitle_labels = []
1060 # (if use_latex_docinfo: collects lists of
1061 # author/organization/contact/address lines)
1062 self.author_stack = []
1063 self.date = []
1065 # PDF properties: pdftitle, pdfauthor
1066 # TODO?: pdfcreator, pdfproducer, pdfsubject, pdfkeywords
1067 self.pdfinfo = []
1068 self.pdfauthor = []
1070 # Stack of section counters so that we don't have to use_latex_toc.
1071 # This will grow and shrink as processing occurs.
1072 # Initialized for potential first-level sections.
1073 self._section_number = [0]
1075 # The current stack of enumerations so that we can expand
1076 # them into a compound enumeration.
1077 self._enumeration_counters = []
1078 # The maximum number of enumeration counters we've used.
1079 # If we go beyond this number, we need to create a new
1080 # counter; otherwise, just reuse an old one.
1081 self._max_enumeration_counters = 0
1083 self._bibitems = []
1085 # object for a table while proccessing.
1086 self.table_stack = []
1087 self.active_table = Table(self, 'longtable', settings.table_style)
1089 # Where to collect the output of visitor methods (default: body)
1090 self.out = self.body
1091 self.out_stack = [] # stack of output collectors
1093 # Process settings
1094 # ~~~~~~~~~~~~~~~~
1095 # Encodings:
1096 # Docutils' output-encoding => TeX input encoding
1097 if self.latex_encoding != 'ascii':
1098 self.requirements['_inputenc'] = (r'\usepackage[%s]{inputenc}'
1099 % self.latex_encoding)
1100 # TeX font encoding
1101 if self.font_encoding:
1102 self.requirements['_fontenc'] = (r'\usepackage[%s]{fontenc}' %
1103 self.font_encoding)
1104 # page layout with typearea (if there are relevant document options)
1105 if (settings.documentclass.find('scr') == -1 and
1106 (self.documentoptions.find('DIV') != -1 or
1107 self.documentoptions.find('BCOR') != -1)):
1108 self.requirements['typearea'] = r'\usepackage{typearea}'
1110 # Stylesheets
1111 # get list of style sheets from settings
1112 styles = utils.get_stylesheet_list(settings)
1113 # adapt path if --stylesheet_path is used
1114 if settings.stylesheet_path and not(self.embed_stylesheet):
1115 styles = [utils.relative_path(settings._destination, sheet)
1116 for sheet in styles]
1117 for sheet in styles:
1118 (base, ext) = os.path.splitext(sheet)
1119 is_package = ext in ['.sty', '']
1120 if self.embed_stylesheet:
1121 if is_package:
1122 sheet = base + '.sty' # adapt package name
1123 # wrap in \makeatletter, \makeatother
1124 wrapper = PreambleCmds.embedded_package_wrapper
1125 else:
1126 wrapper = '%% embedded stylesheet: %s\n%s'
1127 settings.record_dependencies.add(sheet)
1128 self.stylesheet.append(wrapper %
1129 (sheet, io.FileInput(source_path=sheet, encoding='utf-8').read()))
1130 else: # link to style sheet
1131 if is_package:
1132 self.stylesheet.append(r'\usepackage{%s}' % base)
1133 else:
1134 self.stylesheet.append(r'\input{%s}' % sheet)
1136 # PDF setup
1137 if self.hyperlink_color in ('0', 'false', 'False', ''):
1138 self.hyperref_options = ''
1139 else:
1140 self.hyperref_options = 'colorlinks=true,linkcolor=%s,urlcolor=%s' % (
1141 self.hyperlink_color, self.hyperlink_color)
1142 if settings.hyperref_options:
1143 self.hyperref_options += ',' + settings.hyperref_options
1145 # LaTeX Toc
1146 # include all supported sections in toc and PDF bookmarks
1147 # (or use documentclass-default (as currently))?
1148 ## if self.use_latex_toc:
1149 ## self.requirements['tocdepth'] = (r'\setcounter{tocdepth}{%d}' %
1150 ## len(self.d_class.sections))
1152 # LaTeX section numbering
1153 if not self.settings.sectnum_xform: # section numbering by LaTeX:
1154 # sectnum_depth:
1155 # None "sectnum" directive without depth arg -> LaTeX default
1156 # 0 no "sectnum" directive -> no section numbers
1157 # else value of the "depth" argument: translate to LaTeX level
1158 # -1 part (0 with "article" document class)
1159 # 0 chapter (missing in "article" document class)
1160 # 1 section
1161 # 2 subsection
1162 # 3 subsubsection
1163 # 4 paragraph
1164 # 5 subparagraph
1165 if settings.sectnum_depth is not None:
1166 # limit to supported levels
1167 sectnum_depth = min(settings.sectnum_depth,
1168 len(self.d_class.sections))
1169 # adjust to document class and use_part_section settings
1170 if 'chapter' in self.d_class.sections:
1171 sectnum_depth -= 1
1172 if self.d_class.sections[0] == 'part':
1173 sectnum_depth -= 1
1174 self.requirements['sectnum_depth'] = (
1175 r'\setcounter{secnumdepth}{%d}' % sectnum_depth)
1176 # start with specified number:
1177 if (hasattr(settings, 'sectnum_start') and
1178 settings.sectnum_start != 1):
1179 self.requirements['sectnum_start'] = (
1180 r'\setcounter{%s}{%d}' % (self.d_class.sections[0],
1181 settings.sectnum_start-1))
1182 # currently ignored (configure in a stylesheet):
1183 ## settings.sectnum_prefix
1184 ## settings.sectnum_suffix
1187 # Auxiliary Methods
1188 # -----------------
1190 def to_latex_encoding(self,docutils_encoding):
1191 """Translate docutils encoding name into LaTeX's.
1193 Default method is remove "-" and "_" chars from docutils_encoding.
1195 tr = { 'iso-8859-1': 'latin1', # west european
1196 'iso-8859-2': 'latin2', # east european
1197 'iso-8859-3': 'latin3', # esperanto, maltese
1198 'iso-8859-4': 'latin4', # north european, scandinavian, baltic
1199 'iso-8859-5': 'iso88595', # cyrillic (ISO)
1200 'iso-8859-9': 'latin5', # turkish
1201 'iso-8859-15': 'latin9', # latin9, update to latin1.
1202 'mac_cyrillic': 'maccyr', # cyrillic (on Mac)
1203 'windows-1251': 'cp1251', # cyrillic (on Windows)
1204 'koi8-r': 'koi8-r', # cyrillic (Russian)
1205 'koi8-u': 'koi8-u', # cyrillic (Ukrainian)
1206 'windows-1250': 'cp1250', #
1207 'windows-1252': 'cp1252', #
1208 'us-ascii': 'ascii', # ASCII (US)
1209 # unmatched encodings
1210 #'': 'applemac',
1211 #'': 'ansinew', # windows 3.1 ansi
1212 #'': 'ascii', # ASCII encoding for the range 32--127.
1213 #'': 'cp437', # dos latin us
1214 #'': 'cp850', # dos latin 1
1215 #'': 'cp852', # dos latin 2
1216 #'': 'decmulti',
1217 #'': 'latin10',
1218 #'iso-8859-6': '' # arabic
1219 #'iso-8859-7': '' # greek
1220 #'iso-8859-8': '' # hebrew
1221 #'iso-8859-10': '' # latin6, more complete iso-8859-4
1223 encoding = docutils_encoding.lower()
1224 if encoding in tr:
1225 return tr[encoding]
1226 # convert: latin-1, latin_1, utf-8 and similar things
1227 encoding = encoding.replace('_', '').replace('-', '')
1228 # strip the error handler
1229 return encoding.split(':')[0]
1231 def language_label(self, docutil_label):
1232 return self.language_module.labels[docutil_label]
1234 def ensure_math(self, text):
1235 if not hasattr(self, 'ensure_math_re'):
1236 chars = { # lnot,pm,twosuperior,threesuperior,mu,onesuperior,times,div
1237 'latin1' : '\xac\xb1\xb2\xb3\xb5\xb9\xd7\xf7' , # ¬±²³µ¹×÷
1238 # TODO?: use texcomp instead.
1240 self.ensure_math_re = re.compile('([%s])' % chars['latin1'])
1241 text = self.ensure_math_re.sub(r'\\ensuremath{\1}', text)
1242 return text
1244 def encode(self, text):
1245 """Return text with 'problematic' characters escaped.
1247 Escape the ten special printing characters ``# $ % & ~ _ ^ \ { }``,
1248 square brackets ``[ ]``, double quotes and (in OT1) ``< | >``.
1250 Separate ``-`` (and more in literal text) to prevent input ligatures.
1252 Translate non-supported Unicode characters.
1254 if self.verbatim:
1255 return text
1256 # Separate compound characters, e.g. '--' to '-{}-'.
1257 separate_chars = '-'
1258 # In monospace-font, we also separate ',,', '``' and "''" and some
1259 # other characters which can't occur in non-literal text.
1260 if self.literal:
1261 separate_chars += ',`\'"<>'
1262 # LaTeX encoding maps:
1263 special_chars = {
1264 ord('#'): ur'\#',
1265 ord('$'): ur'\$',
1266 ord('%'): ur'\%',
1267 ord('&'): ur'\&',
1268 ord('~'): ur'\textasciitilde{}',
1269 ord('_'): ur'\_',
1270 ord('^'): ur'\textasciicircum{}',
1271 ord('\\'): ur'\textbackslash{}',
1272 ord('{'): ur'\{',
1273 ord('}'): ur'\}',
1274 # Square brackets are ordinary chars and cannot be escaped with '\',
1275 # so we put them in a group '{[}'. (Alternative: ensure that all
1276 # macros with optional arguments are terminated with {} and text
1277 # inside any optional argument is put in a group ``[{text}]``).
1278 # Commands with optional args inside an optional arg must be put
1279 # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
1280 ord('['): ur'{[}',
1281 ord(']'): ur'{]}'
1283 # Unicode chars that are not recognized by LaTeX's utf8 encoding
1284 unsupported_unicode_chars = {
1285 0x00A0: ur'~', # NO-BREAK SPACE
1286 0x00AD: ur'\-', # SOFT HYPHEN
1288 0x2011: ur'\hbox{-}', # NON-BREAKING HYPHEN
1289 0x21d4: ur'$\Leftrightarrow$',
1290 # Docutils footnote symbols:
1291 0x2660: ur'$\spadesuit$',
1292 0x2663: ur'$\clubsuit$',
1294 # Unicode chars that are recognized by LaTeX's utf8 encoding
1295 unicode_chars = {
1296 0x200C: ur'\textcompwordmark', # ZERO WIDTH NON-JOINER
1297 0x2013: ur'\textendash{}',
1298 0x2014: ur'\textemdash{}',
1299 0x2018: ur'\textquoteleft{}',
1300 0x2019: ur'\textquoteright{}',
1301 0x201A: ur'\quotesinglbase{}', # SINGLE LOW-9 QUOTATION MARK
1302 0x201C: ur'\textquotedblleft{}',
1303 0x201D: ur'\textquotedblright{}',
1304 0x201E: ur'\quotedblbase{}', # DOUBLE LOW-9 QUOTATION MARK
1305 0x2030: ur'\textperthousand{}', # PER MILLE SIGN
1306 0x2031: ur'\textpertenthousand{}', # PER TEN THOUSAND SIGN
1307 0x2039: ur'\guilsinglleft{}',
1308 0x203A: ur'\guilsinglright{}',
1309 0x2423: ur'\textvisiblespace{}', # OPEN BOX
1310 0x2020: ur'\dag{}',
1311 0x2021: ur'\ddag{}',
1312 0x2026: ur'\dots{}',
1313 0x2122: ur'\texttrademark{}',
1315 # Unicode chars that require a feature/package to render
1316 pifont_chars = {
1317 0x2665: ur'\ding{170}', # black heartsuit
1318 0x2666: ur'\ding{169}', # black diamondsuit
1320 # recognized with 'utf8', if textcomp is loaded
1321 textcomp_chars = {
1322 # Latin-1 Supplement
1323 0x00a2: ur'\textcent{}', # ¢ CENT SIGN
1324 0x00a4: ur'\textcurrency{}', # ¤ CURRENCY SYMBOL
1325 0x00a5: ur'\textyen{}', # ¥ YEN SIGN
1326 0x00a6: ur'\textbrokenbar{}', # ¦ BROKEN BAR
1327 0x00a7: ur'\textsection{}', # § SECTION SIGN
1328 0x00a8: ur'\textasciidieresis{}', # ¨ DIAERESIS
1329 0x00a9: ur'\textcopyright{}', # © COPYRIGHT SIGN
1330 0x00aa: ur'\textordfeminine{}', # ª FEMININE ORDINAL INDICATOR
1331 0x00ac: ur'\textlnot{}', # ¬ NOT SIGN
1332 0x00ae: ur'\textregistered{}', # ® REGISTERED SIGN
1333 0x00af: ur'\textasciimacron{}', # ¯ MACRON
1334 0x00b0: ur'\textdegree{}', # ° DEGREE SIGN
1335 0x00b1: ur'\textpm{}', # ± PLUS-MINUS SIGN
1336 0x00b2: ur'\texttwosuperior{}', # ² SUPERSCRIPT TWO
1337 0x00b3: ur'\textthreesuperior{}', # ³ SUPERSCRIPT THREE
1338 0x00b4: ur'\textasciiacute{}', # ´ ACUTE ACCENT
1339 0x00b5: ur'\textmu{}', # µ MICRO SIGN
1340 0x00b6: ur'\textparagraph{}', # ¶ PILCROW SIGN # not equal to \textpilcrow
1341 0x00b9: ur'\textonesuperior{}', # ¹ SUPERSCRIPT ONE
1342 0x00ba: ur'\textordmasculine{}', # º MASCULINE ORDINAL INDICATOR
1343 0x00bc: ur'\textonequarter{}', # 1/4 FRACTION
1344 0x00bd: ur'\textonehalf{}', # 1/2 FRACTION
1345 0x00be: ur'\textthreequarters{}', # 3/4 FRACTION
1346 0x00d7: ur'\texttimes{}', # × MULTIPLICATION SIGN
1347 0x00f7: ur'\textdiv{}', # ÷ DIVISION SIGN
1349 0x0192: ur'\textflorin{}', # LATIN SMALL LETTER F WITH HOOK
1350 0x02b9: ur'\textasciiacute{}', # MODIFIER LETTER PRIME
1351 0x02ba: ur'\textacutedbl{}', # MODIFIER LETTER DOUBLE PRIME
1352 0x2016: ur'\textbardbl{}', # DOUBLE VERTICAL LINE
1353 0x2022: ur'\textbullet{}', # BULLET
1354 0x2032: ur'\textasciiacute{}', # PRIME
1355 0x2033: ur'\textacutedbl{}', # DOUBLE PRIME
1356 0x2035: ur'\textasciigrave{}', # REVERSED PRIME
1357 0x2036: ur'\textgravedbl{}', # REVERSED DOUBLE PRIME
1358 0x203b: ur'\textreferencemark{}', # REFERENCE MARK
1359 0x203d: ur'\textinterrobang{}', # INTERROBANG
1360 0x2044: ur'\textfractionsolidus{}', # FRACTION SLASH
1361 0x2045: ur'\textlquill{}', # LEFT SQUARE BRACKET WITH QUILL
1362 0x2046: ur'\textrquill{}', # RIGHT SQUARE BRACKET WITH QUILL
1363 0x2052: ur'\textdiscount{}', # COMMERCIAL MINUS SIGN
1364 0x20a1: ur'\textcolonmonetary{}', # COLON SIGN
1365 0x20a3: ur'\textfrenchfranc{}', # FRENCH FRANC SIGN
1366 0x20a4: ur'\textlira{}', # LIRA SIGN
1367 0x20a6: ur'\textnaira{}', # NAIRA SIGN
1368 0x20a9: ur'\textwon{}', # WON SIGN
1369 0x20ab: ur'\textdong{}', # DONG SIGN
1370 0x20ac: ur'\texteuro{}', # EURO SIGN
1371 0x20b1: ur'\textpeso{}', # PESO SIGN
1372 0x20b2: ur'\textguarani{}', # GUARANI SIGN
1373 0x2103: ur'\textcelsius{}', # DEGREE CELSIUS
1374 0x2116: ur'\textnumero{}', # NUMERO SIGN
1375 0x2117: ur'\textcircledP{}', # SOUND RECORDING COYRIGHT
1376 0x211e: ur'\textrecipe{}', # PRESCRIPTION TAKE
1377 0x2120: ur'\textservicemark{}', # SERVICE MARK
1378 0x2122: ur'\texttrademark{}', # TRADE MARK SIGN
1379 0x2126: ur'\textohm{}', # OHM SIGN
1380 0x2127: ur'\textmho{}', # INVERTED OHM SIGN
1381 0x212e: ur'\textestimated{}', # ESTIMATED SYMBOL
1382 0x2190: ur'\textleftarrow{}', # LEFTWARDS ARROW
1383 0x2191: ur'\textuparrow{}', # UPWARDS ARROW
1384 0x2192: ur'\textrightarrow{}', # RIGHTWARDS ARROW
1385 0x2193: ur'\textdownarrow{}', # DOWNWARDS ARROW
1386 0x2212: ur'\textminus{}', # MINUS SIGN
1387 0x2217: ur'\textasteriskcentered{}', # ASTERISK OPERATOR
1388 0x221a: ur'\textsurd{}', # SQUARE ROOT
1389 0x2422: ur'\textblank{}', # BLANK SYMBOL
1390 0x25e6: ur'\textopenbullet{}', # WHITE BULLET
1391 0x25ef: ur'\textbigcircle{}', # LARGE CIRCLE
1392 0x266a: ur'\textmusicalnote{}', # EIGHTH NOTE
1393 0x26ad: ur'\textmarried{}', # MARRIAGE SYMBOL
1394 0x26ae: ur'\textdivorced{}', # DIVORCE SYMBOL
1395 0x27e8: ur'\textlangle{}', # MATHEMATICAL LEFT ANGLE BRACKET
1396 0x27e9: ur'\textrangle{}', # MATHEMATICAL RIGHT ANGLE BRACKET
1398 # TODO: greek alphabet ... ?
1399 # see also LaTeX codec
1400 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
1401 # and unimap.py from TeXML
1403 # set up the translation table:
1404 table = special_chars
1405 # keep the underscore in citation references
1406 if self.inside_citation_reference_label:
1407 del(table[ord('_')])
1408 # Workarounds for OT1 font-encoding
1409 if self.font_encoding in ['OT1', '']:
1410 # * out-of-order characters in cmtt
1411 if self.literal:
1412 # replace underscore by underlined blank,
1413 # because this has correct width.
1414 table[ord('_')] = u'\\underline{~}'
1415 # the backslash doesn't work, so we use a mirrored slash.
1416 # \reflectbox is provided by graphicx:
1417 self.requirements['graphicx'] = self.graphicx_package
1418 table[ord('\\')] = ur'\reflectbox{/}'
1419 # * ``< | >`` come out as different chars (except for cmtt):
1420 else:
1421 table[ord('|')] = ur'\textbar{}'
1422 table[ord('<')] = ur'\textless{}'
1423 table[ord('>')] = ur'\textgreater{}'
1424 if self.insert_non_breaking_blanks:
1425 table[ord(' ')] = ur'~'
1426 if self.literal:
1427 # double quotes are 'active' in some languages
1428 table[ord('"')] = self.babel.literal_double_quote
1429 # Unicode chars:
1430 table.update(unsupported_unicode_chars)
1431 table.update(pifont_chars)
1432 if not self.latex_encoding.startswith('utf8'):
1433 table.update(unicode_chars)
1434 table.update(textcomp_chars)
1435 # Characters that require a feature/package to render
1436 for ch in text:
1437 if ord(ch) in pifont_chars:
1438 self.requirements['pifont'] = '\\usepackage{pifont}'
1439 if ord(ch) in textcomp_chars:
1440 self.requirements['textcomp'] = PreambleCmds.textcomp
1442 text = text.translate(table)
1444 # Break up input ligatures
1445 for char in separate_chars * 2:
1446 # Do it twice ("* 2") because otherwise we would replace
1447 # '---' by '-{}--'.
1448 text = text.replace(char + char, char + '{}' + char)
1449 # Literal line breaks (in address or literal blocks):
1450 if self.insert_newline:
1451 lines = text.split('\n')
1452 # for blank lines, insert a protected space, to avoid
1453 # ! LaTeX Error: There's no line here to end.
1454 lines = [line + '~'*(not line.lstrip())
1455 for line in lines[:-1]] + lines[-1:]
1456 text = '\\\\\n'.join(lines)
1457 if not self.literal:
1458 text = self.babel.quote_quotes(text)
1459 if self.literal and not self.insert_non_breaking_blanks:
1460 # preserve runs of spaces but allow wrapping
1461 text = text.replace(' ', ' ~')
1462 if not self.latex_encoding.startswith('utf8'):
1463 text = self.ensure_math(text)
1464 return text
1466 def attval(self, text,
1467 whitespace=re.compile('[\n\r\t\v\f]')):
1468 """Cleanse, encode, and return attribute value text."""
1469 return self.encode(whitespace.sub(' ', text))
1471 # TODO: is this used anywhere? -> update (use template) or delete
1472 ## def astext(self):
1473 ## """Assemble document parts and return as string."""
1474 ## head = '\n'.join(self.head_prefix + self.stylesheet + self.head)
1475 ## body = ''.join(self.body_prefix + self.body + self.body_suffix)
1476 ## return head + '\n' + body
1478 def is_inline(self, node):
1479 """Check whether a node represents an inline element"""
1480 return isinstance(node.parent, nodes.TextElement)
1482 def append_hypertargets(self, node):
1483 """Append hypertargets for all ids of `node`"""
1484 # hypertarget places the anchor at the target's baseline,
1485 # so we raise it explicitely
1486 self.out.append('%\n'.join(['\\raisebox{1em}{\\hypertarget{%s}{}}' %
1487 id for id in node['ids']]))
1489 def ids_to_labels(self, node, set_anchor=True):
1490 """Return list of label definitions for all ids of `node`
1492 If `set_anchor` is True, an anchor is set with \phantomsection.
1494 labels = ['\\label{%s}' % id for id in node.get('ids', [])]
1495 if set_anchor and labels:
1496 labels.insert(0, '\\phantomsection')
1497 return labels
1499 def push_output_collector(self, new_out):
1500 self.out_stack.append(self.out)
1501 self.out = new_out
1503 def pop_output_collector(self):
1504 self.out = self.out_stack.pop()
1506 # Visitor methods
1507 # ---------------
1509 def visit_Text(self, node):
1510 self.out.append(self.encode(node.astext()))
1512 def depart_Text(self, node):
1513 pass
1515 def visit_address(self, node):
1516 self.visit_docinfo_item(node, 'address')
1518 def depart_address(self, node):
1519 self.depart_docinfo_item(node)
1521 def visit_admonition(self, node):
1522 self.fallbacks['admonition'] = PreambleCmds.admonition
1523 if 'error' in node['classes']:
1524 self.fallbacks['error'] = PreambleCmds.error
1525 # strip the generic 'admonition' from the list of classes
1526 node['classes'] = [cls for cls in node['classes']
1527 if cls != 'admonition']
1528 self.out.append('\n\\DUadmonition[%s]{\n' % ','.join(node['classes']))
1530 def depart_admonition(self, node=None):
1531 self.out.append('}\n')
1533 def visit_author(self, node):
1534 self.visit_docinfo_item(node, 'author')
1536 def depart_author(self, node):
1537 self.depart_docinfo_item(node)
1539 def visit_authors(self, node):
1540 # not used: visit_author is called anyway for each author.
1541 pass
1543 def depart_authors(self, node):
1544 pass
1546 def visit_block_quote(self, node):
1547 self.out.append( '%\n\\begin{quote}\n')
1548 if node['classes']:
1549 self.visit_inline(node)
1551 def depart_block_quote(self, node):
1552 if node['classes']:
1553 self.depart_inline(node)
1554 self.out.append( '\n\\end{quote}\n')
1556 def visit_bullet_list(self, node):
1557 if self.is_toc_list:
1558 self.out.append( '%\n\\begin{list}{}{}\n' )
1559 else:
1560 self.out.append( '%\n\\begin{itemize}\n' )
1562 def depart_bullet_list(self, node):
1563 if self.is_toc_list:
1564 self.out.append( '\n\\end{list}\n' )
1565 else:
1566 self.out.append( '\n\\end{itemize}\n' )
1568 def visit_superscript(self, node):
1569 self.out.append(r'\textsuperscript{')
1570 if node['classes']:
1571 self.visit_inline(node)
1573 def depart_superscript(self, node):
1574 if node['classes']:
1575 self.depart_inline(node)
1576 self.out.append('}')
1578 def visit_subscript(self, node):
1579 self.out.append(r'\textsubscript{') # requires `fixltx2e`
1580 if node['classes']:
1581 self.visit_inline(node)
1583 def depart_subscript(self, node):
1584 if node['classes']:
1585 self.depart_inline(node)
1586 self.out.append('}')
1588 def visit_caption(self, node):
1589 self.out.append( '\\caption{' )
1591 def depart_caption(self, node):
1592 self.out.append('}\n')
1594 def visit_title_reference(self, node):
1595 self.fallbacks['titlereference'] = PreambleCmds.titlereference
1596 self.out.append(r'\DUroletitlereference{')
1597 if node['classes']:
1598 self.visit_inline(node)
1600 def depart_title_reference(self, node):
1601 if node['classes']:
1602 self.depart_inline(node)
1603 self.out.append( '}' )
1605 def visit_citation(self, node):
1606 # TODO maybe use cite bibitems
1607 if self._use_latex_citations:
1608 self.push_output_collector([])
1609 else:
1610 # TODO: do we need these?
1611 ## self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats
1612 self.out.append(r'\begin{figure}[b]')
1613 self.append_hypertargets(node)
1615 def depart_citation(self, node):
1616 if self._use_latex_citations:
1617 label = self.out[0]
1618 text = ''.join(self.out[1:])
1619 self._bibitems.append([label, text])
1620 self.pop_output_collector()
1621 else:
1622 self.out.append('\\end{figure}\n')
1624 def visit_citation_reference(self, node):
1625 if self._use_latex_citations:
1626 if not self.inside_citation_reference_label:
1627 self.out.append(r'\cite{')
1628 self.inside_citation_reference_label = 1
1629 else:
1630 assert self.body[-1] in (' ', '\n'),\
1631 'unexpected non-whitespace while in reference label'
1632 del self.body[-1]
1633 else:
1634 href = ''
1635 if 'refid' in node:
1636 href = node['refid']
1637 elif 'refname' in node:
1638 href = self.document.nameids[node['refname']]
1639 self.out.append('[\\hyperlink{%s}{' % href)
1641 def depart_citation_reference(self, node):
1642 if self._use_latex_citations:
1643 followup_citation = False
1644 # check for a following citation separated by a space or newline
1645 next_siblings = node.traverse(descend=0, siblings=1,
1646 include_self=0)
1647 if len(next_siblings) > 1:
1648 next = next_siblings[0]
1649 if (isinstance(next, nodes.Text) and
1650 next.astext() in (' ', '\n')):
1651 if next_siblings[1].__class__ == node.__class__:
1652 followup_citation = True
1653 if followup_citation:
1654 self.out.append(',')
1655 else:
1656 self.out.append('}')
1657 self.inside_citation_reference_label = False
1658 else:
1659 self.out.append('}]')
1661 def visit_classifier(self, node):
1662 self.out.append( '(\\textbf{' )
1664 def depart_classifier(self, node):
1665 self.out.append( '})\n' )
1667 def visit_colspec(self, node):
1668 self.active_table.visit_colspec(node)
1670 def depart_colspec(self, node):
1671 pass
1673 def visit_comment(self, node):
1674 # Precede every line with a comment sign, wrap in newlines
1675 self.out.append('\n%% %s\n' % node.astext().replace('\n', '\n% '))
1676 raise nodes.SkipNode
1678 def depart_comment(self, node):
1679 pass
1681 def visit_compound(self, node):
1682 pass
1684 def depart_compound(self, node):
1685 pass
1687 def visit_contact(self, node):
1688 self.visit_docinfo_item(node, 'contact')
1690 def depart_contact(self, node):
1691 self.depart_docinfo_item(node)
1693 def visit_container(self, node):
1694 pass
1696 def depart_container(self, node):
1697 pass
1699 def visit_copyright(self, node):
1700 self.visit_docinfo_item(node, 'copyright')
1702 def depart_copyright(self, node):
1703 self.depart_docinfo_item(node)
1705 def visit_date(self, node):
1706 self.visit_docinfo_item(node, 'date')
1708 def depart_date(self, node):
1709 self.depart_docinfo_item(node)
1711 def visit_decoration(self, node):
1712 # header and footer
1713 pass
1715 def depart_decoration(self, node):
1716 pass
1718 def visit_definition(self, node):
1719 pass
1721 def depart_definition(self, node):
1722 self.out.append('\n')
1724 def visit_definition_list(self, node):
1725 self.out.append( '%\n\\begin{description}\n' )
1727 def depart_definition_list(self, node):
1728 self.out.append( '\\end{description}\n' )
1730 def visit_definition_list_item(self, node):
1731 pass
1733 def depart_definition_list_item(self, node):
1734 pass
1736 def visit_description(self, node):
1737 self.out.append(' ')
1739 def depart_description(self, node):
1740 pass
1742 def visit_docinfo(self, node):
1743 self.push_output_collector(self.docinfo)
1745 def depart_docinfo(self, node):
1746 self.pop_output_collector()
1747 # Some itmes (e.g. author) end up at other places
1748 if self.docinfo:
1749 # tabularx: automatic width of columns, no page breaks allowed.
1750 self.requirements['tabularx'] = r'\usepackage{tabularx}'
1751 self.fallbacks['_providelength'] = PreambleCmds.providelength
1752 self.fallbacks['docinfo'] = PreambleCmds.docinfo
1754 self.docinfo.insert(0, '\n% Docinfo\n'
1755 '\\begin{center}\n'
1756 '\\begin{tabularx}{\\DUdocinfowidth}{lX}\n')
1757 self.docinfo.append('\\end{tabularx}\n'
1758 '\\end{center}\n')
1760 def visit_docinfo_item(self, node, name):
1761 if name == 'author':
1762 self.pdfauthor.append(self.attval(node.astext()))
1763 if self.use_latex_docinfo:
1764 if name in ('author', 'organization', 'contact', 'address'):
1765 # We attach these to the last author. If any of them precedes
1766 # the first author, put them in a separate "author" group
1767 # (in lack of better semantics).
1768 if name == 'author' or not self.author_stack:
1769 self.author_stack.append([])
1770 if name == 'address': # newlines are meaningful
1771 self.insert_newline = True
1772 text = self.encode(node.astext())
1773 self.insert_newline = False
1774 else:
1775 text = self.attval(node.astext())
1776 self.author_stack[-1].append(text)
1777 raise nodes.SkipNode
1778 elif name == 'date':
1779 self.date.append(self.attval(node.astext()))
1780 raise nodes.SkipNode
1781 self.out.append('\\textbf{%s}: &\n\t' % self.language_label(name))
1782 if name == 'address':
1783 self.insert_newline = 1
1784 self.out.append('{\\raggedright\n')
1785 self.context.append(' } \\\\\n')
1786 else:
1787 self.context.append(' \\\\\n')
1789 def depart_docinfo_item(self, node):
1790 self.out.append(self.context.pop())
1791 # for address we did set insert_newline
1792 self.insert_newline = False
1794 def visit_doctest_block(self, node):
1795 self.visit_literal_block(node)
1797 def depart_doctest_block(self, node):
1798 self.depart_literal_block(node)
1800 def visit_document(self, node):
1801 # titled document?
1802 if (self.use_latex_docinfo or len(node) and
1803 isinstance(node[0], nodes.title)):
1804 self.title_labels += self.ids_to_labels(node, set_anchor=False)
1806 def depart_document(self, node):
1807 # Complete header with information gained from walkabout
1808 # * language setup
1809 if (self.babel.otherlanguages or
1810 self.babel.language not in ('', 'english')):
1811 self.requirements['babel'] = self.babel()
1812 # * conditional requirements (before style sheet)
1813 self.requirements = self.requirements.sortedvalues()
1814 # * coditional fallback definitions (after style sheet)
1815 self.fallbacks = self.fallbacks.sortedvalues()
1816 # * PDF properties
1817 self.pdfsetup.append(PreambleCmds.linking % self.hyperref_options)
1818 if self.pdfauthor:
1819 authors = self.author_separator.join(self.pdfauthor)
1820 self.pdfinfo.append(' pdfauthor={%s}' % authors)
1821 if self.pdfinfo:
1822 self.pdfsetup += [r'\hypersetup{'] + self.pdfinfo + ['}']
1823 # Complete body
1824 # * document title (with "use_latex_docinfo" also
1825 # 'author', 'organization', 'contact', 'address' and 'date')
1826 if self.title or (
1827 self.use_latex_docinfo and (self.author_stack or self.date)):
1828 # with the default template, titledata is written to the preamble
1829 self.titledata.append('%%% Title Data')
1830 # \title (empty \title prevents error with \maketitle)
1831 if self.title:
1832 self.title.insert(0, '\phantomsection%\n ')
1833 title = [''.join(self.title)] + self.title_labels
1834 if self.subtitle:
1835 title += [r'\\ % subtitle',
1836 r'\large{%s}' % ''.join(self.subtitle)
1837 ] + self.subtitle_labels
1838 self.titledata.append(r'\title{%s}' % '%\n '.join(title))
1839 # \author (empty \author prevents warning with \maketitle)
1840 authors = ['\\\\\n'.join(author_entry)
1841 for author_entry in self.author_stack]
1842 self.titledata.append(r'\author{%s}' %
1843 ' \\and\n'.join(authors))
1844 # \date (empty \date prevents defaulting to \today)
1845 self.titledata.append(r'\date{%s}' % ', '.join(self.date))
1846 # \maketitle in the body formats title with LaTeX
1847 self.body_pre_docinfo.append('\\maketitle\n')
1849 # * bibliography
1850 # TODO insertion point of bibliography should be configurable.
1851 if self._use_latex_citations and len(self._bibitems)>0:
1852 if not self.bibtex:
1853 widest_label = ''
1854 for bi in self._bibitems:
1855 if len(widest_label)<len(bi[0]):
1856 widest_label = bi[0]
1857 self.out.append('\n\\begin{thebibliography}{%s}\n' %
1858 widest_label)
1859 for bi in self._bibitems:
1860 # cite_key: underscores must not be escaped
1861 cite_key = bi[0].replace(r'\_','_')
1862 self.out.append('\\bibitem[%s]{%s}{%s}\n' %
1863 (bi[0], cite_key, bi[1]))
1864 self.out.append('\\end{thebibliography}\n')
1865 else:
1866 self.out.append('\n\\bibliographystyle{%s}\n' %
1867 self.bibtex[0])
1868 self.out.append('\\bibliography{%s}\n' % self.bibtex[1])
1869 # * make sure to generate a toc file if needed for local contents:
1870 if 'minitoc' in self.requirements and not self.has_latex_toc:
1871 self.out.append('\n\\faketableofcontents % for local ToCs\n')
1873 def visit_emphasis(self, node):
1874 self.out.append('\\emph{')
1875 if node['classes']:
1876 self.visit_inline(node)
1878 def depart_emphasis(self, node):
1879 if node['classes']:
1880 self.depart_inline(node)
1881 self.out.append('}')
1883 def visit_entry(self, node):
1884 self.active_table.visit_entry()
1885 # cell separation
1886 # BUG: the following fails, with more than one multirow
1887 # starting in the second column (or later) see
1888 # ../../../test/functional/input/data/latex.txt
1889 if self.active_table.get_entry_number() == 1:
1890 # if the first row is a multirow, this actually is the second row.
1891 # this gets hairy if rowspans follow each other.
1892 if self.active_table.get_rowspan(0):
1893 count = 0
1894 while self.active_table.get_rowspan(count):
1895 count += 1
1896 self.out.append(' & ')
1897 self.active_table.visit_entry() # increment cell count
1898 else:
1899 self.out.append(' & ')
1900 # multirow, multicolumn
1901 # IN WORK BUG TODO HACK continues here
1902 # multirow in LaTeX simply will enlarge the cell over several rows
1903 # (the following n if n is positive, the former if negative).
1904 if 'morerows' in node and 'morecols' in node:
1905 raise NotImplementedError('Cells that '
1906 'span multiple rows *and* columns are not supported, sorry.')
1907 if 'morerows' in node:
1908 self.requirements['multirow'] = r'\usepackage{multirow}'
1909 count = node['morerows'] + 1
1910 self.active_table.set_rowspan(
1911 self.active_table.get_entry_number()-1,count)
1912 self.out.append('\\multirow{%d}{%s}{%%' %
1913 (count,self.active_table.get_column_width()))
1914 self.context.append('}')
1915 elif 'morecols' in node:
1916 # the vertical bar before column is missing if it is the first
1917 # column. the one after always.
1918 if self.active_table.get_entry_number() == 1:
1919 bar1 = self.active_table.get_vertical_bar()
1920 else:
1921 bar1 = ''
1922 count = node['morecols'] + 1
1923 self.out.append('\\multicolumn{%d}{%sp{%s}%s}{' %
1924 (count, bar1,
1925 self.active_table.get_multicolumn_width(
1926 self.active_table.get_entry_number(),
1927 count),
1928 self.active_table.get_vertical_bar()))
1929 self.context.append('}')
1930 else:
1931 self.context.append('')
1933 # header / not header
1934 if isinstance(node.parent.parent, nodes.thead):
1935 self.out.append('\\textbf{%')
1936 self.context.append('}')
1937 elif self.active_table.is_stub_column():
1938 self.out.append('\\textbf{')
1939 self.context.append('}')
1940 else:
1941 self.context.append('')
1943 def depart_entry(self, node):
1944 self.out.append(self.context.pop()) # header / not header
1945 self.out.append(self.context.pop()) # multirow/column
1946 # if following row is spanned from above.
1947 if self.active_table.get_rowspan(self.active_table.get_entry_number()):
1948 self.out.append(' & ')
1949 self.active_table.visit_entry() # increment cell count
1951 def visit_row(self, node):
1952 self.active_table.visit_row()
1954 def depart_row(self, node):
1955 self.out.extend(self.active_table.depart_row())
1957 def visit_enumerated_list(self, node):
1958 # We create our own enumeration list environment.
1959 # This allows to set the style and starting value
1960 # and unlimited nesting.
1961 enum_style = {'arabic':'arabic',
1962 'loweralpha':'alph',
1963 'upperalpha':'Alph',
1964 'lowerroman':'roman',
1965 'upperroman':'Roman' }
1966 enum_suffix = ''
1967 if 'suffix' in node:
1968 enum_suffix = node['suffix']
1969 enum_prefix = ''
1970 if 'prefix' in node:
1971 enum_prefix = node['prefix']
1972 if self.compound_enumerators:
1973 pref = ''
1974 if self.section_prefix_for_enumerators and self.section_level:
1975 for i in range(self.section_level):
1976 pref += '%d.' % self._section_number[i]
1977 pref = pref[:-1] + self.section_enumerator_separator
1978 enum_prefix += pref
1979 for ctype, cname in self._enumeration_counters:
1980 enum_prefix += '\\%s{%s}.' % (ctype, cname)
1981 enum_type = 'arabic'
1982 if 'enumtype' in node:
1983 enum_type = node['enumtype']
1984 if enum_type in enum_style:
1985 enum_type = enum_style[enum_type]
1987 counter_name = 'listcnt%d' % len(self._enumeration_counters)
1988 self._enumeration_counters.append((enum_type, counter_name))
1989 # If we haven't used this counter name before, then create a
1990 # new counter; otherwise, reset & reuse the old counter.
1991 if len(self._enumeration_counters) > self._max_enumeration_counters:
1992 self._max_enumeration_counters = len(self._enumeration_counters)
1993 self.out.append('\\newcounter{%s}\n' % counter_name)
1994 else:
1995 self.out.append('\\setcounter{%s}{0}\n' % counter_name)
1997 self.out.append('\\begin{list}{%s\\%s{%s}%s}\n' %
1998 (enum_prefix,enum_type,counter_name,enum_suffix))
1999 self.out.append('{\n')
2000 self.out.append('\\usecounter{%s}\n' % counter_name)
2001 # set start after usecounter, because it initializes to zero.
2002 if 'start' in node:
2003 self.out.append('\\addtocounter{%s}{%d}\n' %
2004 (counter_name,node['start']-1))
2005 ## set rightmargin equal to leftmargin
2006 self.out.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
2007 self.out.append('}\n')
2009 def depart_enumerated_list(self, node):
2010 self.out.append('\\end{list}\n')
2011 self._enumeration_counters.pop()
2013 def visit_field(self, node):
2014 # real output is done in siblings: _argument, _body, _name
2015 pass
2017 def depart_field(self, node):
2018 self.out.append('\n')
2019 ##self.out.append('%[depart_field]\n')
2021 def visit_field_argument(self, node):
2022 self.out.append('%[visit_field_argument]\n')
2024 def depart_field_argument(self, node):
2025 self.out.append('%[depart_field_argument]\n')
2027 def visit_field_body(self, node):
2028 pass
2030 def depart_field_body(self, node):
2031 if self.out is self.docinfo:
2032 self.out.append(r'\\')
2034 def visit_field_list(self, node):
2035 if self.out is not self.docinfo:
2036 self.fallbacks['fieldlist'] = PreambleCmds.fieldlist
2037 self.out.append('%\n\\begin{DUfieldlist}\n')
2039 def depart_field_list(self, node):
2040 if self.out is not self.docinfo:
2041 self.out.append('\\end{DUfieldlist}\n')
2043 def visit_field_name(self, node):
2044 if self.out is self.docinfo:
2045 self.out.append('\\textbf{')
2046 else:
2047 # Commands with optional args inside an optional arg must be put
2048 # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
2049 self.out.append('\\item[{')
2051 def depart_field_name(self, node):
2052 if self.out is self.docinfo:
2053 self.out.append('}: &')
2054 else:
2055 self.out.append(':}]')
2057 def visit_figure(self, node):
2058 self.requirements['float_settings'] = PreambleCmds.float_settings
2059 # ! the 'align' attribute should set "outer alignment" !
2060 # For "inner alignment" use LaTeX default alignment (similar to HTML)
2061 ## if ('align' not in node.attributes or
2062 ## node.attributes['align'] == 'center'):
2063 ## align = '\n\\centering'
2064 ## align_end = ''
2065 ## else:
2066 ## # TODO non vertical space for other alignments.
2067 ## align = '\\begin{flush%s}' % node.attributes['align']
2068 ## align_end = '\\end{flush%s}' % node.attributes['align']
2069 ## self.out.append( '\\begin{figure}%s\n' % align )
2070 ## self.context.append( '%s\\end{figure}\n' % align_end )
2071 self.out.append('\\begin{figure}')
2072 if node.get('ids'):
2073 self.out += ['\n'] + self.ids_to_labels(node)
2075 def depart_figure(self, node):
2076 self.out.append('\\end{figure}\n')
2078 def visit_footer(self, node):
2079 self.push_output_collector([])
2080 self.out.append(r'\newcommand{\DUfooter}{')
2082 def depart_footer(self, node):
2083 self.out.append('}')
2084 self.requirements['~footer'] = ''.join(self.out)
2085 self.pop_output_collector()
2087 def visit_footnote(self, node):
2088 try:
2089 backref = node['backrefs'][0]
2090 except IndexError:
2091 backref = node['ids'][0] # no backref, use self-ref instead
2092 if self.settings.figure_footnotes:
2093 self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats
2094 self.out.append('\\begin{figure}[b]')
2095 self.append_hypertargets(node)
2096 if node.get('id') == node.get('name'): # explicite label
2097 self.out += self.ids_to_labels(node)
2098 elif self.docutils_footnotes:
2099 self.fallbacks['footnotes'] = PreambleCmds.footnotes
2100 num,text = node.astext().split(None,1)
2101 if self.settings.footnote_references == 'brackets':
2102 num = '[%s]' % num
2103 self.out.append('%%\n\\DUfootnotetext{%s}{%s}{%s}{' %
2104 (node['ids'][0], backref, self.encode(num)))
2105 if node['ids'] == node['names']:
2106 self.out += self.ids_to_labels(node)
2107 # mask newline to prevent spurious whitespace:
2108 self.out.append('%')
2109 ## else: # TODO: "real" LaTeX \footnote{}s
2111 def depart_footnote(self, node):
2112 if self.figure_footnotes:
2113 self.out.append('\\end{figure}\n')
2114 else:
2115 self.out.append('}\n')
2117 def visit_footnote_reference(self, node):
2118 href = ''
2119 if 'refid' in node:
2120 href = node['refid']
2121 elif 'refname' in node:
2122 href = self.document.nameids[node['refname']]
2123 # if not self.docutils_footnotes:
2124 # TODO: insert footnote content at (or near) this place
2125 # print "footnote-ref to", node['refid']
2126 # footnotes = (self.document.footnotes +
2127 # self.document.autofootnotes +
2128 # self.document.symbol_footnotes)
2129 # for footnote in footnotes:
2130 # # print footnote['ids']
2131 # if node.get('refid', '') in footnote['ids']:
2132 # print 'matches', footnote['ids']
2133 format = self.settings.footnote_references
2134 if format == 'brackets':
2135 self.append_hypertargets(node)
2136 self.out.append('\\hyperlink{%s}{[' % href)
2137 self.context.append(']}')
2138 else:
2139 self.fallbacks['footnotes'] = PreambleCmds.footnotes
2140 self.out.append(r'\DUfootnotemark{%s}{%s}{' %
2141 (node['ids'][0], href))
2142 self.context.append('}')
2144 def depart_footnote_reference(self, node):
2145 self.out.append(self.context.pop())
2147 # footnote/citation label
2148 def label_delim(self, node, bracket, superscript):
2149 if isinstance(node.parent, nodes.footnote):
2150 if not self.figure_footnotes:
2151 raise nodes.SkipNode
2152 if self.settings.footnote_references == 'brackets':
2153 self.out.append(bracket)
2154 else:
2155 self.out.append(superscript)
2156 else:
2157 assert isinstance(node.parent, nodes.citation)
2158 if not self._use_latex_citations:
2159 self.out.append(bracket)
2161 def visit_label(self, node):
2162 """footnote or citation label: in brackets or as superscript"""
2163 self.label_delim(node, '[', '\\textsuperscript{')
2165 def depart_label(self, node):
2166 self.label_delim(node, ']', '}')
2168 # elements generated by the framework e.g. section numbers.
2169 def visit_generated(self, node):
2170 pass
2172 def depart_generated(self, node):
2173 pass
2175 def visit_header(self, node):
2176 self.push_output_collector([])
2177 self.out.append(r'\newcommand{\DUheader}{')
2179 def depart_header(self, node):
2180 self.out.append('}')
2181 self.requirements['~header'] = ''.join(self.out)
2182 self.pop_output_collector()
2184 def to_latex_length(self, length_str, pxunit='px'):
2185 """Convert `length_str` with rst lenght to LaTeX length
2187 match = re.match('(\d*\.?\d*)\s*(\S*)', length_str)
2188 if not match:
2189 return length_str
2190 value, unit = match.groups()[:2]
2191 # no unit or "DTP" points (called 'bp' in TeX):
2192 if unit in ('', 'pt'):
2193 length_str = '%sbp' % value
2194 # percentage: relate to current line width
2195 elif unit == '%':
2196 length_str = '%.3f\\linewidth' % (float(value)/100.0)
2197 elif (unit == 'px') and (pxunit != 'px'):
2198 # length unit px not defined in some tex variants (e.g. XeTeX)
2199 self.fallbacks['_providelength'] = PreambleCmds.providelength
2200 self.fallbacks['px'] = '\n\\DUprovidelength{%s}{1bp}\n' % pxunit
2201 length_str = '%s%s' % (value, pxunit)
2203 return length_str
2205 def visit_image(self, node):
2206 self.requirements['graphicx'] = self.graphicx_package
2207 attrs = node.attributes
2208 # Convert image URI to a local file path and add to dependency list
2209 imagepath = urllib.url2pathname(attrs['uri'])
2210 self.settings.record_dependencies.add(imagepath)
2211 # alignment defaults:
2212 if not 'align' in attrs:
2213 # Set default align of image in a figure to 'center'
2214 if isinstance(node.parent, nodes.figure):
2215 attrs['align'] = 'center'
2216 # query 'align-*' class argument
2217 for cls in node['classes']:
2218 if cls.startswith('align-'):
2219 attrs['align'] = cls.split('-')[1]
2220 # pre- and postfix (prefix inserted in reverse order)
2221 pre = []
2222 post = []
2223 include_graphics_options = []
2224 display_style = ('block-', 'inline-')[self.is_inline(node)]
2225 align_codes = {
2226 # inline images: by default latex aligns the bottom.
2227 'bottom': ('', ''),
2228 'middle': (r'\raisebox{-0.5\height}{', '}'),
2229 'top': (r'\raisebox{-\height}{', '}'),
2230 # block level images:
2231 'center': (r'\noindent\makebox[\textwidth][c]{', '}'),
2232 'left': (r'\noindent{', r'\hfill}'),
2233 'right': (r'\noindent{\hfill', '}'),}
2234 if 'align' in attrs:
2235 try:
2236 align_code = align_codes[attrs['align']]
2237 pre.append(align_code[0])
2238 post.append(align_code[1])
2239 except KeyError:
2240 pass # TODO: warn?
2241 if 'height' in attrs:
2242 include_graphics_options.append('height=%s' %
2243 self.to_latex_length(attrs['height']))
2244 if 'scale' in attrs:
2245 include_graphics_options.append('scale=%f' %
2246 (attrs['scale'] / 100.0))
2247 if 'width' in attrs:
2248 include_graphics_options.append('width=%s' %
2249 self.to_latex_length(attrs['width']))
2250 if not self.is_inline(node):
2251 pre.append('\n')
2252 post.append('\n')
2253 pre.reverse()
2254 self.out.extend(pre)
2255 options = ''
2256 if include_graphics_options:
2257 options = '[%s]' % (','.join(include_graphics_options))
2258 self.out.append('\\includegraphics%s{%s}' % (options, imagepath))
2259 self.out.extend(post)
2261 def depart_image(self, node):
2262 if node.get('ids'):
2263 self.out += self.ids_to_labels(node) + ['\n']
2265 def visit_inline(self, node): # <span>, i.e. custom roles
2266 # Make a copy to keep ``node['classes']`` True if a
2267 # language argument is popped (used in conditional calls of
2268 # depart_inline()):
2269 classes = node['classes'][:]
2270 self.context.append('}' * len(classes))
2271 # handle language specification:
2272 language_tags = [cls for cls in classes
2273 if cls.startswith('language-')]
2274 if language_tags:
2275 language = self.babel.get_language(language_tags[0][9:])
2276 if language:
2277 self.babel.otherlanguages[language] = True
2278 self.out.append(r'\otherlanguage{%s}{' % language)
2279 classes.pop(classes.index(language_tags[0]))
2280 if not classes:
2281 return
2282 # mark up for styling with custom macros
2283 if 'align-center' in classes:
2284 self.fallbacks['align-center'] = PreambleCmds.align_center
2285 self.fallbacks['inline'] = PreambleCmds.inline
2286 self.out += [r'\DUrole{%s}{' % cls for cls in classes]
2288 def depart_inline(self, node):
2289 self.out.append(self.context.pop())
2291 def visit_interpreted(self, node):
2292 # @@@ Incomplete, pending a proper implementation on the
2293 # Parser/Reader end.
2294 self.visit_literal(node)
2296 def depart_interpreted(self, node):
2297 self.depart_literal(node)
2299 def visit_legend(self, node):
2300 self.fallbacks['legend'] = PreambleCmds.legend
2301 self.out.append('\\begin{DUlegend}')
2303 def depart_legend(self, node):
2304 self.out.append('\\end{DUlegend}\n')
2306 def visit_line(self, node):
2307 self.out.append('\item[] ')
2309 def depart_line(self, node):
2310 self.out.append('\n')
2312 def visit_line_block(self, node):
2313 self.fallbacks['_providelength'] = PreambleCmds.providelength
2314 self.fallbacks['lineblock'] = PreambleCmds.lineblock
2315 if isinstance(node.parent, nodes.line_block):
2316 self.out.append('\\item[]\n'
2317 '\\begin{DUlineblock}{\\DUlineblockindent}\n')
2318 else:
2319 self.out.append('\n\\begin{DUlineblock}{0em}\n')
2320 if node['classes']:
2321 self.visit_inline(node)
2322 self.out.append('\n')
2324 def depart_line_block(self, node):
2325 if node['classes']:
2326 self.depart_inline(node)
2327 self.out.append('\n')
2328 self.out.append('\\end{DUlineblock}\n')
2330 def visit_list_item(self, node):
2331 self.out.append('\n\\item ')
2333 def depart_list_item(self, node):
2334 pass
2336 def visit_literal(self, node):
2337 self.literal = True
2338 self.out.append('\\texttt{')
2339 if node['classes']:
2340 self.visit_inline(node)
2342 def depart_literal(self, node):
2343 self.literal = False
2344 if node['classes']:
2345 self.depart_inline(node)
2346 self.out.append('}')
2348 # Literal blocks are used for '::'-prefixed literal-indented
2349 # blocks of text, where the inline markup is not recognized,
2350 # but are also the product of the "parsed-literal" directive,
2351 # where the markup is respected.
2353 # In both cases, we want to use a typewriter/monospaced typeface.
2354 # For "real" literal-blocks, we can use \verbatim, while for all
2355 # the others we must use \mbox or \alltt.
2357 # We can distinguish between the two kinds by the number of
2358 # siblings that compose this node: if it is composed by a
2359 # single element, it's either
2360 # * a real one,
2361 # * a parsed-literal that does not contain any markup, or
2362 # * a parsed-literal containing just one markup construct.
2363 def is_plaintext(self, node):
2364 """Check whether a node can be typeset verbatim"""
2365 return (len(node) == 1) and isinstance(node[0], nodes.Text)
2367 def visit_literal_block(self, node):
2368 """Render a literal block."""
2369 # environments and packages to typeset literal blocks
2370 packages = {'listing': r'\usepackage{moreverb}',
2371 'lstlisting': r'\usepackage{listings}',
2372 'Verbatim': r'\usepackage{fancyvrb}',
2373 # 'verbatim': '',
2374 'verbatimtab': r'\usepackage{moreverb}'}
2376 if not self.active_table.is_open():
2377 # no quote inside tables, to avoid vertical space between
2378 # table border and literal block.
2379 # BUG: fails if normal text preceeds the literal block.
2380 self.out.append('%\n\\begin{quote}')
2381 self.context.append('\n\\end{quote}\n')
2382 else:
2383 self.out.append('\n')
2384 self.context.append('\n')
2385 if self.literal_block_env != '' and self.is_plaintext(node):
2386 self.requirements['literal_block'] = packages.get(
2387 self.literal_block_env, '')
2388 self.verbatim = True
2389 self.out.append('\\begin{%s}%s\n' % (self.literal_block_env,
2390 self.literal_block_options))
2391 else:
2392 self.literal = True
2393 self.insert_newline = True
2394 self.insert_non_breaking_blanks = True
2395 self.out.append('{\\ttfamily \\raggedright \\noindent\n')
2397 def depart_literal_block(self, node):
2398 if self.verbatim:
2399 self.out.append('\n\\end{%s}\n' % self.literal_block_env)
2400 self.verbatim = False
2401 else:
2402 self.out.append('\n}')
2403 self.insert_non_breaking_blanks = False
2404 self.insert_newline = False
2405 self.literal = False
2406 self.out.append(self.context.pop())
2408 ## def visit_meta(self, node):
2409 ## self.out.append('[visit_meta]\n')
2410 # TODO: set keywords for pdf?
2411 # But:
2412 # The reStructuredText "meta" directive creates a "pending" node,
2413 # which contains knowledge that the embedded "meta" node can only
2414 # be handled by HTML-compatible writers. The "pending" node is
2415 # resolved by the docutils.transforms.components.Filter transform,
2416 # which checks that the calling writer supports HTML; if it doesn't,
2417 # the "pending" node (and enclosed "meta" node) is removed from the
2418 # document.
2419 # --- docutils/docs/peps/pep-0258.html#transformer
2421 ## def depart_meta(self, node):
2422 ## self.out.append('[depart_meta]\n')
2424 def visit_math(self, node):
2425 """math role"""
2426 self.requirements['amsmath'] = r'\usepackage{amsmath}'
2427 if node['classes']:
2428 self.visit_inline(node)
2429 self.verbatim = True
2430 self.out.append('$')
2432 def depart_math(self, node):
2433 self.verbatim = False
2434 self.out.append('$')
2435 if node['classes']:
2436 self.depart_inline(node)
2438 def multiline_math(self, code):
2439 """find out whether `code` is a multi-line equation
2441 This is a very simplified test, looking
2442 for line-breaks (``\\``) outside environments.
2444 # cut out environment content:
2445 chunks = code.split(r'\begin{')
2446 toplevel_code = ''.join([chunk.split(r'\end{')[-1]
2447 for chunk in chunks])
2448 return toplevel_code.find(r'\\') >= 0
2450 def visit_math_block(self, node):
2451 self.requirements['amsmath'] = r'\usepackage{amsmath}'
2452 if node['classes']:
2453 self.visit_inline(node)
2454 math_code = node.astext()
2455 if self.multiline_math(math_code):
2456 environment = 'align*'
2457 else:
2458 environment = 'equation*'
2459 self.out.append ('%%\n\\begin{%s}\n' % environment)
2460 self.out.append(math_code)
2461 self.out.append('\n\\end{%s}' % environment)
2462 # Content already processed:
2463 raise nodes.SkipNode
2465 def depart_math_block(self, node):
2466 if node['classes']:
2467 self.depart_inline(node)
2469 def visit_option(self, node):
2470 if self.context[-1]:
2471 # this is not the first option
2472 self.out.append(', ')
2474 def depart_option(self, node):
2475 # flag that the first option is done.
2476 self.context[-1] += 1
2478 def visit_option_argument(self, node):
2479 """Append the delimiter betweeen an option and its argument to body."""
2480 self.out.append(node.get('delimiter', ' '))
2482 def depart_option_argument(self, node):
2483 pass
2485 def visit_option_group(self, node):
2486 self.out.append('\n\\item[')
2487 # flag for first option
2488 self.context.append(0)
2490 def depart_option_group(self, node):
2491 self.context.pop() # the flag
2492 self.out.append('] ')
2494 def visit_option_list(self, node):
2495 self.fallbacks['_providelength'] = PreambleCmds.providelength
2496 self.fallbacks['optionlist'] = PreambleCmds.optionlist
2497 self.out.append('%\n\\begin{DUoptionlist}\n')
2499 def depart_option_list(self, node):
2500 self.out.append('\n\\end{DUoptionlist}\n')
2502 def visit_option_list_item(self, node):
2503 pass
2505 def depart_option_list_item(self, node):
2506 pass
2508 def visit_option_string(self, node):
2509 ##self.out.append(self.starttag(node, 'span', '', CLASS='option'))
2510 pass
2512 def depart_option_string(self, node):
2513 ##self.out.append('</span>')
2514 pass
2516 def visit_organization(self, node):
2517 self.visit_docinfo_item(node, 'organization')
2519 def depart_organization(self, node):
2520 self.depart_docinfo_item(node)
2522 def visit_paragraph(self, node):
2523 # insert blank line, if the paragraph is not first in a list item
2524 # nor follows a non-paragraph node in a compound
2525 index = node.parent.index(node)
2526 if (index == 0 and (isinstance(node.parent, nodes.list_item) or
2527 isinstance(node.parent, nodes.description))):
2528 pass
2529 elif (index > 0 and isinstance(node.parent, nodes.compound) and
2530 not isinstance(node.parent[index - 1], nodes.paragraph) and
2531 not isinstance(node.parent[index - 1], nodes.compound)):
2532 pass
2533 else:
2534 self.out.append('\n')
2535 if node.get('ids'):
2536 self.out += self.ids_to_labels(node) + ['\n']
2537 if node['classes']:
2538 self.visit_inline(node)
2540 def depart_paragraph(self, node):
2541 if node['classes']:
2542 self.depart_inline(node)
2543 self.out.append('\n')
2545 def visit_problematic(self, node):
2546 self.requirements['color'] = PreambleCmds.color
2547 self.out.append('%\n')
2548 self.append_hypertargets(node)
2549 self.out.append(r'\hyperlink{%s}{\textbf{\color{red}' % node['refid'])
2551 def depart_problematic(self, node):
2552 self.out.append('}}')
2554 def visit_raw(self, node):
2555 if not 'latex' in node.get('format', '').split():
2556 raise nodes.SkipNode
2557 if not self.is_inline(node):
2558 self.out.append('\n')
2559 if node['classes']:
2560 self.visit_inline(node)
2561 # append "as-is" skipping any LaTeX-encoding
2562 self.verbatim = True
2564 def depart_raw(self, node):
2565 self.verbatim = False
2566 if node['classes']:
2567 self.depart_inline(node)
2568 if not self.is_inline(node):
2569 self.out.append('\n')
2571 def has_unbalanced_braces(self, string):
2572 """Test whether there are unmatched '{' or '}' characters."""
2573 level = 0
2574 for ch in string:
2575 if ch == '{':
2576 level += 1
2577 if ch == '}':
2578 level -= 1
2579 if level < 0:
2580 return True
2581 return level != 0
2583 def visit_reference(self, node):
2584 # We need to escape #, \, and % if we use the URL in a command.
2585 special_chars = {ord('#'): ur'\#',
2586 ord('%'): ur'\%',
2587 ord('\\'): ur'\\',
2589 # external reference (URL)
2590 if 'refuri' in node:
2591 href = unicode(node['refuri']).translate(special_chars)
2592 # problematic chars double caret and unbalanced braces:
2593 if href.find('^^') != -1 or self.has_unbalanced_braces(href):
2594 self.error(
2595 'External link "%s" not supported by LaTeX.\n'
2596 ' (Must not contain "^^" or unbalanced braces.)' % href)
2597 if node['refuri'] == node.astext():
2598 self.out.append(r'\url{%s}' % href)
2599 raise nodes.SkipNode
2600 self.out.append(r'\href{%s}{' % href)
2601 return
2602 # internal reference
2603 if 'refid' in node:
2604 href = node['refid']
2605 elif 'refname' in node:
2606 href = self.document.nameids[node['refname']]
2607 else:
2608 raise AssertionError('Unknown reference.')
2609 if not self.is_inline(node):
2610 self.out.append('\n')
2611 self.out.append('\\hyperref[%s]{' % href)
2612 if self._reference_label:
2613 self.out.append('\\%s{%s}}' %
2614 (self._reference_label, href.replace('#', '')))
2615 raise nodes.SkipNode
2617 def depart_reference(self, node):
2618 self.out.append('}')
2619 if not self.is_inline(node):
2620 self.out.append('\n')
2622 def visit_revision(self, node):
2623 self.visit_docinfo_item(node, 'revision')
2625 def depart_revision(self, node):
2626 self.depart_docinfo_item(node)
2628 def visit_section(self, node):
2629 self.section_level += 1
2630 # Initialize counter for potential subsections:
2631 self._section_number.append(0)
2632 # Counter for this section's level (initialized by parent section):
2633 self._section_number[self.section_level - 1] += 1
2635 def depart_section(self, node):
2636 # Remove counter for potential subsections:
2637 self._section_number.pop()
2638 self.section_level -= 1
2640 def visit_sidebar(self, node):
2641 self.requirements['color'] = PreambleCmds.color
2642 self.fallbacks['sidebar'] = PreambleCmds.sidebar
2643 self.out.append('\n\\DUsidebar{\n')
2645 def depart_sidebar(self, node):
2646 self.out.append('}\n')
2648 attribution_formats = {'dash': (u'—', ''), # EM DASH
2649 'parentheses': ('(', ')'),
2650 'parens': ('(', ')'),
2651 'none': ('', '')}
2653 def visit_attribution(self, node):
2654 prefix, suffix = self.attribution_formats[self.settings.attribution]
2655 self.out.append('\\nopagebreak\n\n\\raggedleft ')
2656 self.out.append(prefix)
2657 self.context.append(suffix)
2659 def depart_attribution(self, node):
2660 self.out.append(self.context.pop() + '\n')
2662 def visit_status(self, node):
2663 self.visit_docinfo_item(node, 'status')
2665 def depart_status(self, node):
2666 self.depart_docinfo_item(node)
2668 def visit_strong(self, node):
2669 self.out.append('\\textbf{')
2670 if node['classes']:
2671 self.visit_inline(node)
2673 def depart_strong(self, node):
2674 if node['classes']:
2675 self.depart_inline(node)
2676 self.out.append('}')
2678 def visit_substitution_definition(self, node):
2679 raise nodes.SkipNode
2681 def visit_substitution_reference(self, node):
2682 self.unimplemented_visit(node)
2684 def visit_subtitle(self, node):
2685 if isinstance(node.parent, nodes.document):
2686 self.push_output_collector(self.subtitle)
2687 self.subtitle_labels += self.ids_to_labels(node, set_anchor=False)
2688 # section subtitle: "starred" (no number, not in ToC)
2689 elif isinstance(node.parent, nodes.section):
2690 self.out.append(r'\%s*{' %
2691 self.d_class.section(self.section_level + 1))
2692 else:
2693 self.fallbacks['subtitle'] = PreambleCmds.subtitle
2694 self.out.append('\n\\DUsubtitle[%s]{' % node.parent.tagname)
2696 def depart_subtitle(self, node):
2697 if isinstance(node.parent, nodes.document):
2698 self.pop_output_collector()
2699 else:
2700 self.out.append('}\n')
2702 def visit_system_message(self, node):
2703 self.requirements['color'] = PreambleCmds.color
2704 self.fallbacks['title'] = PreambleCmds.title
2705 node['classes'] = ['system-message']
2706 self.visit_admonition(node)
2707 self.out.append('\\DUtitle[system-message]{system-message}\n')
2708 self.append_hypertargets(node)
2709 try:
2710 line = ', line~%s' % node['line']
2711 except KeyError:
2712 line = ''
2713 self.out.append('\n\n{\color{red}%s/%s} in \\texttt{%s}%s\n' %
2714 (node['type'], node['level'],
2715 self.encode(node['source']), line))
2716 if len(node['backrefs']) == 1:
2717 self.out.append('\n\\hyperlink{%s}{' % node['backrefs'][0])
2718 self.context.append('}')
2719 else:
2720 backrefs = ['\\hyperlink{%s}{%d}' % (href, i+1)
2721 for (i, href) in enumerate(node['backrefs'])]
2722 self.context.append('backrefs: ' + ' '.join(backrefs))
2724 def depart_system_message(self, node):
2725 self.out.append(self.context.pop())
2726 self.depart_admonition()
2728 def visit_table(self, node):
2729 self.requirements['table'] = PreambleCmds.table
2730 if self.active_table.is_open():
2731 self.table_stack.append(self.active_table)
2732 # nesting longtable does not work (e.g. 2007-04-18)
2733 self.active_table = Table(self,'tabular',self.settings.table_style)
2734 self.active_table.open()
2735 for cls in node['classes']:
2736 self.active_table.set_table_style(cls)
2737 if self.active_table._table_style == 'booktabs':
2738 self.requirements['booktabs'] = r'\usepackage{booktabs}'
2739 self.out.append('\n' + self.active_table.get_opening())
2741 def depart_table(self, node):
2742 self.out.append(self.active_table.get_closing() + '\n')
2743 self.active_table.close()
2744 if len(self.table_stack)>0:
2745 self.active_table = self.table_stack.pop()
2746 else:
2747 self.active_table.set_table_style(self.settings.table_style)
2748 # Insert hyperlabel after (long)table, as
2749 # other places (beginning, caption) result in LaTeX errors.
2750 if node.get('ids'):
2751 self.out += self.ids_to_labels(node, set_anchor=False) + ['\n']
2753 def visit_target(self, node):
2754 # Skip indirect targets:
2755 if ('refuri' in node # external hyperlink
2756 or 'refid' in node # resolved internal link
2757 or 'refname' in node): # unresolved internal link
2758 ## self.out.append('%% %s\n' % node) # for debugging
2759 return
2760 self.out.append('%\n')
2761 # do we need an anchor (\phantomsection)?
2762 set_anchor = not(isinstance(node.parent, nodes.caption) or
2763 isinstance(node.parent, nodes.title))
2764 # TODO: where else can/must we omit the \phantomsection?
2765 self.out += self.ids_to_labels(node, set_anchor)
2767 def depart_target(self, node):
2768 pass
2770 def visit_tbody(self, node):
2771 # BUG write preamble if not yet done (colspecs not [])
2772 # for tables without heads.
2773 if not self.active_table.get('preamble written'):
2774 self.visit_thead(None)
2775 self.depart_thead(None)
2777 def depart_tbody(self, node):
2778 pass
2780 def visit_term(self, node):
2781 """definition list term"""
2782 # Commands with optional args inside an optional arg must be put
2783 # in a group, e.g. ``\item[{\hyperref[label]{text}}]``.
2784 self.out.append('\\item[{')
2786 def depart_term(self, node):
2787 # \leavevmode results in a line break if the
2788 # term is followed by an item list.
2789 self.out.append('}] \leavevmode ')
2791 def visit_tgroup(self, node):
2792 #self.out.append(self.starttag(node, 'colgroup'))
2793 #self.context.append('</colgroup>\n')
2794 pass
2796 def depart_tgroup(self, node):
2797 pass
2799 _thead_depth = 0
2800 def thead_depth (self):
2801 return self._thead_depth
2803 def visit_thead(self, node):
2804 self._thead_depth += 1
2805 if 1 == self.thead_depth():
2806 self.out.append('{%s}\n' % self.active_table.get_colspecs())
2807 self.active_table.set('preamble written',1)
2808 self.out.append(self.active_table.get_caption())
2809 self.out.extend(self.active_table.visit_thead())
2811 def depart_thead(self, node):
2812 if node is not None:
2813 self.out.extend(self.active_table.depart_thead())
2814 if self.active_table.need_recurse():
2815 node.walkabout(self)
2816 self._thead_depth -= 1
2818 def bookmark(self, node):
2819 """Return label and pdfbookmark string for titles."""
2820 result = ['']
2821 if self.settings.sectnum_xform: # "starred" section cmd
2822 # add to the toc and pdfbookmarks
2823 section_name = self.d_class.section(max(self.section_level, 1))
2824 section_title = self.encode(node.astext())
2825 result.append(r'\addcontentsline{toc}{%s}{%s}' %
2826 (section_name, section_title))
2827 result += self.ids_to_labels(node.parent, set_anchor=False)
2828 return '%\n '.join(result) + '%\n'
2830 def visit_title(self, node):
2831 """Append section and other titles."""
2832 # Document title
2833 if node.parent.tagname == 'document':
2834 self.push_output_collector(self.title)
2835 self.context.append('')
2836 self.pdfinfo.append(' pdftitle={%s},' %
2837 self.encode(node.astext()))
2838 # Topic titles (topic, admonition, sidebar)
2839 elif (isinstance(node.parent, nodes.topic) or
2840 isinstance(node.parent, nodes.admonition) or
2841 isinstance(node.parent, nodes.sidebar)):
2842 self.fallbacks['title'] = PreambleCmds.title
2843 classes = ','.join(node.parent['classes'])
2844 if not classes:
2845 classes = node.tagname
2846 self.out.append('\\DUtitle[%s]{' % classes)
2847 self.context.append('}\n')
2848 # Table caption
2849 elif isinstance(node.parent, nodes.table):
2850 self.push_output_collector(self.active_table.caption)
2851 self.context.append('')
2852 # Section title
2853 else:
2854 self.out.append('\n\n')
2855 self.out.append('%' + '_' * 75)
2856 self.out.append('\n\n')
2858 section_name = self.d_class.section(self.section_level)
2859 section_star = ''
2860 pdfanchor = ''
2861 # number sections?
2862 if (self.settings.sectnum_xform # numbering by Docutils
2863 or (self.section_level > len(self.d_class.sections))):
2864 section_star = '*'
2865 pdfanchor = '\\phantomsection%\n '
2866 self.out.append(r'\%s%s{%s' %
2867 (section_name, section_star, pdfanchor))
2868 # System messages heading in red:
2869 if ('system-messages' in node.parent['classes']):
2870 self.requirements['color'] = PreambleCmds.color
2871 self.out.append('\color{red}')
2872 # label and ToC entry:
2873 self.context.append(self.bookmark(node) + '}\n')
2874 # MAYBE postfix paragraph and subparagraph with \leavemode to
2875 # ensure floats stay in the section and text starts on a new line.
2877 def depart_title(self, node):
2878 self.out.append(self.context.pop())
2879 if (isinstance(node.parent, nodes.table) or
2880 node.parent.tagname == 'document'):
2881 self.pop_output_collector()
2883 def minitoc(self, node, title, depth):
2884 """Generate a local table of contents with LaTeX package minitoc"""
2885 section_name = self.d_class.section(self.section_level)
2886 # name-prefix for current section level
2887 minitoc_names = {'part': 'part', 'chapter': 'mini'}
2888 if 'chapter' not in self.d_class.sections:
2889 minitoc_names['section'] = 'sect'
2890 try:
2891 minitoc_name = minitoc_names[section_name]
2892 except KeyError: # minitoc only supports part- and toplevel
2893 self.warn('Skipping local ToC at %s level.\n' % section_name +
2894 ' Feature not supported with option "use-latex-toc"',
2895 base_node=node)
2896 return
2897 # Requirements/Setup
2898 self.requirements['minitoc'] = PreambleCmds.minitoc
2899 self.requirements['minitoc-'+minitoc_name] = (r'\do%stoc' %
2900 minitoc_name)
2901 # depth: (Docutils defaults to unlimited depth)
2902 maxdepth = len(self.d_class.sections)
2903 self.requirements['minitoc-%s-depth' % minitoc_name] = (
2904 r'\mtcsetdepth{%stoc}{%d}' % (minitoc_name, maxdepth))
2905 # Process 'depth' argument (!Docutils stores a relative depth while
2906 # minitoc expects an absolute depth!):
2907 offset = {'sect': 1, 'mini': 0, 'part': 0}
2908 if 'chapter' in self.d_class.sections:
2909 offset['part'] = -1
2910 if depth:
2911 self.out.append('\\setcounter{%stocdepth}{%d}' %
2912 (minitoc_name, depth + offset[minitoc_name]))
2913 # title:
2914 self.out.append('\\mtcsettitle{%stoc}{%s}\n' % (minitoc_name, title))
2915 # the toc-generating command:
2916 self.out.append('\\%stoc\n' % minitoc_name)
2918 def visit_topic(self, node):
2919 # Topic nodes can be generic topic, abstract, dedication, or ToC.
2920 # table of contents:
2921 if 'contents' in node['classes']:
2922 self.out.append('\n')
2923 self.out += self.ids_to_labels(node)
2924 # add contents to PDF bookmarks sidebar
2925 if isinstance(node.next_node(), nodes.title):
2926 self.out.append('\n\\pdfbookmark[%d]{%s}{%s}\n' %
2927 (self.section_level+1,
2928 node.next_node().astext(),
2929 node.get('ids', ['contents'])[0]
2931 if self.use_latex_toc:
2932 title = ''
2933 if isinstance(node.next_node(), nodes.title):
2934 title = self.encode(node.pop(0).astext())
2935 depth = node.get('depth', 0)
2936 if 'local' in node['classes']:
2937 self.minitoc(node, title, depth)
2938 self.context.append('')
2939 return
2940 if depth:
2941 self.out.append('\\setcounter{tocdepth}{%d}\n' % depth)
2942 if title != 'Contents':
2943 self.out.append('\\renewcommand{\\contentsname}{%s}\n' %
2944 title)
2945 self.out.append('\\tableofcontents\n\n')
2946 self.has_latex_toc = True
2947 else: # Docutils generated contents list
2948 # set flag for visit_bullet_list() and visit_title()
2949 self.is_toc_list = True
2950 self.context.append('')
2951 elif ('abstract' in node['classes'] and
2952 self.settings.use_latex_abstract):
2953 self.push_output_collector(self.abstract)
2954 self.out.append('\\begin{abstract}')
2955 self.context.append('\\end{abstract}\n')
2956 if isinstance(node.next_node(), nodes.title):
2957 node.pop(0) # LaTeX provides its own title
2958 else:
2959 self.fallbacks['topic'] = PreambleCmds.topic
2960 # special topics:
2961 if 'abstract' in node['classes']:
2962 self.fallbacks['abstract'] = PreambleCmds.abstract
2963 self.push_output_collector(self.abstract)
2964 if 'dedication' in node['classes']:
2965 self.fallbacks['dedication'] = PreambleCmds.dedication
2966 self.push_output_collector(self.dedication)
2967 self.out.append('\n\\DUtopic[%s]{\n' % ','.join(node['classes']))
2968 self.context.append('}\n')
2970 def depart_topic(self, node):
2971 self.out.append(self.context.pop())
2972 self.is_toc_list = False
2973 if ('abstract' in node['classes'] or
2974 'dedication' in node['classes']):
2975 self.pop_output_collector()
2977 def visit_rubric(self, node):
2978 self.fallbacks['rubric'] = PreambleCmds.rubric
2979 self.out.append('\n\\DUrubric{')
2980 self.context.append('}\n')
2982 def depart_rubric(self, node):
2983 self.out.append(self.context.pop())
2985 def visit_transition(self, node):
2986 self.fallbacks['transition'] = PreambleCmds.transition
2987 self.out.append('\n\n')
2988 self.out.append('%' + '_' * 75 + '\n')
2989 self.out.append(r'\DUtransition')
2990 self.out.append('\n\n')
2992 def depart_transition(self, node):
2993 pass
2995 def visit_version(self, node):
2996 self.visit_docinfo_item(node, 'version')
2998 def depart_version(self, node):
2999 self.depart_docinfo_item(node)
3001 def unimplemented_visit(self, node):
3002 raise NotImplementedError('visiting unimplemented node type: %s' %
3003 node.__class__.__name__)
3005 # def unknown_visit(self, node):
3006 # def default_visit(self, node):
3008 # vim: set ts=4 et ai :