# LaTeX.pm: output tree as LaTeX
#
# Copyright 2010-2022 Free Software Foundation, Inc.
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# TODO
#
# RELEVANT BUT NOT DECISIVE
#
# If @allowcodebreaks is false, - should be set such that it is not a
# breaking point anymore.
#
# Note that the texinfo.tex code does more, for instance turns off normal
# hyphenation, sets plainfrenchspacing (see \tclose), and sets line breaks
# at _ and - with several special cases, such as no break right after one
# or two hyphen, no break between __ or hyphen.  See near \global\def\code
# in texinfo.tex.
#
#
# breaking in urls is not implemented, maybe there is some support already in
# hyperref.  @urefbreakstyle, @/
#
# Need some thinking/discussion for translations.  In texi2any?  In LaTeX?  If
# in texi2any in general no need for something as complex as gdt leading to a
# Texinfo tree, as we can provide strings to be translated already in LaTeX
# with the same property than Texinfo strings with LaTeX commands avoiding,
# to some extent, dependence on the language and/or encoding.
#
# It seems that \chaptername doesn't become Appendix for a sectioning command
# appearing after \appendix
#
# command that could be used for translation \sectionname does not exist in the
# default case.  it is defined in the pagenote package together with \pagename
# which is page in the default case, but it is unclear if this can be used as a
# basis for translations
#
# The @afourpaper, @afourlatex and @afourwide commands all map to
# papername=a4paper.  It is most likely ok for @afourlatex, but the other two
# should be associated with other geometries.
#
# The \geometry command does not really reset the geometry after
# \begin{document} according to the documentation, something else should be
# used to switch paper definition after document begin if needed.  Not
# necessarily a bad thing not to be able to change paper geometry after the
# beginning of the document.
#
# @pagesizes uses \newgeometry which forgets about previous settings except for
# paper size.  It could be a good thing to change geometry that way, but it is
# not how Texinfo TeX does it.
#
# The environment used for @quotation is quote as it seems to match in term of
# output, but the description of quote does not really match with what is
# supposed to be the @quotation output.
#
# Proper indenting of nested environments, @exdent from nested environment
#
# should we percent encode @email and @uref/@url urls?
#
# Some customization would be relevant, for example the
# document type, fonts, packages...  In particular, as reported by Karl,
# people will surely want to run xelatex or lualatex and use ttf/otf
# fonts, which means getting rid of the {fontenc} call entirely. (And
# using \usepackage{fontspec}\usepackage{unicode-math} instead.)
# We can also imagine people wanting different euro symbols.
# Maybe each one of the default \usepackage's should be configurable.
#
# @shortcontents is not implemented.  Tried shorttoc package but it
# has two limitations that are not in Texinfo, need a main \tableofcontents
# and need to be before @contents.  A code snippet looked good for a
# @shortcontents after @contents, but not before:
#{
#\renewcommand*{\contentsname}{Short contents}
#\setcounter{tocdepth}{0}
#\expandafter\def\csname @starttoc\endcsname#1{\InputIfFileExists{\jobname.#1}{}{}}%
#\tableofcontents
#}
# We could treat LaTeX like HTML with CONTENTS_OUTPUT_LOCATION=aftertop and
# ignore both @contents and @shortcontents.
#
# it seems that \indent only works with \setlength{\parindent}{0pt}
# which makes it quite different from Texinfo @indent.  Implement
# something differently to have the expected output with @indent?
#
#
# CAN WAIT
#
# Use texinfo.cnf?  Here?  in texi2any.pl?
#
# @need is implemented in a specific way, maybe there could be a definition of
# \mil instead.
#
# @group is not implemented.  We could try the minipage environment.
#
# @fonttextsize with \changefontsize does not seems to change fonts much.  It
# seems to change in the text, but only 10pt, and does not seems to change
# sections font sizes.
#
# The \listof formatting does not look so good.  Also it does not use the type
# (name) of float.
#
# in TeX, acronym is in a smaller font (1pt less). Can this be easily done in
# LaTeX?  Is it really useful to do that?
#
# interline spacing in @title multi lines in @titlepage and between multiple
# @author is not as good as in Texinfo TeX output/ example titlepage_classical
# in t/latex_tests.t can show it.
#
# shorttitlepage in Texinfo TeX does not seem to break line.  Probably ok.
#
# for external references it seems that Texinfo TeX points to a file, it could
# be relevant to do the same in LaTeX.
#
# Nothing specific is done for @headings singleafter and @headings doubleafter
# compared to @headings single and @headings double
#
# The support of \global\urefurlonlylinktrue would be rather easy, but
# need to make it a proper @-command first.  Similar for
# \global\def\linkcolor and \global\def\urlcolor.  There are options for
# colors in hyperref, like linkbordercolor but it is unclear whether it
# can be used to distinguish links and urls.
#
# In the manual it is said that majorheading generates a larger vertical
# whitespace before the heading than @chapheading command.  It is not
# implemented.  However, it seems that the chapter level commands in LaTeX
# generate bigger fonts and much more vertical whitespace than in Texinfo TeX
# so maybe it is not needed to do something here.
#
# index entry between @table and first @item causes an empty \item[] to be
# output.
#
# @headitem row is not formatted differently from an item row.  It is
# possible to use the multitable_head to get the same information.
#
# if there is a prototype for a multitable, it is converted to plain
# text to determine the fraction.  If it was done differently, @headitemfont
# may need to be processed, too.
#
# In Texinfo TeX, @code{@slanted{slanted in code}} and
# @slanted{@code{code in slanted}} are different,
# @slanted{@code{code in slanted}} is not slanted.
# According to Karl, we should not try to specify what nested style commands
# should be formatted as.  So this should probably not be considered
# as an issue, at least until we get user reports.
# https://lists.gnu.org/archive/html/bug-texinfo/2006-06/msg00030.html
#
# @indentedblock and @smallindentedblock should not have a wider right
# margin.  The wider margin is because they are in quote environment.
#
# @def* body should not have a wider right margin.  The wider margin
# is because they are in quote environment.

package Texinfo::Convert::LaTeX;

use 5.00405;

# See comment at start of HTML.pm
use if $] >= 5.012, feature => qw(unicode_strings);

use strict;

# To check if there is no erroneous autovivification
#no autovivification qw(fetch delete exists store strict);

use File::Spec;

use Carp qw(cluck confess);

use Texinfo::Commands;
use Texinfo::Common;
use Texinfo::Convert::Texinfo;
use Texinfo::Convert::NodeNameNormalization;
use Texinfo::Convert::Text;
use Texinfo::Convert::Converter;

use vars qw($VERSION @ISA);
@ISA = qw(Texinfo::Convert::Converter);

$VERSION = '7.0.1';

# could export convert_to_latex_math


# commands that are of use for formatting.
my %formatted_line_commands = %Texinfo::Common::formatted_line_commands;
my %formatted_nobrace_commands = %Texinfo::Common::formatted_nobrace_commands;
my %formattable_line_commands = %Texinfo::Common::formattable_line_commands;

my %paper_geometry_commands = (
  'afourpaper' => 'papername=a4paper',
  'afourlatex' => 'papername=a4paper',
  'afourwide' => 'papername=a4paper',
  'afivepaper' => 'papername=a5paper',
  'bsixpaper' => 'papername=b6paper',
  'smallbook' => 'paperheight=9.25in,paperwidth=7in',
);

my %informative_commands;
foreach my $informative_command (keys (%Texinfo::Common::document_settable_at_commands)) {
  $informative_commands{$informative_command} = 1;
}

my %brace_no_arg_commands;
foreach my $command (keys (%Texinfo::Commands::brace_commands)) {
  $brace_no_arg_commands{$command} = 1
    if ($Texinfo::Commands::brace_commands{$command} eq 'noarg');
}
my %accent_commands = %Texinfo::Commands::accent_commands;
my %line_commands = %Texinfo::Commands::line_commands;
my %nobrace_commands = %Texinfo::Commands::nobrace_commands;
my %sectioning_heading_commands = %Texinfo::Commands::sectioning_heading_commands;
my %def_commands = %Texinfo::Commands::def_commands;
my %ref_commands = %Texinfo::Commands::ref_commands;
my %block_commands = %Texinfo::Commands::block_commands;
my %root_commands = %Texinfo::Commands::root_commands;
my %preformatted_commands = %Texinfo::Commands::preformatted_commands;
my %math_commands = %Texinfo::Commands::math_commands;
my %preformatted_code_commands = %Texinfo::Commands::preformatted_code_commands;
my %default_index_commands = %Texinfo::Commands::default_index_commands;
my %heading_spec_commands = %Texinfo::Commands::heading_spec_commands;
my %letter_no_arg_commands = %Texinfo::Commands::letter_no_arg_commands;

my %nobrace_symbol_text = %Texinfo::Common::nobrace_symbol_text;
my %explained_commands = %Texinfo::Common::explained_commands;
my %inline_format_commands = %Texinfo::Common::inline_format_commands;
my %brace_code_commands       = %Texinfo::Common::brace_code_commands;
my %unformatted_brace_command = %Texinfo::Common::unformatted_brace_command;

my %preamble_commands = %Texinfo::Common::preamble_commands;

foreach my $kept_command (keys(%informative_commands),
   keys(%default_index_commands),
   keys(%formattable_line_commands)) {
  $formatted_line_commands{$kept_command} = 1;
}

foreach my $def_command (keys(%def_commands)) {
  $formatted_line_commands{$def_command} = 1 if ($line_commands{$def_command});
}

# TODO command that could be used for translation \sectionname does
# not exist in the default case.  it is defined in the pagenote package
# together with \pagename which is page in the default case, but it is unclear
# if this can be used as a basis for translations
my %LaTeX_in_heading_commands_formatting = (
  # default for texinfo.tex is similar:
  #   \putwordChapter{} \thischapternum: \thischaptername}
  # see doc/txi-zh.tex for how it could be in chinese
  'thischapter' => '\chaptername{} \thechapter{} \chaptertitle{}',
  'thischaptername' => '\chaptertitle{}',
  'thischapternum' => '\thechapter{}',
  #  default for texinfo.tex is similar:
  #   \putwordSection{} \thissectionnum: \thissectionname}
  #'thissection' => '\sectionname{} \thesection{} \sectiontitle{}',
  'thissection' => 'Section \thesection{} \sectiontitle{}',
  'thissectionname' => '\sectiontitle{}',
  'thissectionnum' => '\thesection{}',
  'thisfile' => '',
  'thispage' => '\thepage{}',
  'thistitle' => '\Texinfosettitle{}',
);

foreach my $kept_command (keys(%LaTeX_in_heading_commands_formatting),
                                       'indent', 'noindent') {
  $formatted_nobrace_commands{$kept_command} = 1;
}

my %block_math_commands;
foreach my $block_math_command (keys(%math_commands)) {
  if (exists($block_commands{$block_math_command})) {
    $block_math_commands{$block_math_command} = 1;
  }
}

my %ignored_line_commands;
foreach my $line_command (keys(%line_commands)) {
  $ignored_line_commands{$line_command} = 1
    unless ($formatted_line_commands{$line_command});
}

my %ignored_nobrace_commands;
foreach my $nobrace_command (keys(%nobrace_commands)) {
  $ignored_nobrace_commands{$nobrace_command} = 1
    unless ($formatted_nobrace_commands{$nobrace_command});
}

# from \def\Gin@extensions in graphics-def/pdftex.def
my @LaTeX_image_extensions = (
'pdf','png','jpg','mps','jpeg','jbig2','jb2','PDF','PNG','JPG','JPEG','JBIG2','JB2');

my %section_map = (
   'top' => 'part*',
   'part' => 'part',
   'chapter' => 'chapter',
   'section' => 'section',
   'subsection' => 'subsection',
   'subsubsection' => 'subsubsection',
   # embed in a \Texinfonopagebreakheading call to remove pagebreaks
   'chapheading' => 'Texinfonopagebreakheading{\chapter*}',
   'majorheading' => 'Texinfonopagebreakheading{\chapter*}',
   'heading' => 'section*',
   'subheading' => 'subsection*',
   'subsubheading' => 'subsubsection*',
   'unnumbered' => 'chapter*',
   'centerchap' => 'chapter*',
   'unnumberedsec' => 'section*',
   'unnumberedsubsec' => 'subsection*',
   'unnumberedsubsubsec' => 'subsubsection*',
   'appendix' => 'chapter',
   'appendixsec' => 'section',
   'appendixsubsec' => 'subsection',
   'appendixsubsubsec' => 'subsubsection',
);

my %LaTeX_no_arg_brace_commands = (
   # textmode
  'cmd_text' => {
    'TeX' => '\TeX{}',
    'LaTeX' => '\LaTeX{}',
    'bullet' => '\textbullet{}',
    'copyright' => '\copyright{}',
    'registeredsymbol' => '\circledR{}',
    'dots' => '\dots{}\@',
    'enddots' => '\dots{}',
    'equiv' => '$\equiv{}$',
    'error' => '\fbox{error}',
    'expansion' => '$\mapsto{}$',
    'arrow' => '$\rightarrow{}$',
    'minus' => '-',
    'point' => '$\star{}$',
    'print' => '$\dashv{}$',
    'result' => '$\Rightarrow{}$',
    'pounds' => '\textsterling{}',
    'atchar', => '@',
    'lbracechar' => '\{',
    'rbracechar' => '\}',
    'backslashchar' => '\textbackslash{}',
    'hashchar' => '\#',
    'comma' => ',',
    'ampchar' => '\&',
    'euro' => '\euro{}',
    'geq' => '$\geq{}$',
    'leq' => '$\leq{}$',
    'textdegree' => '\textdegree{}',
    'today' => '\today{}',
    'tie' => '~'
  },
  'cmd_math' => {
    # error in math with \TeX \LaTeX, spacing command used not allowed
    # so use plain text
    'TeX' => 'TeX',
    'LaTeX' => 'LaTeX',
    'bullet' => '\bullet{}',
    'copyright' => '\copyright{}',
    'registeredsymbol' => '\circledR{}',
    'dots' => '\dots{}',
    'enddots' => '\dots{}',
    'equiv' => '\equiv{}',
    'error' => '\fbox{error}',
    'expansion' => '\mapsto{}',
    'arrow' => '\rightarrow{}',
    'minus' => '-',
    'point' => '\star{}',
    'print' => '\dashv{}',
    'result' => '\Rightarrow{}',
    'pounds' => '\mathsterling{}',
    'atchar', => '@',
    'lbracechar' => '\{',
    'rbracechar' => '\}',
    # this follows the Texinfo manual, however there is no obvious
    # visible effect
    'backslashchar' => '\mathtt{\backslash{}}',
    'hashchar' => '\#',
    'comma' => ',',
    'ampchar' => '\&',
    'euro' => '\euro{}',
    'geq' => '\geq{}',
    'leq' => '\leq{}',
    'textdegree' => '^{\circ{}}',
    'today' => '\today{}',
    'tie' => '\hbox{}',
  }
);

# the corresponding LaTeX commands can only appear in text mode
# so we switch to text mode to output them if in math
my %LaTeX_text_only_no_arg_brace_commands = (
  'exclamdown' => 'textexclamdown',
  'questiondown' => 'textquestiondown',
  'ordf' => 'textordfeminine',
  'ordm' => 'textordmasculine',
  'quotedblleft' => 'textquotedblleft',
  'quotedblright' => 'textquotedblright',
  'quoteleft' => 'textquoteleft',
  'quoteright' => 'textquoteright',
  'quotedblbase' => 'quotedblbase',
  'quotesinglbase' => 'quotesinglbase',
  'guillemetleft', => 'guillemotleft',
  'guillemetright' => 'guillemotright',
  'guillemotleft' => 'guillemotleft',
  'guillemotright' => 'guillemotright',
  'guilsinglleft' => 'guilsinglleft',
  'guilsinglright' => 'guilsinglright',
);

foreach my $letter_no_arg_command ('aa','AA','ae','oe','AE','OE','o','O',
                                   'ss','l','L','DH','dh','TH','th') {
  $LaTeX_text_only_no_arg_brace_commands{$letter_no_arg_command}
   = $letter_no_arg_command;
}

foreach my $text_only_no_arg_brace_command
     (keys(%LaTeX_text_only_no_arg_brace_commands)) {
  my $LaTeX_command =
    "\\$LaTeX_text_only_no_arg_brace_commands{$text_only_no_arg_brace_command}\{\}";
  $LaTeX_no_arg_brace_commands{'cmd_text'}->{$text_only_no_arg_brace_command}
    = $LaTeX_command;
  $LaTeX_no_arg_brace_commands{'cmd_math'}->{$text_only_no_arg_brace_command}
    = '\mathord{\text{'.$LaTeX_command.'}}';
}

# dotless is special
my %LaTeX_accent_commands = (
   # textmode
  'cmd_text' => {
    ',' => 'c',
    'ringaccent' => 'r',
    'H' => 'H',
    'dotaccent' => '.',
    'ubaraccent' => 'b',
    'udotaccent' => 'd',
    'ogonek' => 'k',
    'tieaccent' => 't',
  },
  'cmd_math' => {
    '"' => 'ddot',
    '~' => 'tilde',
    '^' => 'hat',
    '`' => 'grave',
    "'" => 'acute',
    '=' => 'bar',
    'ringaccent' => 'mathring',
    'dotaccent' => 'dot',
    'u' => 'breve',
    'v' => 'check',
  }
);

foreach my $accent_command (keys %{$LaTeX_accent_commands{'cmd_math'}}) {
  if (not exists($LaTeX_accent_commands{'cmd_text'}->{$accent_command})) {
    $LaTeX_accent_commands{'cmd_text'}->{$accent_command} = $accent_command;
  }
}


my %ignored_commands = (%ignored_line_commands, %ignored_nobrace_commands);
# processed as part of the index command or type formatting, or ignored.
foreach my $ignored_brace_commands (
  'sortas', 'seeentry', 'seealso', 'errormsg') {
  $ignored_commands{$ignored_brace_commands} = 1;
}

# titlepage content is directly formatted at document begin
foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', 'copying',
  'documentdescription', 'titlepage') {
  $ignored_commands{$ignored_block_commands} = 1;
}

my %LaTeX_list_environments = (
  'itemize' => 'itemize',
  'enumerate' => 'enumerate',
);

my %format_raw_commands;
foreach my $block_command (keys(%block_commands)) {
  $ignored_commands{$block_command} = 1
    if ($block_commands{$block_command} eq 'menu');
  $LaTeX_list_environments{$block_command} = 'description'
    if ($block_commands{$block_command} eq 'item_line');
  $format_raw_commands{$block_command} = 1
    if ($block_commands{$block_command} eq 'format_raw');
}

my %block_raw_commands = %format_raw_commands;
foreach my $block_raw_command ('verbatim') {
  $block_raw_commands{$block_raw_command} = 1
}

my @LaTeX_same_block_commands = (
  'titlepage', 'verbatim');

my $small_font_size = 'footnotesize';

my %LaTeX_environment_commands = (
  'raggedright' => ['flushleft'],
  'flushleft' => ['flushleft', 'Texinfopreformatted'],
  'flushright' => ['flushright', 'Texinfopreformatted'],
  'quotation' => ['quote'],
  'smallquotation' => ['quote', $small_font_size],
  'indentedblock' => ['quote'],
  'smallindentedblock' => ['quote', $small_font_size],
  'cartouche' => ['mdframed'],
  'itemize' => ['itemize'],
  'enumerate' => ['enumerate'],
  'table' => ['description'],
  'vtable' => ['description'],
  'ftable' => ['description'],
  'example' => ['Texinfoindented'],
  'lisp' => ['Texinfoindented'],
  'display' => ['Texinfoindented'],
);

my %LaTeX_environment_options = (
  'cartouche' => {'mdframed' => 'style=Texinfocartouche'},
);

my %LaTeX_environment_packages = (
  'cartouche' => ['mdframed'],
);

my %LaTeX_fixed_width_environments = (
  'verbatim' => 1,
  'Texinfopreformatted' => 1,
);

foreach my $environment_command (@LaTeX_same_block_commands) {
  $LaTeX_environment_commands{$environment_command} = [$environment_command];
}

my %ignorable_space_types;
foreach my $type ('ignorable_spaces_after_command',
            'spaces_at_end',
            'spaces_after_close_brace') {
  $ignorable_space_types{$type} = 1;
}

# ignore 'command_as_argument_inserted' in order to use the default
# setting for @itemize if there is no argument
my %ignored_types;
foreach my $type ('preamble_before_beginning',
            'preamble_before_setfilename', 'command_as_argument_inserted') {
  $ignored_types{$type} = 1;
}

my %ignorable_types = %ignorable_space_types;
foreach my $ignored_type(keys(%ignored_types)) {
  $ignorable_types{$ignored_type} = 1;
}

# The following code is used to define style commands with more
# complex code than a LaTeX command.
#
# It was designed initially to setup combined fonts similar to texinfo.tex
# fonts, corresponding to code like \ttsl, combining typewriter and slanted.
# The idea was to use constructs like \ttfamily\textsl to have a cumulative
# effect.  However, it seems that a constructs like \texttt{\textsl{cumulate}}
# do combine the styles, so nothing complex is needed for that case.
#
# As a side note it is not so easy to check the font type combinations
# results as they depend on the fonts.  With \usepackage[T1]{fontenc},
# used in the default case, there is no difference between typewriter
# and bold + typewriter, and \textbf{{\ttfamily\textsl{kbd in strong}}}
# seems to be in italic.  it is better to look at the results with
# \usepackage{lmodern}.

my $style_command_new_commands_prefix = 'Texinfocommandstyle';
# if new commands are setup for styles, they are in this hash
my %style_brace_format_command_new_commands;

# setup a new command
sub register_style_format_command($$$$$)
{
  my $formatting_context = shift;
  my $command = shift;
  my $formatting = shift;
  my $style_ref = shift;
  my $new_commands_ref = shift;

  # 'cmd_text' to 'text'
  my $formatting_context_text = $formatting_context;
  $formatting_context_text =~ s/^cmd_//;

  my $specific_style_command_name
   = "${style_command_new_commands_prefix}${formatting_context_text}$command";
  my $specific_style_command = '\\'.$specific_style_command_name;
  $style_ref->{$formatting_context}->{$command} = $specific_style_command;
  $new_commands_ref->{$formatting_context}->{$command}
    = "$specific_style_command\[1]{{$formatting\{#1}}}";
  return $specific_style_command_name;
}

# @-commands that stop code context
my %roman_style_commands = (
  'r' => 1,
);

# All those commands run with the text.
# math and verb are special and implemented separately.
# There is specific code for some commands, such as kbd, r
# in addition to using this hash.
my %LaTeX_style_brace_commands = (
  'cmd_text' => {
    'hyphenation' => '\\hyphenation',
    'w' => '\\hbox',
    'sub' => '\\textsubscript',
    'sup' => '\\textsuperscript',
    'r' => '\\textnormal',
    'sc' => '\\textsc',
    'sansserif' => '\\textsf',
  },
  'cmd_math' => {
    'hyphenation' => '',
    'w' => '\\hbox',
    'sub' => '_',
    'sup' => '^',
    'r' => '\\mathrm',
    'sc' => '', # no obvious way to do it in math mode, not switching to
                # text mode only for this command
    'sansserif' => '\\mathsf',
  }
);

foreach my $context (keys(%LaTeX_style_brace_commands)) {
  $style_brace_format_command_new_commands{$context} = {};
}

# embrac does not propagate in these commands.  But we want
# upside braces in these commands.  So we make them known to
# embrac.  Only locally, otherwise other commands can be broken.
my %need_known_embrac;
# other commands needed?
foreach my $LaTeX_style_command_name ('textsc', 'textbf', 'texttt') {
  $need_known_embrac{'\\'.$LaTeX_style_command_name} = $LaTeX_style_command_name;
}

# we want to keep those @-commands in roman slanted everywhere in text
# so use \normalfont{} to remove other font effects
foreach my $always_slanted_roman_commands ('cite', 'var') {
  register_style_format_command('cmd_text', $always_slanted_roman_commands,
                        '\\normalfont{}\\textsl', \%LaTeX_style_brace_commands,
                        \%style_brace_format_command_new_commands);
  # it seems better to use explicitly mathit, otherwise the space between
  # the letters in argument can be important for some letters (such as ff),
  # corresponding to letters being multiplied rather than to words, which
  # is a better interpretation for @-command arguments.
  # https://tex.stackexchange.com/questions/448069/fix-ugly-kerning-in-equation-subscript
  # In tests, \mathnormal didn't avoid the issue, and numbers
  # were small, so use \mathit, which also slants the numbers.
  $LaTeX_style_brace_commands{'cmd_math'}->{$always_slanted_roman_commands}
       = '\\mathit';
  $roman_style_commands{$always_slanted_roman_commands} = 1;
}

# specific style for kbd: slanted and typewriter.  code @-command
# formatting is used instead if needed, see _kbd_code_style.
register_style_format_command('cmd_text', 'kbd',
                        '\\ttfamily\\textsl', \%LaTeX_style_brace_commands,
                        \%style_brace_format_command_new_commands);

# FIXME headitemfont
my @asis_commands = ('asis', 'clicksequence', 'headitemfont');

foreach my $asis_command (@asis_commands) {
  $LaTeX_style_brace_commands{'cmd_text'}->{$asis_command} = '';
  $LaTeX_style_brace_commands{'cmd_math'}->{$asis_command} = '';
}

# in texinfo.tex, @dfn is slanted.
my @slanted_commands = ('dfn', 'slanted');
foreach my $slanted_command (@slanted_commands) {
  $LaTeX_style_brace_commands{'cmd_text'}->{$slanted_command} = '\\textsl';
  $LaTeX_style_brace_commands{'cmd_math'}->{$slanted_command} = '\\mathit';
}

my @emphasized_commands = ('emph');
foreach my $emphasized_command (@emphasized_commands) {
  $LaTeX_style_brace_commands{'cmd_text'}->{$emphasized_command} = '\\emph';
  $LaTeX_style_brace_commands{'cmd_math'}->{$emphasized_command} = '\\mathit';
}

my @bold_commands = ('strong', 'b');
foreach my $bold_command (@bold_commands) {
  $LaTeX_style_brace_commands{'cmd_text'}->{$bold_command} = '\\textbf';
  $LaTeX_style_brace_commands{'cmd_math'}->{$bold_command} = '\\mathbf';
}

my @italics_commands = ('i');
foreach my $italics_command (@italics_commands) {
  $LaTeX_style_brace_commands{'cmd_text'}->{$italics_command} = '\\textit';
  $LaTeX_style_brace_commands{'cmd_math'}->{$italics_command} = '\\mathit';
}

my @typewriter_commands = ('t', 'code', 'samp', 'key', 'env', 'file',
 'command', 'option', 'indicateurl');

foreach my $typewriter_command (@typewriter_commands) {
  $LaTeX_style_brace_commands{'cmd_text'}->{$typewriter_command} = '\\texttt';
  $LaTeX_style_brace_commands{'cmd_math'}->{$typewriter_command} = '\\mathtt';
}

my @quoted_commands = ('samp', 'indicateurl');

my %quotes_map;
# Quotes are reset in converter_initialize and unicode quotes are used
# if @documentencoding utf-8 is used.
foreach my $quoted_command (@quoted_commands) {
  $quotes_map{$quoted_command} = ["`", "'"];
}

# Format in description for @*table argument

# note that if each command was formatted with format= option of
# enumitem \description, the command would need to be formatted
# with a final command, and possibly in a default bold font
# that would need to be isolated with \normalfont.  However, since
# a parbox with each items on different lines is used to avoid having
# too much spacing, there is no such constraint.  In any case, commands
# are defined for every style command if needed.
my %description_command_format;

my $description_command_new_commands_prefix = 'Texinfotablestyle';

# if new commands are setup for descriptions, they are in this hash
my %description_command_new_commands = ();

foreach my $command (keys(%{$LaTeX_style_brace_commands{'cmd_text'}})) {
  # avoids hyphenation @-command
  next if ($unformatted_brace_command{$command});
  my $description_format = $LaTeX_style_brace_commands{'cmd_text'}->{$command};

  if ($quotes_map{$command}) {
    # Setup command used to format in tables for quoted commands.  Note that
    # the quotes used in that context are not modified by OUTPUT_ENCODING_NAME
    # nor *_QUOTE_SYMBOL.
    my $specific_format_command
      = "\\${description_command_new_commands_prefix}$command";
    # does not happen currently
    if ($description_format eq '') {
      $description_command_new_commands{$command} =
            "$specific_format_command\[1]{\\ifstrempty{#1}{}{{`#1'}}";
    } else {
      # We use \ifstrempty to avoid outputting an empty
      # quotation if there is no item.  Note that it does
      # not work as intended if there is no optional parameter
      # for item, like
      #   \item some text
      # but works for
      #   \item[] some text
      $description_command_new_commands{$command}
        = "$specific_format_command\[1]{\\ifstrempty{#1}{}{`$description_format\{#1}'}}";
    }
    $description_command_format{$command} = $specific_format_command;
  } elsif ($description_format ne '') {
    $description_command_format{$command} = $description_format;
  }
}


my %defaults = (
  'ENABLE_ENCODING'      => 0,
  'FORMAT_MENU'          => 'nomenu',
  'EXTENSION'            => 'tex',

  'documentlanguage'     => undef,

  'converted_format'     => 'latex',

  # FIXME this sets an option that is not a customization variable
  # and will not be documented anywhere.  It should probably be better
  # to add and document a customization in the Texinfo manual if needed.
  # For LaTeX in general, it could make sense to have some customization,
  # for example of packages, fonts, document type, to be discussed/though
  # about how to setup this customization.
  # FIXME any idea what could be used?
  'floats_extension'     => 'tfl',
);


sub converter_defaults($$)
{
  return %defaults;
}

# Converter state keys:
#
# custom_heading
# description_format_commands
# extra_definitions: Texinfo specific LaTeX command definitions that
#                     need to be output based on the commands used to
#                     format the Texinfo code
# fixed_width_environments: fixed width environment used for formatting
# formatting_context: formatting context information for a whole context,
#                     for instance in a footnote.  See _push_new_context
#                     for the list of items set in formatting_context.
# index_entries
# indices_information
# list_environments
# latex_floats
# normalized_float_latex
# normalized_nodes_associated_section: associates anchor and node not already
#                     associated to section to the next or previous section.
# packages: packages needed by the LaTeX formatting of Texinfo code
# page_styles
# style_brace_format_commands

sub converter_initialize($)
{
  my $self = shift;

  %{$self->{'ignored_types'}} = %ignored_types;
  %{$self->{'ignorable_space_types'}} = %ignorable_space_types;
  %{$self->{'ignored_commands'}} = %ignored_commands;

  foreach my $format (keys(%format_raw_commands)) {
    $self->{'ignored_commands'}->{$format} = 1
       unless ($self->{'expanded_formats_hash'}->{$format});
  }

  %{$self->{'quotes_map'}} = %quotes_map;
  $self->{'convert_text_options'}
      = {Texinfo::Convert::Text::copy_options_for_convert_text($self, 1)};

  # this condition means that there is no way to turn off
  # @U expansion to utf-8 characters even though this
  # could output characters that are not known in the
  # fontenc and will lead to an error.
  # This is described in the Texinfo manual, but not in
  # in a precise way.
  # FIXME add a customization variable?  Use a fontenc with more points?
  if ($self->get_conf('OUTPUT_ENCODING_NAME')
      and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') {
    # cache this to avoid redoing calls to get_conf
    $self->{'to_utf8'} = 1;

    if ($self->get_conf('ENABLE_ENCODING')) {
      foreach my $quoted_command (@quoted_commands) {
        # Directed single quotes
        $self->{'quotes_map'}->{$quoted_command} = ["\x{2018}", "\x{2019}"];
      }
    }
  }
  if (defined($self->get_conf('OPEN_QUOTE_SYMBOL'))) {
    foreach my $quoted_command (@quoted_commands) {
      $self->{'quotes_map'}->{$quoted_command}->[0]
       = $self->get_conf('OPEN_QUOTE_SYMBOL');
    }
  }
  if (defined($self->get_conf('CLOSE_QUOTE_SYMBOL'))) {
    foreach my $quoted_command (@quoted_commands) {
      $self->{'quotes_map'}->{$quoted_command}->[1]
       = $self->get_conf('CLOSE_QUOTE_SYMBOL');
    }
  }
  # some caching to avoid calling get_conf
  $self->{'enable_encoding'} = $self->get_conf('ENABLE_ENCODING');
  $self->{'output_encoding_name'} = $self->get_conf('OUTPUT_ENCODING_NAME');
  $self->{'debug'} = $self->get_conf('DEBUG');

  return $self;
}

my %LaTeX_floats = (
 'figure' => '\listoffigures',
 'table' => '\listoftables',
);

# associate float normalized types to latex float environment names
sub _prepare_floats($)
{
  my $self = shift;
  if ($self->{'floats'}) {
    $self->{'normalized_float_latex'} = {};
    $self->{'latex_floats'} = {};
    foreach my $normalized_float_type (sort(keys(%{$self->{'floats'}}))) {
      my $latex_variable_float_name = $normalized_float_type;
      # note that with that transformation, some float types
      # may be put together
      $latex_variable_float_name =~ s/[^a-zA-z]//g;
      if (exists($LaTeX_floats{lc($latex_variable_float_name)})) {
        $self->{'normalized_float_latex'}->{$normalized_float_type}
          = lc($latex_variable_float_name);
      } else {
        # for floats without type, and to avoid name clashes
        my $latex_float_name = 'TexinfoFloat'.$latex_variable_float_name;
        if (exists($self->{'latex_floats'}->{$latex_float_name})) {
          while (exists($self->{'latex_floats'}->{$latex_float_name})) {
            $latex_float_name .= 'a';
          }
        }
        $self->{'latex_floats'}->{$latex_float_name}
          = $normalized_float_type;
        $self->{'normalized_float_latex'}->{$normalized_float_type}
          = $latex_float_name;
      }
    }
  }
}

sub _prepare_indices($)
{
  my $self = shift;

  my $index_names = $self->{'indices_information'};
  if ($index_names) {
    my $merged_index_entries
        = Texinfo::Structuring::merge_indices($index_names);
    # select non empty indices
    if ($merged_index_entries) {
      $self->{'index_entries'} = {};
      foreach my $index_name (keys(%{$merged_index_entries})) {
        # print STDERR "PI $index_name\n";
        # print STDERR "".$merged_index_entries->{$index_name}."\n";
        #print STDERR " -> ".join("|", @{$merged_index_entries->{$index_name}})."\n";
        if (scalar(@{$merged_index_entries->{$index_name}})) {
          $self->{'index_entries'}->{$index_name} = $merged_index_entries->{$index_name};
        }
      }
    }
  }
}


sub _prepare_conversion($;$)
{
  my $self = shift;
  my $root = shift;

  # initialization for a new output
  $self->{'page_styles'} = {};
  $self->{'list_environments'} = {};
  $self->{'description_format_commands'} = {};
  $self->{'style_brace_format_commands'} = {};
  foreach my $context (keys(%style_brace_format_command_new_commands)) {
    $self->{'style_brace_format_commands'}->{$context} = {};
  }
  $self->{'packages'} = {};
  $self->{'extra_definitions'} = {};
  $self->{'fixed_width_environments'} = {};
  # something different is done for the first custom heading.
  # Not sure that values could be there, but delete anyway
  # to be clear.
  delete($self->{'custom_heading'});
  delete($self->{'index_entries'});

  if (defined($root)) {
    $self->_associate_other_nodes_to_sections($root);
  }

  if ($self->{'global_commands'}->{'settitle'}) {
    my $settitle_root = $self->{'global_commands'}->{'settitle'};
    if ($settitle_root->{'args'}->[0]
        and $settitle_root->{'args'}->[0]->{'contents'}
        and not ($settitle_root->{'extra'}
                 and $settitle_root->{'extra'}->{'missing_argument'})) {
      $self->{'settitle_tree'} =
         {'contents' => $settitle_root->{'args'}->[0]->{'contents'}};
    }
  }
  $self->_prepare_floats();
  $self->_prepare_indices();
}

sub _associate_other_nodes_to_sections($$)
{
  my ($self, $root) = @_;

  # associate lone nodes with sectioning commands
  my $additional_node_section_associations = {};

  my $current_sectioning_command;
  # nodes not already associated as no section has been seen,
  # associate to the first section
  my $pending_nodes = [];

  foreach my $element_content (@{$root->{'contents'}}) {
    if ($element_content->{'cmdname'}
        and $element_content->{'cmdname'} eq 'node') {
      if (not $element_content->{'extra'}->{'associated_section'}
          and $element_content->{'extra'}->{'normalized'}) {
        if (defined($current_sectioning_command)) {
          $additional_node_section_associations->{$element_content->{'extra'}->{'normalized'}}
            = $current_sectioning_command;
        } else {
          push @$pending_nodes, $element_content->{'extra'}->{'normalized'};
        }
      }
    } elsif ($element_content->{'cmdname'}
          and $root_commands{$element_content->{'cmdname'}}) {
      $current_sectioning_command = $element_content;
      if (scalar(@$pending_nodes)) {
        foreach my $normalized_node_name (@$pending_nodes) {
          $additional_node_section_associations->{$normalized_node_name}
            = $current_sectioning_command
        }
        $pending_nodes = [];
      }
    }
  }
  # If there are no sectioning commands and there are nodes,
  # $pending_nodes won't be empty and no node is associated.
  #print STDERR "No sectioning commands but nodes\n"
  #  if (scalar(@$pending_nodes) > 0);
  $self->{'normalized_nodes_associated_section'}
    = $additional_node_section_associations;
}

# this type marks where the \begin{document} should be,
# after the @-commands in preamble.  It is not setup
# when using parse_texi_piece only.
my $latex_document_type = 'preamble_before_content';

sub output($$)
{
  my ($self, $root) = @_;

  my ($output_file, $destination_directory, $output_filename)
    = $self->determine_files_and_directory();

  my ($encoded_destination_directory, $dir_encoding)
    = $self->encoded_output_file_name($destination_directory);
  my $succeeded
    = $self->create_destination_directory($encoded_destination_directory,
                                          $destination_directory);
  return undef unless $succeeded;

  my $fh;
  my $encoded_output_file;
  if (! $output_file eq '') {
    my $path_encoding;
    ($encoded_output_file, $path_encoding)
      = $self->encoded_output_file_name($output_file);
    my $error_message;
    ($fh, $error_message) = Texinfo::Common::output_files_open_out(
                             $self->output_files_information(), $self,
                             $encoded_output_file);
    if (!$fh) {
      $self->document_error($self,
             sprintf(__("could not open %s for writing: %s"),
                                    $output_file, $error_message));
      return undef;
    }
  }

  my $modified_root;

  # determine if there is a Top node at the end of the document
  my $in_top_node = undef;
  foreach my $element_content (@{$root->{'contents'}}) {
    my $node_element;
    if ($element_content->{'cmdname'}) {
      my $cmdname = $element_content->{'cmdname'};
      if ($cmdname eq 'node') {
        $node_element = $element_content;
      } elsif ($cmdname eq 'part' and $element_content->{'extra'}
               and $element_content->{'extra'}->{'part_following_node'}) {
        $node_element = $element_content->{'extra'}->{'part_following_node'};
      }
      if ($node_element or $cmdname eq 'part') {
        if ($node_element and $node_element->{'extra'}
            and $node_element->{'extra'}->{'normalized'}
            and $node_element->{'extra'}->{'normalized'} eq 'Top') {
          $in_top_node = 1;
        } else {
          if ($in_top_node) {
            $in_top_node = 0;
            last;
          }
        }
      }
    }
  }
  # nothing after Top node the end, mark that Top node is ignored
  # in a container that can be used to mark that content should not
  # be ignored anymore.
  if ($in_top_node) {
    $modified_root = {'contents' => [ @{$root->{'contents'}} ], 'type' => $root->{'type'}}
      if (not defined($modified_root));
    push @{$modified_root->{'contents'}},
        {'type' => 'ignored_top_node_paragraph', 'contents' => [
         {'type' => 'paragraph', 'contents' => [
          {'text' => "\n(`Top' node ignored)\n", 'type' => 'ignored_top_node'}]}]};
  }

  if (not defined($modified_root)) {
    $modified_root = $root;
  }


  $self->_prepare_conversion($modified_root);

  my $result = '';
  $result .= $self->_latex_begin_output();
  $result .= $self->convert_tree($modified_root);
  $result .= $self->_latex_footer();

  my $output = '';
  $output .= $self->write_or_return($self->_latex_header(), $fh);
  $output .= $self->write_or_return($result, $fh);

  #print STDERR "OUTPUT fh:$fh|F:$output_file|$result";
  if ($fh and $output_file ne '-') {
    Texinfo::Common::output_files_register_closed(
                  $self->output_files_information(), $encoded_output_file);
    if (!close ($fh)) {
      $self->document_error($self,
                   sprintf(__("error on closing %s: %s"),
                                    $output_file, $!));
    }
  }
  return $output;
}

# we allow the converter to already be in a context, but if
# not create one.
sub convert($$)
{
  my $self = shift;
  my $root = shift;

  $self->_prepare_conversion($root);

  my $new_context;
  if (not exists($self->{'formatting_context'})
      or scalar(@{$self->{'formatting_context'}}) == 0) {
    _push_new_context($self, 'document');
    $new_context = 1;
  }

  my $result = $self->_convert($root);

  if ($new_context) {
    _pop_context($self);
  }
  return $result;
}

# a context should have been set by the caller
sub convert_tree($$)
{
  my $self = shift;
  my $root = shift;

  my $new_context;
  if (not exists($self->{'formatting_context'})
      or scalar(@{$self->{'formatting_context'}}) == 0) {
    _push_new_context($self, 'tree');
    $new_context = 1;
  }

  my $result = $self->_convert($root);

  if ($new_context) {
    _pop_context($self);
  }
  return $result;
}

sub copy_options_for_convert_to_latex_math($)
{
  my $self = shift;
  my %options;
  foreach my $option_name ('DEBUG', 'ENABLE_ENCODING', 'OUTPUT_ENCODING_NAME',
                           'TEST') {
    $options{$option_name} = $self->get_conf($option_name)
                    if (defined($self->get_conf($option_name)));
  }
  return %options;
}

# convert texinfo tree to LaTeX math
# Errors are not passed, nor the converter.  However the errors and warnings
# generated by the LaTeX conversion are few and not relevant for math tree
# fragments.  Relevant errors should be generated when parsing the Texinfo,
# or when converting the final LaTeX.
sub convert_to_latex_math($$;$$)
{
  my $self = shift;
  my $root = shift;
  my $options = shift;
  my $math_style = shift;

  $math_style = 'one-line' if (not defined($math_style));

  if (not defined($self)) {
    $self = Texinfo::Convert::LaTeX->converter($options);
  }

  _push_new_context($self, 'convert_to_math');

  push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_math';
  push @{$self->{'formatting_context'}->[-1]->{'math_style'}}, $math_style;

  my $result = $self->_convert($root);

  _pop_context($self);

  return $result;
}

my %LaTeX_encoding_names_map = (
  'utf-8' => 'utf8',
  'iso-8859-1' => 'latin1',
);

# book or report?
my $documentclass = 'book';

my %front_main_matter_definitions = (
  'book' =>   {'main'  => '\mainmatter',
               'front' => '\frontmatter'},
  'report' => {'main'  => '\clearpage\pagenumbering{arabic}',
               'front' => '\clearpage\pagenumbering{roman}'}
);

# not used as it is complicated to use section and chapter title
# NB this will not work any more as the \Texinfoset... macros are
# not used, and \pagestyle is used directly.
my $fancyhdr_preamble =
'% called when setting single headers
% use \nouppercase to match with Texinfo TeX style
\newcommand{\Texinfosetsingleheader}{\pagestyle{fancy}
\fancyhf{}
\lhead{\nouppercase{\leftmark}}
\rhead{\thepage}
}

% called when setting double headers
\newcommand{\Texinfosetdoubleheader}{\pagestyle{fancy}
\fancyhf{}
\fancyhead[LE,RO]{\thepage}
\fancyhead[RE]{\Texinfosettitle}
\fancyhead[LO]{\nouppercase{\leftmark}}
}

% for part and chapter, which call \thispagestyle{plain}
\fancypagestyle{plain}{ %
 \fancyhf{}
 \fancyhead[LE,RO]{\thepage}
}

% match Texinfo TeX style
\renewcommand{\headrulewidth}{0pt}%';

# TODO translation
my $default_title = 'No Title';

sub _latex_header() {
  my $self = shift;
  # LaTeX code appearing after packages.  Do it first to be able to
  # select packages based on the code output here.
  my $header_code = '';
  my $settitle;
  if ($self->{'settitle_tree'}) {
    $settitle = $self->convert_tree($self->{'settitle_tree'});
  } else {
    $settitle = $default_title;
  }
  $header_code .= "\\makeatletter\n";

  # for @thistitle and headers
  $header_code .= "\\newcommand{\\Texinfosettitle}{$settitle}%\n";
  $header_code .= "\n";

  if ($self->{'floats'}) {
    foreach my $normalized_float_type (sort(keys(%{$self->{'normalized_float_latex'}}))) {
      my $latex_float_name
        = $self->{'normalized_float_latex'}->{$normalized_float_type};
      if (not exists($LaTeX_floats{$latex_float_name})) {
        my $float_type = '';
        if ($normalized_float_type ne '') {
          _push_new_context($self, 'float_type '.$normalized_float_type);
          my $float = $self->{'floats'}->{$normalized_float_type}->[0];
          my $float_type_contents = $float->{'extra'}->{'type'}->{'content'};
          my $float_type = _convert($self, {'contents' => $float_type_contents});
          _pop_context($self);
        }
        my $floats_extension = $self->{'floats_extension'};
        $header_code .= "% new float for type `$normalized_float_type'\n";
        $header_code .= "\\newfloat{$latex_float_name}{htb}{$floats_extension}[chapter]
\\floatname{$latex_float_name}{$float_type}
";
      }
    }
  }
  if ($self->{'index_entries'}
      and scalar(keys(%{$self->{'index_entries'}}))) {
    $header_code .= "% no index headers or page break\n";
    $header_code .= "\\indexsetup{level=\\relax,toclevel=section,noclearpage}%\n";

    foreach my $index_name (sort(keys(%{$self->{'index_entries'}}))) {
      $header_code .= "\\makeindex[name=$index_name,title=]%\n";
    }
    $header_code .= "\n";
  }
  # define additional commands used in @*table description format
  foreach my $command (sort(keys(%description_command_new_commands))) {
    if ($self->{'description_format_commands'}->{$command}) {
      $header_code .= '% command used in \description format for '.$command."\n";
      $header_code .= "\\newcommand".$description_command_new_commands{$command}."%\n";
      $header_code .= "\n";
    }
  }
  foreach my $command_context (sort(keys(%style_brace_format_command_new_commands))) {
    if ($self->{'style_brace_format_commands'}->{$command_context}) {
      foreach my $command
         (sort(keys(%{$style_brace_format_command_new_commands{$command_context}}))) {
        if ($self->{'style_brace_format_commands'}->{$command_context}->{$command}) {
          $header_code .= '% style command for '.$command." in '$command_context' formatting context\n";
          $header_code .= "\\newcommand"
             .$style_brace_format_command_new_commands{$command_context}->{$command}."%\n";
          $header_code .= "\n";
        }
      }
    }
  }

  if ($documentclass eq 'book') {
    $header_code .=
'% redefine the \mainmatter command such that it does not clear page
% as if in double page
\renewcommand\mainmatter{\clearpage\@mainmattertrue\pagenumbering{arabic}}
';
  }

  $header_code .=
'\newenvironment{Texinfopreformatted}{%
  \\par\\GNUTobeylines\\obeyspaces\\frenchspacing\\parskip=\\z@\\parindent=\\z@}{}
{\catcode`\^^M=13 \gdef\GNUTobeylines{\catcode`\^^M=13 \def^^M{\null\par}}}
';

  $header_code .=
'\newenvironment{Texinfoindented}{\begin{list}{}{}\item\relax}{\end{list}}

';

  if ($self->{'packages'}->{'babel'}) {
    $header_code .= '
% this allows to select languages based on bcp47 codes.  bcp47 is a superset
% of the LL_CC ISO 639-2 LL ISO 3166 CC information of @documentlanguage
\babeladjust{
  autoload.bcp47 = on,
  autoload.bcp47.options = import
}
';
  }

  # disactivate microtype for fixed-width environments
  if (scalar(keys(%{$self->{'fixed_width_environments'}}))) {
    if ($self->{'packages'}->{'microtype'}) {
      foreach my $no_microtype_environment (sort(keys(%{$self->{'fixed_width_environments'}}))) {
        $header_code .= "\\AtBeginEnvironment{$no_microtype_environment}"
                        ."{\\microtypesetup{activate=false}}\n";
      }
    }
    $header_code .= "\n";
  }

  if (scalar(keys(%{$self->{'list_environments'}}))) {
    $header_code .= "% set defaults for lists that match Texinfo TeX formatting\n";
    if ($self->{'list_environments'}->{'description'}) {
      $header_code .= "\\setlist[description]{style=nextline, font=\\normalfont}\n";
    }
    if  ($self->{'list_environments'}->{'itemize'}) {
      $header_code .= "\\setlist[itemize]{label=\\textbullet}\n";
    }
    if  ($self->{'list_environments'}->{'enumerate'}) {
      $header_code .= "\\setlist[enumerate]{label=\\arabic*.}\n";
    }
    $header_code .= "\n";
  }
  $header_code .= '% used for substitutions in commands
\newcommand{\Texinfoplaceholder}[1]{}

';

  if ($self->{'page_styles'}->{'single'}) {
    $header_code .=
'\newpagestyle{single}{\sethead[\chaptername{} \thechapter{} \chaptertitle{}][][\thepage]
                              {\chaptername{} \thechapter{} \chaptertitle{}}{}{\thepage}}

';
  }

  if ($self->{'page_styles'}->{'double'}) {
    $header_code .=
'\newpagestyle{double}{\sethead[\thepage{}][][\Texinfosettitle]
                              {\chaptername{} \thechapter{} \chaptertitle{}}{}{\thepage}}

';
  }

  if ($self->{'extra_definitions'}->{'Texinfonopagebreakheading'}) {
    $header_code .=
'% avoid pagebreak and headings setting for a sectioning command
\newcommand{\Texinfonopagebreakheading}[2]{{\let\clearpage\relax \let\cleardoublepage\relax \let\thispagestyle\Texinfoplaceholder #1{#2}}}

';
  }

  if ($self->{'packages'}->{'mdframed'}) {
   $header_code .= '% the mdframed style for @cartouche
\mdfdefinestyle{Texinfocartouche}{
innertopmargin=10pt, innerbottommargin=10pt,%
roundcorner=10pt}

';
  }

  if ($self->{'packages'}->{'embrac'}) {
    # in order to have brackets and parenthese upright in slanted typewriter
    # \textsl{\texttt, \EmbracMakeKnown{texttt} is needed.  However, we only set it
    # locally, otherwise \hyperref in \texttt can break.
    $header_code .= '% braces are upright in italic and slanted only in @def*
% so it is turned off here, and turned on @def* lines
\EmbracOff{}%

'
  }

  $header_code .= '% allow line breaking at underscore
\let\Texinfounderscore\_
\renewcommand{\_}{\Texinfounderscore\discretionary{}{}{}}
';

  # this is in order to be able to run pdflatex even
  # if files do not exist, or filenames cannot be
  # processed by LaTeX
  if ($self->get_conf('TEST')) {
    $header_code .=
'\renewcommand{\includegraphics}[1]{\fbox{FIG \detokenize{#1}}}

';
  }

  # amsfonts for \circledR
  # amsmath for \text in math
  # T1 fontenc for \DH, \guillemotleft, ...
  # eurosym for \euro
  # textcomp for \textdegree in older LaTeX
  # graphicx for \includegraphics
  # needspace for \needspace. In texlive-latex-extra in debian
  # etoolbox for \patchcmd, \ifstrempty and \AtBeginEnvironment.
  # In texlive-latex-recommended in debian
  # fontsize for \changefontsize. In texlive-latex-extra in debian
  # mdframed is used for the formatting of @cartouche,
  # microtype is used for @microtype
  # microtype requires cm-super installed, or to use lmodern package.
  # In texlive-latex-recommended in debian.
  # framemethod=TikZ is needed for roundcorner.
  # Possibility for hyperref for color:
  # \usepackage[linkbordercolor={0 0 0}]{hyperref}
  # titleps is used and not fancyhdr as with fancyhdr it is hard to get
  # the section or chapter title
  my $header = "\\documentclass{$documentclass}\n";
  if ($self->{'index_entries'}
      and scalar(keys(%{$self->{'index_entries'}}))) {
    $header .= "\\usepackage{imakeidx}\n";
  }
  $header .= '\usepackage{amsfonts}
\usepackage{amsmath}
\usepackage[gen]{eurosym}
\usepackage[T1]{fontenc}
\usepackage{textcomp}
\usepackage{graphicx}
';
  if ($self->{'packages'}->{'needspace'}) {
    $header .= "\\usepackage{needspace}\n";
  }
  if ($self->{'packages'}->{'microtype'}) {
    $header .= "\\usepackage[activate=false]{microtype}\n";
  }
  $header .= '\usepackage{etoolbox}
';
  if ($self->{'packages'}->{'array'}) {
    $header .= "\\usepackage{array}\n";
  }
  if ($self->{'packages'}->{'embrac'}) {
    $header .= "\\usepackage{embrac}\n";
    $header .= "\\usepackage{expl3}\n";
  }
  if ($self->{'packages'}->{'tabularx'}) {
    $header .= "\\usepackage{tabularx}\n";
  }
  if ($self->{'packages'}->{'mdframed'}) {
    # framemethod=tikz needed for roundcorners for @cartouche
    $header .= "\\usepackage[framemethod=tikz]{mdframed}\n";
  }
  if ($self->{'packages'}->{'fontsize'}) {
    $header .= "\\usepackage{fontsize}\n";
  }
  if (scalar(keys(%{$self->{'list_environments'}}))) {
    $header .= "\\usepackage{enumitem}\n";
  }
  if ($self->{'packages'}->{'geometry'}) {
    $header .= "\\usepackage{geometry}\n";
  }
  $header .= '\usepackage{titleps}
';
  if ($self->{'floats'}) {
    $header .= "\\usepackage{float}\n";
  }
  if ($self->{'packages'}->{'babel'}) {
    $header .= "\\usepackage{babel}\n";
  }
  $header .= '% use hidelinks to remove boxes around links to be similar to Texinfo TeX
\usepackage[hidelinks]{hyperref}
';
  if ($self->{'output_encoding_name'}) {
    my $encoding = $self->{'output_encoding_name'};
    if (defined($LaTeX_encoding_names_map{$encoding})) {
      $encoding = $LaTeX_encoding_names_map{$encoding};
    }# else {
      # FIXME Warn?
    #}
    $header .= "\\usepackage[$encoding]{inputenc}\n";
  }
  #if ($self->{'global_commands'}->{'shortcontents'}) {
  #  # in texlive-latex-extra in debian
  #  $header .= "\\usepackage{shorttoc}\n";
  #}
  $header_code .= "\\makeatother\n";
  $header .= "\n";

  return $header . $header_code;
}

sub _latex_begin_output($)
{
  my $self = shift;

  my $header = '';
  # setup defaults
  $header .= "% set default for \@setchapternewpage\n";
  $header .= _set_chapter_new_page($self, 'on');
  $header .= "\n";
  # setup headings before titlepage to have no headings
  # before titlepage.  They will be set to 'on' after
  # the titlepage if there is a titlepage
  if (exists($self->{'global_commands'}->{'titlepage'})
      or exists($self->{'global_commands'}->{'shorttitlepage'})) {
    $header .= "% no headings before titlepage\n";
    $header .= _set_headings($self, 'off');
    $header .= "\n";
  }
  return $header;
}

sub _begin_document($)
{
  my $self = shift;

  my $result = '';
  $result .= '\begin{document}
';
  if (exists($self->{'global_commands'}->{'titlepage'})
      or exists($self->{'global_commands'}->{'shorttitlepage'})) {
    $result .= "\n";
    $result .= $front_main_matter_definitions{$documentclass}->{'front'}."\n";

    if (exists($self->{'global_commands'}->{'titlepage'})) {
      my $element = $self->{'global_commands'}->{'titlepage'};
      # start a group such that the changes are forgotten when front cover is done
      # define glues dimensions that are used in front cover formatting.
      # Taken from Texinfo TeX.
      # FIXME replace \\newskip by \\newlen?
      $result .= "\\begin{titlepage}\n";
      $result .= "\\begingroup
\\newskip\\titlepagetopglue \\titlepagetopglue = 1.5in
\\newskip\\titlepagebottomglue \\titlepagebottomglue = 2pc
\\setlength{\\parindent}{0pt}\n";
      $result .= "% Leave some space at the very top of the page.
    \\vglue\\titlepagetopglue\n";
      $self->{'titlepage_formatting'} = {'in_front_cover' => 1};
      _push_new_context($self, 'titlepage');
      $result .= $self->_convert({'contents' => $element->{'contents'}});
      _pop_context($self);
      $result .= _finish_front_cover_page($self);
      $result .= "\\end{titlepage}\n";
    } else {
      my $element = $self->{'global_commands'}->{'shorttitlepage'};
      my $title_text = _title_font($self, $element);
      $result .= "\\begin{titlepage}\n";
      $result .= "{\\raggedright $title_text}\n";
      # first newpage ends the title page, phantom and second newpage
      # adds a blank page
      $result .= "\\newpage{}\n\\phantom{blabla}\\newpage{}\n";
      $result .= "\\end{titlepage}\n";
    }
    $result .= _set_headings($self, 'on');
    $result .= $front_main_matter_definitions{$documentclass}->{'main'}."\n";
    $self->{'titlepage_done'} = 1;
  }

  if (exists($self->{'global_commands'}->{'contents'})
      and $self->{'structuring'}
      and $self->{'structuring'}->{'sectioning_root'}
      and not (defined($self->get_conf('CONTENTS_OUTPUT_LOCATION'))
               and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline')) {
    $result .= "\\tableofcontents\\newpage\n";
  }


  return $result;
}

sub _latex_footer {
  return
'\end{document}
';
}

# all the new contexts should be created with that function
sub _push_new_context($$)
{
  my $self = shift;
  my $context_name = shift;

  push @{$self->{'formatting_context'}},
     {
       'code' => [0],
       'context_name' => $context_name,
       'dot_not_end_sentence' => 0,
       'embrac' => [],
       'in_quotation' => 0,
       'in_sectioning_command_heading' => 0,
       'in_skipped_node_top' => 0,
       'math_style' => [],
       'no_eol' => [],
       'nr_table_items_context' => [],
       'preformatted_context' => [],
       # can be ctx_text, ctx_math or ctx_raw
       'text_context' => ['ctx_text'],
       'table_command_format' => [],
     };
}

# for debug
sub _show_top_context_stack($)
{
  my $self = shift;

  my $top_context = $self->{'formatting_context'}->[-1];
  my @all_keys;
  foreach my $key (sort (keys (%$top_context))) {
    my $key_str = $key;
    # keep only the first letters to have something not too long
    $key_str =~ s/(.{4}).*/$1/s;
    my $context_item = $top_context->{$key};
    my $context_item_value;
    if (not defined($context_item)) {
      $context_item_value = 'UNDEF';
    } elsif (ref ($context_item) eq 'ARRAY') {
      $context_item_value = join('|', @$context_item);
    } else {
      $context_item_value = $context_item;
    }
    push @all_keys, "$key_str:$context_item_value";
  }
  return scalar(@{$self->{'formatting_context'}})." ".join('; ', @all_keys);
}

sub _pop_context($)
{
  my $self = shift;
  pop @{$self->{'formatting_context'}};
}

# FIXME should $ ~ be protected?
sub _protect_url($$)
{
  my ($self, $text) = @_;

  $text =~ s/([{}\\#%])/\\$1/g;
  return $text;
}

# in index entries !"@ have special meaning and need to be quoted with "
sub _protect_index_text($)
{
  my $text = shift;
  $text =~ s/([!|"@])/"$1/g;

  # if " is preceded by \ it does not quote the next character unless the
  # \ itself is preceded by ".
  $text =~ s/\\"/"\\"/g;
  return $text;
}

# Protect LaTeX special characters.
sub _protect_text($$)
{
  my ($self, $text) = @_;

  if ($self->{'formatting_context'}->[-1]->{'text_context'}->[-1] eq 'ctx_math') {
    # FIXME are there any special characters to protect in math mode,
    # for instance # and ~?
  } elsif ($self->{'formatting_context'}->[-1]->{'text_context'}->[-1]
             ne 'ctx_raw') {
    # temporarily replace \ with a control character
    $text =~ s/\\/\x08/g;

    # replace the other special characters
    $text =~ s/([#%&{}_\$])/\\$1/g;
    $text =~ s/~/\\~{}/g;
    $text =~ s/\^/\\^{}/g;

    $text =~ s/\x08/\\textbackslash{}/g;

    if ($self->{'formatting_context'}->[-1]->{'code'}->[-1]) {
      # Prevent extra space after punctuation.  (We could use \frenchspacing
      # in the output, but this can break in section titles with hyperref.)
      $text =~ s/([.?!:;,]) /$1\\ /g;

      # In case initial space follows punctuation from a separate element,
      # like @code{@var{?} a}.
      $text =~ s/^ /\\ /g;

      # Under T1 encoding there are several ligatures even in fixed width fonts
      $text =~ s/---/{-}{-}{-}/g;
      $text =~ s/--/{-}{-}/g;
      $text =~ s/``/{`}{`}/g;
      $text =~ s/''/{'}{'}/g;
    }
    # Disable these ligatures everywhere
    $text =~ s/,,/{,}{,}/g;
    $text =~ s/<</{<}{<}/g;
    $text =~ s/>>/{>}{>}/g;
    $text =~ s/\?`/{?}{`}/g;
    $text =~ s/!`/{!}{`}/g;

    if ($self->{'formatting_context'}->[-1]->{'dot_not_end_sentence'}) {
      $text =~ s/\./\.\\@/g;
    }
  }
  return $text;
}

sub _set_headings($$)
{
  my ($self, $headings_spec) = @_;

  my $headings_type;
  if ($headings_spec eq 'on') {
    $headings_type = 'single';
    my $setchapternewpage_spec = $self->get_conf('setchapternewpage');
    if (defined($setchapternewpage_spec)
        and $setchapternewpage_spec eq 'odd') {
      $headings_type = 'double';
    }
  } elsif ($headings_spec eq 'doubleafter') {
    $headings_type = 'double';
  } elsif ($headings_spec eq 'singleafter') {
    $headings_type = 'single';
  } elsif ($headings_spec eq 'off' or $headings_spec eq 'single'
           or $headings_spec eq 'double') {
    $headings_type = $headings_spec;
  }

  if (not defined($headings_type)) {
    confess("_set_headings no type for $headings_spec");
  }

  my $result = '';
  if ($headings_type eq 'single') {
    $result = "\\pagestyle{single}%\n";
  } elsif ($headings_type eq 'double') {
    $result = "\\pagestyle{double}%\n";
  } elsif ($headings_type eq 'off') {
    $result = "\\pagestyle{empty}%\n";
  }
  $self->{'page_styles'}->{$headings_type} = 1;
  return $result;
}

my %custom_headings_map = (
  'everyheading' => ['head', ''],
  'everyfooting' => ['foot', ''],
  'evenheading'  => ['head', 'E'],
  'evenfooting'  => ['foot', 'E'],
  'oddheading',  => ['head', 'O'],
  'oddfooting'   => ['foot', 'O'],
);

# this function converts the specification to LaTex and add or
# replace the footing or heading specifications.
sub _set_custom_headings($$$)
{
  my ($self, $cmdname, $headings_spec) = @_;
  my ($head_or_foot, $page_spec) = @{$custom_headings_map{$cmdname}};

  my $location_index = -1;
  my @headings = ('', '', '');
  _push_new_context($self, 'custom_heading');
  foreach my $location_heading_spec (@$headings_spec) {
    $location_index++;
    my $heading = $self->_convert({'contents' => $location_heading_spec});
    $heading =~ s/^\s*//;
    $heading =~ s/\s*$//;
    $headings[$location_index] = $heading;
  }
  _pop_context($self);

  my @replaced_specs;
  if ($page_spec eq '') {
    @replaced_specs = ('E', 'O');
  } else {
    @replaced_specs = ($page_spec);
  }
  my $first_custom_heading;
  if (not exists($self->{'custom_heading'})) {
    $first_custom_heading = 1;
    $self->{'custom_heading'} = {$head_or_foot => {}};
  } elsif (!exists($self->{'custom_heading'}->{$head_or_foot})) {
    $self->{'custom_heading'}->{$head_or_foot} = {};
  }
  foreach my $spec (@replaced_specs) {
    $self->{'custom_heading'}->{$head_or_foot}->{$spec} = \@headings;
  }
  return _format_heading_command($self, $first_custom_heading);
}

my @head_foot_order = ('head', 'foot');
my @even_odd_order = (['E', 'bracket'], ['O', 'brace']);
sub _format_heading_command($$)
{
  my $self = shift;
  my $first_custom_heading = shift;

  my $result = '';
  if ($first_custom_heading) {
    $result .= "\\newpagestyle{custom}{%\n";
  } else {
    $result .= "\\renewpagestyle{custom}{%\n";
  }
  foreach my $head_or_foot (@head_foot_order) {
    if (exists($self->{'custom_heading'}->{$head_or_foot})) {
      my $head_or_foot_spec = $self->{'custom_heading'}->{$head_or_foot};
      $result .= '\set' . $head_or_foot;
      foreach my $even_odd_and_separator (@even_odd_order) {
        my ($even_or_odd, $separator) = @{$even_odd_and_separator};
        my $headings;
        if (exists($head_or_foot_spec->{$even_or_odd})) {
          $headings = $head_or_foot_spec->{$even_or_odd};
        } else {
          $headings = ['', '', ''];
        }
        foreach my $heading (@{$headings}) {
          if ($separator eq 'bracket') {
            $result .= '['.$heading.']';
          } else {
            $result .= '{'.$heading.'}';
          }
        }
        $result .= "%\n";
      }
    }
  }
  $result .= "}%\n";
  $result .= "\\pagestyle{custom}%\n";
  return $result;
}


# to change the chapter we substitute in the \chapter command.
# REMARK it is fragile as it depends on the LaTeX codes. It is also
# most probably specific of the documentclass.  It is present in both
# report and book document classes in 2021
my $odd_chapter_new_page_code = '\if@openright\cleardoublepage\else\clearpage\fi';
my $default_chapter_page_code = $odd_chapter_new_page_code;
# To make sure that we substitute the right code, we add a
# distinctive code that does nothing.  This is needed when
# code is simply removed or when simple code is substituted.
my $chapter_new_page_marking_placeholder
   = '\Texinfoplaceholder{setchapternewpage placeholder}';

my %setchapternewpage_new_page_spec_code = (
  'on' => $chapter_new_page_marking_placeholder.'\clearpage',
  'off' => $chapter_new_page_marking_placeholder.'',
  'odd' => $odd_chapter_new_page_code,
);

# Note that the code should probably be different if the default code
# wes not distinctive enough
sub _set_chapter_new_page($$)
{
  my ($self, $setchapternewpage_spec) = @_;
  
  my $substituted_code;
  if (defined($self->{'prev_chapter_new_page_substitution'})) {
    $substituted_code = $self->{'prev_chapter_new_page_substitution'};
  } else {
    $substituted_code = $default_chapter_page_code;
  }
  my $new_code = $setchapternewpage_new_page_spec_code{$setchapternewpage_spec};

  my $result = '';
  # do not substitute if it is the same, for instance
  # if setting the same as document class default or setting twice
  if ($new_code ne $substituted_code) {
    $result .= '\makeatletter
\patchcmd{\chapter}{'.$substituted_code.'}{'.$new_code.'}{}{}
\makeatother
';
  }

  # reset headings after titlepage only, or immediately
  # if there is no titlepage
  if ((not $self->{'global_commands'}->{'titlepage'}
       and not $self->{'global_commands'}->{'shorttitlepage'})
      or $self->{'titlepage_done'}) {
    $result .= _set_headings($self, 'on');
  }

  $self->{'prev_chapter_new_page_substitution'} = $new_code;
  
  return $result;
}

my %small_font_preformatted_commands;
foreach my $small_font_preformatted_command (
     grep {/^small/} keys(%preformatted_commands)) {
  $small_font_preformatted_commands{$small_font_preformatted_command} = 1;
}

sub _open_preformatted($$)
{
  my $self = shift;
  my $element = shift;

  my $command = $self->{'formatting_context'}->[-1]->{'preformatted_context'}->[-1];

  if ($preformatted_code_commands{$command}) {
    push @{$self->{'formatting_context'}->[-1]->{'code'}}, 1;
  }

  my $result = '';
  $result .= '\\begin{Texinfopreformatted}%'."\n";
  $self->{'fixed_width_environments'}->{'Texinfopreformatted'} = 1;
  # The % comments out the newline to avoid extra vertical space.

  if ($preformatted_code_commands{$command}) {
    $result .= '\\ttfamily ';
  }
  if ($small_font_preformatted_commands{$command}) {
    $result .= "\\$small_font_size ";
  }
  return $result;
}

sub _close_preformatted($$)
{
  my $self = shift;
  my $element = shift;

  my $command = $self->{'formatting_context'}->[-1]->{'preformatted_context'}->[-1];
  if ($preformatted_code_commands{$command}) {
    pop @{$self->{'formatting_context'}->[-1]->{'code'}};
  }
  return '\\end{Texinfopreformatted}'."\n";
}

sub _open_preformatted_command($$)
{
  my $self = shift;
  my $command = shift;
  
  push @{$self->{'formatting_context'}->[-1]->{'preformatted_context'}}, $command;
  return '';
}

sub _close_preformatted_command($$)
{
  my $self = shift;
  my $command = shift;

  my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'preformatted_context'}};
    die if ($old_context ne $command);
  return '';
}

sub _open_preformatted_stack($$)
{
  my $self = shift;
  my $stack = shift;

  my $result = '';
  foreach my $preformatted_command (@$stack) {
    $result .= _open_preformatted_command($self, $preformatted_command);
  }
  return $result;
}

sub _close_preformatted_stack($$)
{
  my $self = shift;
  my $stack = shift;

  my $result = '';
  foreach my $preformatted_command (reverse @$stack) {
    $result .= _close_preformatted_command($self, $preformatted_command);
  }
  return $result;
}

sub _title_font($$)
{
  my $self = shift;
  my $element = shift;

  if ($element->{'args'}->[0] and $element->{'args'}->[0]->{'contents'}) {
    # in Texinfo TeX seems a bit smaller, but LARGE seems too small
    my $result = "{\\huge \\bfseries ";
    $result .= _convert($self, {'contents' => $element->{'args'}->[0]->{'contents'}});
    $result .= '}';
    return $result;
  }
  return '';
}

sub _set_environment_options($$$)
{
  my $self = shift;
  my $command = shift;
  my $element = shift;

  if (exists($LaTeX_environment_options{$command})) {
    return $LaTeX_environment_options{$command};
  }

  if ($command eq 'enumerate') {
    my $environment = $LaTeX_environment_commands{$command}[0];
    if ($element->{'extra'} and
        exists($element->{'extra'}->{'enumerate_specification'})) {
      my $specification = $element->{'extra'}->{'enumerate_specification'};
      if ($specification eq 'a') {
        return {$environment => 'label=\alph*.'};
      } elsif ($specification eq 'A') {
        return {$environment => 'label=\Alph*.'};
      }
      if ($specification =~ /^[a-z]+$/) {
        return {$environment
             => 'label=\alph*.,start='.(ord($specification) - ord('a') + 1)};
      } elsif ($specification =~ /^[A-Z]+$/) {
        return {$environment
             => 'label=\Alph*.,start='.(ord($specification) - ord('A') + 1)};
      } else {
        return {$environment => "start=$specification"};
      }
    }
  } elsif ($command eq 'itemize') {
    my $environment = $LaTeX_environment_commands{$command}[0];
    if ($element->{'extra'} and $element->{'extra'}->{'command_as_argument'}
        and $element->{'extra'}->{'command_as_argument'}->{'cmdname'} eq 'w') {
      # the result with \hbox{} would probably have been the same,
      # but using an empty label is more consistent with the Texinfo manual
      return {$environment => 'label={}'};
    } elsif ($element->{'args'} and $element->{'args'}->[0]->{'contents'}) {
      # NOTE when @itemize is in a preformatted environment (@example...),
      # we are not in a preformatted type here, such that the conversion
      # does not take into account the preformatted environment.  Ok or best.
      my $itemize_label = _convert($self, $element->{'args'}->[0]);
      if ($itemize_label ne '') {
        return {$environment => 'label='.$itemize_label};
      }
    }
  }
  return undef;
}

sub _xtable_description_command_format($$)
{
  my $self = shift;
  my $element = shift;

  if ($element->{'extra'}
      and $element->{'extra'}->{'command_as_argument'}) {
    my $command_as_argument
      = $element->{'extra'}->{'command_as_argument'}->{'cmdname'};
    $command_as_argument = 'code' if ($command_as_argument eq 'kbd'
                                      and _kbd_code_style($self));
    if (exists($description_command_format{$command_as_argument})
        and $description_command_format{$command_as_argument} ne '') {
      # gather for outputting in the preamble if associated to a new command
      if (exists($description_command_new_commands{$command_as_argument})) {
        $self->{'description_format_commands'}->{$command_as_argument} = 1;
      } elsif ($style_brace_format_command_new_commands{'cmd_text'}->{$command_as_argument}) {
        $self->{'style_brace_format_commands'}->{'cmd_text'}
                                                     ->{$command_as_argument} = 1;
      }
      return $description_command_format{$command_as_argument}
    }
  }
  return undef;
}

sub _kbd_code_style($)
{
  my $self = shift;
  my $kbdinputstyle = $self->get_conf('kbdinputstyle');
  return (defined($kbdinputstyle)
          and ($kbdinputstyle eq 'code'
            or ($kbdinputstyle eq 'example'
              and (not (scalar(@{$self->{'formatting_context'}->[-1]->{'preformatted_context'}})
                        and $preformatted_code_commands{$self->{'formatting_context'}->[-1]->{'preformatted_context'}->[-1]})))));
}

sub _finish_front_cover_page($)
{
  my $self = shift;
  my $result = '';
  if ($self->{'titlepage_formatting'}
      and $self->{'titlepage_formatting'}->{'in_front_cover'}) {
    # add a rule if there was a @title (same as in Texinfo TeX)
    if ($self->{'titlepage_formatting'}->{'title'}) {
      delete $self->{'titlepage_formatting'}->{'title'};
      $result .= '\vskip4pt \hrule height 2pt width \hsize
  \vskip\titlepagebottomglue
';
    }
    $result .= "\\endgroup\n";
    $self->{'titlepage_formatting'}->{'in_front_cover'} = 0;
  }
  return $result;
}

sub _tree_anchor_label {
  my $node_content = shift;

  my $label = Texinfo::Convert::NodeNameNormalization::normalize_node
    ({'contents' => $node_content});
  return "anchor:$label";
}

my %LaTeX_see_index_commands_text = (
  'seeentry' => 'see',
  'seealso' => 'seealso'
);

sub _index_entry($$)
{
  my $self = shift;
  my $element = shift;
  if ($element->{'extra'} and $element->{'extra'}->{'index_entry'}) {
    my $entry = $element->{'extra'}->{'index_entry'};
    my $entry_index_name = $entry->{'index_name'};
    my $index_name = $entry_index_name;
    if ($self->{'indices_information'}->{$entry_index_name}->{'merged_in'}) {
      $index_name
        = $self->{'indices_information'}->{$entry_index_name}->{'merged_in'};
    }
    my $in_code = 0;
    if ($self->{'indices_information'}->{$entry_index_name}->{'in_code'}) {
      $in_code = 1;
    }
    my $options
      = Texinfo::Structuring::setup_index_entry_keys_formatting($self);
    my $current_entry = $element;
    my $current_sortas;
    my $subentry_commands = [$element];
    if (exists($element->{'extra'}->{'sortas'})) {
      $current_sortas = $element->{'extra'}->{'sortas'};
    }
    my $subentries = [[{'contents' => $entry->{'content_normalized'}},
                         $current_sortas]];
    while ($current_entry->{'extra'}
      and $current_entry->{'extra'}->{'subentry'}) {
      $current_entry = $current_entry->{'extra'}->{'subentry'};
      my $current_sortas;
      if (exists($current_entry->{'extra'}->{'sortas'})) {
        $current_sortas = $current_entry->{'extra'}->{'sortas'};
      }
      push @$subentries, [$current_entry->{'args'}->[0], $current_sortas];
      push @$subentry_commands, $current_entry;
    }
    _push_new_context($self, 'index_entry');
    $self->{'formatting_context'}->[-1]->{'index'} = 1;
    my @result;
    foreach my $subentry_entry_and_sortas (@$subentries) {
      my $sortas;
      my ($subentry, $subentry_sortas) = @$subentry_entry_and_sortas;
      if ($in_code) {
        push @{$self->{'formatting_context'}->[-1]->{'code'}}, 1;
      }
      my $index_entry = _convert($self, $subentry);
      if ($in_code) {
        pop @{$self->{'formatting_context'}->[-1]->{'code'}};
      }
      # always setup a string to sort with as we may use commands
      $sortas = Texinfo::Structuring::index_entry_sort_string($entry,
                               $subentry, $subentry_sortas, $options);
      my $result = '';
      if (defined($sortas)) {
        # | in sort key breaks with hyperref
        $sortas =~ s/\|//g;
        $result = _protect_text($self, $sortas);
        $result =~ s/\\[{}]//g; # cannot have unmatched braces in index entry
        $result = _protect_index_text($result).'@';
      }
      if ($in_code) {
        $result .= "\\texttt{" . _protect_index_text($index_entry) . "}";
      } else {
        $result .= _protect_index_text($index_entry);
      }
      push @result, $result;
    }
    my $seeresult = '';
   SEEENTRY:
    foreach my $subentry_command (@$subentry_commands) {
      foreach my $seecommand (('seeentry', 'seealso')) {
        if ($subentry_command->{'extra'}->{$seecommand}
            and $subentry_command->{'extra'}->{$seecommand}->{'args'}->[0]) {
          my $seeconverted = _convert($self,
                   $subentry_command->{'extra'}->{$seecommand}->{'args'}->[0]);
          $seeresult = '|'.$LaTeX_see_index_commands_text{$seecommand}.'{'
                     .$seeconverted.'}';
          last SEEENTRY;
        }
      }
    }
    _pop_context($self);
    return "\\index[$index_name]{".join('!',@result).$seeresult."}%\n";
  }
  return '';
}

# turn off embrac for an opening @-command
sub _stop_embrac
{
  my $self = shift;
  my $result = shift;
  my $did_stop_embrac = 0;

  if ($self->{'formatting_context'}->[-1]->{'embrac'}
      and $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]
      and $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]->{'status'} == 1) {
    $result .= '\EmbracOff{}';
    $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]->{'status'} = 0;
    $did_stop_embrac = 1;
  }
  return ($result, $did_stop_embrac)
}

# turn on embrac, should be after closing an @-command that lead
# to turning off embrac
sub _restart_embrac_if_needed
{
  my $self = shift;
  my $result = shift;
  my $did_stop_embrac = shift;

  if ($did_stop_embrac) {
    $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]->{'status'} = 1;
    $result .= '\EmbracOn{}';
  }
  return $result;
}

sub _convert($$);

# Convert the Texinfo tree under $ELEMENT
sub _convert($$)
{
  my ($self, $element) = @_;

  if ($self->{'debug'}) {
    print STDERR "CONVLTX ".Texinfo::Common::debug_print_element_short($element)."\n";
    if ($self->{'debug'} > 4) {
      print STDERR "    CTX "._show_top_context_stack($self)."\n";
    }
  }

  my $type = $element->{'type'};
  my $cmdname = $element->{'cmdname'};

  if ((defined($type) and $self->{'ignored_types'}->{$type})
       or (defined($cmdname)
            and ($self->{'ignored_commands'}->{$cmdname}
                 or ($Texinfo::Commands::brace_commands{$cmdname}
                     and $Texinfo::Commands::brace_commands{$cmdname} eq 'inline'
                     and $cmdname ne 'inlinefmtifelse'
                     and (($inline_format_commands{$cmdname}
                          and (!$element->{'extra'}->{'format'}
                               or !$self->{'expanded_formats_hash'}->{$element->{'extra'}->{'format'}}))
                         or (!$inline_format_commands{$cmdname}
                             and !defined($element->{'extra'}->{'expand_index'}))))))) {
    return '';
  }
  my $result = '';

  if ($self->{'formatting_context'}->[-1]->{'in_skipped_node_top'}) {
    my $node_element;
    if (defined($cmdname) and $cmdname eq 'node') {
      $node_element = $element;
    } elsif (defined($cmdname) and $cmdname eq 'part' and $element->{'extra'}
             and $element->{'extra'}->{'part_following_node'}) {
      $node_element = $element->{'extra'}->{'part_following_node'};
    }
    if (($node_element
         and not ($node_element->{'extra'}
                  and $node_element->{'extra'}->{'normalized'}
                  and $node_element->{'extra'}->{'normalized'} eq 'Top'))
         or (defined($type) and $type eq 'ignored_top_node_paragraph')
        or (defined($cmdname) and $cmdname eq 'part')) {
      $self->{'formatting_context'}->[-1]->{'in_skipped_node_top'} = 0;
    } elsif (not ((defined($cmdname)
                   and ($informative_commands{$cmdname}
                        or $sectioning_heading_commands{$cmdname}
                        or $cmdname eq 'float'
                        or $cmdname eq 'anchor'))
                   or ($type and $type eq 'paragraph'))) {
      return '';
    }
  }

  # in ignorable spaces, keep only form feeds.
  if ($type and $self->{'ignorable_space_types'}->{$type}) {
    if ($type eq 'spaces_after_close_brace') {
      if ($element->{'text'} =~ /\f/) {
        $result = '\par{}';
      }
    }
    return $result;
  }

  if ($type and ($type eq 'empty_line')) {
    if ($element->{'text'} =~ /\f/) {
      $result = '\par{}';
    }
    return $result."\n";
  }

  # process text
  if (defined($element->{'text'})) {
    if (!$type or $type ne 'untranslated') {
      my $result = _protect_text($self, $element->{'text'});
      return $result;
    } else {
      my $tree = $self->gdt($element->{'text'});
      my $converted = _convert($self, $tree);
      return $converted;
    }
  }

  if ($element->{'extra'}) {
    if ($element->{'extra'}->{'missing_argument'}
             and (!$element->{'contents'} or !@{$element->{'contents'}})) {
      return '';
    }
  }

  # for displaymath that closes the preformatted
  my $preformatted_to_reopen;
  if ($cmdname) {
    my $unknown_command;
    my $command_format_context = 'cmd_text';
    if ($self->{'formatting_context'}->[-1]->{'text_context'}->[-1] eq 'ctx_math') {
      $command_format_context = 'cmd_math';
    }
    my $did_stop_embrac;
    if (defined($nobrace_symbol_text{$cmdname})) {
      if ($cmdname eq ':') {
        if ($command_format_context ne 'cmd_math') {
          $result .= "\\\@";
        }
      } elsif ($cmdname eq '*') {
        if ($command_format_context ne 'cmd_math') {
          if ($self->{'formatting_context'}->[-1]->{'no_eol'}
              and $self->{'formatting_context'}->[-1]->{'no_eol'}->[-1]) {
            # in tabularx in @def* we ignore @*
            $result = ' ';
          } else {
            # FIXME \leavevmode{} is added to avoid
            # ! LaTeX Error: There's no line here to end.
            # but it is not clearly correct
            $result = "\\leavevmode{}\\\\";
            #$result = "\\linebreak[4]\n";
          }
        } else {
          if ($self->{'formatting_context'}->[-1]->{'math_style'}->[-1]
              eq 'one-line') {
            $result .= "";
          } else {
            # no such case for now, but could be in the future
            $result .= "\\\\";
          }
        }
      } elsif ($cmdname eq '.' or $cmdname eq '?' or $cmdname eq '!') {
        if ($command_format_context ne 'cmd_math') {
          $result .= "\\\@";
        }
        $result .= $cmdname;
      } elsif ($cmdname eq ' ' or $cmdname eq "\n" or $cmdname eq "\t") {
        $result .= "\\ {}";
      } elsif ($cmdname eq '-') {
        $result .= "\\-{}";

      } elsif ($cmdname eq '{' or $cmdname eq '}') {
        # Index entries need balanced braces so we can't use \{ and \}.
        if ($self->{'formatting_context'}->[-1]->{'index'}) {
          if ($cmdname eq '{') {
            if ($self->{'formatting_context'}->[-1]->{'text_context'}->[-1]
                 eq 'ctx_math') {
                $result .= '\\lbrace{}';
            } else {
                $result .= '\\textbraceleft{}';
            }
          } elsif ($cmdname eq '}') {
            if ($self->{'formatting_context'}->[-1]->{'text_context'}->[-1]
                 eq 'ctx_math') {
                $result .= '\\rbrace{}';
            } else {
                $result .= '\\textbraceright{}';
            }
          }
        } else {
          # always protect, even in math mode 
          $result .= "\\$cmdname";
        }
      } elsif ($cmdname eq '\\'
             and $self->{'formatting_context'}->[-1]->{'text_context'}->[-1]
                 eq 'ctx_math') {
        $result .= '\\backslash{}';
      } else {
        $result .= _protect_text($self, $nobrace_symbol_text{$cmdname});
      }
      return $result;
    } elsif (exists($brace_no_arg_commands{$cmdname})) {
      my $converted_command = $cmdname;
      if ($cmdname eq 'click' and $element->{'extra'}
        and exists($element->{'extra'}->{'clickstyle'})) {
        $converted_command = $element->{'extra'}->{'clickstyle'};
      }
      if ($self->{'enable_encoding'}) {
        my $encoding = $self->{'output_encoding_name'};
        if ($letter_no_arg_commands{$converted_command}) {
          my $conversion
            = Texinfo::Convert::Unicode::brace_no_arg_command($converted_command,
                                                              $encoding);
          if (defined($conversion)) {
            $result .= $conversion;
            return $result;
          }
        }
      }
      if (exists($LaTeX_no_arg_brace_commands{$command_format_context}->{$converted_command})) {
        if ($converted_command eq 'error'
            and $self->{'formatting_context'}->[-1]->{'in_sectioning_command_heading'}) {
          # in a sectioning command, the contents bookmark is also generated, and
          # some commands do not play well with the contents bookmark.  In particular
          # \fbox.  \texorpdfstring allows to specify a different output for
          # the string in contents bookmark.
          #
          # TODO Note that other commands than @error are not perfect in contents
          # bookmarks, in particular all the commands formatted in math disappear.
          # However the other commands have no clear string representations,
          # being removed in the contents bookmark strings is not so bad until
          # a better solution is found
          #
          # See also
          # https://github.com/latex3/hyperref/issues/207#issuecomment-920712424
          $result .= '\texorpdfstring{'.
            $LaTeX_no_arg_brace_commands{$command_format_context}->{$converted_command}
            # FIXME translation
            .'}{error}'
        } else {
          $result .= $LaTeX_no_arg_brace_commands{$command_format_context}->{$converted_command};
        }
      } else {
        die "BUG: unknown brace_no_arg_commands $cmdname $converted_command\n";
      }
      return $result;
    # commands with braces
    } elsif ($accent_commands{$cmdname}) {
      if ($self->{'enable_encoding'}) {
        my $encoding = $self->{'output_encoding_name'};
        my $sc;
        my $accented_text
           = Texinfo::Convert::Text::text_accents($element, $encoding, $sc);
        $result .= _protect_text($self, $accented_text);
      } else {
        my $accent_arg = '';

        if ($LaTeX_accent_commands{$command_format_context}->{$cmdname}) {
          $result .= "\\$LaTeX_accent_commands{$command_format_context}->{$cmdname}\{";
          if ($element->{'args'}) {
            $accent_arg = _convert($self, $element->{'args'}->[0]);
          }
          $result .= $accent_arg;
          $result .= '}';
        } elsif ($cmdname eq 'dotless') {
          if ($element->{'args'}) {
            $accent_arg = _convert($self, $element->{'args'}->[0]);
          }
          if ($accent_arg eq 'i' or $accent_arg eq 'j') {
            if ($command_format_context eq 'cmd_math') {
              $result .= "\\${accent_arg}math{}";
            } else {
              $result .= "\\${accent_arg}{}";
            }
          } else {
            # should be an error, but we do not care, it is better if it is
            # handled during parsing
            $result .= _protect_text($self, $accent_arg);
          }
          return $result;
        # accent without math mode command, use slanted text
        } elsif ($command_format_context eq 'cmd_math'
                 and $LaTeX_accent_commands{'cmd_text'}->{$cmdname}) {
          $result .= "\\textsl{\\$LaTeX_accent_commands{'cmd_text'}->{$cmdname}\{";
          # we do not want accents within to be math accents
          if ($element->{'args'}) {
            push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_text';
            $accent_arg = _convert($self, $element->{'args'}->[0]);
            my $old_context
              = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
          }
          $result .= $accent_arg;
          $result .= '}}';
        }
      }
      return $result;
    } elsif (exists($LaTeX_style_brace_commands{'cmd_text'}->{$cmdname})
         or ($element->{'type'}
             and $element->{'type'} eq 'definfoenclose_command')) {
      my $did_stop_embrac = 0;
      ($result, $did_stop_embrac) = _stop_embrac($self, $result)
         if ($cmdname eq 'r');
      my $formatted_cmdname;
      if ($cmdname eq 'kbd' and _kbd_code_style($self)) {
        # use code for kbd formatting if needed
        $formatted_cmdname = 'code';
      } else {
        $formatted_cmdname = $cmdname;
      }
      if ($brace_code_commands{$cmdname}) {
        push @{$self->{'formatting_context'}->[-1]->{'code'}}, 1;
      } elsif ($roman_style_commands{$cmdname}) {
        push @{$self->{'formatting_context'}->[-1]->{'code'}}, 0;
      }
      if ($self->{'quotes_map'}->{$formatted_cmdname}) {
        $result .= $self->{'quotes_map'}->{$formatted_cmdname}->[0];
      }
      my $command_format_context = $command_format_context;
      # gather for outputting in the preamble if associated to a new command
      if ($style_brace_format_command_new_commands{$command_format_context}
                                                     ->{$formatted_cmdname}) {
        $self->{'style_brace_format_commands'}->{$command_format_context}
                                                     ->{$formatted_cmdname} = 1;
      }
      if ($LaTeX_style_brace_commands{$command_format_context}->{$formatted_cmdname}) {
        my $LaTeX_style_command
          = $LaTeX_style_brace_commands{$command_format_context}->{$formatted_cmdname};
        if ($need_known_embrac{$LaTeX_style_command}
            and $self->{'formatting_context'}->[-1]->{'embrac'}
            and $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]
            and $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]->{'status'} == 1) {
          my $defined_style_embrac = $need_known_embrac{$LaTeX_style_command};
          if (not $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]
                ->{'made_known'}->{$defined_style_embrac}) {
            $self->{'formatting_context'}->[-1]->{'embrac'}->[-1]
                       ->{'made_known'}->{$defined_style_embrac} = 1;
          }
        }
        $result .= "$LaTeX_style_command\{";
      }
      if ($element->{'args'}) {
        $result .= _convert($self, $element->{'args'}->[0]);
      }
      if ($LaTeX_style_brace_commands{$command_format_context}->{$formatted_cmdname}) {
        $result .= '}';
      }
      if ($self->{'quotes_map'}->{$formatted_cmdname}) {
        $result .= $self->{'quotes_map'}->{$formatted_cmdname}->[1];
      }
      if ($brace_code_commands{$cmdname}) {
        pop @{$self->{'formatting_context'}->[-1]->{'code'}};
      } elsif ($roman_style_commands{$cmdname}) {
        pop @{$self->{'formatting_context'}->[-1]->{'code'}};
      }
      $result = _restart_embrac_if_needed($self, $result, $did_stop_embrac);
      return $result;
    } elsif ($cmdname eq 'dmn') {
      $result .= '\\thinspace ';
      if ($element->{'args'}) {
        $result .= _convert($self, $element->{'args'}->[0]);
      }
    } elsif ($cmdname eq 'verb') {
      # NOTE \verb is forbidden in other macros in LaTeX.  We do
      # not enforce this constraint here, nor warn.  Checking
      # whether we are in another LaTeX macro would probably be a pain.
      # It should be ok, though, as it is described as an error in the manual:
      #   It is not reliable to use @verb inside other Texinfo constructs
      $result .= "\\verb" .$element->{'extra'}->{'delimiter'};
      push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_raw';
      if ($element->{'args'}) {
        $result .= _convert($self, $element->{'args'}->[0]);
      }
      my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
      die if ($old_context ne 'ctx_raw');
      $result .= $element->{'extra'}->{'delimiter'};
      return $result;
    } elsif ($cmdname eq 'image') {
      if (defined($element->{'args'}->[0])
          and $element->{'args'}->[0]->{'contents'}
          and @{$element->{'args'}->[0]->{'contents'}}) {
        # distinguish text basefile used to find the file and
        # converted basefile with special characters escaped
        my $basefile = Texinfo::Convert::Text::convert_to_text(
         {'contents' => $element->{'args'}->[0]->{'contents'}},
         {'code' => 1, %{$self->{'convert_text_options'}}});

        # warn if no file is found, even though the basefile is used
        # in any case.
        my $image_file_found;
        foreach my $extension (@LaTeX_image_extensions) {
          my ($file_name, $file_name_encoding)
             = $self->encoded_input_file_name("$basefile.$extension");
          my $located_file =
            $self->Texinfo::Common::locate_include_file($file_name);
          if (defined($located_file)) {
            $image_file_found = 1;
            last;
          }
        }
        if (not $image_file_found) {
          $self->line_warn($self,
                         sprintf(__("\@image file `%s' (for LaTeX) not found"),
                                 $basefile),
                           $element->{'source_info'});
        }
        # Use the basename and not the file found.  It is agreed that it is
        # better, since in any case the files are moved.
        # If the file path found was to be used it should be decoded to perl
        # codepoints too.
        # using basefile with escaped characters, no extension to let LaTeX
        # choose the extension
        # FIXME not clear at all what can be in filenames here,
        # what should be escaped and how
        my $converted_basefile = $basefile;
        # for now minimal protection.  Not sure that % is problematic
        $converted_basefile =~ s/([%{}\\])/\\$1/g;
        my $image_file = $converted_basefile;
        my $width;
        if ((@{$element->{'args'}} >= 2)
              and defined($element->{'args'}->[1])
              and $element->{'args'}->[1]->{'contents'}
              and @{$element->{'args'}->[1]->{'contents'}}){
          push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_raw';
          $width = _convert($self, {'contents'
                         => $element->{'args'}->[1]->{'contents'}});
          my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
          die if ($old_context ne 'ctx_raw');
          if ($width !~ /\S/) {
            $width = undef;
          }
        }
        my $height;
        if ((@{$element->{'args'}} >= 3)
              and defined($element->{'args'}->[2])
              and $element->{'args'}->[2]->{'contents'}
              and @{$element->{'args'}->[2]->{'contents'}}) {
          push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_raw';
          $height = _convert($self, {'contents'
                         => $element->{'args'}->[2]->{'contents'}});
          my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
          die if ($old_context ne 'ctx_raw');
          if ($height !~ /\S/) {
            $height = undef;
          }
        }
        $result .= "\\includegraphics";
        if (defined($width) or defined($height)) {
          $result .= "[";
          if (defined($width)) {
            $result .= "width=$width";
            if (defined($height)) {
              $result .= ",";
            }
          }
          if (defined($height)) {
            $result .= "height=$height";
          }
          $result .= "]";
        }
        $result .= "{$image_file}";
      }
      return $result;
    } elsif ($cmdname eq 'email') {
      if ($element->{'args'}) {
        my $name;
        my $converted_name;
        my $email;
        my $email_text;
        if (scalar (@{$element->{'args'}}) == 2
            and defined($element->{'args'}->[1])
            and $element->{'args'}->[1]->{'contents'}
            and @{$element->{'args'}->[1]->{'contents'}}) {
          $name = $element->{'args'}->[1]->{'contents'};
          $converted_name = _convert($self, {'contents' => $name});
        }
        if (defined($element->{'args'}->[0])
            and $element->{'args'}->[0]->{'contents'}
            and @{$element->{'args'}->[0]->{'contents'}}) {
          $email = $element->{'args'}->[0]->{'contents'};
          $email_text
            = $self->_protect_url(Texinfo::Convert::Text::convert_to_text(
                {'contents' => $email},
                {'code' => 1, %{$self->{'convert_text_options'}}}));
        }
        if ($name and $email) {
          $result .= "\\href{mailto:$email_text}{$converted_name}";
        } elsif ($email) {
          $result .= "\\href{mailto:$email_text}{\\nolinkurl{$email_text}}";
        } elsif ($name) {
          $result .= $converted_name;
        }
      }
      return $result;
    } elsif ($cmdname eq 'uref' or $cmdname eq 'url') {
      if ($element->{'args'}) {
        if (scalar(@{$element->{'args'}}) == 3
             and defined($element->{'args'}->[2])
             and $element->{'args'}->[2]->{'contents'}
             and @{$element->{'args'}->[2]->{'contents'}}) {
          unshift @{$self->{'current_contents'}->[-1]},
            {'contents' => $element->{'args'}->[2]->{'contents'}};
        } elsif ($element->{'args'}->[0]
                 and $element->{'args'}->[0]->{'contents'}
                 and @{$element->{'args'}->[0]->{'contents'}}) {
          my $url_content = $element->{'args'}->[0]->{'contents'};
          my $url_text = $self->_protect_url(
            Texinfo::Convert::Text::convert_to_text(
               {'contents' => $url_content},
               {'code' => 1, %{$self->{'convert_text_options'}}}));
          if (scalar(@{$element->{'args'}}) == 2
              and defined($element->{'args'}->[1])
              and $element->{'args'}->[1]->{'contents'}
              and @{$element->{'args'}->[1]->{'contents'}}) {
            my $description = _convert($self, {'contents',
                                   $element->{'args'}->[1]->{'contents'}});
            my $text = $self->gdt('{text} ({url})',
                          {'text' => $description, 'url' => "\\nolinkurl{$url_text}"},
                                       'translated_text');
            $result .= "\\href{$url_text}{$text}";
            return $result;
          } else {
            $result .= "\\url{$url_text}";
            return $result;
          }
        } elsif (scalar(@{$element->{'args'}}) == 2
                 and defined($element->{'args'}->[1])
                 and $element->{'args'}->[1]->{'contents'}
                 and @{$element->{'args'}->[1]->{'contents'}}) {
          unshift @{$self->{'current_contents'}->[-1]},
            {'contents' => $element->{'args'}->[1]->{'contents'}};
        }
      }
      return $result;
    } elsif ($cmdname eq 'footnote') {
      _push_new_context($self, 'footnote');
      $result .= '\footnote{';
      $result .= $self->_convert($element->{'args'}->[0]);
      $result .= '}';
      _pop_context($self);
      return $result;
    } elsif ($cmdname eq 'anchor') {
      my $anchor_label = _tree_anchor_label($element->{'extra'}->{'node_content'});
      $result .= "\\label{$anchor_label}%\n";
      return $result;
    } elsif ($ref_commands{$cmdname}) {
      if (scalar(@{$element->{'args'}})) {
        my @args;
        for my $arg (@{$element->{'args'}}) {
          if (defined $arg->{'contents'} and @{$arg->{'contents'}}) {
            push @args, $arg->{'contents'};
          } else {
            push @args, undef;
          }
        }
        if ($cmdname eq 'inforef' and scalar(@args) == 3) {
          $args[3] = $args[2];
          $args[2] = undef;
        }
        my $book = '';
        if (defined($args[4])) {
          $book = _convert($self, {'contents' => $args[4]});
        }

        my $file_contents;
        # FIXME not sure if Texinfo TeX uses the external node manual
        # specified as part of the node name with manual name prependended
        # in parentheses
        if (defined($args[3])) {
          $file_contents = $args[3];
        } elsif ($element->{'extra'}
                 and $element->{'extra'}->{'node_argument'}
                 and defined($element->{'extra'}->{'node_argument'}->{'normalized'})
                 and $element->{'extra'}->{'node_argument'}->{'manual_content'}) {
          $file_contents = $element->{'extra'}->{'node_argument'}->{'manual_content'};
        }
        my $filename = '';
        if ($file_contents) {
          push @{$self->{'formatting_context'}->[-1]->{'code'}}, 1;
          $filename = _convert($self, {'contents' => $file_contents});
          pop @{$self->{'formatting_context'}->[-1]->{'code'}};
        }
        
        if ($cmdname ne 'inforef' and $book eq '' and $filename eq ''
            and $element->{'extra'}
            and $element->{'extra'}->{'node_argument'}
            and defined($element->{'extra'}->{'node_argument'}->{'normalized'})
            and !$element->{'extra'}->{'node_argument'}->{'manual_content'}
            and $self->{'labels'}
            and $self->{'labels'}->{$element->{'extra'}->{'node_argument'}->{'normalized'}}) {
          # internal reference
          my $reference
           = $self->{'labels'}->{$element->{'extra'}->{'node_argument'}->{'normalized'}};
          my $reference_node_content = $reference->{'extra'}->{'node_content'};
          
          my $section_command;
          if ($reference->{'extra'}->{'associated_section'}) {
            $section_command = $reference->{'extra'}->{'associated_section'};
          } elsif ($reference->{'cmdname'} ne 'float') {
            my $normalized_name
              = $element->{'extra'}->{'node_argument'}->{'normalized'};
            if ($self->{'normalized_nodes_associated_section'}
                and $self->{'normalized_nodes_associated_section'}->{$normalized_name}) {
              $section_command
                = $self->{'normalized_nodes_associated_section'}->{$normalized_name};
            } elsif ($reference->{'cmdname'} eq 'node') {
              # can only happen if there is no sectioning commands at all,
              # otherwise it would have been associated in
              # _associate_other_nodes_to_sections.  Nothing to do in that case.
            } else {
              # an anchor.  Find associated section using top level parent @-command.
              my $current = $reference;
              while ($current->{'parent'}) {
                $current = $current->{'parent'};
                if ($current->{'cmdname'}
                    and $root_commands{$current->{'cmdname'}}) {
                  if ($current->{'cmdname'} ne 'node') {
                    $section_command = $current;
                  } else {
                    if ($current->{'extra'}->{'associated_section'}) {
                      $section_command = $current->{'extra'}->{'associated_section'};
                    } elsif (exists($current->{'extra'}->{'normalized'})
                             and $self->{'normalized_nodes_associated_section'}
                                        ->{$current->{'extra'}->{'normalized'}}) {
                      $section_command
                        = $self->{'normalized_nodes_associated_section'}
                                           ->{$current->{'extra'}->{'normalized'}};
                    }
                  }
                  last;
                } elsif ($current->{'type'}
                         and $current->{'type'} eq 'before_node_section') {
                  # anchor before Top node, can be in copying, titlepage, or directly
                  # in the main document.  Could also be before setfilename.
                  last;
                }
              }
              if (defined($section_command)) {
                # set the association with anchor
                $self->{'normalized_nodes_associated_section'}->{$normalized_name}
                  = $section_command;
              } elsif (not defined($current->{'parent'})) {
                # that means that it is an anchor, but we did not find an root
                # sectioning command nor 'before_node_section' type, which
                # should not be possible.
                print STDERR "BUG/TODO assoc ".$reference->{'cmdname'}.": $normalized_name: ".join("|", sort(keys(%{$reference->{'extra'}})))."\n";
              }
            }
          }
          # reference to a float with a label
          my $float_type;
          if (exists($reference->{'cmdname'})
              and $reference->{'cmdname'} eq 'float') {
            if ($reference->{'extra'}->{'type'}
                and $reference->{'extra'}->{'type'}->{'normalized'} ne '') {
              my $float_type_contents = $reference->{'extra'}->{'type'}->{'content'};
              $float_type = _convert($self, {'contents' => $float_type_contents});
            } else {
              $float_type = '';
            }
          }
          my $text_representation;
          if ($self->{'formatting_context'}->[-1]->{'in_sectioning_command_heading'}) {
            # hyperref leads, understandably to some errors in a heading for
            # the table of content.  In that case, setup a text representation.
            $text_representation = '';
          }
          # TODO: should translate
          my $reference_result = '';
          if ($cmdname eq 'xref') {
            $reference_result = "See ";
          } elsif ($cmdname eq 'pxref') {
            $reference_result = "see ";
          } elsif ($cmdname eq 'ref') {
          }
          $text_representation .= $reference_result
            if defined($text_representation);
          my $name;
          if (defined($args[2])) {
            $name = $args[2];
          } elsif (not defined($float_type)) {
            if (defined($self->get_conf('xrefautomaticsectiontitle'))
                and $self->get_conf('xrefautomaticsectiontitle') eq 'on'
                and $section_command) {
              $name = $section_command->{'args'}->[0]->{'contents'};
            } else {
              $name = $reference_node_content;
            }
          }
          my $reference_label = _tree_anchor_label($reference_node_content);

          my $name_text;
          if (defined($name)) {
            $name_text = _convert($self, {'contents' => $name});
            $text_representation .= $name_text if (defined($text_representation));
          }

          # FIXME translation
          if (defined($float_type)) {
            # no page for float reference in Texinfo TeX
            if (defined($name_text)) {
              $reference_result .= "\\hyperref[$reference_label]{$name_text}";
            } else {
              if ($float_type ne '') {
                $reference_result
                  .= "\\hyperref[$reference_label]{$float_type~\\ref*{$reference_label}}";
                $text_representation .= $float_type
                     if (defined($text_representation));
              } else {
                $reference_result
                  .= "\\hyperref[$reference_label]{\\ref*{$reference_label}}";
              }
            }
          } else {
            # FIXME seems like a , should be added last, but only if not
            # followed by punctuation which means a painful look ahead
            # code to do...  From the Texinfo manual:
            # When processing with TeX, a comma is automatically inserted after the page number
            # for cross-references to within the same manual, unless the closing brace of the argument
            # is followed by non-whitespace (such as a comma or period).
            #
            # If an unwanted comma is added, follow the argument with a command such as @:
            if ($reference->{'cmdname'} and $reference->{'cmdname'} eq 'node'
                and $section_command) {
              if ($section_command->{'structure'}->{'section_level'} > 1) {
                # TODO command that could be used for translation \sectionname does
                # not exist in the default case.  it is defined in the pagenote package together with
                # \pagename which is page in the default case, but it is unclear if this
                # can be used as a basis for translations
                $reference_result
                  .= "\\hyperref[$reference_label]{Section~\\ref*{$reference_label} [$name_text], page~\\pageref*{$reference_label}}";
              } else {
                # TODO translation
                $reference_result
                  .= "\\hyperref[$reference_label]{\\chaptername~\\ref*{$reference_label} [$name_text], page~\\pageref*{$reference_label}}";
              }
            } else {
              # anchor or document without sectioning commands
              # TODO translation
              $reference_result
                .= "\\hyperref[$reference_label]{[$name_text], page~\\pageref*{$reference_label}}";
            }
          }
          if (not defined($text_representation)) {
            $result .= $reference_result;
          } else {
            $result .= '\texorpdfstring{'.$reference_result.'}{'
                             .$text_representation.'}';
          }
          return $result;
        } else {
          # external ref
          # TODO hyper reference to manual file which seems to be implemented
          # in recent Texinfo TeX
          # TODO: should translate
          if ($cmdname eq 'xref') {
            $result .= "See ";
          } elsif ($cmdname eq 'pxref') {
            $result .= "see ";
          } elsif ($cmdname eq 'ref') {
          }
          my $name;
          if (defined($args[2])) {
            $name = $args[2];
          } elsif (defined($args[0])) {
            $name = $args[0];
          }
          my $name_text;
          if (defined($name)) {
            $name_text = _convert($self, {'contents' => $name});
          }
          
          if ($book ne '') {
            if (defined ($name_text)) {
              # TODO translation
              $result .= "Section ``$name_text'' in \\textsl{$book}";
            } else {
              $result .= "\\textsl{$book}";
            }
          } elsif ($filename ne '') {
            if (defined ($name_text)) {
              # TODO translation
              $result .= "Section ``$name_text'' in \\texttt{$filename}";
            } else {
              $result .= "\\texttt{$filename}";
            }
          } elsif ($name_text) {
            $result .= $name_text;
          }
        }
        return $result;
      }
      return $result;
    } elsif ($explained_commands{$cmdname}) {
      if ($element->{'args'}
          and defined($element->{'args'}->[0])
          and $element->{'args'}->[0]->{'contents'}
          and @{$element->{'args'}->[0]->{'contents'}}) {
        # in abbr spaces never end a sentence.
        my $argument;
        if ($cmdname eq 'abbr') {
          $argument = {'type' => '_dot_not_end_sentence',
                       'contents' => $element->{'args'}->[0]->{'contents'}};
        } else {
        # TODO in TeX, acronym is in a smaller font (1pt less).
          $argument = { 'contents' => $element->{'args'}->[0]->{'contents'}};
        }
        if (scalar (@{$element->{'args'}}) == 2
            and defined($element->{'args'}->[-1])
            and $element->{'args'}->[-1]->{'contents'}
            and @{$element->{'args'}->[-1]->{'contents'}}) {
          my $prepended = $self->gdt('{abbr_or_acronym} ({explanation})',
                           {'abbr_or_acronym' => $argument,
                            'explanation' => $element->{'args'}->[-1]->{'contents'}});
          $result .= _convert($self, $prepended);
        } else {
          $result .= _convert($self, $argument);
        }
      }
      return $result;
    } elsif ($Texinfo::Commands::brace_commands{$cmdname}
             and $Texinfo::Commands::brace_commands{$cmdname} eq 'inline') {
      my $arg_index = 1;
      if ($cmdname eq 'inlinefmtifelse'
          and (!$element->{'extra'}->{'format'}
               or !$self->{'expanded_formats_hash'}->{$element->{'extra'}->{'format'}})) {
        $arg_index = 2;
      }
      if (scalar(@{$element->{'args'}}) > $arg_index
         and defined($element->{'args'}->[$arg_index])
         and $element->{'args'}->[$arg_index]->{'contents'}
         and scalar(@{$element->{'args'}->[$arg_index]->{'contents'}})) {
        if ($cmdname eq 'inlineraw') {
          push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_raw';
        }
        $result .= _convert($self, {'contents'
                         => $element->{'args'}->[$arg_index]->{'contents'}});
        if ($cmdname eq 'inlineraw') {
          my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
          die if ($old_context ne 'ctx_raw');
        }
      }
      return $result;
    } elsif ($math_commands{$cmdname}) {
      push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_math';
      if (not exists($block_commands{$cmdname})) {
        push @{$self->{'formatting_context'}->[-1]->{'math_style'}}, 'one-line';
        if ($cmdname eq 'math') {
          if ($element->{'args'}) {
            $result .= '$';
            $result .= _convert($self, $element->{'args'}->[0]);
            $result .= '$';
          }
        }
        my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
        die if ($old_context ne 'ctx_math');
        my $old_math_style = pop @{$self->{'formatting_context'}->[-1]->{'math_style'}};
        die if ($old_math_style ne 'one-line');
        return $result;
      } else {
        if ($cmdname eq 'displaymath') {
          push @{$self->{'formatting_context'}->[-1]->{'math_style'}}, 'one-line';
          # close all preformatted formats
          $preformatted_to_reopen
              = [@{$self->{'formatting_context'}->[-1]->{'preformatted_context'}}];
          $result .= _close_preformatted_stack($self, $preformatted_to_reopen);
          $result .= "\\[\n";
        }
      }
    } elsif ($cmdname eq 'caption' or $cmdname eq 'shortcaption') {
      if (not defined($element->{'extra'})
          or not defined($element->{'extra'}->{'float'})) {
        return $result;
      }
      my $float = $element->{'extra'}->{'float'};
      my $shortcaption;
      if ($cmdname eq 'shortcaption') {
        if ($float->{'extra'}->{'caption'}) {
          # nothing to do, will use @shortcaption when converting @caption
          return $result;
        } else {
          # shortcaption used as caption;
        }
      } else {
        if ($float->{'extra'}->{'shortcaption'}) {
          $shortcaption = $float->{'extra'}->{'shortcaption'};
        }
      }
      my $caption_text = '';
      if ($element->{'args'}->[0]->{'contents'}) {
        _push_new_context($self, 'latex_caption');
         $caption_text = _convert($self,
           {'contents' => $element->{'args'}->[0]->{'contents'}});
        _pop_context($self);
      }
      
      $result .= '\caption';

      if (defined($shortcaption)
          and $shortcaption->{'args'}->[0]->{'contents'}) {
        _push_new_context($self, 'latex_shortcaption');
        my $shortcaption_text = _convert($self,
                 {'contents' => $shortcaption->{'args'}->[0]->{'contents'}});
        _pop_context($self);
        $result .= '['.$shortcaption_text.']';
      }
      $result .= "{$caption_text}\n";
      return $result;
    } elsif ($cmdname eq 'titlefont') {
      $result .= _title_font($self, $element);
      return $result;
    } elsif ($cmdname eq 'U') {
      my $arg;
      if ($element->{'args'}
          and $element->{'args'}->[0]
          and $element->{'args'}->[0]->{'contents'}
          and $element->{'args'}->[0]->{'contents'}->[0]
          and $element->{'args'}->[0]->{'contents'}->[0]->{'text'}) {
        $arg = $element->{'args'}->[0]->{'contents'}->[0]->{'text'};
      }
      if ($arg) {
        # Syntactic checks on the value were already done in Parser.pm,
        # but we have one more thing to test: since this is the one
        # place where we might output actual UTF-8 binary bytes, we have
        # to check that it is possible.  If not, silently fall back to
        # plain text, on the theory that the user wants something.
        # Note that being able to output an unicode point as encoded
        # character does not mean that LaTeX will be able to process it.
        my $res;
        if ($self->{'to_utf8'}) {
          my $possible_conversion
            = Texinfo::Convert::Unicode::check_unicode_point_conversion($arg,
                                                             $self->{'debug'});
          if ($possible_conversion) {
            $res = chr(hex($arg)); # ok to call chr
          } else {
            $res = "U+$arg";
          }
        } else {
          $res = "U+$arg";  # not outputting UTF-8
        }
        $result .= _protect_text($self, $res);
      }
      return $result;
    } elsif ($cmdname eq 'value') {
      my $expansion = $self->gdt('@{No value for `{value}\'@}',
                          {'value' => $element->{'extra'}->{'flag'}});
      $expansion = {'type' => 'paragraph',
                    'contents' => [$expansion]};
      $result .= _convert($self, $expansion);
      return $result;
    # block commands
    } elsif (exists($block_commands{$cmdname})) {
      if ($LaTeX_environment_commands{$cmdname}) {
        my $environment_options = _set_environment_options($self, $cmdname, $element);
        foreach my $environment (@{$LaTeX_environment_commands{$cmdname}}) {
          $self->{'fixed_width_environments'}->{$environment} = 1
            if ($LaTeX_fixed_width_environments{$environment});
          $result .= "\\begin{".$environment."}";
          if (defined($environment_options) and
              exists($environment_options->{$environment})) {
            $result .= '['.$environment_options->{$environment}.']';
          }
          # For @flushright and @flushleft, which don't use 'preformatted'
          # elements, unlike @display and @format.  This comments out the
          # newline.
          $result .= '%' if $environment eq 'Texinfopreformatted';
          $result .= "\n";
        }
        if ($LaTeX_environment_packages{$cmdname}) {
          foreach my $package (@{$LaTeX_environment_packages{$cmdname}}) {
            $self->{'packages'}->{$package} = 1;
          }
        }
        if ($LaTeX_list_environments{$cmdname}) {
          $self->{'list_environments'}->{$LaTeX_list_environments{$cmdname}} = 1;
        }
      }
      if ($preformatted_commands{$cmdname}) {
        _open_preformatted_command($self, $cmdname);
      } elsif ($block_raw_commands{$cmdname}) {
        push @{$self->{'formatting_context'}->[-1]->{'text_context'}}, 'ctx_raw';
      }
      if ($block_commands{$cmdname}
          and $block_commands{$cmdname} eq 'item_line') {
        # may be undef, in particular if the command is not a style command,
        # for example @email
        my $description_command_format
          = _xtable_description_command_format($self, $element);
        push @{$self->{'formatting_context'}->[-1]->{'table_command_format'}},
                $description_command_format;
      }
      if ($cmdname eq 'quotation' or $cmdname eq 'smallquotation') {
        # this is only used to avoid @author converted as
        # a @titlepage author, for a @quotation in @titlepage @author
        $self->{'formatting_context'}->[-1]->{'in_quotation'} += 1;
        if ($element->{'args'} and $element->{'args'}->[0]
            and $element->{'args'}->[0]->{'contents'}
            and @{$element->{'args'}->[0]->{'contents'}}) {
          my $prepended = $self->gdt('@b{{quotation_arg}:} ',
             {'quotation_arg' => $element->{'args'}->[0]->{'contents'}});
          $result .= $self->_convert($prepended);
        }
      } elsif ($cmdname eq 'multitable') {
        # for m{} in tabular header
        $self->{'packages'}->{'array'} = 1;
        $result .= '\begin{tabular}{';
        my @fractions;
        if ($element->{'extra'}->{'columnfractions'}) {
          @fractions
      = @{$element->{'extra'}->{'columnfractions'}->{'extra'}->{'misc_args'}};
        } elsif ($element->{'extra'}->{'prototypes'}) {
          my @prototypes_length;
          my $total_length = 0.;
          foreach my $prototype (@{$element->{'extra'}->{'prototypes'}}) {
            # not clear what to do here.  For now use the text width
            my $prototype_text
              = Texinfo::Convert::Text::convert_to_text($prototype,
                 $self->{'convert_text_options'});
            my $length = Texinfo::Convert::Unicode::string_width($prototype_text);
            $total_length += $length;
            push @prototypes_length, $length;
          }
          if ($total_length > 0.) {
            foreach my $length (@prototypes_length) {
              push @fractions, $length / $total_length;
            }
          }
        }
        $result .= join(' ', map {'m{'.$_.'\textwidth}'} @fractions);
        $result .= "}%\n";
      } elsif ($cmdname eq 'float') {
        if (not $self->{'formatting_context'}->[-1]->{'in_skipped_node_top'}) {
          my $normalized_float_type = '';
          if ($element->{'extra'}->{'type'}) {
            $normalized_float_type = $element->{'extra'}->{'type'}->{'normalized'};
          }
          if (not exists($self->{'normalized_float_latex'}->{$normalized_float_type})) {
            cluck("\@float $normalized_float_type: not found\n");
            return '';
          }
          my $latex_float_name = $self->{'normalized_float_latex'}->{$normalized_float_type};
          _push_new_context($self, 'float'.$latex_float_name);
          $result .= "\\begin{$latex_float_name}\n";
        }
      }
    } elsif ($cmdname eq 'node' or $sectioning_heading_commands{$cmdname}) {
      my $node_element;
      if ($cmdname eq 'node') {
        $node_element = $element;
      } elsif ($cmdname eq 'part' and $element->{'extra'}
               and $element->{'extra'}->{'part_following_node'}) {
        $node_element = $element->{'extra'}->{'part_following_node'};
      }
      if ($node_element and $node_element->{'extra'}
          and $node_element->{'extra'}->{'normalized'}
          and $node_element->{'extra'}->{'normalized'} eq 'Top') {
        $self->{'formatting_context'}->[-1]->{'in_skipped_node_top'} = 1;
      }
      if ($cmdname eq 'node') {
        # add the label only if not associated with a section
        if (not $element->{'extra'}->{'associated_section'}) {
          my $node_label
            = _tree_anchor_label($element->{'extra'}->{'node_content'});
          $result .= "\\label{$node_label}%\n";
        }
      } else {
        if ($cmdname eq 'appendix' and not $self->{'appendix_done'}) {
          $result .= "\\appendix\n";
          $self->{'appendix_done'} = 1;
        }
        if (not $self->{'formatting_context'}->[-1]->{'in_skipped_node_top'}) {
          my $heading = '';
          if ($element->{'args'}->[0]->{'contents'}) {
            # It is useful to know that this is a heading formatting as
            # the formatted heading is in the table of content, and some formatting
            # may be different for that case, for instance with \texorpdfstring
            $self->{'formatting_context'}->[-1]->{'in_sectioning_command_heading'} = 1;
            $heading = $self->_convert({'contents' => $element->{'args'}->[0]->{'contents'}});
            $self->{'formatting_context'}->[-1]->{'in_sectioning_command_heading'} = 0;
          }
          my $section_cmd = $section_map{$cmdname};
          die "BUG: no section_map for $cmdname"
                     if (not defined($section_map{$cmdname}));
          if ($cmdname ne 'centerchap') {
            $result .= "\\".$section_cmd."{{$heading}}\n";
          } else {
            $result .= "\\".$section_cmd."{{\\centering $heading}}\n";
          }
          # NOTE we used an extra layer of { } to avoid buggy interactions with
          # square brackets when the titleps package is being used.
          $self->{'extra_definitions'}->{'Texinfonopagebreakheading'} = 1
            if $section_cmd =~ /^Texinfonopagebreakheading\{/;
        }
        # we add a label even if in_skipped_node_top (should only
        # be for the Top node, as another node ends in_skipped_node_top).
        if ($element->{'extra'}->{'associated_node'}) {
          my $associated_node = $element->{'extra'}->{'associated_node'};
          my $node_label
            = _tree_anchor_label($associated_node->{'extra'}->{'node_content'});
          $result .= "\\label{$node_label}%\n";
        }
      }
    } elsif (($cmdname eq 'item' or $cmdname eq 'itemx')
            and $element->{'args'} and $element->{'args'}->[0]
            and $element->{'args'}->[0]->{'type'}
            and $element->{'args'}->[0]->{'type'} eq 'line_arg') {
      # item in @*table
      my $last_item = 0;
      if ($element->{'args'}->[0]->{'contents'}) {
        my $code_style = 0;
        my $table_command = $element->{'parent'}->{'parent'}->{'parent'};
        if ($table_command->{'extra'}
            and $table_command->{'extra'}->{'command_as_argument'}) {
          my $command_as_argument
            = $table_command->{'extra'}->{'command_as_argument'}->{'cmdname'};
          if ($brace_code_commands{$command_as_argument}) {
            $code_style = 1;
          }
        }
        if ($code_style) {
          push @{$self->{'formatting_context'}->[-1]->{'code'}}, 1;
        }
        my $converted_arg = _convert($self, $element->{'args'}->[0]);
        if ($code_style) {
          pop @{$self->{'formatting_context'}->[-1]->{'code'}};
        }
        $self->{'formatting_context'}->[-1]->{'nr_table_items_context'}->[-1] -= 1;
        my $description_format_command
          = $self->{'formatting_context'}->[-1]->{'table_command_format'}->[-1];
        if (defined($description_format_command)) {
          $converted_arg = "${description_format_command}\{${converted_arg}\}";
        }
        $result .= $converted_arg;
        if ($self->{'formatting_context'}->[-1]->{'nr_table_items_context'}->[-1] > 0) {
          $result .= '\\\\'."\n";
        } else {
          $last_item = 1;
        }
      }
      my $index_entry = _index_entry($self, $element);
      if ($index_entry ne '' and $last_item) {
        $result .= "\n";
      }
      $result .= $index_entry;
    } elsif ($cmdname eq 'item' and $element->{'parent'}->{'cmdname'}
             and $block_commands{$element->{'parent'}->{'cmdname'}}
             and $block_commands{$element->{'parent'}->{'cmdname'}} eq 'item_container') {
      # item in @enumerate and @itemize
      $result .= '\item ';
    } elsif ($cmdname eq 'headitem' or $cmdname eq 'item'
             or $cmdname eq 'tab') {
      # nothing to do here.  The condition ensures that the commands are not
      # considered as unknown commands in the else below.
    } elsif ($cmdname eq 'center') {
      if ($element->{'args'}->[0]->{'contents'}) {
        $result .= "\\begin{center}\n";
        $result .= $self->_convert (
                       {'contents' => $element->{'args'}->[0]->{'contents'}});
        $result .= "\n\\end{center}\n";
      }
      return $result;
    } elsif ($cmdname eq 'exdent') {
      if ($element->{'args'}->[0]->{'contents'}) {
        # FIXME \leavevmode{} is added to avoid
        # ! LaTeX Error: There's no line here to end.
        # but it is not clearly correct
        $result .= "\\leavevmode{}\\\\\n";
        $result .= "\\hbox{\\kern -\\leftmargin}%\n";
        $result .= $self->_convert(
                   {'contents' => $element->{'args'}->[0]->{'contents'}})."\n";
        $result .= "\\\\\n";
      }
      return $result;
    } elsif ($cmdname eq 'verbatiminclude') {
      my $expansion = Texinfo::Convert::Utils::expand_verbatiminclude($self,
                                                              $self, $element);
      unshift @{$self->{'current_contents'}->[-1]}, $expansion
        if ($expansion);
      return $result;
    } elsif ($cmdname eq 'insertcopying') {
      if ($self->{'global_commands'}
          and $self->{'global_commands'}->{'copying'}) {
        unshift @{$self->{'current_contents'}->[-1]},
           {'contents' => $self->{'global_commands'}->{'copying'}->{'contents'}};
      }
      return $result;
    } elsif ($cmdname eq 'printindex') {
      my $index_name;
      if ($element->{'extra'} and $element->{'extra'}->{'misc_args'}
          and defined($element->{'extra'}->{'misc_args'}->[0])) {
        $index_name = $element->{'extra'}->{'misc_args'}->[0];
        if (exists($self->{'index_entries'})
            and exists($self->{'index_entries'}->{$index_name})) {
          $result .= "\\printindex[$index_name]\n";
        }
      }
      return $result;
    } elsif ($cmdname eq 'listoffloats') {
      if ($element->{'extra'} and $element->{'extra'}->{'type'}
          and defined($element->{'extra'}->{'type'}->{'normalized'})
          and $self->{'floats'}
          and $self->{'floats'}->{$element->{'extra'}->{'type'}->{'normalized'}}
          and @{$self->{'floats'}->{$element->{'extra'}->{'type'}->{'normalized'}}}) {
        my $normalized_float_type = $element->{'extra'}->{'type'}->{'normalized'};
        if (not exists($self->{'normalized_float_latex'}->{$normalized_float_type})) {
          cluck("\@listoffloats $normalized_float_type: not found\n");
          return $result;
        }
        my $latex_float_name = $self->{'normalized_float_latex'}->{$normalized_float_type};
        # a listoffigures in @example leads to an error in the
        # lof file.  So stop and restart preformatted environments
        $preformatted_to_reopen
            = [@{$self->{'formatting_context'}->[-1]->{'preformatted_context'}}];
        $result .= _close_preformatted_stack($self, $preformatted_to_reopen);
        if (exists($LaTeX_floats{$latex_float_name})) {
          $result .= $LaTeX_floats{$latex_float_name}."\n";
        } else {
          $result .= "\\listof{$latex_float_name}{}\n";
        }
        $result .= _open_preformatted_stack($self, $preformatted_to_reopen);
      }
      return $result;
    } elsif ($cmdname eq 'page') {
      $result .= _finish_front_cover_page($self);
      # the phantom is added such that successive new pages create blank pages
      $result .= "\\newpage{}%\n\\phantom{blabla}%\n";
      return $result;
    } elsif ($cmdname eq 'indent') {
      # TODO it seems that \indent only works with \setlength{\parindent}{0pt}
      # which makes it quite different from Texinfo @indent
      #$result .= "\\indent{}";
    } elsif ($cmdname eq 'noindent') {
      # spaces after noindent are in ignorable_space_types and are therefore
      # munged which is ok as otherwise it could add a leading space
      $result .= "\\noindent{}";
      return $result;
    } elsif ($cmdname eq 'sp') {
      my $sp_nr = 1;
      if ($element->{'extra'}->{'misc_args'}->[0]) {
        # this useless copy avoids perl changing the type to integer!
        $sp_nr = $element->{'extra'}->{'misc_args'}->[0];
      }
      # FIXME \vskip is a TeX primitive, so the syntax seems to be
      # different from LaTeX, and some people warn against using
      # TeX primitives.  However there is no obvious corresponding
      # command in LaTeX, except for adding enough \\.
      $result .= "\\vskip $sp_nr\\baselineskip %\n";
      return $result;
    } elsif ($cmdname eq 'need') {
      $self->{'packages'}->{'needspace'} = 1;
      if ($element->{'extra'}->{'misc_args'}->[0]) {
        my $need_value = 0.001 * $element->{'extra'}->{'misc_args'}->[0];
        $result .= "\\needspace{${need_value}pt}%\n";
      }
      return $result;
    } elsif (defined($LaTeX_in_heading_commands_formatting{$cmdname})) {
      $result .= $LaTeX_in_heading_commands_formatting{$cmdname};
      return $result;
    } elsif ($cmdname eq 'title') {
      my $title_text = _title_font($self, $element);
      # FIXME In Texinfo TeX the interline space seems more even
      $result .= "{\\raggedright $title_text}\n";
      # same formatting for the rule as in Texinfo TeX
      $result .= "\\vskip 4pt \\hrule height 4pt width \\hsize \\vskip 4pt\n";
      # TODO warn if not in titlepage?  Or even not in
      # $self->{'titlepage_formatting'}->{'in_front_cover'}
      $self->{'titlepage_formatting'}->{'title'} = 1
         if ($self->{'titlepage_formatting'});
    } elsif ($cmdname eq 'subtitle') {
      if ($element->{'args'}->[0]->{'contents'}) {
        my $subtitle_text = _convert($self,
               {'contents' => $element->{'args'}->[0]->{'contents'}});
        # too much vertical spacing with flushright environment
        #$result .= "\\begin{flushright}\n";
        #$result .= $subtitle_text."\n";
        #$result .= "\\end{flushright}\n";
        $result .= "\\rightline{$subtitle_text}\n";
      }
    } elsif ($cmdname eq 'author') {
      if (not $self->{'formatting_context'}->[-1]->{'in_quotation'}) {
        if ($element->{'args'}->[0]->{'contents'}) {
          my $author_name = _convert($self,
                       {'contents' => $element->{'args'}->[0]->{'contents'}});
          if ($self->{'titlepage_formatting'}
              and $self->{'titlepage_formatting'}->{'in_front_cover'}) {
            if (not $self->{'titlepage_formatting'}->{'author'}) {
              # first author, add space before
              $self->{'titlepage_formatting'}->{'author'} = 1;
              $result .= "\\vskip 0pt plus 1filll\n";
            }
            # use \leftline as in Texinfo TeX
            # FIXME In Texinfo TeX the interline space between @author lines
            # seems better
            $result .= "\\leftline{\\Large \\bfseries $author_name}%\n";
          } else {
            # author in regular text.  Should not happen
            $result .= "{\\bfseries $author_name}%\n";
          }
        }
        return $result;
      }
    } elsif ($cmdname eq 'vskip') {
      if ($element->{'extra'}->{'misc_args'}->[0]) {
        # no need for space in front and end of line they are in the
        # argument
        $result .= "\\vskip$element->{'extra'}->{'misc_args'}->[0]";
      }
      return $result;
    } elsif ($cmdname eq 'contents') {
      if (defined($self->get_conf('CONTENTS_OUTPUT_LOCATION'))
          and $self->get_conf('CONTENTS_OUTPUT_LOCATION') eq 'inline'
          and $self->{'structuring'}
          and $self->{'structuring'}->{'sectioning_root'}
          and not $self->{'formatting_context'}->[-1]->{'in_skipped_node_top'}) {
        $result .= "\\tableofcontents\\newpage\n";
      }
      return $result;
    } elsif ($cmdname eq 'shortcontents' or $cmdname eq 'summarycontents') {
      if ($self->{'structuring'}
            and $self->{'structuring'}->{'sectioning_root'}) {
        # TODO see notes at the beginning
        $result .= '';
      }
      return $result;
    } elsif ($heading_spec_commands{$cmdname}) {
      if ($element->{'args'} and $element->{'args'}->[0]
          and $element->{'args'}->[0]->{'contents'}) {
        my $custom_headings_specification
         = Texinfo::Common::split_custom_heading_command_contents(
                                $element->{'args'}->[0]->{'contents'});
        $result .= _set_custom_headings($self, $cmdname,
                                        $custom_headings_specification);
      }
      return $result;
    # @-commands that have an information for the formatting
    } elsif ($informative_commands{$cmdname}) {

      Texinfo::Common::set_informative_command_value($self, $element);

      if ($cmdname eq 'documentlanguage') {
        my $language = $self->get_conf('documentlanguage');
        $language =~ s/_/-/;
        $result .= "\\selectlanguage{$language}%\n";
        $self->{'packages'}->{'babel'} = 1;
      } elsif ($cmdname eq 'pagesizes') {
        my $pagesize_spec = _convert($self, $element->{'args'}->[0]);
        my @pagesize_args = split(/\s*,\s*/, $pagesize_spec);
        my @geometry;
        my $height = shift @pagesize_args;
        if (defined($height) and $height ne '') {
          push @geometry, "textheight=$height";
        }
        my $width = shift @pagesize_args;
        if (defined($width) and $width ne '') {
          push @geometry, "textwidth=$width";
        }
        if (scalar(@geometry)) {
          $result .= "\\newgeometry{".join(',', @geometry)."}\n";
          $self->{'packages'}->{'geometry'} = 1;
        }
      } elsif ($cmdname eq 'paragraphindent'
          and $element->{'extra'}->{'misc_args'}->[0]) {
        my $indentation_spec = $element->{'extra'}->{'misc_args'}->[0];
        if ($indentation_spec eq 'asis') {
          # not implemented here, same as in TeX.
          return $result;
        } else {
          my $indentation_spec_arg = $indentation_spec.'em';
          if ($indentation_spec eq '0' or $indentation_spec eq 'none') {
            $indentation_spec_arg = '0pt';
          }
          $result .= "\\setlength{\\parindent}{$indentation_spec_arg}\n";
        }
      } elsif ($cmdname eq 'firstparagraphindent'
          and $element->{'extra'}->{'misc_args'}->[0]) {
        my $indentation_spec = $element->{'extra'}->{'misc_args'}->[0];
        $result .= "\\makeatletter\n";
        if ($indentation_spec eq 'insert') {
          # From LaTeX indentfirst package: "LaTeX uses the switch
          # \if@afterindent to decide whether to indent after a section
          # heading. We just need to make sure that this is always true."
          $result .= "\\let\\\@afterindentfalse\\\@afterindenttrue\n";
          $result .= "\\\@afterindenttrue\n";
        } elsif ($indentation_spec eq 'none') {
          # restore original definition
          $result .= '\\def\\@afterindentfalse{'
                     . "\\let\\if\@afterindent\\iffalse}\n";
        }
        $result .= "\\makeatother\n";
      } elsif ($cmdname eq 'frenchspacing'
               and $element->{'extra'}->{'misc_args'}->[0]) {
        my $frenchspacing_spec = $element->{'extra'}->{'misc_args'}->[0];
        if ($frenchspacing_spec eq 'on') {
          $result .= "\\frenchspacing\n";
        } elsif ($frenchspacing_spec eq 'off') {
          $result .= "\\nonfrenchspacing\n";
        }
      } elsif ($cmdname eq 'setchapternewpage'
               and $element->{'extra'}->{'misc_args'}->[0]) {
        my $setchapternewpage_spec = $element->{'extra'}->{'misc_args'}->[0];
        $result .= _set_chapter_new_page($self, $setchapternewpage_spec);
      } elsif ($cmdname eq 'headings'
               and $element->{'extra'}->{'misc_args'}->[0]) {
        my $headings_spec = $element->{'extra'}->{'misc_args'}->[0];
        $result .= _set_headings($self, $headings_spec);
      } elsif ($cmdname eq 'fonttextsize'
               and $element->{'extra'}
               and $element->{'extra'}->{'misc_args'}
               and $element->{'extra'}->{'misc_args'}->[0]) {
        my $fontsize = $element->{'extra'}->{'misc_args'}->[0];
        # default dimension for changefontsize is pt
        $result .= "\\changefontsize{$fontsize}\n";
        $self->{'packages'}->{'fontsize'} = 1;
      } elsif ($paper_geometry_commands{$cmdname}) {
        $result .= "\\geometry{$paper_geometry_commands{$cmdname}}%\n";
        $self->{'packages'}->{'geometry'} = 1;
      } elsif ($cmdname eq 'microtype'
               and $element->{'extra'}->{'misc_args'}->[0]) {
        my $microtype_spec = $element->{'extra'}->{'misc_args'}->[0];
        if ($microtype_spec eq 'on') {
          $result .= "\\microtypesetup{activate=true}%\n";
        } elsif ($microtype_spec eq 'off') {
          $result .= "\\microtypesetup{activate=false}%\n";
        }
        $self->{'packages'}->{'microtype'} = 1;
      }
      return $result;
    } else {
      $unknown_command = 1;
    }
    if ($unknown_command
        and !($element->{'extra'}
                and ($element->{'extra'}->{'index_entry'}))
        # commands like def*x are not processed above, since only the def_line
        # associated is processed. If they have no name and no category they
        # are not considered as index entries either so they have a specific
        # condition
        and !($def_commands{$cmdname}
              and $cmdname =~ /x$/)) {
      # this is output for errormsg
      warn "Unhandled $cmdname\n";
      #$result .= "!!!!!!!!! Unhandled $cmdname !!!!!!!!!\n";
    }
  }

  # open 'type' constructs.
  if ($element->{'type'}) {
    if ($element->{'type'} eq 'index_entry_command') {
      $result .= _index_entry($self, $element);
    }
    if ($element->{'type'} eq 'def_line') {
      if ($element->{'extra'} and $element->{'extra'}->{'def_parsed_hash'}
             and %{$element->{'extra'}->{'def_parsed_hash'}}) {
        my $arguments
            = Texinfo::Convert::Utils::definition_arguments_content($element);
        my $command;
        if ($Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}}) {
          $command
           = $Texinfo::Common::def_aliases{$element->{'extra'}->{'def_command'}};
        } else {
          $command = $element->{'extra'}->{'def_command'};
        }

        my $deftypefnnewline = ($self->get_conf('deftypefnnewline') eq 'on'
               and ($command eq 'deftypefn' or $command eq 'deftypeop'));

        my $name;
        if ($element->{'extra'}->{'def_parsed_hash'}->{'name'}) {
          $name = $element->{'extra'}->{'def_parsed_hash'}->{'name'};
        } else {
          $name = '';
        }
        my $def_space = ' ';
        if ($element->{'extra'}->{'omit_def_name_space'}) {
          $def_space = '';
        }

        my $converted_category;
        my $category
          = Texinfo::Convert::Utils::definition_category_tree($self, $element);;
        if (defined($category)) {
          # category is converted in normal text context
          my $converted = _convert($self, $category);
          $converted_category = "[$converted]";
        }

        $self->{'packages'}->{'tabularx'} = 1;
        my $def_line_result = '';
        # First column (X) is as wide as possible, second column (r) is for
        # category.  @{} removes space at left side of table.
        # Without \noindent, a def* after a section beginning is indented
        $def_line_result .= "\\noindent\\begin{tabularx}{\\linewidth}{\@{}Xr}\n";

        # This stops the definition line overlapping the category in
        # case it is hard to break the first line.
        $def_line_result .= "\\rightskip=5em plus 1 fill ";

        # In case definition "line" doesn't fit on one line.
        $def_line_result .= "\\hangindent=2em ";

        # turn off hyphenation
        $def_line_result .= "\\hyphenpenalty=10000\n";

        $def_line_result .= '\texttt{';

        # no end of line in tabularx
        push @{$self->{'formatting_context'}->[-1]->{'no_eol'}}, 1;
        # the def* line except for the category is converted in code context
        push @{$self->{'formatting_context'}->[-1]->{'code'}}, 1;

        if ($element->{'extra'}->{'def_parsed_hash'}->{'type'}) {
          $def_line_result .=  _convert($self,
            $element->{'extra'}->{'def_parsed_hash'}->{'type'});
          if ($deftypefnnewline) {
            if (defined($converted_category)) {
              $def_line_result .= "}& $converted_category\\\\\n\\texttt{"
            } else {
              $def_line_result .= "}\\\\\n\\texttt{";
            }
          } else {
            $def_line_result .= ' ';
          }
        }
        $def_line_result .= _convert($self, $name) if $name;
        # will contain the command names that have been made known to
        # embrac, like texttt and need to have the symbol undefined
        # such that they can be redefined later
        my $known_embrac_commands;
        if ($arguments) {
          $def_line_result .= $def_space;
          if ($Texinfo::Common::def_no_var_arg_commands{$command}) {
            $def_line_result .= _convert($self, {'contents' => $arguments});
          } else {
            $self->{'packages'}->{'embrac'} = 1;
            # we want slanted roman and not slanted typewriter, including
            # ligatures, as if @r{@slanted{...}} had been used, so output
            # \textnormal and push 0 on 'code' context.
            $def_line_result .= '\EmbracOn{}\textnormal{\textsl{';
            push @{$self->{'formatting_context'}->[-1]->{'code'}}, 0;
            push @{$self->{'formatting_context'}->[-1]->{'embrac'}},
              {'status' => 1, 'made_known' => {}};

            $def_line_result .= _convert($self, {'contents' => $arguments});

            # \EmbracOff{} is probably not really needed here as \EmbracOn{}
            # should be local to the texttt
            $def_line_result .= '}}\EmbracOff{}'; # \textnormal \textsl
            pop @{$self->{'formatting_context'}->[-1]->{'code'}};
            my $closed_embrac
              = pop @{$self->{'formatting_context'}->[-1]->{'embrac'}};
            if (scalar(keys(%{$closed_embrac->{'made_known'}}))) {
              $known_embrac_commands
                = [sort(keys(%{$closed_embrac->{'made_known'}}))]
            }
          }
        }

        pop @{$self->{'formatting_context'}->[-1]->{'code'}};
        $def_line_result .= '}'; # \texttt
        pop @{$self->{'formatting_context'}->[-1]->{'no_eol'}};

        $def_line_result .= "& $converted_category\n"
          if (defined($converted_category) and not $deftypefnnewline);

        $def_line_result .= "\\end{tabularx}\n";
        # Add commands associated to embrac, prepended to be before tabularx.
        # If done in tabularx there are redefinition errors, cells are
        # probably expanded more than once.
        #
        # also postpend undefine symbols associated with commands that have
        # been made known to embrac, such that they can be redefined later
        #
        # TODO currently setting EmbracMakeKnown twice leads to an error.
        # this is triggered by the tests layout formatting_latex test.
        # So do not use it for now.
        if (0 and defined($known_embrac_commands)) {
          $def_line_result .= "\\ExplSyntaxOn%\n";
          foreach my $defined_style_embrac (@{$known_embrac_commands}) {
            # before the tabularx
            $def_line_result = '\EmbracMakeKnown{'.$defined_style_embrac.'}'
                               ."%\n" .$def_line_result;
            $def_line_result .= '\cs_undefine:N{\embrac_'.$defined_style_embrac.':nn}'.
        '\cs_undefine:N{\embrac_orig_'.$defined_style_embrac.':n}'.
        '\cs_undefine:N{\__embrac_'.$defined_style_embrac.':n}%
';
          }
          $def_line_result .= "\\ExplSyntaxOff%\n";
        }
        $result .= "\n".$def_line_result;
      }
      $result .= "\n";
      $result .= _index_entry($self, $element);
    } elsif ($element->{'type'} eq 'def_item') {
      $result .= "\\begin{quote}\n";
      # Remove vertical space and start paragaph, avoiding adding
      # more vertical space.
      $result .= "\\unskip{\\parskip=0pt\\noindent}%\n";
    } elsif ($element->{'type'} eq 'table_term') {
      $result .= '\item[{\parbox[b]{\linewidth}{%'."\n";
      # count @item/@itemx to add //\n to each except for the last
      my $nr_item = 0;
      foreach my $content (@{$element->{'contents'}}) {
        if ($content->{'cmdname'} and
            ($content->{'cmdname'} eq 'item'
             or $content->{'cmdname'} eq 'itemx')) {
          $nr_item++;
        }
      }
      push @{$self->{'formatting_context'}->[-1]->{'nr_table_items_context'}},
             $nr_item;
    } elsif ($element->{'type'} eq 'inter_item') {
      # the whole @item/@itemx formatting is put in a parbox, in which
      # there should not be paragraphs, so we remove empty lines from
      # inter_item.  We format directly here while ignoring the empty lines
      # for performance reasons, and not below like all the contents, to
      # avoid needing to check if in table items using a context information,
      # we know that only comments and index entries or a lone preformatted
      # should be in inter_item.
      if ($element->{'contents'}) {
        my $contents;
        # if in an preformatted context, ie in @example, the inter_item
        # content is within a preformatted.  In that case we use content
        # from within the preformatted.  We do not do anything that is done
        # in _open_preformatted/_close_preformatted as the only things
        # that should be in inter_item, besides empty lines we want to
        # remove, are comments and index entries, which formatting should
        # not be affected.
        if ($element->{'contents'}->[0]->{'type'}
            and $element->{'contents'}->[0]->{'type'} eq 'preformatted') {
          $contents = $element->{'contents'}->[0]->{'contents'}
            if $element->{'contents'}->[0]->{'contents'};
        } else {
          $contents = $element->{'contents'};
        }
        foreach my $content (@$contents) {
          $result .= _convert($self, $content)
            unless ($content->{'type'} and $content->{'type'} eq 'empty_line');
        }
      }
      return $result;
    } elsif ($element->{'type'} eq 'preformatted') {
      $result .= _open_preformatted($self, $element);
    } elsif ($element->{'type'} eq '_dot_not_end_sentence') {
      $self->{'formatting_context'}->[-1]->{'dot_not_end_sentence'} += 1;
    } elsif ($element->{'type'} eq 'bracketed') {
      $result .= _protect_text($self, '{');
    }
  }

  # The processing of contents is done here.
  if ($element->{'contents'}) {
    my @contents = @{$element->{'contents'}};
    push @{$self->{'current_contents'}}, \@contents;
    while (@contents) {
      my $content = shift @contents;
      my $text = _convert($self, $content);
      $result .= $text;
      if ($self->{'debug'} and $self->{'debug'} > 2) {
        my @str_contents = ();
        foreach my $item_content (@contents) {
          push @str_contents, Texinfo::Common::debug_print_element_short($item_content);
        }
        print STDERR "C ".Texinfo::Common::debug_print_element_short($element).": ".join("|", @str_contents)."\n";
      }
    }
    pop @{$self->{'current_contents'}};
  }

  # now closing. First, close types.
  if ($type) {
    if ($type eq '_dot_not_end_sentence') {
      $self->{'formatting_context'}->[-1]->{'dot_not_end_sentence'} -= 1;
    } elsif ($type eq 'def_item') {
      $result .= "\\end{quote}\n";
    } elsif ($type eq 'table_term') {
      $result .= '}}]'."\n";
      pop @{$self->{'formatting_context'}->[-1]->{'nr_table_items_context'}};
    } elsif ($type eq 'bracketed') {
      $result .= _protect_text($self, '}');
    } elsif ($type eq 'preformatted') {
      $result .= _close_preformatted($self, $element);
    } elsif ($type eq 'before_item') {
      # LaTeX environments do not accept text before the first item, add an item
      if ($result =~ /\S/) {
        # FIXME if there is only an index element it is content and
        # triggers an empty \item.  It is mitigated by
        # move_index_entries_after_items for enumerate and itemize, but not
        # for @*table.  For @*table there is relate_index_entries_to_table_entries
        # but it has no obvious use here
        if ($block_commands{$element->{'parent'}->{'cmdname'}}
            and $block_commands{$element->{'parent'}->{'cmdname'}} eq 'item_line') {
          # it is important to have an empty optional argument otherwise
          # a quoted command will output the quotes, even with a detection
          # of empty argument (tested with diverse possibilities)
          $result = '\item[] '.$result;
        } else {
          $result = '\item '.$result;
        }
      }
    } elsif ($type eq 'row') {
      chomp($result);
      # can happen with diverse added @-command, in particular index commands
      if ($result =~ /%$/) {
        $result .= "\n";
      }
      $result .= '\\\\'."\n";
    } elsif ($type eq $latex_document_type) {
      # type marking the beginning of content
      $result .= _begin_document($self);
    }
  }

  # close commands
  if ($cmdname) {
    if ($block_commands{$cmdname} and $block_commands{$cmdname} eq 'item_line') {
      pop @{$self->{'formatting_context'}->[-1]->{'table_command_format'}};
    }
    if ($LaTeX_environment_commands{$cmdname}) {
      foreach my $environment (reverse @{$LaTeX_environment_commands{$cmdname}}) {
        $result .= "\\end{".$environment."}\n";
      }
    }
    if ($preformatted_commands{$cmdname}) {
      _close_preformatted_command($self, $cmdname);
    }
    if ($cmdname eq 'float') {
      # do that at the end of the float to be sure that it is after
      # the caption
      if ($element->{'extra'} and $element->{'extra'}->{'node_content'}) {
        my $float_label
          = _tree_anchor_label($element->{'extra'}->{'node_content'});
        $result .= "\\label{$float_label}%\n";
      }
      if (not $self->{'formatting_context'}->[-1]->{'in_skipped_node_top'}) {
        my $normalized_float_type = '';
        if ($element->{'extra'}->{'type'}) {
          $normalized_float_type = $element->{'extra'}->{'type'}->{'normalized'};
        }
        # this should never happen as we returned at the command
        # open.  If this happens it means that the tree has been modified...
        if (not exists($self->{'normalized_float_latex'}->{$normalized_float_type})) {
          confess("\@float $normalized_float_type: not found\n");
        }
        my $latex_float_name = $self->{'normalized_float_latex'}->{$normalized_float_type};
        $result .= "\\end{$latex_float_name}\n";
        _pop_context($self);
      }
    } elsif ($cmdname eq 'quotation'
               or $cmdname eq 'smallquotation') {
      if ($element->{'extra'} and $element->{'extra'}->{'authors'}) {
        # NOTE when @quotation is in a preformatted environment (@example...),
        # we are not in a preformatted type here, such that the conversion
        # does not take into account the preformatted environment.  Probably best.
        foreach my $author (@{$element->{'extra'}->{'authors'}}) {
          if ($author->{'args'}->[0]->{'contents'}) {
            $result .= _convert($self,
                 $self->gdt('@center --- @emph{{author}}',
                    {'author' => $author->{'args'}->[0]->{'contents'}}));
          }
        }
      }
      $self->{'formatting_context'}->[-1]->{'in_quotation'} -= 1;
    } elsif ($cmdname eq 'multitable') {
      $result .= '\end{tabular}%'."\n";
    }

    # close the contexts and register the cells
    if ($block_raw_commands{$cmdname}) {
      my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
      die if ($old_context ne 'ctx_raw');
    } elsif ($block_math_commands{$cmdname}) {
      my $old_context = pop @{$self->{'formatting_context'}->[-1]->{'text_context'}};
      die if ($old_context ne 'ctx_math');
      if ($cmdname eq 'displaymath') {
        $result .= "\\]\n";
        # reopen all preformatted commands
        $result .= _open_preformatted_stack($self, $preformatted_to_reopen);
      }
      my $old_math_style = pop @{$self->{'formatting_context'}->[-1]->{'math_style'}};
      die if ($old_math_style ne 'one-line');
    } elsif ($element->{'parent'}->{'type'}
             and $element->{'parent'}->{'type'} eq 'row') {
      my $cell_nr = $element->{'extra'}->{'cell_number'};
      my $multitable = $element->{'parent'}->{'parent'}->{'parent'};
      my $max_columns = $multitable->{'extra'}->{'max_columns'};
      my $add_eol = 0;
      $add_eol = 1 if (chomp($result));
      if ($cell_nr < $max_columns) {
        $result .= '&';
      }
      $result .= "\n" if ($add_eol);
    }
  }

  return $result;
}

1;
