PageRenderTime 72ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/code/docutils/writers/latex2e/__init__.py

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