| # 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.3'; |
| |
| # 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; |