# -*- coding: utf-8 -*-
"""
    sphinx.writers.latex
    ~~~~~~~~~~~~~~~~~~~~

    Custom docutils writer for LaTeX.

    Much of this code is adapted from Dave Kuhlman's "docpy" writer from his
    docutils sandbox.

    :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import re
import sys
from os import path
from collections import defaultdict

from six import itervalues, text_type
from docutils import nodes, writers
from docutils.writers.latex2e import Babel

from sphinx import addnodes
from sphinx import highlighting
from sphinx.errors import SphinxError
from sphinx.locale import admonitionlabels, _
from sphinx.util import split_into, logging
from sphinx.util.i18n import format_date
from sphinx.util.nodes import clean_astext, traverse_parent
from sphinx.util.template import LaTeXRenderer
from sphinx.util.texescape import tex_escape_map, tex_replace_map

if False:
    # For type annotation
    from typing import Any, Callable, Dict, Iterator, List, Pattern, Tuple, Set, Union  # NOQA
    from sphinx.builder import Builder  # NOQA

logger = logging.getLogger(__name__)

BEGIN_DOC = r'''
\begin{document}
%(shorthandoff)s
%(maketitle)s
%(tableofcontents)s
'''


URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:')
SECNUMDEPTH = 3
LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection",
                     "subsubsection", "paragraph", "subparagraph"]

DEFAULT_SETTINGS = {
    'latex_engine':    'pdflatex',
    'papersize':       'letterpaper',
    'pointsize':       '10pt',
    'pxunit':          '.75bp',
    'classoptions':    '',
    'extraclassoptions': '',
    'maxlistdepth':    '',
    'sphinxpkgoptions':     'dontkeepoldnames',
    'sphinxsetup':     '',
    'passoptionstopackages': '',
    'geometry':        '\\usepackage{geometry}',
    'inputenc':        '',
    'utf8extra':       '',
    'cmappkg':         '\\usepackage{cmap}',
    'fontenc':         '\\usepackage[T1]{fontenc}',
    'amsmath':         '\\usepackage{amsmath,amssymb,amstext}',
    'multilingual':    '',
    'babel':           '\\usepackage{babel}',
    'polyglossia':     '',
    'fontpkg':         '\\usepackage{times}',
    'fncychap':        '\\usepackage[Bjarne]{fncychap}',
    'hyperref':        ('% Include hyperref last.\n'
                        '\\usepackage{hyperref}\n'
                        '% Fix anchor placement for figures with captions.\n'
                        '\\usepackage{hypcap}% it must be loaded after hyperref.\n'
                        '% Set up styles of URL: it should be placed after hyperref.\n'
                        '\\urlstyle{same}'),
    'usepackages':     '',
    'numfig_format':   '',
    'contentsname':    '',
    'preamble':        '',
    'title':           '',
    'date':            '',
    'release':         '',
    'author':          '',
    'logo':            '\\vbox{}',
    'releasename':     '',
    'makeindex':       '\\makeindex',
    'shorthandoff':    '',
    'maketitle':       '\\maketitle',
    'tableofcontents': '\\sphinxtableofcontents',
    'atendofbody':     '',
    'printindex':      '\\printindex',
    'transition':      '\n\n\\bigskip\\hrule\\bigskip\n\n',
    'figure_align':    'htbp',
    'tocdepth':        '',
    'secnumdepth':     '',
    'pageautorefname': '',
    'literalblockpto': '',
}  # type: Dict[unicode, unicode]

ADDITIONAL_SETTINGS = {
    'pdflatex': {
        'inputenc':     '\\usepackage[utf8]{inputenc}',
        'utf8extra':   ('\\ifdefined\\DeclareUnicodeCharacter\n'
                        ' \\ifdefined\\DeclareUnicodeCharacterAsOptional\n'
                        '  \\DeclareUnicodeCharacter{"00A0}{\\nobreakspace}\n'
                        '  \\DeclareUnicodeCharacter{"2500}{\\sphinxunichar{2500}}\n'
                        '  \\DeclareUnicodeCharacter{"2502}{\\sphinxunichar{2502}}\n'
                        '  \\DeclareUnicodeCharacter{"2514}{\\sphinxunichar{2514}}\n'
                        '  \\DeclareUnicodeCharacter{"251C}{\\sphinxunichar{251C}}\n'
                        '  \\DeclareUnicodeCharacter{"2572}{\\textbackslash}\n'
                        ' \\else\n'
                        '  \\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n'
                        '  \\DeclareUnicodeCharacter{2500}{\\sphinxunichar{2500}}\n'
                        '  \\DeclareUnicodeCharacter{2502}{\\sphinxunichar{2502}}\n'
                        '  \\DeclareUnicodeCharacter{2514}{\\sphinxunichar{2514}}\n'
                        '  \\DeclareUnicodeCharacter{251C}{\\sphinxunichar{251C}}\n'
                        '  \\DeclareUnicodeCharacter{2572}{\\textbackslash}\n'
                        ' \\fi\n'
                        '\\fi'),
    },
    'xelatex': {
        'latex_engine': 'xelatex',
        'polyglossia':  '\\usepackage{polyglossia}',
        'babel':        '',
        'fontenc':      '\\usepackage{fontspec}',
        'fontpkg':      '',
        'utf8extra':   ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0'
                        '{\\leavevmode\\nobreak\\ }'),
    },
    'lualatex': {
        'latex_engine': 'lualatex',
        'polyglossia':  '\\usepackage{polyglossia}',
        'babel':        '',
        'fontenc':      '\\usepackage{fontspec}',
        'fontpkg':      '',
        'utf8extra':   ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0'
                        '{\\leavevmode\\nobreak\\ }'),
    },
    'platex': {
        'latex_engine': 'platex',
        'geometry':     '\\usepackage[dvipdfm]{geometry}',
    },
}  # type: Dict[unicode, Dict[unicode, unicode]]


class collected_footnote(nodes.footnote):
    """Footnotes that are collected are assigned this class."""


class UnsupportedError(SphinxError):
    category = 'Markup is unsupported in LaTeX'


class LaTeXWriter(writers.Writer):

    supported = ('sphinxlatex',)

    settings_spec = ('LaTeX writer options', '', (
        ('Document name', ['--docname'], {'default': ''}),
        ('Document class', ['--docclass'], {'default': 'manual'}),
        ('Author', ['--author'], {'default': ''}),
    ))
    settings_defaults = {}  # type: Dict

    output = None

    def __init__(self, builder):
        # type: (Builder) -> None
        writers.Writer.__init__(self)
        self.builder = builder

    def translate(self):
        # type: () -> None
        transform = ShowUrlsTransform(self.document)
        transform.apply()
        visitor = self.builder.create_translator(self.document, self.builder)
        self.document.walkabout(visitor)
        self.output = visitor.astext()


# Helper classes

class ExtBabel(Babel):
    def __init__(self, language_code):
        # type: (unicode) -> None
        super(ExtBabel, self).__init__(language_code or '')
        self.language_code = language_code

    def get_shorthandoff(self):
        # type: () -> unicode
        shortlang = self.language.split('_')[0]
        if shortlang in ('de', 'ngerman', 'sl', 'slovene', 'pt', 'portuges',
                         'es', 'spanish', 'nl', 'dutch', 'pl', 'polish', 'it',
                         'italian'):
            return '\\ifnum\\catcode`\\"=\\active\\shorthandoff{"}\\fi'
        elif shortlang in ('tr', 'turkish'):
            # memo: if ever Sphinx starts supporting 'Latin', do as for Turkish
            return '\\ifnum\\catcode`\\=\\string=\\active\\shorthandoff{=}\\fi'
        return ''

    def uses_cyrillic(self):
        # type: () -> bool
        shortlang = self.language.split('_')[0]
        return shortlang in ('bg', 'bulgarian', 'kk', 'kazakh',
                             'mn', 'mongolian', 'ru', 'russian',
                             'uk', 'ukrainian')

    def is_supported_language(self):
        # type: () -> bool
        return bool(super(ExtBabel, self).get_language())

    def get_language(self):
        # type: () -> unicode
        language = super(ExtBabel, self).get_language()
        if not language:
            return 'english'  # fallback to english
        else:
            return language


class ShowUrlsTransform(object):
    expanded = False

    def __init__(self, document):
        # type: (nodes.Node) -> None
        self.document = document

    def apply(self):
        # type: () -> None
        # replace id_prefix temporarily
        id_prefix = self.document.settings.id_prefix
        self.document.settings.id_prefix = 'show_urls'

        self.expand_show_urls()
        if self.expanded:
            self.renumber_footnotes()

        # restore id_prefix
        self.document.settings.id_prefix = id_prefix

    def expand_show_urls(self):
        # type: () -> None
        show_urls = self.document.settings.env.config.latex_show_urls
        if show_urls is False or show_urls == 'no':
            return

        for node in self.document.traverse(nodes.reference):
            uri = node.get('refuri', '')
            if uri.startswith(URI_SCHEMES):
                if uri.startswith('mailto:'):
                    uri = uri[7:]
                if node.astext() != uri:
                    index = node.parent.index(node)
                    if show_urls == 'footnote':
                        footnote_nodes = self.create_footnote(uri)
                        for i, fn in enumerate(footnote_nodes):
                            node.parent.insert(index + i + 1, fn)

                        self.expanded = True
                    else:  # all other true values (b/w compat)
                        textnode = nodes.Text(" (%s)" % uri)
                        node.parent.insert(index + 1, textnode)

    def create_footnote(self, uri):
        # type: (unicode) -> List[Union[nodes.footnote, nodes.footnote_ref]]
        label = nodes.label('', '#')
        para = nodes.paragraph()
        para.append(nodes.reference('', nodes.Text(uri), refuri=uri, nolinkurl=True))
        footnote = nodes.footnote(uri, label, para, auto=1)
        footnote['names'].append('#')
        self.document.note_autofootnote(footnote)

        label = nodes.Text('#')
        footnote_ref = nodes.footnote_reference('[#]_', label, auto=1,
                                                refid=footnote['ids'][0])
        self.document.note_autofootnote_ref(footnote_ref)
        footnote.add_backref(footnote_ref['ids'][0])

        return [footnote, footnote_ref]

    def renumber_footnotes(self):
        # type: () -> None
        def is_used_number(number):
            # type: (unicode) -> bool
            for node in self.document.traverse(nodes.footnote):
                if not node.get('auto') and number in node['names']:
                    return True

            return False

        def is_auto_footnote(node):
            # type: (nodes.Node) -> bool
            return isinstance(node, nodes.footnote) and node.get('auto')

        def footnote_ref_by(node):
            # type: (nodes.Node) -> Callable[[nodes.Node], bool]
            ids = node['ids']
            parent = list(traverse_parent(node, (nodes.document, addnodes.start_of_file)))[0]

            def is_footnote_ref(node):
                # type: (nodes.Node) -> bool
                return (isinstance(node, nodes.footnote_reference) and
                        ids[0] == node['refid'] and
                        parent in list(traverse_parent(node)))

            return is_footnote_ref

        startnum = 1
        for footnote in self.document.traverse(is_auto_footnote):
            while True:
                label = str(startnum)
                startnum += 1
                if not is_used_number(label):
                    break

            old_label = footnote[0].astext()
            footnote.remove(footnote[0])
            footnote.insert(0, nodes.label('', label))
            if old_label in footnote['names']:
                footnote['names'].remove(old_label)
            footnote['names'].append(label)

            for footnote_ref in self.document.traverse(footnote_ref_by(footnote)):
                footnote_ref.remove(footnote_ref[0])
                footnote_ref += nodes.Text(label)


class Table(object):
    """A table data"""

    def __init__(self, node):
        # type: (nodes.table) -> None
        self.header = []                        # type: List[unicode]
        self.body = []                          # type: List[unicode]
        self.align = node.get('align')
        self.colcount = 0
        self.colspec = None                     # type: unicode
        self.colwidths = []                     # type: List[int]
        self.has_problematic = False
        self.has_oldproblematic = False
        self.has_verbatim = False
        self.caption = None                     # type: List[unicode]
        self.caption_footnotetexts = []         # type: List[unicode]
        self.header_footnotetexts = []          # type: List[unicode]
        self.stubs = []                         # type: List[int]

        # current position
        self.col = 0
        self.row = 0

        # for internal use
        self.classes = node.get('classes', [])  # type: List[unicode]
        self.cells = defaultdict(int)           # type: Dict[Tuple[int, int], int]
                                                # it maps table location to cell_id
                                                # (cell = rectangular area)
        self.cell_id = 0                        # last assigned cell_id

    def is_longtable(self):
        # type: () -> bool
        """True if and only if table uses longtable environment."""
        return self.row > 30 or 'longtable' in self.classes

    def get_table_type(self):
        # type: () -> unicode
        """Returns the LaTeX environment name for the table.

        The class currently supports:

        * longtable
        * tabular
        * tabulary
        """
        if self.is_longtable():
            return 'longtable'
        elif self.has_verbatim:
            return 'tabular'
        elif self.colspec:
            return 'tabulary'
        elif self.has_problematic or (self.colwidths and 'colwidths-given' in self.classes):
            return 'tabular'
        else:
            return 'tabulary'

    def get_colspec(self):
        # type: () -> unicode
        """Returns a column spec of table.

        This is what LaTeX calls the 'preamble argument' of the used table environment.

        .. note:: the ``\\X`` and ``T`` column type specifiers are defined in ``sphinx.sty``.
        """
        if self.colspec:
            return self.colspec
        elif self.colwidths and 'colwidths-given' in self.classes:
            total = sum(self.colwidths)
            colspecs = ['\\X{%d}{%d}' % (width, total) for width in self.colwidths]
            return '{|%s|}\n' % '|'.join(colspecs)
        elif self.has_problematic:
            return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount)
        elif self.get_table_type() == 'tabulary':
            # sphinx.sty sets T to be J by default.
            return '{|' + ('T|' * self.colcount) + '}\n'
        elif self.has_oldproblematic:
            return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount)
        else:
            return '{|' + ('l|' * self.colcount) + '}\n'

    def add_cell(self, height, width):
        # type: (int, int) -> None
        """Adds a new cell to a table.

        It will be located at current position: (``self.row``, ``self.col``).
        """
        self.cell_id += 1
        for col in range(width):
            for row in range(height):
                assert self.cells[(self.row + row, self.col + col)] == 0
                self.cells[(self.row + row, self.col + col)] = self.cell_id

    def cell(self, row=None, col=None):
        # type: (int, int) -> TableCell
        """Returns a cell object (i.e. rectangular area) containing given position.

        If no option arguments: ``row`` or ``col`` are given, the current position;
        ``self.row`` and ``self.col`` are used to get a cell object by default.
        """
        try:
            if row is None:
                row = self.row
            if col is None:
                col = self.col
            return TableCell(self, row, col)
        except IndexError:
            return None


class TableCell(object):
    """A cell data of tables."""

    def __init__(self, table, row, col):
        # type: (Table, int, int) -> None
        if table.cells[(row, col)] == 0:
            raise IndexError

        self.table = table
        self.cell_id = table.cells[(row, col)]
        self.row = row
        self.col = col

        # adjust position for multirow/multicol cell
        while table.cells[(self.row - 1, self.col)] == self.cell_id:
            self.row -= 1
        while table.cells[(self.row, self.col - 1)] == self.cell_id:
            self.col -= 1

    @property
    def width(self):
        # type: () -> int
        """Returns the cell width."""
        width = 0
        while self.table.cells[(self.row, self.col + width)] == self.cell_id:
            width += 1
        return width

    @property
    def height(self):
        # type: () -> int
        """Returns the cell height."""
        height = 0
        while self.table.cells[(self.row + height, self.col)] == self.cell_id:
            height += 1
        return height


def escape_abbr(text):
    # type: (unicode) -> unicode
    """Adjust spacing after abbreviations."""
    return re.sub(r'\.(?=\s|$)', r'.\@', text)


def rstdim_to_latexdim(width_str):
    # type: (unicode) -> unicode
    """Convert `width_str` with rst length to LaTeX length."""
    match = re.match(r'^(\d*\.?\d*)\s*(\S*)$', width_str)
    if not match:
        raise ValueError
    res = width_str
    amount, unit = match.groups()[:2]
    float(amount)  # validate amount is float
    if unit in ('', "px"):
        res = "%s\\sphinxpxdimen" % amount
    elif unit == 'pt':
        res = '%sbp' % amount  # convert to 'bp'
    elif unit == "%":
        res = "%.3f\\linewidth" % (float(amount) / 100.0)
    return res


class LaTeXTranslator(nodes.NodeVisitor):

    ignore_missing_images = False

    # sphinx specific document classes
    docclasses = ('howto', 'manual')

    def __init__(self, document, builder):
        # type: (nodes.Node, Builder) -> None
        nodes.NodeVisitor.__init__(self, document)
        self.builder = builder
        self.body = []  # type: List[unicode]

        # flags
        self.in_title = 0
        self.in_production_list = 0
        self.in_footnote = 0
        self.in_caption = 0
        self.in_container_literal_block = 0
        self.in_term = 0
        self.needs_linetrimming = 0
        self.in_minipage = 0
        self.first_document = 1
        self.this_is_the_title = 1
        self.literal_whitespace = 0
        self.no_contractions = 0
        self.in_parsed_literal = 0
        self.compact_list = 0
        self.first_param = 0

        # sort out some elements
        self.elements = DEFAULT_SETTINGS.copy()
        self.elements.update(ADDITIONAL_SETTINGS.get(builder.config.latex_engine, {}))
        # allow the user to override them all
        self.check_latex_elements()
        self.elements.update(builder.config.latex_elements)

        # but some have other interface in config file
        self.elements.update({
            'wrapperclass': self.format_docclass(document.settings.docclass),
            # if empty, the title is set to the first section title
            'title':        document.settings.title,    # treat as a raw LaTeX code
            'release':      self.encode(builder.config.release),
            'author':       document.settings.author,   # treat as a raw LaTeX code
            'indexname':    _('Index'),
        })
        if not self.elements['releasename']:
            self.elements.update({
                'releasename':  _('Release'),
            })
        if builder.config.latex_keep_old_macro_names:
            self.elements['sphinxpkgoptions'] = ''

        # we assume LaTeX class provides \chapter command except in case
        # of non-Japanese 'howto' case
        self.sectionnames = LATEXSECTIONNAMES[:]
        if document.settings.docclass == 'howto':
            docclass = builder.config.latex_docclass.get('howto', 'article')
            if docclass[0] == 'j':  # Japanese class...
                pass
            else:
                self.sectionnames.remove('chapter')
        else:
            docclass = builder.config.latex_docclass.get('manual', 'report')
        self.elements['docclass'] = docclass

        # determine top section level
        self.top_sectionlevel = 1
        if builder.config.latex_toplevel_sectioning:
            try:
                self.top_sectionlevel = \
                    self.sectionnames.index(builder.config.latex_toplevel_sectioning)
            except ValueError:
                logger.warning('unknown %r toplevel_sectioning for class %r' %
                               (builder.config.latex_toplevel_sectioning, docclass))

        if builder.config.today:
            self.elements['date'] = builder.config.today
        else:
            self.elements['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'),  # type: ignore  # NOQA
                                                language=builder.config.language)
        if builder.config.latex_logo:
            # no need for \\noindent here, used in flushright
            self.elements['logo'] = '\\sphinxincludegraphics{%s}\\par' % \
                                    path.basename(builder.config.latex_logo)

        if builder.config.language \
           and 'fncychap' not in builder.config.latex_elements:
            # use Sonny style if any language specified
            self.elements['fncychap'] = '\\usepackage[Sonny]{fncychap}'

        self.babel = ExtBabel(builder.config.language)
        if builder.config.language and not self.babel.is_supported_language():
            # emit warning if specified language is invalid
            # (only emitting, nothing changed to processing)
            logger.warning('no Babel option known for language %r',
                           builder.config.language)

        # simply use babel.get_language() always, as get_language() returns
        # 'english' even if language is invalid or empty
        self.elements['classoptions'] += ',' + self.babel.get_language()

        # set up multilingual module...
        # 'babel' key is public and user setting must be obeyed
        if self.elements['babel']:
            # this branch is not taken for xelatex/lualatex if default settings
            self.elements['multilingual'] = self.elements['babel']
            if builder.config.language:
                self.elements['shorthandoff'] = self.babel.get_shorthandoff()

                # Times fonts don't work with Cyrillic languages
                if self.babel.uses_cyrillic() \
                   and 'fontpkg' not in builder.config.latex_elements:
                    self.elements['fontpkg'] = ''

                # pTeX (Japanese TeX) for support
                if builder.config.language == 'ja':
                    # use dvipdfmx as default class option in Japanese
                    self.elements['classoptions'] = ',dvipdfmx'
                    # disable babel which has not publishing quality in Japanese
                    self.elements['babel'] = ''
                    self.elements['multilingual'] = ''
                    # disable fncychap in Japanese documents
                    self.elements['fncychap'] = ''
        elif self.elements['polyglossia']:
            self.elements['multilingual'] = '%s\n\\setmainlanguage{%s}' % \
                (self.elements['polyglossia'], self.babel.get_language())

        if getattr(builder, 'usepackages', None):
            def declare_package(packagename, options=None):
                # type:(unicode, unicode) -> unicode
                if options:
                    return '\\usepackage[%s]{%s}' % (options, packagename)
                else:
                    return '\\usepackage{%s}' % (packagename,)
            usepackages = (declare_package(*p) for p in builder.usepackages)
            self.elements['usepackages'] += "\n".join(usepackages)
        if document.get('tocdepth'):
            # reduce tocdepth if `part` or `chapter` is used for top_sectionlevel
            #   tocdepth = -1: show only parts
            #   tocdepth =  0: show parts and chapters
            #   tocdepth =  1: show parts, chapters and sections
            #   tocdepth =  2: show parts, chapters, sections and subsections
            #   ...

            tocdepth = document['tocdepth'] + self.top_sectionlevel - 2
            if len(self.sectionnames) < 7 and self.top_sectionlevel > 0:
                tocdepth += 1  # because top_sectionlevel is shifted by -1
            if tocdepth > 5:   # 5 corresponds to subparagraph
                logger.warning('too large :maxdepth:, ignored.')
                tocdepth = 5

            self.elements['tocdepth'] = '\\setcounter{tocdepth}{%d}' % tocdepth
            if tocdepth >= SECNUMDEPTH:
                # Increase secnumdepth if tocdepth is deeper than default SECNUMDEPTH
                self.elements['secnumdepth'] = '\\setcounter{secnumdepth}{%d}' % tocdepth

        if getattr(document.settings, 'contentsname', None):
            self.elements['contentsname'] = \
                self.babel_renewcommand('\\contentsname', document.settings.contentsname)

        if self.elements['maxlistdepth']:
            self.elements['sphinxpkgoptions'] += (',maxlistdepth=%s' %
                                                  self.elements['maxlistdepth'])
        if self.elements['sphinxpkgoptions']:
            self.elements['sphinxpkgoptions'] = ('[%s]' %
                                                 self.elements['sphinxpkgoptions'])
        if self.elements['sphinxsetup']:
            self.elements['sphinxsetup'] = ('\\sphinxsetup{%s}' %
                                            self.elements['sphinxsetup'])
        if self.elements['extraclassoptions']:
            self.elements['classoptions'] += ',' + \
                                             self.elements['extraclassoptions']
        self.elements['literalblockpto'] = (
            self.babel_renewcommand(
                '\\literalblockcontinuedname', self.encode(_('continued from previous page'))
            ) +
            self.babel_renewcommand(
                '\\literalblockcontinuesname', self.encode(_('continues on next page'))
            )
        )
        self.elements['pageautorefname'] = \
            self.babel_defmacro('\\pageautorefname', self.encode(_('page')))
        self.elements['numfig_format'] = self.generate_numfig_format(builder)

        self.highlighter = highlighting.PygmentsBridge(
            'latex',
            builder.config.pygments_style, builder.config.trim_doctest_flags)
        self.context = []               # type: List[Any]
        self.descstack = []             # type: List[unicode]
        self.bibitems = []              # type: List[List[unicode]]
        self.table = None               # type: Table
        self.next_table_colspec = None  # type: unicode
        # stack of [language, linenothreshold] settings per file
        # the first item here is the default and must not be changed
        # the second item is the default for the master file and can be changed
        # by .. highlight:: directive in the master file
        self.hlsettingstack = 2 * [[builder.config.highlight_language,
                                    sys.maxsize]]
        self.bodystack = []             # type: List[List[unicode]]
        self.footnotestack = []         # type: List[Dict[unicode, List[Union[collected_footnote, bool]]]]  # NOQA
        self.footnote_restricted = False
        self.pending_footnotes = []     # type: List[nodes.footnote_reference]
        self.curfilestack = []          # type: List[unicode]
        self.handled_abbrs = set()      # type: Set[unicode]
        self.next_hyperlink_ids = {}    # type: Dict[unicode, Set[unicode]]
        self.next_section_ids = set()   # type: Set[unicode]

    def pushbody(self, newbody):
        # type: (List[unicode]) -> None
        self.bodystack.append(self.body)
        self.body = newbody

    def popbody(self):
        # type: () -> List[unicode]
        body = self.body
        self.body = self.bodystack.pop()
        return body

    def push_hyperlink_ids(self, figtype, ids):
        # type: (unicode, Set[unicode]) -> None
        hyperlink_ids = self.next_hyperlink_ids.setdefault(figtype, set())
        hyperlink_ids.update(ids)

    def pop_hyperlink_ids(self, figtype):
        # type: (unicode) -> Set[unicode]
        return self.next_hyperlink_ids.pop(figtype, set())

    def check_latex_elements(self):
        # type: () -> None
        for key in self.builder.config.latex_elements:
            if key not in self.elements:
                msg = _("Unknown configure key: latex_elements[%r] is ignored.")
                logger.warning(msg % key)

    def restrict_footnote(self, node):
        # type: (nodes.Node) -> None
        if self.footnote_restricted is False:
            self.footnote_restricted = node
            self.pending_footnotes = []

    def unrestrict_footnote(self, node):
        # type: (nodes.Node) -> None
        if self.footnote_restricted == node:
            self.footnote_restricted = False
            for footnode in self.pending_footnotes:
                footnode['footnotetext'] = True
                footnode.walkabout(self)
            self.pending_footnotes = []

    def format_docclass(self, docclass):
        # type: (unicode) -> unicode
        """ prepends prefix to sphinx document classes
        """
        if docclass in self.docclasses:
            docclass = 'sphinx' + docclass
        return docclass

    def astext(self):
        # type: () -> unicode
        self.elements.update({
            'body': u''.join(self.body),
            'indices': self.generate_indices()
        })
        return self.render('latex.tex_t', self.elements)

    def hypertarget(self, id, withdoc=True, anchor=True):
        # type: (unicode, bool, bool) -> unicode
        if withdoc:
            id = self.curfilestack[-1] + ':' + id
        return (anchor and '\\phantomsection' or '') + \
            '\\label{%s}' % self.idescape(id)

    def hyperlink(self, id):
        # type: (unicode) -> unicode
        return '{\\hyperref[%s]{' % self.idescape(id)

    def hyperpageref(self, id):
        # type: (unicode) -> unicode
        return '\\autopageref*{%s}' % self.idescape(id)

    def idescape(self, id):
        # type: (unicode) -> unicode
        return '\\detokenize{%s}' % text_type(id).translate(tex_replace_map).\
            encode('ascii', 'backslashreplace').decode('ascii').\
            replace('\\', '_')

    def babel_renewcommand(self, command, definition):
        # type: (unicode, unicode) -> unicode
        if self.elements['multilingual']:
            prefix = '\\addto\\captions%s{' % self.babel.get_language()
            suffix = '}'
        else:  # babel is disabled (mainly for Japanese environment)
            prefix = ''
            suffix = ''

        return ('%s\\renewcommand{%s}{%s}%s\n' % (prefix, command, definition, suffix))

    def babel_defmacro(self, name, definition):
        # type: (unicode, unicode) -> unicode
        if self.elements['babel']:
            prefix = '\\addto\\extras%s{' % self.babel.get_language()
            suffix = '}'
        else:  # babel is disabled (mainly for Japanese environment)
            prefix = ''
            suffix = ''

        return ('%s\\def%s{%s}%s\n' % (prefix, name, definition, suffix))

    def generate_numfig_format(self, builder):
        # type: (Builder) -> unicode
        ret = []  # type: List[unicode]
        figure = self.builder.config.numfig_format['figure'].split('%s', 1)
        if len(figure) == 1:
            ret.append('\\def\\fnum@figure{%s}\n' %
                       text_type(figure[0]).strip().translate(tex_escape_map))
        else:
            definition = text_type(figure[0]).strip().translate(tex_escape_map)
            ret.append(self.babel_renewcommand('\\figurename', definition))
            if figure[1]:
                ret.append('\\makeatletter\n')
                ret.append('\\def\\fnum@figure{\\figurename\\thefigure%s}\n' %
                           text_type(figure[1]).strip().translate(tex_escape_map))
                ret.append('\\makeatother\n')

        table = self.builder.config.numfig_format['table'].split('%s', 1)
        if len(table) == 1:
            ret.append('\\def\\fnum@table{%s}\n' %
                       text_type(table[0]).strip().translate(tex_escape_map))
        else:
            definition = text_type(table[0]).strip().translate(tex_escape_map)
            ret.append(self.babel_renewcommand('\\tablename', definition))
            if table[1]:
                ret.append('\\makeatletter\n')
                ret.append('\\def\\fnum@table{\\tablename\\thetable%s}\n' %
                           text_type(table[1]).strip().translate(tex_escape_map))
                ret.append('\\makeatother\n')

        codeblock = self.builder.config.numfig_format['code-block'].split('%s', 1)
        if len(codeblock) == 1:
            pass  # FIXME
        else:
            definition = text_type(codeblock[0]).strip().translate(tex_escape_map)
            ret.append(self.babel_renewcommand('\\literalblockname', definition))
            if codeblock[1]:
                pass  # FIXME

        return ''.join(ret)

    def generate_indices(self):
        # type: (Builder) -> unicode
        def generate(content, collapsed):
            # type: (List[Tuple[unicode, List[Tuple[unicode, unicode, unicode, unicode, unicode]]]], bool) -> None  # NOQA
            ret.append('\\begin{sphinxtheindex}\n')
            ret.append('\\def\\bigletter#1{{\\Large\\sffamily#1}'
                       '\\nopagebreak\\vspace{1mm}}\n')
            for i, (letter, entries) in enumerate(content):
                if i > 0:
                    ret.append('\\indexspace\n')
                ret.append('\\bigletter{%s}\n' %
                           text_type(letter).translate(tex_escape_map))
                for entry in entries:
                    if not entry[3]:
                        continue
                    ret.append('\\item {\\sphinxstyleindexentry{%s}}' % self.encode(entry[0]))
                    if entry[4]:
                        # add "extra" info
                        ret.append('\\sphinxstyleindexextra{%s}' % self.encode(entry[4]))
                    ret.append('\\sphinxstyleindexpageref{%s:%s}\n' %
                               (entry[2], self.idescape(entry[3])))
            ret.append('\\end{sphinxtheindex}\n')

        ret = []
        # latex_domain_indices can be False/True or a list of index names
        indices_config = self.builder.config.latex_domain_indices
        if indices_config:
            for domain in itervalues(self.builder.env.domains):
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    content, collapsed = indexcls(domain).generate(
                        self.builder.docnames)
                    if not content:
                        continue
                    ret.append(u'\\renewcommand{\\indexname}{%s}\n' %
                               indexcls.localname)
                    generate(content, collapsed)

        return ''.join(ret)

    def render(self, template_name, variables):
        # type: (unicode, Dict) -> unicode
        template_path = path.join(self.builder.srcdir, '_templates', template_name)
        if path.exists(template_path):
            return LaTeXRenderer().render(template_path, variables)
        else:
            return LaTeXRenderer().render(template_name, variables)

    def visit_document(self, node):
        # type: (nodes.Node) -> None
        self.footnotestack.append(self.collect_footnotes(node))
        self.curfilestack.append(node.get('docname', ''))
        if self.first_document == 1:
            # the first document is all the regular content ...
            self.body.append(BEGIN_DOC % self.elements)
            self.first_document = 0
        elif self.first_document == 0:
            # ... and all others are the appendices
            self.body.append(u'\n\\appendix\n')
            self.first_document = -1
        if 'docname' in node:
            self.body.append(self.hypertarget(':doc'))
        # "- 1" because the level is increased before the title is visited
        self.sectionlevel = self.top_sectionlevel - 1

    def depart_document(self, node):
        # type: (nodes.Node) -> None
        if self.bibitems:
            widest_label = ""  # type: unicode
            for bi in self.bibitems:
                if len(widest_label) < len(bi[0]):
                    widest_label = bi[0]
            self.body.append(u'\n\\begin{sphinxthebibliography}{%s}\n' % widest_label)
            for bi in self.bibitems:
                target = self.hypertarget(bi[2] + ':' + bi[3],
                                          withdoc=False)
                self.body.append(u'\\bibitem[%s]{%s}{%s %s}\n' %
                                 (self.encode(bi[0]), self.idescape(bi[0]),
                                  target, bi[1]))
            self.body.append(u'\\end{sphinxthebibliography}\n')
            self.bibitems = []

    def visit_start_of_file(self, node):
        # type: (nodes.Node) -> None
        # collect new footnotes
        self.footnotestack.append(self.collect_footnotes(node))
        # also add a document target
        self.next_section_ids.add(':doc')
        self.curfilestack.append(node['docname'])
        # use default highlight settings for new file
        self.hlsettingstack.append(self.hlsettingstack[0])

    def collect_footnotes(self, node):
        # type: (nodes.Node) -> Dict[unicode, List[Union[collected_footnote, bool]]]
        def footnotes_under(n):
            # type: (nodes.Node) -> Iterator[nodes.Node]
            if isinstance(n, nodes.footnote):
                yield n
            else:
                for c in n.children:
                    if isinstance(c, addnodes.start_of_file):
                        continue
                    for k in footnotes_under(c):
                        yield k

        fnotes = {}  # type: Dict[unicode, List[Union[collected_footnote, bool]]]
        for fn in footnotes_under(node):
            num = fn.children[0].astext().strip()
            newnode = collected_footnote(*fn.children, number=num)
            fnotes[num] = [newnode, False]
        return fnotes

    def depart_start_of_file(self, node):
        # type: (nodes.Node) -> None
        self.footnotestack.pop()
        self.curfilestack.pop()
        self.hlsettingstack.pop()

    def visit_highlightlang(self, node):
        # type: (nodes.Node) -> None
        self.hlsettingstack[-1] = [node['lang'], node['linenothreshold']]
        raise nodes.SkipNode

    def visit_section(self, node):
        # type: (nodes.Node) -> None
        if not self.this_is_the_title:
            self.sectionlevel += 1
        self.body.append('\n\n')
        if node.get('ids'):
            self.next_section_ids.update(node['ids'])

    def depart_section(self, node):
        # type: (nodes.Node) -> None
        self.sectionlevel = max(self.sectionlevel - 1,
                                self.top_sectionlevel - 1)

    def visit_problematic(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'{\color{red}\bfseries{}')

    def depart_problematic(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_topic(self, node):
        # type: (nodes.Node) -> None
        self.in_minipage = 1
        self.body.append('\n\\begin{sphinxShadowBox}\n')

    def depart_topic(self, node):
        # type: (nodes.Node) -> None
        self.in_minipage = 0
        self.body.append('\\end{sphinxShadowBox}\n')
    visit_sidebar = visit_topic
    depart_sidebar = depart_topic

    def visit_glossary(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_glossary(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_productionlist(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\n\\begin{productionlist}\n')
        self.in_production_list = 1

    def depart_productionlist(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{productionlist}\n\n')
        self.in_production_list = 0

    def visit_production(self, node):
        # type: (nodes.Node) -> None
        if node['tokenname']:
            tn = node['tokenname']
            self.body.append(self.hypertarget('grammar-token-' + tn))
            self.body.append('\\production{%s}{' % self.encode(tn))
        else:
            self.body.append('\\productioncont{')

    def depart_production(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}\n')

    def visit_transition(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.elements['transition'])

    def depart_transition(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_title(self, node):
        # type: (nodes.Node) -> None
        parent = node.parent
        if isinstance(parent, addnodes.seealso):
            # the environment already handles this
            raise nodes.SkipNode
        elif isinstance(parent, nodes.section):
            if self.this_is_the_title:
                if len(node.children) != 1 and not isinstance(node.children[0],
                                                              nodes.Text):
                    logger.warning('document title is not a single Text node',
                                   location=(self.curfilestack[-1], node.line))
                if not self.elements['title']:
                    # text needs to be escaped since it is inserted into
                    # the output literally
                    self.elements['title'] = node.astext().translate(tex_escape_map)
                self.this_is_the_title = 0
                raise nodes.SkipNode
            else:
                short = ''
                if node.traverse(nodes.image):
                    short = ('[%s]' %
                             u' '.join(clean_astext(node).split()).translate(tex_escape_map))

                try:
                    self.body.append(r'\%s%s{' % (self.sectionnames[self.sectionlevel], short))
                except IndexError:
                    # just use "subparagraph", it's not numbered anyway
                    self.body.append(r'\%s%s{' % (self.sectionnames[-1], short))
                self.context.append('}\n')

                self.restrict_footnote(node)
                if self.next_section_ids:
                    for id in self.next_section_ids:
                        self.context[-1] += self.hypertarget(id, anchor=False)
                    self.next_section_ids.clear()
        elif isinstance(parent, nodes.topic):
            self.body.append(r'\sphinxstyletopictitle{')
            self.context.append('}\n')
        elif isinstance(parent, nodes.sidebar):
            self.body.append(r'\sphinxstylesidebartitle{')
            self.context.append('}\n')
        elif isinstance(parent, nodes.Admonition):
            self.body.append('{')
            self.context.append('}\n')
        elif isinstance(parent, nodes.table):
            # Redirect body output until title is finished.
            self.pushbody([])
            self.restrict_footnote(node)
        else:
            logger.warning('encountered title node not in section, topic, table, '
                           'admonition or sidebar',
                           location=(self.curfilestack[-1], node.line or ''))
            self.body.append('\\sphinxstyleothertitle{')
            self.context.append('}\n')
        self.in_title = 1

    def depart_title(self, node):
        # type: (nodes.Node) -> None
        self.in_title = 0
        if isinstance(node.parent, nodes.table):
            self.table.caption = self.popbody()
            # temporary buffer for footnotes from caption
            self.pushbody([])
            self.unrestrict_footnote(node)
            # the footnote texts from caption
            self.table.caption_footnotetexts = self.popbody()
        else:
            self.body.append(self.context.pop())
            self.unrestrict_footnote(node)

    def visit_subtitle(self, node):
        # type: (nodes.Node) -> None
        if isinstance(node.parent, nodes.sidebar):
            self.body.append('\\sphinxstylesidebarsubtitle{')
            self.context.append('}\n')
        else:
            self.context.append('')

    def depart_subtitle(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.context.pop())

    def visit_desc(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\n\\begin{fulllineitems}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_desc(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\end{fulllineitems}\n\n')

    def _visit_signature_line(self, node):
        # type: (nodes.Node) -> None
        for child in node:
            if isinstance(child, addnodes.desc_parameterlist):
                self.body.append(r'\pysiglinewithargsret{')
                break
        else:
            self.body.append(r'\pysigline{')

    def _depart_signature_line(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_desc_signature(self, node):
        # type: (nodes.Node) -> None
        if node.parent['objtype'] != 'describe' and node['ids']:
            hyper = self.hypertarget(node['ids'][0])
        else:
            hyper = ''
        self.body.append(hyper)
        if not node.get('is_multiline'):
            self._visit_signature_line(node)
        else:
            self.body.append('%\n\\pysigstartmultiline\n')

    def depart_desc_signature(self, node):
        # type: (nodes.Node) -> None
        if not node.get('is_multiline'):
            self._depart_signature_line(node)
        else:
            self.body.append('%\n\\pysigstopmultiline')

    def visit_desc_signature_line(self, node):
        # type: (nodes.Node) -> None
        self._visit_signature_line(node)

    def depart_desc_signature_line(self, node):
        # type: (nodes.Node) -> None
        self._depart_signature_line(node)

    def visit_desc_addname(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxcode{')
        self.literal_whitespace += 1

    def depart_desc_addname(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')
        self.literal_whitespace -= 1

    def visit_desc_type(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_desc_type(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_desc_returns(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'{ $\rightarrow$ ')

    def depart_desc_returns(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'}')

    def visit_desc_name(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxbfcode{')
        self.no_contractions += 1
        self.literal_whitespace += 1

    def depart_desc_name(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')
        self.literal_whitespace -= 1
        self.no_contractions -= 1

    def visit_desc_parameterlist(self, node):
        # type: (nodes.Node) -> None
        # close name, open parameterlist
        self.body.append('}{')
        self.first_param = 1

    def depart_desc_parameterlist(self, node):
        # type: (nodes.Node) -> None
        # close parameterlist, open return annotation
        self.body.append('}{')

    def visit_desc_parameter(self, node):
        # type: (nodes.Node) -> None
        if not self.first_param:
            self.body.append(', ')
        else:
            self.first_param = 0
        if not node.hasattr('noemph'):
            self.body.append(r'\emph{')

    def depart_desc_parameter(self, node):
        # type: (nodes.Node) -> None
        if not node.hasattr('noemph'):
            self.body.append('}')

    def visit_desc_optional(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxoptional{')

    def depart_desc_optional(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_desc_annotation(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxbfcode{')

    def depart_desc_annotation(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_desc_content(self, node):
        # type: (nodes.Node) -> None
        if node.children and not isinstance(node.children[0], nodes.paragraph):
            # avoid empty desc environment which causes a formatting bug
            self.body.append('~')

    def depart_desc_content(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_seealso(self, node):
        # type: (nodes.Node) -> None
        self.body.append(u'\n\n\\sphinxstrong{%s:}\n\n' % admonitionlabels['seealso'])

    def depart_seealso(self, node):
        # type: (nodes.Node) -> None
        self.body.append("\n\n")

    def visit_rubric(self, node):
        # type: (nodes.Node) -> None
        if len(node.children) == 1 and node.children[0].astext() in \
           ('Footnotes', _('Footnotes')):
            raise nodes.SkipNode
        self.body.append('\\paragraph{')
        self.context.append('}\n')
        self.in_title = 1

    def depart_rubric(self, node):
        # type: (nodes.Node) -> None
        self.in_title = 0
        self.body.append(self.context.pop())

    def visit_footnote(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    def visit_collected_footnote(self, node):
        # type: (nodes.Node) -> None
        self.in_footnote += 1
        if 'footnotetext' in node:
            self.body.append('%%\n\\begin{footnotetext}[%s]'
                             '\\sphinxAtStartFootnote\n' % node['number'])
        else:
            if self.in_parsed_literal:
                self.body.append('\\begin{footnote}[%s]' % node['number'])
            else:
                self.body.append('%%\n\\begin{footnote}[%s]' % node['number'])
            self.body.append('\\sphinxAtStartFootnote\n')

    def depart_collected_footnote(self, node):
        # type: (nodes.Node) -> None
        if 'footnotetext' in node:
            # the \ignorespaces in particular for after table header use
            self.body.append('%\n\\end{footnotetext}\\ignorespaces ')
        else:
            if self.in_parsed_literal:
                self.body.append('\\end{footnote}')
            else:
                self.body.append('%\n\\end{footnote}')
        self.in_footnote -= 1

    def visit_label(self, node):
        # type: (nodes.Node) -> None
        if isinstance(node.parent, nodes.citation):
            self.bibitems[-1][0] = node.astext()
            self.bibitems[-1][2] = self.curfilestack[-1]
            self.bibitems[-1][3] = node.parent['ids'][0]
        raise nodes.SkipNode

    def visit_tabular_col_spec(self, node):
        # type: (nodes.Node) -> None
        self.next_table_colspec = node['spec']
        raise nodes.SkipNode

    def visit_table(self, node):
        # type: (nodes.Node) -> None
        if self.table:
            raise UnsupportedError(
                '%s:%s: nested tables are not yet implemented.' %
                (self.curfilestack[-1], node.line or ''))
        self.table = Table(node)
        if self.next_table_colspec:
            self.table.colspec = '{%s}\n' % self.next_table_colspec
        self.next_table_colspec = None

    def depart_table(self, node):
        # type: (nodes.Node) -> None
        labels = ''  # type: unicode
        for labelid in self.pop_hyperlink_ids('table'):
            labels += self.hypertarget(labelid, anchor=False)
        if node['ids']:
            labels += self.hypertarget(node['ids'][0], anchor=False)

        table_type = self.table.get_table_type()
        table = self.render(table_type + '.tex_t',
                            dict(table=self.table, labels=labels))
        self.body.append("\n\n")
        self.body.append(table)
        self.body.append("\n")

        self.table = None

    def visit_colspec(self, node):
        # type: (nodes.Node) -> None
        self.table.colcount += 1
        if 'colwidth' in node:
            self.table.colwidths.append(node['colwidth'])
        if 'stub' in node:
            self.table.stubs.append(self.table.colcount - 1)

    def depart_colspec(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_tgroup(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_tgroup(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_thead(self, node):
        # type: (nodes.Node) -> None
        # Redirect head output until header is finished.
        self.pushbody(self.table.header)
        # footnotes in longtable header must be restricted
        self.restrict_footnote(node)

    def depart_thead(self, node):
        # type: (nodes.Node) -> None
        self.popbody()
        # temporary buffer for footnotes from table header
        self.pushbody([])
        self.unrestrict_footnote(node)
        # the footnote texts from header
        self.table.header_footnotetexts = self.popbody()

    def visit_tbody(self, node):
        # type: (nodes.Node) -> None
        # Redirect body output until table is finished.
        self.pushbody(self.table.body)
        # insert footnotetexts from header at start of body (due to longtable)
        # those from caption are handled by templates (to allow caption at foot)
        self.body.extend(self.table.header_footnotetexts)

    def depart_tbody(self, node):
        # type: (nodes.Node) -> None
        self.popbody()

    def visit_row(self, node):
        # type: (nodes.Node) -> None
        self.table.col = 0

        # fill columns if the row starts with the bottom of multirow cell
        while True:
            cell = self.table.cell(self.table.row, self.table.col)
            if cell is None:  # not a bottom of multirow cell
                break
            else:  # a bottom of multirow cell
                self.table.col += cell.width
                if cell.col:
                    self.body.append('&')
                if cell.width == 1:
                    # insert suitable strut for equalizing row heights in given multirow
                    self.body.append('\\sphinxtablestrut{%d}' % cell.cell_id)
                else:  # use \multicolumn for wide multirow cell
                    self.body.append('\\multicolumn{%d}{|l|}'
                                     '{\\sphinxtablestrut{%d}}' %
                                     (cell.width, cell.cell_id))

    def depart_row(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\\\\n')
        cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)]
        underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells]
        if all(underlined):
            self.body.append('\\hline')
        else:
            i = 0
            underlined.extend([False])  # sentinel
            while i < len(underlined):
                if underlined[i] is True:
                    j = underlined[i:].index(False)
                    self.body.append('\\cline{%d-%d}' % (i + 1, i + j))
                    i += j
                i += 1
        self.table.row += 1

    def visit_entry(self, node):
        # type: (nodes.Node) -> None
        if self.table.col > 0:
            self.body.append('&')
        self.table.add_cell(node.get('morerows', 0) + 1, node.get('morecols', 0) + 1)
        cell = self.table.cell()
        context = ''
        if cell.width > 1:
            if self.builder.config.latex_use_latex_multicolumn:
                if self.table.col == 0:
                    self.body.append('\\multicolumn{%d}{|l|}{%%\n' % cell.width)
                else:
                    self.body.append('\\multicolumn{%d}{l|}{%%\n' % cell.width)
                context = '}%\n'
            else:
                self.body.append('\\sphinxstartmulticolumn{%d}%%\n' % cell.width)
                context = '\\sphinxstopmulticolumn\n'
        if cell.height > 1:
            # \sphinxmultirow 2nd arg "cell_id" will serve as id for LaTeX macros as well
            self.body.append('\\sphinxmultirow{%d}{%d}{%%\n' % (cell.height, cell.cell_id))
            context = '}%\n' + context
        if cell.width > 1 or cell.height > 1:
            self.body.append('\\begin{varwidth}[t]{\\sphinxcolwidth{%d}{%d}}\n'
                             % (cell.width, self.table.colcount))
            context = ('\\par\n\\vskip-\\baselineskip'
                       '\\vbox{\\hbox{\\strut}}\\end{varwidth}%\n') + context
            self.needs_linetrimming = 1
        if len(node) > 2 and len(node.astext().split('\n')) > 2:
            self.needs_linetrimming = 1
        if len(node.traverse(nodes.paragraph)) >= 2:
            self.table.has_oldproblematic = True
        if isinstance(node.parent.parent, nodes.thead) or (cell.col in self.table.stubs):
            if len(node) == 1 and isinstance(node[0], nodes.paragraph) and node.astext() == '':
                pass
            else:
                self.body.append('\\sphinxstylethead{\\sphinxstyletheadfamily ')
                context = '\\unskip}\\relax ' + context
        if self.needs_linetrimming:
            self.pushbody([])
        self.context.append(context)

    def depart_entry(self, node):
        # type: (nodes.Node) -> None
        if self.needs_linetrimming:
            self.needs_linetrimming = 0
            body = self.popbody()

            # Remove empty lines from top of merged cell
            while body and body[0] == "\n":
                body.pop(0)
            self.body.extend(body)

        self.body.append(self.context.pop())

        cell = self.table.cell()
        self.table.col += cell.width

        # fill columns if next ones are a bottom of wide-multirow cell
        while True:
            nextcell = self.table.cell()
            if nextcell is None:  # not a bottom of multirow cell
                break
            else:  # a bottom part of multirow cell
                self.table.col += nextcell.width
                self.body.append('&')
                if nextcell.width == 1:
                    # insert suitable strut for equalizing row heights in multirow
                    # they also serve to clear colour panels which would hide the text
                    self.body.append('\\sphinxtablestrut{%d}' % nextcell.cell_id)
                else:
                    # use \multicolumn for wide multirow cell
                    self.body.append('\\multicolumn{%d}{l|}'
                                     '{\\sphinxtablestrut{%d}}' %
                                     (nextcell.width, nextcell.cell_id))

    def visit_acks(self, node):
        # type: (nodes.Node) -> None
        # this is a list in the source, but should be rendered as a
        # comma-separated list here
        self.body.append('\n\n')
        self.body.append(', '.join(n.astext()
                                   for n in node.children[0].children) + '.')
        self.body.append('\n\n')
        raise nodes.SkipNode

    def visit_bullet_list(self, node):
        # type: (nodes.Node) -> None
        if not self.compact_list:
            self.body.append('\\begin{itemize}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_bullet_list(self, node):
        # type: (nodes.Node) -> None
        if not self.compact_list:
            self.body.append('\\end{itemize}\n')

    def visit_enumerated_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\begin{enumerate}\n')
        if 'start' in node:
            self.body.append('\\setcounter{enumi}{%d}\n' % (node['start'] - 1))
        if self.table:
            self.table.has_problematic = True

    def depart_enumerated_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{enumerate}\n')

    def visit_list_item(self, node):
        # type: (nodes.Node) -> None
        # Append "{}" in case the next character is "[", which would break
        # LaTeX's list environment (no numbering and the "[" is not printed).
        self.body.append(r'\item {} ')

    def depart_list_item(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n')

    def visit_definition_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\begin{description}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_definition_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{description}\n')

    def visit_definition_list_item(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_definition_list_item(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_term(self, node):
        # type: (nodes.Node) -> None
        self.in_term += 1
        ctx = '}] \\leavevmode'  # type: unicode
        if node.get('ids'):
            ctx += self.hypertarget(node['ids'][0])
        self.body.append('\\item[{')
        self.restrict_footnote(node)
        self.context.append(ctx)

    def depart_term(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.context.pop())
        self.unrestrict_footnote(node)
        self.in_term -= 1

    def visit_classifier(self, node):
        # type: (nodes.Node) -> None
        self.body.append('{[}')

    def depart_classifier(self, node):
        # type: (nodes.Node) -> None
        self.body.append('{]}')

    def visit_definition(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_definition(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n')

    def visit_field_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\begin{quote}\\begin{description}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_field_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{description}\\end{quote}\n')

    def visit_field(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_field(self, node):
        # type: (nodes.Node) -> None
        pass

    visit_field_name = visit_term
    depart_field_name = depart_term

    visit_field_body = visit_definition
    depart_field_body = depart_definition

    def visit_paragraph(self, node):
        # type: (nodes.Node) -> None
        index = node.parent.index(node)
        if (index > 0 and isinstance(node.parent, nodes.compound) and
                not isinstance(node.parent[index - 1], nodes.paragraph) and
                not isinstance(node.parent[index - 1], nodes.compound)):
            # insert blank line, if the paragraph follows a non-paragraph node in a compound
            self.body.append('\\noindent\n')
        elif index == 0 and isinstance(node.parent, nodes.footnote):
            # don't insert blank line, if the paragraph is first child of a footnote
            pass
        else:
            self.body.append('\n')

    def depart_paragraph(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n')

    def visit_centered(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\begin{center}')
        if self.table:
            self.table.has_problematic = True

    def depart_centered(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\end{center}')

    def visit_hlist(self, node):
        # type: (nodes.Node) -> None
        # for now, we don't support a more compact list format
        # don't add individual itemize environments, but one for all columns
        self.compact_list += 1
        self.body.append('\\begin{itemize}\\setlength{\\itemsep}{0pt}'
                         '\\setlength{\\parskip}{0pt}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_hlist(self, node):
        # type: (nodes.Node) -> None
        self.compact_list -= 1
        self.body.append('\\end{itemize}\n')

    def visit_hlistcol(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_hlistcol(self, node):
        # type: (nodes.Node) -> None
        pass

    def latex_image_length(self, width_str):
        # type: (nodes.Node) -> unicode
        try:
            return rstdim_to_latexdim(width_str)
        except ValueError:
            logger.warning('dimension unit %s is invalid. Ignored.', width_str)
            return None

    def is_inline(self, node):
        # type: (nodes.Node) -> bool
        """Check whether a node represents an inline element."""
        return isinstance(node.parent, nodes.TextElement)

    def visit_image(self, node):
        # type: (nodes.Node) -> None
        attrs = node.attributes
        pre = []    # type: List[unicode]
                    # in reverse order
        post = []   # type: List[unicode]
        include_graphics_options = []
        is_inline = self.is_inline(node)
        if 'width' in attrs:
            w = self.latex_image_length(attrs['width'])
            if w:
                include_graphics_options.append('width=%s' % w)
        if 'height' in attrs:
            h = self.latex_image_length(attrs['height'])
            if h:
                include_graphics_options.append('height=%s' % h)
        if 'scale' in attrs:
            if include_graphics_options:
                # unfortunately passing "height=1cm,scale=2.0" to \includegraphics
                # does not result in a height of 2cm. We must scale afterwards.
                pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
                post.append('}')
            else:
                # if no "width" nor "height", \sphinxincludegraphics will fit
                # to the available text width if oversized after rescaling.
                include_graphics_options.append('scale=%s'
                                                % (float(attrs['scale']) / 100.0))
        if 'align' in attrs:
            align_prepost = {
                # By default latex aligns the top of an image.
                (1, 'top'): ('', ''),
                (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
                (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
                (0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'),
                # These 2 don't exactly do the right thing.  The image should
                # be floated alongside the paragraph.  See
                # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
                (0, 'left'): ('{', '\\hspace*{\\fill}}'),
                (0, 'right'): ('{\\hspace*{\\fill}', '}'),
            }
            try:
                pre.append(align_prepost[is_inline, attrs['align']][0])
                post.append(align_prepost[is_inline, attrs['align']][1])
            except KeyError:
                pass
        if self.in_parsed_literal:
            pre.append('{\\sphinxunactivateextrasandspace ')
            post.append('}')
        if not is_inline:
            pre.append('\n\\noindent')
            post.append('\n')
        pre.reverse()
        if node['uri'] in self.builder.images:
            uri = self.builder.images[node['uri']]
        else:
            # missing image!
            if self.ignore_missing_images:
                return
            uri = node['uri']
        if uri.find('://') != -1:
            # ignore remote images
            return
        self.body.extend(pre)
        options = ''
        if include_graphics_options:
            options = '[%s]' % ','.join(include_graphics_options)
        base, ext = path.splitext(uri)
        if self.in_title and base:
            # Lowercase tokens forcely because some fncychap themes capitalize
            # the options of \sphinxincludegraphics unexpectly (ex. WIDTH=...).
            self.body.append('\\lowercase{\\sphinxincludegraphics%s}{{%s}%s}' %
                             (options, base, ext))
        else:
            self.body.append('\\sphinxincludegraphics%s{{%s}%s}' %
                             (options, base, ext))
        self.body.extend(post)

    def depart_image(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_figure(self, node):
        # type: (nodes.Node) -> None
        ids = ''  # type: unicode
        for id in self.pop_hyperlink_ids('figure'):
            ids += self.hypertarget(id, anchor=False)
        if node['ids']:
            ids += self.hypertarget(node['ids'][0], anchor=False)
        self.restrict_footnote(node)
        if (len(node.children) and
           isinstance(node.children[0], nodes.image) and
           node.children[0]['ids']):
            ids += self.hypertarget(node.children[0]['ids'][0], anchor=False)
        if self.table:
            # TODO: support align option
            if 'width' in node:
                length = self.latex_image_length(node['width'])
                if length:
                    self.body.append('\\begin{sphinxfigure-in-table}[%s]\n'
                                     '\\centering\n' % length)
            else:
                self.body.append('\\begin{sphinxfigure-in-table}\n\\centering\n')
            if any(isinstance(child, nodes.caption) for child in node):
                self.body.append('\\capstart')
            self.context.append(ids + '\\end{sphinxfigure-in-table}\\relax\n')
        elif node.get('align', '') in ('left', 'right'):
            length = None
            if 'width' in node:
                length = self.latex_image_length(node['width'])
            elif 'width' in node[0]:
                length = self.latex_image_length(node[0]['width'])
            self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
                             (node['align'] == 'right' and 'r' or 'l', length or '0pt'))
            self.context.append(ids + '\\end{wrapfigure}\n')
        elif self.in_minipage:
            if ('align' not in node.attributes or
                    node.attributes['align'] == 'center'):
                self.body.append('\n\\begin{center}')
                self.context.append('\\end{center}\n')
            else:
                self.body.append('\n\\begin{flush%s}' % node.attributes['align'])
                self.context.append('\\end{flush%s}\n' % node.attributes['align'])
        else:
            if ('align' not in node.attributes or
                    node.attributes['align'] == 'center'):
                # centering does not add vertical space like center.
                align = '\n\\centering'
                align_end = ''
            else:
                # TODO non vertical space for other alignments.
                align = '\\begin{flush%s}' % node.attributes['align']
                align_end = '\\end{flush%s}' % node.attributes['align']
            self.body.append('\n\\begin{figure}[%s]%s\n' % (
                self.elements['figure_align'], align))
            if any(isinstance(child, nodes.caption) for child in node):
                self.body.append('\\capstart\n')
            self.context.append(ids + align_end + '\\end{figure}\n')

    def depart_figure(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.context.pop())
        self.unrestrict_footnote(node)

    def visit_caption(self, node):
        # type: (nodes.Node) -> None
        self.in_caption += 1
        self.restrict_footnote(node)
        if self.in_container_literal_block:
            self.body.append('\\sphinxSetupCaptionForVerbatim{')
        elif self.in_minipage and isinstance(node.parent, nodes.figure):
            self.body.append('\\captionof{figure}{')
        elif self.table and node.parent.tagname == 'figure':
            self.body.append('\\sphinxfigcaption{')
        else:
            self.body.append('\\caption{')

    def depart_caption(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')
        self.in_caption -= 1
        self.unrestrict_footnote(node)

    def visit_legend(self, node):
        self.body.append('\n\\begin{sphinxlegend}')

    def depart_legend(self, node):
        self.body.append('\\end{sphinxlegend}\n')

    def visit_admonition(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\begin{sphinxadmonition}{note}')

    def depart_admonition(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{sphinxadmonition}\n')

    def _make_visit_admonition(name):
        # type: (unicode) -> Callable[[LaTeXTranslator, nodes.Node], None]
        def visit_admonition(self, node):
            # type: (nodes.Node) -> None
            self.body.append(u'\n\\begin{sphinxadmonition}{%s}{%s:}' %
                             (name, admonitionlabels[name]))
        return visit_admonition

    def _depart_named_admonition(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{sphinxadmonition}\n')

    visit_attention = _make_visit_admonition('attention')
    depart_attention = _depart_named_admonition
    visit_caution = _make_visit_admonition('caution')
    depart_caution = _depart_named_admonition
    visit_danger = _make_visit_admonition('danger')
    depart_danger = _depart_named_admonition
    visit_error = _make_visit_admonition('error')
    depart_error = _depart_named_admonition
    visit_hint = _make_visit_admonition('hint')
    depart_hint = _depart_named_admonition
    visit_important = _make_visit_admonition('important')
    depart_important = _depart_named_admonition
    visit_note = _make_visit_admonition('note')
    depart_note = _depart_named_admonition
    visit_tip = _make_visit_admonition('tip')
    depart_tip = _depart_named_admonition
    visit_warning = _make_visit_admonition('warning')
    depart_warning = _depart_named_admonition

    def visit_versionmodified(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_versionmodified(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_target(self, node):
        # type: (nodes.Node) -> None
        def add_target(id):
            # type: (unicode) -> None
            # indexing uses standard LaTeX index markup, so the targets
            # will be generated differently
            if id.startswith('index-'):
                return

            # insert blank line, if the target follows a paragraph node
            index = node.parent.index(node)
            if index > 0 and isinstance(node.parent[index - 1], nodes.paragraph):
                self.body.append('\n')

            # do not generate \phantomsection in \section{}
            anchor = not self.in_title
            self.body.append(self.hypertarget(id, anchor=anchor))

        # postpone the labels until after the sectioning command
        parindex = node.parent.index(node)
        try:
            try:
                next = node.parent[parindex + 1]
            except IndexError:
                # last node in parent, look at next after parent
                # (for section of equal level) if it exists
                if node.parent.parent is not None:
                    next = node.parent.parent[
                        node.parent.parent.index(node.parent)]
                else:
                    raise
            if isinstance(next, nodes.section):
                if node.get('refid'):
                    self.next_section_ids.add(node['refid'])
                self.next_section_ids.update(node['ids'])
                return
            else:
                domain = self.builder.env.get_domain('std')
                figtype = domain.get_figtype(next)
                if figtype and domain.get_numfig_title(next):
                    ids = set()
                    # labels for figures go in the figure body, not before
                    if node.get('refid'):
                        ids.add(node['refid'])
                    ids.update(node['ids'])
                    self.push_hyperlink_ids(figtype, ids)
                    return
        except IndexError:
            pass
        if 'refuri' in node:
            return
        if node.get('refid'):
            add_target(node['refid'])
        for id in node['ids']:
            add_target(id)

    def depart_target(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_attribution(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\begin{flushright}\n')
        self.body.append('---')

    def depart_attribution(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\end{flushright}\n')

    def visit_index(self, node, scre=re.compile(r';\s*')):
        # type: (nodes.Node, Pattern) -> None
        if not node.get('inline', True):
            self.body.append('\n')
        entries = node['entries']
        for type, string, tid, ismain, key_ in entries:
            m = ''
            if ismain:
                m = '|textbf'
            try:
                if type == 'single':
                    p = scre.sub('!', self.encode(string))
                    self.body.append(r'\index{%s%s}' % (p, m))
                elif type == 'pair':
                    p1, p2 = [self.encode(x) for x in split_into(2, 'pair', string)]
                    self.body.append(r'\index{%s!%s%s}\index{%s!%s%s}' %
                                     (p1, p2, m, p2, p1, m))
                elif type == 'triple':
                    p1, p2, p3 = [self.encode(x)
                                  for x in split_into(3, 'triple', string)]
                    self.body.append(
                        r'\index{%s!%s %s%s}\index{%s!%s, %s%s}'
                        r'\index{%s!%s %s%s}' %
                        (p1, p2, p3, m, p2, p3, p1, m, p3, p1, p2, m))
                elif type == 'see':
                    p1, p2 = [self.encode(x) for x in split_into(2, 'see', string)]
                    self.body.append(r'\index{%s|see{%s}}' % (p1, p2))
                elif type == 'seealso':
                    p1, p2 = [self.encode(x) for x in split_into(2, 'seealso', string)]
                    self.body.append(r'\index{%s|see{%s}}' % (p1, p2))
                else:
                    logger.warning('unknown index entry type %s found', type)
            except ValueError as err:
                logger.warning(str(err))
        if not node.get('inline', True):
            self.body.append('\\ignorespaces ')
        raise nodes.SkipNode

    def visit_raw(self, node):
        # type: (nodes.Node) -> None
        if not self.is_inline(node):
            self.body.append('\n')
        if 'latex' in node.get('format', '').split():
            self.body.append(node.astext())
        if not self.is_inline(node):
            self.body.append('\n')
        raise nodes.SkipNode

    def visit_reference(self, node):
        # type: (nodes.Node) -> None
        if not self.in_title:
            for id in node.get('ids'):
                anchor = not self.in_caption
                self.body += self.hypertarget(id, anchor=anchor)
        uri = node.get('refuri', '')
        if not uri and node.get('refid'):
            uri = '%' + self.curfilestack[-1] + '#' + node['refid']
        if self.in_title or not uri:
            self.context.append('')
        elif uri.startswith('#'):
            # references to labels in the same document
            id = self.curfilestack[-1] + ':' + uri[1:]
            self.body.append(self.hyperlink(id))
            self.body.append(r'\emph{')
            if self.builder.config.latex_show_pagerefs and not \
                    self.in_production_list:
                self.context.append('}}} (%s)' % self.hyperpageref(id))
            else:
                self.context.append('}}}')
        elif uri.startswith('%'):
            # references to documents or labels inside documents
            hashindex = uri.find('#')
            if hashindex == -1:
                # reference to the document
                id = uri[1:] + '::doc'
            else:
                # reference to a label
                id = uri[1:].replace('#', ':')
            self.body.append(self.hyperlink(id))
            if len(node) and hasattr(node[0], 'attributes') and \
               'std-term' in node[0].get('classes', []):
                # don't add a pageref for glossary terms
                self.context.append('}}}')
                # mark up as termreference
                self.body.append(r'\sphinxtermref{')
            else:
                self.body.append(r'\sphinxcrossref{')
                if self.builder.config.latex_show_pagerefs and not \
                   self.in_production_list:
                    self.context.append('}}} (%s)' % self.hyperpageref(id))
                else:
                    self.context.append('}}}')
        else:
            if len(node) == 1 and uri == node[0]:
                if node.get('nolinkurl'):
                    self.body.append('\\sphinxnolinkurl{%s}' % self.encode_uri(uri))
                else:
                    self.body.append('\\sphinxurl{%s}' % self.encode_uri(uri))
                raise nodes.SkipNode
            else:
                self.body.append('\\sphinxhref{%s}{' % self.encode_uri(uri))
                self.context.append('}')

    def depart_reference(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.context.pop())

    def visit_number_reference(self, node):
        # type: (nodes.Node) -> None
        if node.get('refid'):
            id = self.curfilestack[-1] + ':' + node['refid']
        else:
            id = node.get('refuri', '')[1:].replace('#', ':')

        title = node.get('title', '%s')
        title = text_type(title).translate(tex_escape_map).replace('\\%s', '%s')
        if '\\{name\\}' in title or '\\{number\\}' in title:
            # new style format (cf. "Fig.%{number}")
            title = title.replace('\\{name\\}', '{name}').replace('\\{number\\}', '{number}')
            text = escape_abbr(title).format(name='\\nameref{%s}' % self.idescape(id),
                                             number='\\ref{%s}' % self.idescape(id))
        else:
            # old style format (cf. "Fig.%{number}")
            text = escape_abbr(title) % ('\\ref{%s}' % self.idescape(id))
        hyperref = '\\hyperref[%s]{%s}' % (self.idescape(id), text)
        self.body.append(hyperref)

        raise nodes.SkipNode

    def visit_download_reference(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_download_reference(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_pending_xref(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_pending_xref(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_emphasis(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxstyleemphasis{')

    def depart_emphasis(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_literal_emphasis(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxstyleliteralemphasis{')
        self.no_contractions += 1

    def depart_literal_emphasis(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')
        self.no_contractions -= 1

    def visit_strong(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxstylestrong{')

    def depart_strong(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_literal_strong(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxstyleliteralstrong{')
        self.no_contractions += 1

    def depart_literal_strong(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')
        self.no_contractions -= 1

    def visit_abbreviation(self, node):
        # type: (nodes.Node) -> None
        abbr = node.astext()
        self.body.append(r'\sphinxstyleabbreviation{')
        # spell out the explanation once
        if node.hasattr('explanation') and abbr not in self.handled_abbrs:
            self.context.append('} (%s)' % self.encode(node['explanation']))
            self.handled_abbrs.add(abbr)
        else:
            self.context.append('}')

    def depart_abbreviation(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.context.pop())

    def visit_manpage(self, node):
        # type: (nodes.Node) -> Any
        return self.visit_literal_emphasis(node)

    def depart_manpage(self, node):
        # type: (nodes.Node) -> Any
        return self.depart_literal_emphasis(node)

    def visit_title_reference(self, node):
        # type: (nodes.Node) -> None
        self.body.append(r'\sphinxtitleref{')

    def depart_title_reference(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}')

    def visit_citation(self, node):
        # type: (nodes.Node) -> None
        # TODO maybe use cite bibitems
        # bibitem: [citelabel, citetext, docname, citeid]
        self.bibitems.append(['', '', '', ''])
        self.context.append(len(self.body))

    def depart_citation(self, node):
        # type: (nodes.Node) -> None
        size = self.context.pop()
        text = ''.join(self.body[size:])
        del self.body[size:]
        self.bibitems[-1][1] = text

    def visit_citation_reference(self, node):
        # type: (nodes.Node) -> None
        # This is currently never encountered, since citation_reference nodes
        # are already replaced by pending_xref nodes in the environment.
        self.body.append('\\cite{%s}' % self.idescape(node.astext()))
        raise nodes.SkipNode

    def visit_literal(self, node):
        # type: (nodes.Node) -> None
        self.no_contractions += 1
        if self.in_title:
            self.body.append(r'\sphinxstyleliteralintitle{')
        else:
            self.body.append(r'\sphinxcode{')

    def depart_literal(self, node):
        # type: (nodes.Node) -> None
        self.no_contractions -= 1
        self.body.append('}')

    def visit_footnote_reference(self, node):
        # type: (nodes.Node) -> None
        num = node.astext().strip()
        try:
            footnode, used = self.footnotestack[-1][num]
        except (KeyError, IndexError):
            raise nodes.SkipNode
        # if a footnote has been inserted once, it shouldn't be repeated
        # by the next reference
        if used:
            self.body.append('\\sphinxfootnotemark[%s]' % num)
        elif self.footnote_restricted:
            self.footnotestack[-1][num][1] = True
            self.body.append('\\sphinxfootnotemark[%s]' % num)
            self.pending_footnotes.append(footnode)
        else:
            self.footnotestack[-1][num][1] = True
            footnode.walkabout(self)  # type: ignore
        raise nodes.SkipChildren

    def depart_footnote_reference(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_literal_block(self, node):
        # type: (nodes.Node) -> None
        if node.rawsource != node.astext():
            # most probably a parsed-literal block -- don't highlight
            self.in_parsed_literal += 1
            self.body.append('\\begin{sphinxalltt}\n')
        else:
            ids = ''  # type: unicode
            for id in self.pop_hyperlink_ids('code-block'):
                ids += self.hypertarget(id, anchor=False)
            if node['ids']:
                # suppress with anchor=False \phantomsection insertion
                ids += self.hypertarget(node['ids'][0], anchor=False)
            # LaTeX code will insert \phantomsection prior to \label
            if ids and not self.in_footnote:
                self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + ids + '}')
            code = node.astext()
            lang = self.hlsettingstack[-1][0]
            linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
            highlight_args = node.get('highlight_args', {})
            hllines = '\\fvset{hllines={, %s,}}%%' %\
                      str(highlight_args.get('hl_lines', []))[1:-1]
            if 'language' in node:
                # code-block directives
                lang = node['language']
                highlight_args['force'] = True
            if 'linenos' in node:
                linenos = node['linenos']
            if lang is self.hlsettingstack[0][0]:
                # only pass highlighter options for original language
                opts = self.builder.config.highlight_options
            else:
                opts = {}

            hlcode = self.highlighter.highlight_block(
                code, lang, opts=opts, linenos=linenos,
                location=(self.curfilestack[-1], node.line), **highlight_args
            )
            # workaround for Unicode issue
            hlcode = hlcode.replace(u'€', u'@texteuro[]')
            if self.in_footnote:
                self.body.append('\n\\sphinxSetupCodeBlockInFootnote')
                hlcode = hlcode.replace('\\begin{Verbatim}',
                                        '\\begin{sphinxVerbatim}')
            # if in table raise verbatim flag to avoid "tabulary" environment
            # and opt for sphinxVerbatimintable to handle caption & long lines
            elif self.table:
                self.table.has_problematic = True
                self.table.has_verbatim = True
                hlcode = hlcode.replace('\\begin{Verbatim}',
                                        '\\begin{sphinxVerbatimintable}')
            else:
                hlcode = hlcode.replace('\\begin{Verbatim}',
                                        '\\begin{sphinxVerbatim}')
            # get consistent trailer
            hlcode = hlcode.rstrip()[:-14]  # strip \end{Verbatim}
            if self.table and not self.in_footnote:
                hlcode += '\\end{sphinxVerbatimintable}'
            else:
                hlcode += '\\end{sphinxVerbatim}'
            self.body.append('\n' + hllines + '\n' + hlcode + '\n')
            if ids:
                self.body.append('\\let\\sphinxLiteralBlockLabel\\empty\n')
            raise nodes.SkipNode

    def depart_literal_block(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n\\end{sphinxalltt}\n')
        self.in_parsed_literal -= 1
    visit_doctest_block = visit_literal_block
    depart_doctest_block = depart_literal_block

    def visit_line(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\item[] ')

    def depart_line(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n')

    def visit_line_block(self, node):
        # type: (nodes.Node) -> None
        if isinstance(node.parent, nodes.line_block):
            self.body.append('\\item[]\n'
                             '\\begin{DUlineblock}{\\DUlineblockindent}\n')
        else:
            self.body.append('\n\\begin{DUlineblock}{0em}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_line_block(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{DUlineblock}\n')

    def visit_block_quote(self, node):
        # type: (nodes.Node) -> None
        # If the block quote contains a single object and that object
        # is a list, then generate a list not a block quote.
        # This lets us indent lists.
        done = 0
        if len(node.children) == 1:
            child = node.children[0]
            if isinstance(child, nodes.bullet_list) or \
                    isinstance(child, nodes.enumerated_list):
                done = 1
        if not done:
            self.body.append('\\begin{quote}\n')
            if self.table:
                self.table.has_problematic = True

    def depart_block_quote(self, node):
        # type: (nodes.Node) -> None
        done = 0
        if len(node.children) == 1:
            child = node.children[0]
            if isinstance(child, nodes.bullet_list) or \
                    isinstance(child, nodes.enumerated_list):
                done = 1
        if not done:
            self.body.append('\\end{quote}\n')

    # option node handling copied from docutils' latex writer

    def visit_option(self, node):
        # type: (nodes.Node) -> None
        if self.context[-1]:
            # this is not the first option
            self.body.append(', ')

    def depart_option(self, node):
        # type: (nodes.Node) -> None
        # flag that the first option is done.
        self.context[-1] += 1

    def visit_option_argument(self, node):
        # type: (nodes.Node) -> None
        """The delimiter betweeen an option and its argument."""
        self.body.append(node.get('delimiter', ' '))

    def depart_option_argument(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_option_group(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\item [')
        # flag for first option
        self.context.append(0)

    def depart_option_group(self, node):
        # type: (nodes.Node) -> None
        self.context.pop()  # the flag
        self.body.append('] ')

    def visit_option_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\begin{optionlist}{3cm}\n')
        if self.table:
            self.table.has_problematic = True

    def depart_option_list(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\\end{optionlist}\n')

    def visit_option_list_item(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_option_list_item(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_option_string(self, node):
        # type: (nodes.Node) -> None
        ostring = node.astext()
        self.no_contractions += 1
        self.body.append(self.encode(ostring))
        self.no_contractions -= 1
        raise nodes.SkipNode

    def visit_description(self, node):
        # type: (nodes.Node) -> None
        self.body.append(' ')

    def depart_description(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_superscript(self, node):
        # type: (nodes.Node) -> None
        self.body.append('$^{\\text{')

    def depart_superscript(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}}$')

    def visit_subscript(self, node):
        # type: (nodes.Node) -> None
        self.body.append('$_{\\text{')

    def depart_subscript(self, node):
        # type: (nodes.Node) -> None
        self.body.append('}}$')

    def visit_substitution_definition(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    def visit_substitution_reference(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    def visit_inline(self, node):
        # type: (nodes.Node) -> None
        classes = node.get('classes', [])
        if classes in [['menuselection'], ['guilabel']]:
            self.body.append(r'\sphinxmenuselection{')
            self.context.append('}')
        elif classes in [['accelerator']]:
            self.body.append(r'\sphinxaccelerator{')
            self.context.append('}')
        elif classes and not self.in_title:
            self.body.append(r'\DUrole{%s}{' % ','.join(classes))
            self.context.append('}')
        else:
            self.context.append('')

    def depart_inline(self, node):
        # type: (nodes.Node) -> None
        self.body.append(self.context.pop())

    def visit_generated(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_generated(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_compound(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_compound(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_container(self, node):
        # type: (nodes.Node) -> None
        if node.get('literal_block'):
            self.in_container_literal_block += 1
            ids = ''  # type: unicode
            for id in self.pop_hyperlink_ids('code-block'):
                ids += self.hypertarget(id, anchor=False)
            if node['ids']:
                # suppress with anchor=False \phantomsection insertion
                ids += self.hypertarget(node['ids'][0], anchor=False)
            # define label for use in caption.
            if ids:
                self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + ids + '}\n')

    def depart_container(self, node):
        # type: (nodes.Node) -> None
        if node.get('literal_block'):
            self.in_container_literal_block -= 1
            self.body.append('\\let\\sphinxVerbatimTitle\\empty\n')
            self.body.append('\\let\\sphinxLiteralBlockLabel\\empty\n')

    def visit_decoration(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_decoration(self, node):
        # type: (nodes.Node) -> None
        pass

    # docutils-generated elements that we don't support

    def visit_header(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    def visit_footer(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    def visit_docinfo(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    # text handling

    def encode(self, text):
        # type: (unicode) -> unicode
        text = text_type(text).translate(tex_escape_map)
        if self.literal_whitespace:
            # Insert a blank before the newline, to avoid
            # ! LaTeX Error: There's no line here to end.
            text = text.replace(u'\n', u'~\\\\\n').replace(u' ', u'~')
        if self.no_contractions:
            text = text.replace('--', u'-{-}')
            text = text.replace("''", u"'{'}")
        return text

    def encode_uri(self, text):
        # type: (unicode) -> unicode
        # in \href, the tilde is allowed and must be represented literally
        return self.encode(text).replace('\\textasciitilde{}', '~')

    def visit_Text(self, node):
        # type: (nodes.Node) -> None
        text = self.encode(node.astext())
        self.body.append(text)

    def depart_Text(self, node):
        # type: (nodes.Node) -> None
        pass

    def visit_comment(self, node):
        # type: (nodes.Node) -> None
        raise nodes.SkipNode

    def visit_meta(self, node):
        # type: (nodes.Node) -> None
        # only valid for HTML
        raise nodes.SkipNode

    def visit_system_message(self, node):
        # type: (nodes.Node) -> None
        pass

    def depart_system_message(self, node):
        # type: (nodes.Node) -> None
        self.body.append('\n')

    def visit_math(self, node):
        # type: (nodes.Node) -> None
        logger.warning('using "math" markup without a Sphinx math extension '
                       'active, please use one of the math extensions '
                       'described at http://sphinx-doc.org/ext/math.html',
                       location=(self.curfilestack[-1], node.line))
        raise nodes.SkipNode

    visit_math_block = visit_math

    def unknown_visit(self, node):
        # type: (nodes.Node) -> None
        raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
