| # $Id: Plaintext.pm 6991 2016-02-06 12:16:13Z gavin $ |
| # Plaintext.pm: output tree as text with filling. |
| # |
| # Copyright 2010, 2011, 2012, 2013, 2014, 2015 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/>. |
| # |
| # Original author: Patrice Dumas <pertusus@free.fr> |
| |
| package Texinfo::Convert::Plaintext; |
| |
| use 5.00405; |
| use strict; |
| |
| use Texinfo::Convert::Converter; |
| use Texinfo::Common; |
| use Texinfo::Convert::Texinfo; |
| use Texinfo::Convert::Paragraph; |
| use Texinfo::Convert::Text; |
| use Texinfo::Convert::Line; |
| use Texinfo::Convert::UnFilled; |
| |
| |
| use Carp qw(cluck); |
| |
| require Exporter; |
| use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); |
| @ISA = qw(Exporter Texinfo::Convert::Converter); |
| |
| # Items to export into callers namespace by default. Note: do not export |
| # names by default without a very good reason. Use EXPORT_OK instead. |
| # Do not simply export all your public functions/methods/constants. |
| |
| # This allows declaration use Texinfo::Convert::Plaintext ':all'; |
| # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK |
| # will save memory. |
| %EXPORT_TAGS = ( 'all' => [ qw( |
| convert |
| output |
| ) ] ); |
| |
| @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); |
| |
| @EXPORT = qw( |
| ); |
| |
| $VERSION = '6.1'; |
| |
| # misc commands that are of use for formatting. |
| my %formatting_misc_commands = %Texinfo::Convert::Text::formatting_misc_commands; |
| |
| my $NO_NUMBER_FOOTNOTE_SYMBOL = '*'; |
| |
| my @informative_global_commands = ('paragraphindent', 'firstparagraphindent', |
| 'frenchspacing', 'documentencoding', 'footnotestyle', 'documentlanguage', |
| 'contents', 'shortcontents', 'summarycontents', 'setcontentsaftertitlepage', |
| 'setshortcontentsaftertitlepage', 'deftypefnnewline'); |
| |
| my %informative_commands; |
| foreach my $informative_command (@informative_global_commands) { |
| $informative_commands{$informative_command} = 1; |
| } |
| |
| my %no_brace_commands = %Texinfo::Common::no_brace_commands; |
| my %brace_no_arg_commands; |
| foreach my $command (keys (%Texinfo::Common::brace_commands)) { |
| $brace_no_arg_commands{$command} = 1 |
| if ($Texinfo::Common::brace_commands{$command} == 0); |
| } |
| my %accent_commands = %Texinfo::Common::accent_commands; |
| my %misc_commands = %Texinfo::Common::misc_commands; |
| my %sectioning_commands = %Texinfo::Common::sectioning_commands; |
| my %def_commands = %Texinfo::Common::def_commands; |
| my %ref_commands = %Texinfo::Common::ref_commands; |
| my %block_commands = %Texinfo::Common::block_commands; |
| my %menu_commands = %Texinfo::Common::menu_commands; |
| my %root_commands = %Texinfo::Common::root_commands; |
| my %preformatted_commands = %Texinfo::Common::preformatted_commands; |
| my %explained_commands = %Texinfo::Common::explained_commands; |
| my %inline_format_commands = %Texinfo::Common::inline_format_commands; |
| my %inline_commands = %Texinfo::Common::inline_commands; |
| my %item_container_commands = %Texinfo::Common::item_container_commands; |
| my %raw_commands = %Texinfo::Common::raw_commands; |
| my %format_raw_commands = %Texinfo::Common::format_raw_commands; |
| my %code_style_commands = %Texinfo::Common::code_style_commands; |
| my %regular_font_style_commands = %Texinfo::Common::regular_font_style_commands; |
| my %preformatted_code_commands = %Texinfo::Common::preformatted_code_commands; |
| my %default_index_commands = %Texinfo::Common::default_index_commands; |
| my %letter_no_arg_commands = %Texinfo::Common::letter_no_arg_commands; |
| |
| foreach my $kept_command(keys (%informative_commands), |
| keys (%default_index_commands), |
| 'verbatiminclude', 'insertcopying', |
| 'listoffloats', 'printindex', ) { |
| $formatting_misc_commands{$kept_command} = 1; |
| } |
| |
| foreach my $def_command (keys(%def_commands)) { |
| $formatting_misc_commands{$def_command} = 1 if ($misc_commands{$def_command}); |
| } |
| |
| # There are 6 stacks that define the context. |
| # context: relevant for alignement of text. Set in math, footnote, |
| # listoffloats, flush_commands, preformatted_context_commands |
| # (preformatted + menu + verbatim), and raw commands if |
| # on top level. |
| # format_context: used for the count of paragraphs and for the indentation. |
| # Set in footnote, for all commands relevant for indenting, like |
| # @*table, @itemize, @enumerate, preformatted commands, |
| # @*quotation, @def*, and also menu commands, @flushleft, |
| # @flushright, @float, in multitable cell and raw commands if at |
| # toplevel. |
| # text_element_context: for the max columns and the counter in the line |
| # position (although the counter in the line is taken over by |
| # the formatter once a formatter is opened). |
| # Set in footnote and in multitable cells. |
| # formatters: a formatter environment has stacks for formatting context. |
| # Also holds a container, an objects that does the counting |
| # of columns, actual indentation. In general, it is better not |
| # to have formatters in parallel, but it may happen. |
| # count_context: holds the bytes count, the lines count and the location |
| # of the commands that have their byte count or lines count |
| # recorded. It is set for out of document formatting to avoid |
| # counting some converted text, but it is also set when it has |
| # to be modified afterwards, for aligned commands or multitable |
| # cells for example. |
| # document_context: Used to keep track if we are in a multitable. |
| |
| # formatters have their own stack |
| # in container |
| # 'upper_case' |
| # 'var' |
| # 'font_type_stack' |
| # |
| # paragraph number incremented with paragraphs, center, listoffloats |
| # and block commands except: html and such, group, raggedright, menu*, float |
| |
| my %default_preformatted_context_commands = (%preformatted_commands, |
| %format_raw_commands); |
| foreach my $preformatted_command ('verbatim', keys(%menu_commands)) { |
| $default_preformatted_context_commands{$preformatted_command} = 1; |
| } |
| |
| my %ignored_misc_commands; |
| foreach my $misc_command (keys(%misc_commands)) { |
| $ignored_misc_commands{$misc_command} = 1 |
| unless ($formatting_misc_commands{$misc_command}); |
| } |
| |
| my %ignored_commands = %ignored_misc_commands; |
| foreach my $ignored_brace_commands ('caption', 'shortcaption', |
| 'hyphenation', 'sortas') { |
| $ignored_commands{$ignored_brace_commands} = 1; |
| } |
| |
| my %item_indent_format_length = ('enumerate' => 2, |
| 'itemize' => 3, |
| 'table' => 0, |
| 'vtable' => 0, |
| 'ftable' => 0, |
| ); |
| |
| my $indent_length = 5; |
| |
| my %indented_commands; |
| foreach my $indented_command (keys(%item_indent_format_length), |
| keys(%preformatted_commands), 'quotation', 'smallquotation', |
| 'indentedblock', 'smallindentedblock', |
| keys(%def_commands)) { |
| $indented_commands{$indented_command} = 1 |
| if exists($block_commands{$indented_command}); |
| } |
| |
| my %default_format_context_commands = %indented_commands; |
| |
| foreach my $non_indented('format', 'smallformat') { |
| delete $indented_commands{$non_indented}; |
| } |
| |
| foreach my $format_context_command (keys(%menu_commands), 'verbatim', |
| 'flushleft', 'flushright', 'multitable', 'float') { |
| $default_format_context_commands{$format_context_command} = 1; |
| } |
| |
| my %flush_commands = ( |
| 'flushleft' => 1, |
| 'flushright' => 1 |
| ); |
| |
| # commands that leads to advancing the paragraph number. This is mostly |
| # used to determine the first line, in fact. |
| my %advance_paragraph_count_commands; |
| foreach my $command (keys(%block_commands)) { |
| next if ($menu_commands{$command} |
| or $block_commands{$command} eq 'raw'); |
| $advance_paragraph_count_commands{$command} = 1; |
| } |
| |
| # group and raggedright do more than not advancing para, they should also |
| # be transparent with respect to paragraph number counting. |
| foreach my $not_advancing_para ('group', 'raggedright', |
| 'titlepage', 'copying', 'documentdescription', 'float') { |
| delete $advance_paragraph_count_commands{$not_advancing_para}; |
| } |
| |
| foreach my $advancing_para('center', 'verbatim', 'listoffloats') { |
| $advance_paragraph_count_commands{$advancing_para} = 1; |
| } |
| |
| foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', 'copying', |
| 'documentdescription', 'titlepage', 'direntry') { |
| $ignored_commands{$ignored_block_commands} = 1; |
| } |
| |
| my %punctuation_no_arg_commands; |
| foreach my $punctuation_command('enddots', 'exclamdown', 'questiondown') { |
| $punctuation_no_arg_commands{$punctuation_command} = 1; |
| } |
| |
| my %upper_case_commands = ( |
| 'sc' => 1, |
| 'var' => 1 |
| ); |
| |
| my %ignorable_space_types; |
| foreach my $type ('empty_line_after_command', |
| 'empty_spaces_after_command', 'spaces_at_end', |
| 'empty_spaces_before_argument', 'empty_spaces_before_paragraph', |
| 'empty_spaces_after_close_brace', |
| 'empty_space_at_end_def_bracketed') { |
| $ignorable_space_types{$type} = 1; |
| } |
| |
| my %ignored_types; |
| foreach my $type ('preamble', |
| 'preamble_before_setfilename') { |
| $ignored_types{$type} = 1; |
| } |
| |
| my %ignorable_types = %ignorable_space_types; |
| foreach my $ignored_type(keys(%ignored_types)) { |
| $ignorable_types{$ignored_type} = 1; |
| } |
| |
| # All those commands run with the text. |
| my %style_map = ( |
| 'strong' => '*', |
| 'dfn' => '"', |
| 'emph' => '_', |
| ); |
| |
| foreach my $command (keys(%style_map)) { |
| $style_map{$command} = [$style_map{$command}, $style_map{$command}]; |
| } |
| |
| # math is special |
| my @asis_commands = ('asis', 'w', 'b', 'ctrl', 'i', 'sc', 't', 'r', |
| 'slanted', 'sansserif', 'var', 'verb', 'clicksequence', |
| 'headitemfont', 'dmn'); |
| |
| foreach my $asis_command (@asis_commands) { |
| $style_map{$asis_command} = ['', '']; |
| } |
| |
| my @quoted_commands = ('cite', 'code', 'command', 'env', 'file', 'kbd', |
| 'option', 'samp', 'indicateurl'); |
| |
| # %non_quoted_commands_when_nested have no quote when in code command contexts |
| my %non_quoted_commands_when_nested; |
| |
| # Quotes are reset in converter_initialize and unicode quotes are used |
| # if @documentencoding utf-8 is used. |
| foreach my $quoted_command (@quoted_commands) { |
| $style_map{$quoted_command} = ["'", "'"]; |
| if ($code_style_commands{$quoted_command}) { |
| $non_quoted_commands_when_nested{$quoted_command} = 1; |
| } |
| } |
| # always quoted even when nested |
| delete $non_quoted_commands_when_nested{'samp'}; |
| delete $non_quoted_commands_when_nested{'indicateurl'}; |
| |
| $style_map{'key'} = ['<', '>']; |
| $style_map{'sub'} = ['_{', '}']; |
| $style_map{'sup'} = ['^{', '}']; |
| |
| # Commands producing styles that are output in node names and index entries. |
| my %index_style_commands; |
| for my $index_style_command ('strong', 'emph', 'sub', 'sup', 'key') { |
| $index_style_commands{$index_style_command} = 1; |
| } |
| |
| |
| # in those commands, there is no addition of double space after a dot. |
| # math is special |
| my %no_punctation_munging_commands; |
| foreach my $command ('var', 'cite', 'dmn', keys(%code_style_commands)) { |
| $no_punctation_munging_commands{$command} = 1; |
| } |
| |
| my %defaults = ( |
| 'ENABLE_ENCODING' => 1, |
| 'SHOW_MENU' => 0, |
| #'EXTENSION' => 'info', |
| 'EXTENSION' => 'txt', |
| #'USE_SETFILENAME_EXTENSION' => 1, |
| 'INFO_SPECIAL_CHARS_WARNING' => 1, |
| |
| #'OUTFILE' => undef, |
| 'OUTFILE' => '-', |
| 'SUBDIR' => undef, |
| 'documentlanguage' => undef, |
| |
| 'output_format' => '', |
| 'USE_NODES' => 1, |
| ); |
| |
| sub push_top_formatter($$) |
| { |
| my $self = shift; |
| my $top_context = shift; |
| push @{$self->{'context'}}, $top_context; |
| push @{$self->{'format_context'}}, { |
| 'cmdname' => '_top_format', |
| 'indent_level' => 0, |
| 'paragraph_count' => 0, |
| }; |
| push @{$self->{'text_element_context'}}, { |
| 'max' => $self->{'fillcolumn'} |
| }; |
| push @{$self->{'document_context'}}, { |
| 'in_multitable' => 0 |
| }; |
| |
| # This is not really meant to be used, as contents should open |
| # their own formatters, however it happens that there is some text |
| # outside any content that needs to be formatted, as @sp for example. |
| push @{$self->{'formatters'}}, $self->new_formatter('line'); |
| $self->{'formatters'}->[-1]->{'_top_formatter'} = 1; |
| } |
| |
| |
| my %contents_commands = ( |
| 'contents' => 1, |
| 'shortcontents' => 1, |
| 'summarycontents' => 1, |
| ); |
| |
| sub converter_defaults($$) |
| { |
| return %defaults; |
| } |
| |
| sub converter_global_commands($) |
| { |
| return @informative_global_commands; |
| } |
| |
| sub converter_initialize($) |
| { |
| my $self = shift; |
| |
| $self->{'context'} = []; |
| $self->{'format_context'} = []; |
| $self->{'empty_lines_count'} = undef; |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, |
| 'locations' => []}; |
| |
| %{$self->{'ignored_types'}} = %ignored_types; |
| %{$self->{'ignorable_space_types'}} = %ignorable_space_types; |
| %{$self->{'ignored_commands'}} = %ignored_commands; |
| # this is dynamic because raw formats may either be full commands if |
| # isolated, or simple text if in a paragraph |
| %{$self->{'format_context_commands'}} = %default_format_context_commands; |
| %{$self->{'preformatted_context_commands'}} |
| = %default_preformatted_context_commands; |
| $self->{'footnote_index'} = 0; |
| $self->{'pending_footnotes'} = []; |
| |
| foreach my $format (keys(%format_raw_commands)) { |
| $self->{'ignored_commands'}->{$format} = 1 |
| unless ($self->{'expanded_formats_hash'}->{$format}); |
| } |
| |
| %{$self->{'style_map'}} = %style_map; |
| if ($self->get_conf('ENABLE_ENCODING') and $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; |
| |
| foreach my $quoted_command (@quoted_commands) { |
| # Directed single quotes |
| $self->{'style_map'}->{$quoted_command} = ["\x{2018}", "\x{2019}"]; |
| } |
| # Directed double quotes |
| $self->{'style_map'}->{'dfn'} = ["\x{201C}", "\x{201D}"]; |
| } |
| $self->{'convert_text_options'} |
| = {Texinfo::Common::_convert_text_options($self)}; |
| if (defined($self->get_conf('OPEN_QUOTE_SYMBOL'))) { |
| foreach my $quoted_command (@quoted_commands) { |
| $self->{'style_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->{'style_map'}->{$quoted_command}->[1] |
| = $self->get_conf('CLOSE_QUOTE_SYMBOL'); |
| } |
| } |
| if ($self->get_conf('FILLCOLUMN')) { |
| $self->{'fillcolumn'} = $self->get_conf('FILLCOLUMN'); |
| # else it's already set via the defaults |
| } |
| # This needs to be here to take into account $self->{'fillcolumn'}. |
| $self->push_top_formatter('_Root_context'); |
| # some caching to avoid calling get_conf |
| if ($self->get_conf('OUTPUT_PERL_ENCODING')) { |
| $self->{'output_perl_encoding'} = $self->get_conf('OUTPUT_PERL_ENCODING'); |
| } else { |
| $self->{'output_perl_encoding'} = ''; |
| } |
| $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; |
| } |
| |
| sub _count_context_bug_message($$$) |
| { |
| my $self = shift; |
| my $precision = shift; |
| my $element = shift; |
| |
| if (scalar(@{$self->{'count_context'}}) != 1) { |
| my $element_text; |
| if ($element) { |
| $element_text |
| = Texinfo::Structuring::_print_element_command_texi($element); |
| } else { |
| $element_text = ''; |
| } |
| $self->_bug_message("Too much count_context ${precision}(". |
| scalar(@{$self->{'count_context'}}). "): ". $element_text, $element); |
| die; |
| } |
| } |
| |
| sub _convert_element($$) |
| { |
| my $self = shift; |
| my $element = shift; |
| |
| my $result = ''; |
| |
| print STDERR "NEW NODE\n" if ($self->{'debug'}); |
| |
| $result .= $self->_convert($element); |
| |
| $self->_count_context_bug_message('', $element); |
| |
| print STDERR "END NODE ($self->{'count_context'}->[-1]->{'lines'},$self->{'count_context'}->[-1]->{'bytes'})\n" if ($self->{'debug'}); |
| |
| $result .= $self->_footnotes($element); |
| |
| $self->_count_context_bug_message('footnotes ', $element); |
| |
| print STDERR "AFTER FOOTNOTES ($self->{'count_context'}->[-1]->{'lines'},$self->{'count_context'}->[-1]->{'bytes'})\n" if ($self->{'debug'}); |
| |
| return $result; |
| } |
| |
| sub convert($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| |
| my $result = ''; |
| |
| my $elements = Texinfo::Structuring::split_by_node($root); |
| $self->{'empty_lines_count'} = 1; |
| if (!defined($elements)) { |
| $result = $self->_convert($root); |
| $self->_count_context_bug_message('no element '); |
| my $footnotes = $self->_footnotes(); |
| $self->_count_context_bug_message('no element footnotes '); |
| $result .= $footnotes; |
| } else { |
| foreach my $node (@$elements) { |
| my $node_text = _convert_element($self, $node); |
| $result .= $node_text; |
| } |
| } |
| |
| return $result; |
| } |
| |
| sub convert_tree($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| |
| $self->{'empty_lines_count'} = 1; |
| my $result; |
| if ($root->{'type'} and $root->{'type'} eq 'element') { |
| $result = _convert_element($self, $root); |
| } else { |
| $result = $self->_convert($root); |
| } |
| return $result; |
| } |
| |
| # old implementation of output that does not allow for splitting. |
| sub _output_old($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| |
| my $outfile = '-'; |
| $self->set_conf('OUTFILE', '-'); |
| $self->_set_outfile(); |
| if (defined($self->{'output_file'})) { |
| $outfile = $self->{'output_file'}; |
| } |
| |
| my $fh; |
| if ($outfile ne '') { |
| if ($self->get_conf('VERBOSE')) { |
| print STDERR "Output file $outfile\n"; |
| } |
| $fh = $self->Texinfo::Common::open_out($outfile); |
| if (!$fh) { |
| $self->document_error(sprintf($self->__("could not open %s for writing: %s"), |
| $outfile, $!)); |
| return undef; |
| } |
| } |
| my $result = $self->convert($root); |
| if (defined($result)) { |
| if (defined($fh)) { |
| print $fh $result; |
| } else { |
| return $result; |
| } |
| } |
| return undef; |
| } |
| |
| my $end_sentence = quotemeta('.?!'); |
| my $after_punctuation = quotemeta('"\')]'); |
| |
| sub _protect_sentence_ends ($) { |
| my $text = shift; |
| # Avoid suppressing end of sentence, by inserting a control character |
| # in front of the full stop. The choice of BS for this is arbitrary. |
| $text =~ s/(?<=[^[:upper:]]) |
| (?=[$end_sentence][$after_punctuation]*(?:\s|$)) |
| /\x08/gx; |
| |
| # Also insert a control character at end of string, to protect a full stop |
| # that may follow later. |
| |
| #$text =~ s/(?<=[^[:upper:]][$after_punctuation]*)$/\x08/; |
| # Perl doesn't support "variable length lookbehind" |
| |
| $text = reverse $text; |
| $text =~ s/^(?=[$after_punctuation]* |
| (?:[^[:upper:]\s]|[\x{202f}\x{00a0}])) |
| /\x08/x; |
| $text = reverse $text; |
| |
| return $text; |
| } |
| |
| # Convert ``, '', `, ', ---, -- in $COMMAND->{'text'} to their output, |
| # possibly coverting to upper case as well. |
| sub _process_text($$$) |
| { |
| my $self = shift; |
| my $command = shift; |
| my $context = shift; |
| my $text = $command->{'text'}; |
| |
| if ($context->{'upper_case'} |
| or $self->{'formatters'}[-1]->{'var'}) { |
| $text = _protect_sentence_ends($text); |
| |
| if ($self->{'debug'}) { |
| my $debug_text = $text; |
| $debug_text =~ s/\x08/!!/g; |
| print STDERR "markers:<$debug_text>\n"; |
| } |
| |
| $text = uc($text); |
| } |
| |
| if ($self->{'to_utf8'}) { |
| return Texinfo::Convert::Unicode::unicode_text($text, |
| $context->{'font_type_stack'}->[-1]->{'monospace'}); |
| } elsif (!$context->{'font_type_stack'}->[-1]->{'monospace'}) { |
| $text =~ s/---/\x{1F}/g; |
| $text =~ s/--/-/g; |
| $text =~ s/\x{1F}/--/g; |
| $text =~ s/``/"/g; |
| $text =~ s/\'\'/"/g; |
| $text =~ s/`/'/g; |
| } |
| return $text; |
| } |
| |
| sub new_formatter($$;$) |
| { |
| my $self = shift; |
| my $type = shift; |
| my $conf = shift; |
| |
| my $first_indent_length = $conf->{'first_indent_length'}; |
| delete $conf->{'first_indent_length'}; |
| |
| my $container; |
| my $container_conf = { |
| 'max' => $self->{'text_element_context'}->[-1]->{'max'}, |
| 'indent_level' => $self->{'format_context'}->[-1]->{'indent_level'}, |
| }; |
| $container_conf->{'frenchspacing'} = 1 |
| if ($self->{'conf'}->{'frenchspacing'} eq 'on'); |
| $container_conf->{'counter'} |
| = $self->{'text_element_context'}->[-1]->{'counter'} |
| if (defined($self->{'text_element_context'}->[-1]->{'counter'})); |
| $container_conf->{'DEBUG'} = 1 if ($self->{'debug'}); |
| if ($conf) { |
| foreach my $key (keys(%$conf)) { |
| $container_conf->{$key} = $conf->{$key}; |
| } |
| } |
| my $indent = $container_conf->{'indent_length'}; |
| $indent = $indent_length*$container_conf->{'indent_level'} |
| if (!defined($indent)); |
| |
| if ($first_indent_length) { |
| $container_conf->{'indent_length'} = $first_indent_length; |
| $container_conf->{'indent_length_next'} = $indent; |
| } else { |
| $container_conf->{'indent_length'} = $indent; |
| } |
| |
| if ($type eq 'line') { |
| $container = Texinfo::Convert::Line->new($container_conf); |
| } elsif ($type eq 'paragraph') { |
| $container = Texinfo::Convert::Paragraph->new($container_conf); |
| } elsif ($type eq 'unfilled') { |
| $container = Texinfo::Convert::UnFilled->new($container_conf); |
| } else { |
| die "Unknown container type $type\n"; |
| } |
| |
| if ($flush_commands{$self->{'context'}->[-1]}) { |
| $container->set_space_protection(undef, 1, 1); |
| } |
| |
| my $formatter = {'container' => $container, 'upper_case' => 0, |
| #'code' => 0, 'code_command'=> 0, |
| 'font_type_stack' => [{}], |
| 'w' => 0, 'type' => $type, |
| 'frenchspacing_stack' => [$self->{'conf'}->{'frenchspacing'}], |
| 'suppress_styles' => $conf->{'suppress_styles'}}; |
| |
| if ($type eq 'unfilled') { |
| foreach my $context (reverse(@{$self->{'context'}})) { |
| if ($menu_commands{$context}) { |
| last; |
| } elsif ($preformatted_code_commands{$context} |
| or $format_raw_commands{$context}) { |
| $formatter->{'font_type_stack'}->[-1]->{'monospace'} = 1; |
| $formatter->{'font_type_stack'}->[-1]->{'code_command'} = 1 |
| if ($preformatted_code_commands{$context}); |
| last; |
| } |
| } |
| } |
| print STDERR "NEW FORMATTER($type)\n" if ($self->{'debug'}); |
| return $formatter; |
| } |
| |
| sub convert_line($$;$) |
| { |
| my $self = shift; |
| my $converted = shift; |
| my $conf = shift; |
| my $formatter = $self->new_formatter('line', $conf); |
| push @{$self->{'formatters'}}, $formatter; |
| my $text = $self->_convert($converted); |
| $text .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->end()); |
| pop @{$self->{'formatters'}}; |
| return $text; |
| } |
| |
| sub convert_unfilled($$;$) |
| { |
| my $self = shift; |
| my $converted = shift; |
| my $conf = shift; |
| my $formatter = $self->new_formatter('unfilled', $conf); |
| #$formatter->{'code'} = 1; |
| $formatter->{'font_type_stack'}->[-1]->{'monospace'} = 1; |
| push @{$self->{'formatters'}}, $formatter; |
| my $result = $self->_convert($converted); |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->end()); |
| pop @{$self->{'formatters'}}; |
| return $result; |
| } |
| |
| sub count_bytes($$) |
| { |
| my $self = shift; |
| my $string = shift; |
| |
| return Texinfo::Common::count_bytes($self, $string, |
| $self->{'output_perl_encoding'}); |
| } |
| |
| sub _add_text_count($$) |
| { |
| my $self = shift; |
| my $text = shift; |
| if (!$self->{'count_context'}->[-1]->{'pending_text'}) { |
| $self->{'count_context'}->[-1]->{'pending_text'} = ''; |
| } |
| $self->{'count_context'}->[-1]->{'pending_text'} .= $text; |
| } |
| |
| sub _add_lines_count($$) |
| { |
| my $self = shift; |
| my $lines_count = shift; |
| $self->{'count_context'}->[-1]->{'lines'} += $lines_count; |
| } |
| |
| # Update $SELF->{'count_context'}->[-1]->{'bytes'} by counting the text that |
| # hasn't been counted yet. It is faster to count the text all together than |
| # piece by piece in _add_text_count. |
| sub _update_count_context($) |
| { |
| my $self = shift; |
| if ($self->{'count_context'}->[-1]->{'pending_text'}) { |
| $self->{'count_context'}->[-1]->{'bytes'} += |
| Texinfo::Common::count_bytes($self, |
| $self->{'count_context'}->[-1]->{'pending_text'}, |
| $self->{'output_perl_encoding'}); |
| $self->{'count_context'}->[-1]->{'pending_text'} = ''; |
| } |
| } |
| |
| # Save the line and byte offset of $ROOT. |
| sub _add_location($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| my $location = { 'lines' => $self->{'count_context'}->[-1]->{'lines'} }; |
| push @{$self->{'count_context'}->[-1]->{'locations'}}, $location; |
| if (!($root->{'extra'} and $root->{'extra'}->{'index_entry'})) { |
| _update_count_context($self); |
| $location->{'bytes'} = $self->{'count_context'}->[-1]->{'bytes'}; |
| $location->{'root'} = $root; |
| } else { |
| $location->{'index_entry'} = $root; |
| } |
| return $location; |
| } |
| |
| sub _add_image($$$$;$) |
| { |
| my $self = shift; |
| my $root = shift; |
| my $lines_count = shift; |
| my $image_width = shift; |
| my $no_align = shift; |
| |
| push @{$self->{'count_context'}->[-1]->{'images'}}, { |
| 'lines' => $self->{'count_context'}->[-1]->{'lines'}, |
| 'lines_count' => $lines_count, |
| 'image_width' => $image_width, |
| 'no_align' => $no_align, |
| # may be used for debugging? |
| #'_ref' => $root, |
| }; |
| } |
| |
| sub _count_added($$$) |
| { |
| my $self = shift; |
| my $container = shift; |
| my $text = shift; |
| |
| $self->_add_lines_count($container->end_line_count()); |
| |
| #$self->_add_text_count($text); |
| #$self->{'count_context'}->[-1]->{'bytes'} += |
| # Texinfo::Common::count_bytes($self, $text, |
| # $self->{'output_perl_encoding'}); |
| if (!defined $self->{'count_context'}->[-1]->{'pending_text'}) { |
| $self->{'count_context'}->[-1]->{'pending_text'} = ''; |
| } |
| $self->{'count_context'}->[-1]->{'pending_text'} .= $text; |
| return $text; |
| } |
| |
| sub _update_locations_counts($$) |
| { |
| my $self = shift; |
| my $locations = shift; |
| _update_count_context($self); |
| foreach my $location (@$locations) { |
| $location->{'bytes'} += $self->{'count_context'}->[-1]->{'bytes'} |
| if (defined($location->{'bytes'})); |
| $location->{'lines'} += $self->{'count_context'}->[-1]->{'lines'} |
| if (defined($location->{'lines'})); |
| } |
| } |
| |
| sub _add_newline_if_needed($) { |
| my $self = shift; |
| if (defined($self->{'empty_lines_count'}) |
| and $self->{'empty_lines_count'} == 0) { |
| _add_text_count($self, "\n"); |
| _add_lines_count($self, 1); |
| $self->{'empty_lines_count'} = 1; |
| return "\n"; |
| } |
| return ''; |
| } |
| |
| my $footnote_indent = 3; |
| sub _footnotes($;$) |
| { |
| my $self = shift; |
| my $element = shift; |
| |
| $element = undef if ($element and $element->{'extra'}->{'no_node'}); |
| |
| my $result = ''; |
| if (scalar(@{$self->{'pending_footnotes'}})) { |
| $result .= _add_newline_if_needed($self); |
| print STDERR "FOOTNOTES ".scalar(@{$self->{'pending_footnotes'}})."\n" |
| if ($self->{'debug'}); |
| if ($self->get_conf('footnotestyle') eq 'end' or !defined($element)) { |
| my $footnotes_header = " ---------- Footnotes ----------\n\n"; |
| $result .= $footnotes_header; |
| _add_text_count($self, $footnotes_header); |
| _add_lines_count($self, 2); |
| $self->{'empty_lines_count'} = 1; |
| } else { |
| |
| my $node_contents = [@{$element->{'extra'}->{'node'}->{'extra'}->{'node_content'}}, |
| {'text' => '-Footnotes'}]; |
| my $normalized |
| = Texinfo::Convert::NodeNameNormalization::normalize_node( |
| {'contents' => $node_contents}); |
| my $footnotes_node = { |
| 'cmdname' => 'node', |
| 'node_up' => $element->{'extra'}->{'node'}, |
| 'extra' => {'node_content' => $node_contents, |
| 'normalized' => $normalized} |
| }; |
| $result .= $self->_node($footnotes_node); |
| $self->{'node'} = $footnotes_node; |
| } |
| while (@{$self->{'pending_footnotes'}}) { |
| my $footnote = shift (@{$self->{'pending_footnotes'}}); |
| |
| # If nested within another footnote and footnotestyle is separate, |
| # the element here will be the parent element and not the footnote |
| # element, while the pxref will point to the name with the |
| # footnote node taken into account. Not really problematic as |
| # nested footnotes are not right. |
| if ($element) { |
| my $node_contents = [@{$element->{'extra'}->{'node'}->{'extra'}->{'node_content'}}, |
| {'text' => "-Footnote-$footnote->{'number'}"}]; |
| my $normalized |
| = Texinfo::Convert::NodeNameNormalization::normalize_node({'contents' => $node_contents}); |
| $self->_add_location({'cmdname' => 'anchor', |
| 'extra' => {'node_content' => $node_contents, |
| 'normalized' => $normalized} |
| }); |
| } |
| # this pushes on 'context', 'formatters', 'format_context', |
| # 'text_element_context' and 'document_context' |
| $self->push_top_formatter('footnote'); |
| my $formatted_footnote_number; |
| if ($self->get_conf('NUMBER_FOOTNOTES')) { |
| $formatted_footnote_number = $footnote->{'number'}; |
| } else { |
| $formatted_footnote_number = $NO_NUMBER_FOOTNOTE_SYMBOL; |
| } |
| my $footnote_text = ' ' x $footnote_indent |
| . "($formatted_footnote_number) "; |
| $result .= $footnote_text; |
| $self->{'text_element_context'}->[-1]->{'counter'} += |
| Texinfo::Convert::Unicode::string_width($footnote_text); |
| _add_text_count($self, $footnote_text); |
| $self->{'empty_lines_count'} = 0; |
| |
| $result .= $self->_convert($footnote->{'root'}->{'args'}->[0]); |
| $result .= _add_newline_if_needed($self); |
| |
| my $old_context = pop @{$self->{'context'}}; |
| die if ($old_context ne 'footnote'); |
| pop @{$self->{'formatters'}}; |
| pop @{$self->{'format_context'}}; |
| pop @{$self->{'text_element_context'}}; |
| pop @{$self->{'document_context'}}; |
| } |
| } |
| $self->{'footnote_index'} = 0; |
| |
| return $result; |
| } |
| |
| sub _compute_spaces_align_line($$$;$) |
| { |
| my $line_width = shift; |
| my $max_column = shift; |
| my $direction = shift; |
| my $no_align = shift; |
| |
| my $spaces_prepended; |
| if ($line_width > $max_column or $no_align) { |
| $spaces_prepended = 0; |
| } elsif ($direction eq 'center') { |
| # if no int we may end up with floats... |
| $spaces_prepended = int(($max_column -1 - $line_width) /2); |
| } else { |
| $spaces_prepended = ($max_column -1 - $line_width); |
| } |
| return $spaces_prepended; |
| } |
| |
| sub _align_lines($$$$$$) |
| { |
| my $self = shift; |
| my $text = shift; |
| my $max_column = shift; |
| my $direction = shift; |
| my $locations = shift; |
| my $images = shift; |
| |
| my $result = ''; |
| |
| my $updated_locations = {}; |
| if ($locations and @$locations) { |
| foreach my $location (@$locations) { |
| next unless (defined($location->{'bytes'})); |
| #print STDERR "L anchor $location->{'root'}->{'extra'}->{'normalized'}: $location->{'lines'} ($location->{'bytes'})\n"; |
| push @{$updated_locations->{$location->{'lines'}}}, $location; |
| } |
| } |
| my $images_marks = {}; |
| if ($images and @$images) { |
| foreach my $image (@$images) { |
| #print STDERR "I $image->{'lines'}, $image->{'lines_count'}, $image->{'image_width'}\n"; |
| if ($image->{'lines_count'} > 1) { |
| if (!$images_marks->{$image->{'lines'}}) { |
| $images_marks->{$image->{'lines'}} = $image; |
| }# else { |
| # Happens in Info with the special construct as, in that |
| # case, there are no lines! So no error... |
| # $self->_bug_message("more than one image with lines on $image->{'lines'}"); |
| # in that case, the $image->{'lines'} is not in sync with the |
| # lines count. So the second image will be treated as simple text. |
| #} |
| } |
| } |
| } |
| |
| my $bytes_count = 0; |
| my $delta_bytes = 0; |
| my $line_index = 0; |
| my $image; |
| my $image_lines_count; |
| my $image_prepended_spaces; |
| foreach my $line (split /^/, $text) { |
| my $line_bytes_begin = 0; |
| my $line_bytes_end = 0; |
| my $removed_line_bytes_end = 0; |
| my $removed_line_bytes_begin = 0; |
| |
| my ($new_image, $new_image_prepended_spaces); |
| if ($images_marks->{$line_index}) { |
| $new_image = $images_marks->{$line_index}; |
| $image_lines_count = 0; |
| $new_image_prepended_spaces |
| = _compute_spaces_align_line($new_image->{'image_width'}, $max_column, |
| $direction, $new_image->{'no_align'}); |
| if (!defined($image)) { |
| $image = $new_image; |
| $image_prepended_spaces = $new_image_prepended_spaces; |
| $new_image = undef; |
| } |
| } |
| |
| my $orig_line; |
| if (!$image) { |
| my $chomped = chomp($line); |
| # for debugging. |
| $orig_line = $line; |
| $removed_line_bytes_end -= $self->count_bytes($chomped); |
| #$line_bytes_end -= $self->count_bytes($chomped); |
| $line =~ s/^(\s*)//; |
| $removed_line_bytes_begin -= $self->count_bytes($1); |
| #$line_bytes_begin -= $self->count_bytes($1); |
| $line =~ s/(\s*)$//; |
| $removed_line_bytes_end -= $self->count_bytes($1); |
| #$line_bytes_end -= $self->count_bytes($1); |
| my $line_width = Texinfo::Convert::Unicode::string_width($line); |
| if ($line_width == 0) { |
| $result .= "\n"; |
| $line_bytes_end += $self->count_bytes("\n"); |
| $bytes_count += $self->count_bytes("\n"); |
| } else { |
| my $spaces_prepended |
| = _compute_spaces_align_line($line_width, $max_column, $direction); |
| $result .= ' ' x$spaces_prepended . $line ."\n"; |
| $line_bytes_begin += $self->count_bytes(' ' x$spaces_prepended); |
| $line_bytes_end += $self->count_bytes("\n"); |
| $bytes_count += $line_bytes_begin + $line_bytes_end |
| + $self->count_bytes($line); |
| } |
| } else { |
| $image_lines_count++; |
| my $prepended_spaces = $image_prepended_spaces; |
| # adjust if there is something else that the image on the first or |
| # last line. The adjustment is approximate. |
| if (($image_lines_count == 1 or $image_lines_count == $image->{'lines_count'}) |
| and Texinfo::Convert::Unicode::string_width($line) > $image->{'image_width'}) { |
| $prepended_spaces |
| -= Texinfo::Convert::Unicode::string_width($line) - $image->{'image_width'}; |
| $prepended_spaces = 0 if ($prepended_spaces < 0); |
| } |
| $result .= ' ' x$prepended_spaces . $line; |
| $line_bytes_begin += $self->count_bytes(' ' x$prepended_spaces); |
| $bytes_count += $line_bytes_begin + $self->count_bytes($line); |
| if ($new_image) { |
| $image = $new_image; |
| $image_prepended_spaces = $new_image_prepended_spaces; |
| } elsif ($image_lines_count == $image->{'lines_count'}) { |
| $image = undef; |
| $image_lines_count = undef; |
| $image_prepended_spaces = undef; |
| } |
| } |
| |
| if ($updated_locations->{$line_index}) { |
| foreach my $location (@{$updated_locations->{$line_index}}) { |
| $location->{'bytes'} += $line_bytes_begin + $removed_line_bytes_begin |
| + $delta_bytes; |
| #print STDERR "UPDATE ALIGN: $location->{'root'}->{'extra'}->{'normalized'}: ($location->{'bytes'})\n"; |
| } |
| } |
| $delta_bytes += $line_bytes_begin + $line_bytes_end |
| + $removed_line_bytes_begin + $removed_line_bytes_end; |
| #print STDERR "ALIGN $orig_line ($line_index. lbb $line_bytes_begin, lbe $line_bytes_end, rlbb $removed_line_bytes_begin, rlbe $removed_line_bytes_end delta $delta_bytes, bytes_count $bytes_count)\n"; |
| $line_index++; |
| } |
| return ($result, $bytes_count); |
| } |
| |
| sub _align_environment($$$$) |
| { |
| my $self = shift; |
| my $result = shift; |
| my $max = shift; |
| my $align = shift; |
| |
| _update_count_context($self); |
| my $counts = pop @{$self->{'count_context'}}; |
| my $bytes_count; |
| ($result, $bytes_count) = $self->_align_lines($result, $max, |
| $align, $counts->{'locations'}, $counts->{'images'}); |
| $self->_update_locations_counts($counts->{'locations'}); |
| $self->{'count_context'}->[-1]->{'bytes'} += $bytes_count; |
| $self->{'count_context'}->[-1]->{'lines'} += $counts->{'lines'}; |
| push @{$self->{'count_context'}->[-1]->{'locations'}}, |
| @{$counts->{'locations'}}; |
| return $result; |
| } |
| |
| sub _contents($$$) |
| { |
| my $self = shift; |
| my $section_root = shift; |
| my $contents_or_shortcontents = shift; |
| |
| my $contents = 1 if ($contents_or_shortcontents eq 'contents'); |
| |
| # no sections |
| return ('', 0) if (!$section_root or !$section_root->{'section_childs'}); |
| my $root_level = $section_root->{'section_childs'}->[0]->{'level'}; |
| foreach my $top_section(@{$section_root->{'section_childs'}}) { |
| $root_level = $top_section->{'level'} |
| if ($top_section->{'level'} < $root_level); |
| } |
| |
| my $result = ''; |
| my $lines_count = 0; |
| # This is done like that because the tree may not be well formed if |
| # there is a @part after a @chapter for example. |
| foreach my $top_section (@{$section_root->{'section_childs'}}) { |
| my $section = $top_section; |
| SECTION: |
| while ($section) { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| my $section_title_tree; |
| if (defined($section->{'number'}) |
| and ($self->get_conf('NUMBER_SECTIONS') |
| or !defined($self->get_conf('NUMBER_SECTIONS')))) { |
| if ($section->{'cmdname'} eq 'appendix' and $section->{'level'} == 1) { |
| $section_title_tree = $self->gdt('Appendix {number} {section_title}', |
| {'number' => {'text' => $section->{'number'}}, |
| 'section_title' |
| => {'contents' => $section->{'extra'}->{'misc_content'}}}); |
| } else { |
| $section_title_tree = $self->gdt('{number} {section_title}', |
| {'number' => {'text' => $section->{'number'}}, |
| 'section_title' |
| => {'contents' => $section->{'extra'}->{'misc_content'}}}); |
| } |
| } else { |
| $section_title_tree = {'contents' => $section->{'extra'}->{'misc_content'}}; |
| } |
| my $section_title = $self->convert_line( |
| {'contents' => [$section_title_tree], |
| 'type' => 'frenchspacing'}); |
| pop @{$self->{'count_context'}}; |
| my $text = $section_title; |
| chomp ($text); |
| $text .= "\n"; |
| my $repeat_count = 2 * ($section->{'level'} - ($root_level+1)); |
| ($result .= (' ' x $repeat_count)) if $repeat_count > 0; |
| $result .= $text; |
| $lines_count++; |
| if ($section->{'section_childs'} |
| and ($contents or $section->{'level'} < $root_level+1)) { |
| $section = $section->{'section_childs'}->[0]; |
| } elsif ($section->{'section_next'}) { |
| last if ($section eq $top_section); |
| $section = $section->{'section_next'}; |
| } else { |
| last if ($section eq $top_section); |
| while ($section->{'section_up'}) { |
| $section = $section->{'section_up'}; |
| last SECTION if ($section eq $top_section); |
| if ($section->{'section_next'}) { |
| $section = $section->{'section_next'}; |
| last; |
| } |
| } |
| } |
| } |
| } |
| return ($result, $lines_count); |
| } |
| |
| sub _menu($$) |
| { |
| my $self = shift; |
| my $menu_command = shift; |
| |
| if ($menu_command->{'cmdname'} eq 'menu') { |
| my $result = "* Menu:\n\n"; |
| _add_text_count($self, $result); |
| _add_lines_count($self, 2); |
| return $result; |
| } else { |
| return ''; |
| } |
| } |
| |
| sub _printindex($$) |
| { |
| my $self = shift; |
| my $printindex = shift; |
| return $self->_printindex_formatted($printindex); |
| } |
| |
| sub _normalize_top_node($) |
| { |
| my $node = shift; |
| return Texinfo::Common::normalize_top_node_name($node); |
| } |
| |
| # convert and cache a node name. $NODE is a node element. |
| sub _node_line($$) |
| { |
| my $self = shift; |
| my $node = shift; |
| if (!$self->{'node_lines_text'}->{$node}) { |
| my $node_text = {'type' => '_code', |
| 'contents' => $node->{'extra'}->{'node_content'}}; |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| $self->{'node_lines_text'}->{$node}->{'text'} |
| = _normalize_top_node($self->convert_line($node_text, |
| {'suppress_styles' => 1})); |
| _update_count_context($self); |
| my $end_context = pop @{$self->{'count_context'}}; |
| $self->{'node_lines_text'}->{$node}->{'count'} |
| = $end_context->{'bytes'}; |
| } |
| return ($self->{'node_lines_text'}->{$node}->{'text'}, |
| $self->{'node_lines_text'}->{$node}->{'count'}); |
| } |
| |
| my $index_length_to_node = 41; |
| |
| sub _printindex_formatted($$;$) |
| { |
| my $self = shift; |
| my $printindex = shift; |
| my $in_info = shift; |
| |
| my $index_name; |
| |
| if ($printindex->{'extra'} and $printindex->{'extra'}->{'misc_args'} |
| and defined($printindex->{'extra'}->{'misc_args'}->[0])) { |
| $index_name = $printindex->{'extra'}->{'misc_args'}->[0]; |
| } else { |
| return ''; |
| } |
| |
| # this is not redone for each index, only once |
| if (!defined($self->{'index_entries'}) and $self->{'parser'}) { |
| |
| my ($index_names, $merged_indices) |
| = $self->{'parser'}->indices_information(); |
| my $merged_index_entries |
| = Texinfo::Structuring::merge_indices($index_names); |
| $self->{'index_entries'} |
| = $self->Texinfo::Structuring::sort_indices($merged_index_entries, |
| $index_names); |
| $self->{'index_names'} = $index_names; |
| } |
| if (!$self->{'index_entries'} or !$self->{'index_entries'}->{$index_name} |
| or ! @{$self->{'index_entries'}->{$index_name}}) { |
| return ''; |
| } |
| |
| my $result = ''; |
| $result .= _add_newline_if_needed($self); |
| if ($in_info) { |
| my $info_printindex_magic = "\x{00}\x{08}[index\x{00}\x{08}]\n"; |
| $result .= $info_printindex_magic; |
| _add_text_count($self, $info_printindex_magic); |
| _add_lines_count($self, 1); |
| } |
| my $heading = "* Menu:\n\n"; |
| |
| $result .= $heading; |
| _add_text_count($self, $heading); |
| _add_lines_count($self, 2); |
| |
| # first determine the line numbers for the spacing of their formatting |
| my %line_nrs; |
| my %entry_nodes; |
| my $max_index_line_nr_string_length = 0; |
| my %ignored_entries; |
| foreach my $entry (@{$self->{'index_entries'}->{$index_name}}) { |
| my $line_nr; |
| |
| if (defined ($self->{'index_entries_line_location'}->{$entry->{'command'}})) { |
| $line_nr = $self->{'index_entries_line_location'}->{$entry->{'command'}}->{'lines'}; |
| # ignore index entries in special regions that haven't been seen |
| } elsif ($entry->{'region'}) { |
| $ignored_entries{$entry} = 1; |
| next; |
| } |
| |
| my $node; |
| # priority given to the location determined dynamically as the |
| # index entry may be in footnote. |
| if (defined($self->{'index_entries_line_location'}->{$entry->{'command'}}->{'node'})) { |
| $node = $self->{'index_entries_line_location'}->{$entry->{'command'}}->{'node'}; |
| } elsif (defined($entry->{'node'})) { |
| $node = $entry->{'node'}; |
| } |
| $entry_nodes{$entry} = $node; |
| if (!defined($node)) { |
| $line_nr = 0; |
| } elsif($in_info) { |
| $line_nr = 3 if (defined($line_nr) and $line_nr < 3); |
| $line_nr = 4 if (!defined($line_nr)); |
| } else { |
| $line_nr = 0 if (!defined($line_nr)); |
| } |
| my $index_line_nr_string_length = |
| Texinfo::Convert::Unicode::string_width($line_nr); |
| $max_index_line_nr_string_length = $index_line_nr_string_length |
| if ($max_index_line_nr_string_length < $index_line_nr_string_length); |
| $line_nrs{$entry} = $line_nr; |
| } |
| |
| # this is used to count entries that are the same |
| my %entry_counts = (); |
| |
| foreach my $entry (@{$self->{'index_entries'}->{$index_name}}) { |
| #my @keys = keys(%$entry); |
| #print STDERR "$index_name $entry: @keys\n"; |
| next if ($ignored_entries{$entry}); |
| my $entry_tree = {'contents' => $entry->{'content'}}; |
| if ($entry->{'in_code'}) { |
| $entry_tree->{'type'} = '_code'; |
| } else { |
| $entry_tree->{'type'} = 'frenchspacing'; |
| } |
| my $entry_text = ''; |
| $entry_text .= $self->convert_line($entry_tree, {'indent' => 0, |
| 'suppress_styles' => 1}); |
| next if ($entry_text !~ /\S/); |
| # FIXME protect instead |
| if ($entry_text =~ /:/ and $self->get_conf('INDEX_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn (sprintf($self->__("Index entry in \@%s with : produces invalid Info: %s"), |
| $entry->{'index_at_command'}, |
| Texinfo::Convert::Texinfo::convert($entry_tree)), |
| $entry->{'command'}->{'line_nr'}); |
| } |
| |
| my $entry_nr = ''; |
| if (!defined($entry_counts{$entry_text})) { |
| $entry_counts{$entry_text} = 0; |
| } else { |
| $entry_counts{$entry_text}++; |
| $entry_nr = ' <'.$entry_counts{$entry_text}.'>'; |
| _add_text_count($self, $entry_nr); |
| } |
| my $entry_line = "* $entry_text${entry_nr}: "; |
| _add_text_count($self, "* ".": "); |
| #_add_text_count($self, $entry_line); |
| |
| my $line_width = Texinfo::Convert::Unicode::string_width($entry_line); |
| my $entry_line_addition = ''; |
| if ($line_width < $index_length_to_node) { |
| my $spaces = ' ' x ($index_length_to_node - $line_width); |
| $entry_line_addition .= $spaces; |
| _add_text_count($self, $spaces); |
| } |
| my $node = $entry_nodes{$entry}; |
| |
| if (!defined($node)) { |
| # cache the transformation to text and byte counting, as |
| # it is likeky that there is more than one such entry |
| if (!$self->{'outside_of_any_node_text'}) { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| my $node_text = $self->gdt('(outside of any node)'); |
| $self->{'outside_of_any_node_text'}->{'text'} |
| = $self->convert_line($node_text); |
| _update_count_context($self); |
| my $end_context = pop @{$self->{'count_context'}}; |
| $self->{'outside_of_any_node_text'}->{'count'} |
| = $end_context->{'bytes'}; |
| } |
| $entry_line_addition .= $self->{'outside_of_any_node_text'}->{'text'}; |
| $self->{'count_context'}->[-1]->{'bytes'} |
| += $self->{'outside_of_any_node_text'}->{'count'}; |
| # FIXME when outside of sectioning commands this message was already |
| # done by the Parser. |
| # Warn, only once. |
| if (!$self->{'index_entries_no_node'}->{$entry}) { |
| $self->line_warn(sprintf($self->__("entry for index `%s' outside of any node"), |
| $index_name), $entry->{'command'}->{'line_nr'}); |
| $self->{'index_entries_no_node'}->{$entry} = 1; |
| } |
| } else { |
| my ($node_line, $byte_count) = $self->_node_line($node); |
| $entry_line_addition .= $node_line; |
| $self->{'count_context'}->[-1]->{'bytes'} += $byte_count; |
| } |
| $entry_line_addition .= '.'; |
| _add_text_count($self, '.'); |
| |
| $entry_line .= $entry_line_addition; |
| $result .= $entry_line; |
| |
| my $line_nr = $line_nrs{$entry}; |
| my $line_nr_spaces = sprintf("%${max_index_line_nr_string_length}d", $line_nr); |
| my $line_part = "(line ${line_nr_spaces})"; |
| $line_width += Texinfo::Convert::Unicode::string_width($entry_line_addition); |
| my $line_part_width = Texinfo::Convert::Unicode::string_width($line_part); |
| if ($line_width + $line_part_width +1 > $self->{'fillcolumn'}) { |
| $line_part = "\n" . ' ' x ($self->{'fillcolumn'} - $line_part_width) |
| . "$line_part\n"; |
| _add_lines_count($self, 1); |
| } else { |
| $line_part |
| = ' ' x ($self->{'fillcolumn'} - $line_part_width - $line_width) |
| . "$line_part\n"; |
| } |
| _add_lines_count($self, 1); |
| _add_text_count($self, $line_part); |
| $result .= $line_part; |
| } |
| |
| $result .= "\n"; |
| _add_text_count($self, "\n"); |
| _add_lines_count($self, 1); |
| |
| return $result; |
| } |
| |
| |
| sub _node($$) |
| { |
| my $self = shift; |
| my $node = shift; |
| |
| return ''; |
| } |
| |
| # no error in plaintext |
| sub _error_outside_of_any_node($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| } |
| |
| sub _anchor($$) |
| { |
| my $self = shift; |
| my $anchor = shift; |
| |
| if (!($self->{'multiple_pass'} or $self->{'in_copying_header'})) { |
| $self->_add_location($anchor); |
| $self->_error_outside_of_any_node($anchor); |
| } |
| return ''; |
| } |
| |
| my $listoffloat_entry_length = 41; |
| my $listoffloat_append = '...'; |
| |
| sub ensure_end_of_line($$) |
| { |
| my $self = shift; |
| my $text = shift; |
| my $chomped = chomp ($text); |
| if ($chomped) { |
| $self->{'count_context'}->[-1]->{'bytes'} -= $self->count_bytes($chomped); |
| $self->{'count_context'}->[-1]->{'lines'} -= 1; |
| } |
| $text .= "\n"; |
| _add_text_count($self, "\n"); |
| _add_lines_count($self, 1); |
| return $text; |
| } |
| |
| sub _image_text($$$) |
| { |
| my $self = shift; |
| my $root = shift; |
| my $basefile = shift; |
| |
| my $txt_file = $self->Texinfo::Common::locate_include_file($basefile.'.txt'); |
| if (!defined($txt_file)) { |
| return undef; |
| } else { |
| my $filehandle = do { local *FH }; |
| if (open ($filehandle, $txt_file)) { |
| binmode($filehandle, ":encoding(" |
| .$self->get_conf('INPUT_PERL_ENCODING').")") |
| if (defined($self->get_conf('INPUT_PERL_ENCODING'))); |
| my $result = ''; |
| my $max_width = 0; |
| while (<$filehandle>) { |
| my $width = Texinfo::Convert::Unicode::string_width($_); |
| if ($width > $max_width) { |
| $max_width = $width; |
| } |
| $result .= $_; |
| } |
| # remove last end of line |
| chomp ($result); |
| if (!close ($filehandle)) { |
| $self->document_warn(sprintf($self->__("error on closing image text file %s: %s"), |
| $txt_file, $!)); |
| } |
| return ($result, $max_width); |
| } else { |
| $self->line_warn(sprintf($self->__("\@image file `%s' unreadable: %s"), |
| $txt_file, $!), $root->{'line_nr'}); |
| } |
| } |
| return undef; |
| } |
| |
| sub _image_formatted_text($$$$) |
| { |
| my $self = shift; |
| my $root = shift; |
| my $basefile = shift; |
| my $text = shift; |
| |
| my $result; |
| if (defined($text)) { |
| $result = $text; |
| } elsif (defined($root->{'extra'}->{'brace_command_contents'}->[3])) { |
| $result = '[' .Texinfo::Convert::Text::convert( |
| {'contents' => $root->{'extra'}->{'brace_command_contents'}->[3]}, |
| $self->{'convert_text_options'}) .']'; |
| } else { |
| $self->line_warn(sprintf($self->__( |
| "could not find \@image file `%s.txt' nor alternate text"), |
| $basefile), $root->{'line_nr'}); |
| $result = '['.$basefile.']'; |
| } |
| return $result; |
| } |
| |
| sub _image($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| |
| if (defined($root->{'extra'}->{'brace_command_contents'}->[0])) { |
| my $basefile = Texinfo::Convert::Text::convert( |
| {'contents' => $root->{'extra'}->{'brace_command_contents'}->[0]}, |
| {'code' => 1, %{$self->{'convert_text_options'}}}); |
| my ($text, $width) = $self->_image_text($root, $basefile); |
| my $result = $self->_image_formatted_text($root, $basefile, $text); |
| my $lines_count = ($result =~ tr/\n/\n/); |
| if (!defined($width)) { |
| $width = Texinfo::Convert::Unicode::string_width($result); |
| } |
| # the last line is part of the image but do not have a new line, |
| # so 1 is added to $lines_count to have the number of lines of |
| # the image |
| $self->_add_image($root, $lines_count+1, $width); |
| return ($result, $lines_count); |
| } |
| return ('', 0); |
| } |
| |
| sub _get_form_feeds($) |
| { |
| my $form_feeds = shift; |
| $form_feeds =~ s/^[^\f]*//; |
| $form_feeds =~ s/[^\f]$//; |
| return $form_feeds; |
| } |
| |
| sub _convert($$); |
| |
| # Convert the Texinfo tree under $ROOT to plain text. Note that this |
| # function should be called as "$self->_convert" to allow the |
| # DebugTexinfo::DebugCount::_convert method to override this one. |
| sub _convert($$) |
| { |
| my $self = shift; |
| my $root = shift; |
| |
| my $formatter = $self->{'formatters'}->[-1]; |
| |
| if ($self->{'debug'}) { |
| my $is_top_formatter = 0; |
| $is_top_formatter = 1 if ($formatter->{'_top_formatter'}); |
| my $empty_lines_count = ''; |
| $empty_lines_count = $self->{'empty_lines_count'} |
| if defined($self->{'empty_lines_count'}); |
| print STDERR "ROOT:$root (".join('|',@{$self->{'context'}})."), formatters ".scalar(@{$self->{'formatters'}}) . " ->"; |
| print STDERR " cmd: $root->{'cmdname'}," if ($root->{'cmdname'}); |
| print STDERR " type: $root->{'type'}" if ($root->{'type'}); |
| my $text = $root->{'text'}; |
| if (defined($text)) { |
| my $text_escaped_spaces |
| = Texinfo::Convert::Paragraph::_print_escaped_spaces($text); |
| print STDERR " text: $text_escaped_spaces"; |
| } |
| print STDERR "\n"; |
| |
| print STDERR " empty_lines $empty_lines_count,top_fmter $is_top_formatter,format_ctxt $self->{'format_context'}->[-1]->{'cmdname'},para_cnt $self->{'format_context'}->[-1]->{'paragraph_count'},indent_lvl $self->{'format_context'}->[-1]->{'indent_level'}," |
| .(defined($self->{'text_element_context'}->[-1]->{'counter'}) ? "counter $self->{'text_element_context'}->[-1]->{'counter'}," : '') |
| ."max $self->{'text_element_context'}->[-1]->{'max'}\n"; |
| #print STDERR " Special def_command: $root->{'extra'}->{'def_command'}\n" |
| # if (defined($root->{'extra'}) and $root->{'extra'}->{'def_command'}); |
| if ($formatter) { |
| my $monospace = $formatter->{'font_type_stack'}->[-1]->{'monospace'}; |
| $monospace = 'UNDEF' if (!defined($monospace)); |
| print STDERR " Container:($monospace,$formatter->{'upper_case'},$formatter->{'frenchspacing_stack'}->[-1]) "; |
| $formatter->{'container'}->dump(); |
| } |
| } |
| |
| if (($root->{'type'} and $self->{'ignored_types'}->{$root->{'type'}}) |
| or ($root->{'cmdname'} |
| and ($self->{'ignored_commands'}->{$root->{'cmdname'}} |
| or ($inline_commands{$root->{'cmdname'}} |
| and $root->{'cmdname'} ne 'inlinefmtifelse' |
| and (($inline_format_commands{$root->{'cmdname'}} |
| and (!$root->{'extra'}->{'format'} |
| or !$self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}})) |
| or (!$inline_format_commands{$root->{'cmdname'}} |
| and !defined($root->{'extra'}->{'expand_index'}))))))) { |
| print STDERR "IGNORED\n" if ($self->{'debug'}); |
| return ''; |
| } |
| my $result = ''; |
| |
| # in ignorable spaces, keep only form feeds. |
| if ($root->{'type'} and $self->{'ignorable_space_types'}->{$root->{'type'}} |
| and ($root->{'type'} ne 'empty_spaces_before_paragraph' |
| or $self->get_conf('paragraphindent') ne 'asis')) { |
| if ($root->{'text'} =~ /\f/) { |
| $result = _get_form_feeds($root->{'text'}); |
| } |
| _add_text_count($self, $result); |
| print STDERR "IGNORABLE SPACE: ($result)\n" if ($self->{'debug'}); |
| return $result; |
| } |
| |
| # First handle empty lines. This has to be done before the handling |
| # of text below to be sure that an empty line is always processed |
| # especially |
| if ($root->{'type'} and ($root->{'type'} eq 'empty_line' |
| or $root->{'type'} eq 'after_description_line')) { |
| if ($self->{'debug'}) { |
| my $count = $self->{'empty_lines_count'}; |
| $count = '' if (!defined($count)); |
| print STDERR "EMPTY_LINE ($count)\n"; |
| } |
| delete $self->{'text_element_context'}->[-1]->{'counter'}; |
| $self->{'empty_lines_count'}++; |
| if ($self->{'empty_lines_count'} <= 1 |
| or $self->{'preformatted_context_commands'}->{$self->{'context'}->[-1]}) { |
| $result = ""; |
| if ($root->{'text'} =~ /\f/) { |
| $result .= _get_form_feeds($root->{'text'}); |
| _add_text_count($self, $result); |
| } |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text("\n")); |
| return $result; |
| } else { |
| return ''; |
| } |
| } |
| |
| # process text |
| if (defined($root->{'text'})) { |
| if (!$formatter->{'_top_formatter'}) { |
| if ($root->{'type'} and ($root->{'type'} eq 'raw' |
| or $root->{'type'} eq 'last_raw_newline')) { |
| $result = _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next($root->{'text'})); |
| } else { |
| my $text = _process_text($self, $root, $formatter); |
| $result = _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text($text)); |
| } |
| return $result; |
| # the following is only possible if paragraphindent is set to asis |
| } elsif ($root->{'type'} and $root->{'type'} eq 'empty_spaces_before_paragraph') { |
| _add_text_count($self, $root->{'text'}); |
| return $root->{'text'}; |
| # ignore text outside of any format, but warn if ignored text not empty |
| } elsif ($root->{'text'} =~ /\S/) { |
| $self->_bug_message("ignored text not empty `$root->{'text'}'", $root); |
| return ''; |
| } |
| } |
| |
| if ($root->{'extra'}) { |
| #if ($root->{'extra'}->{'invalid_nesting'}) { |
| # print STDERR "INVALID_NESTING\n" if ($self->{'debug'}); |
| # return ''; |
| #} elsif ($root->{'extra'}->{'missing_argument'} |
| if ($root->{'extra'}->{'missing_argument'} |
| and (!$root->{'contents'} or !@{$root->{'contents'}})) { |
| print STDERR "MISSING_ARGUMENT\n" if ($self->{'debug'}); |
| return ''; |
| } |
| } |
| |
| if ($root->{'extra'} and $root->{'extra'}->{'index_entry'} |
| and !$self->{'multiple_pass'} and !$self->{'in_copying_header'}) { |
| my $location = $self->_add_location($root); |
| # remove a 'lines' from $location if at the very end of a node |
| # since it will lead to the next node otherwise. |
| if ($root->{'cmdname'} and $root->{'cmdname'} =~ /index/) { |
| my $following_not_empty; |
| my @parents = @{$self->{'current_roots'}}; |
| my @parent_contents = @{$self->{'current_contents'}}; |
| while (@parents) { |
| my $parent = pop @parents; |
| my $parent_content = pop @parent_contents; |
| if ($parent->{'type'} and $parent->{'type'} eq 'paragraph') { |
| $following_not_empty = 1; |
| last; |
| } |
| foreach my $following_content (@$parent_content) { |
| unless (($following_content->{'type'} |
| and ($following_content->{'type'} eq 'empty_line' |
| or $ignorable_types{$following_content->{'type'}})) |
| or ($following_content->{'cmdname'} |
| and ($following_content->{'cmdname'} eq 'c' |
| or $following_content->{'cmdname'} eq 'comment'))) { |
| $following_not_empty = 1; |
| last; |
| } |
| } |
| last if $following_not_empty; |
| if ($parent->{'cmdname'} and $root_commands{$parent->{'cmdname'}}) { |
| last; |
| } |
| } |
| if (! $following_not_empty) { |
| print STDERR "INDEX ENTRY $root->{'cmdname'} followed by empty lines\n" |
| if ($self->{'debug'}); |
| $location->{'lines'}--; |
| } |
| } |
| # this covers the special case for index entry not associated with a |
| # node but seen. this will be an index entry in @copying, |
| # in @insertcopying. |
| # This also covers the case of an index entry in a node added by a |
| # @footnote with footnotestyle separate. |
| if ($self->{'node'}) { |
| $location->{'node'} = $self->{'node'}; |
| } |
| $self->{'index_entries_line_location'}->{$root} = $location; |
| print STDERR "INDEX ENTRY lines_count $location->{'lines'}, index_entry $location->{'index_entry'}\n" |
| if ($self->{'debug'}); |
| } |
| |
| my $cell; |
| my $preformatted; |
| if ($root->{'cmdname'}) { |
| my $unknown_command; |
| my $command = $root->{'cmdname'}; |
| if (defined($no_brace_commands{$command})) { |
| if ($command eq ':') { |
| $formatter->{'container'}->remove_end_sentence(); |
| return ''; |
| } elsif ($command eq '*') { |
| $result = _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_pending_word()); |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->end_line()); |
| } elsif ($command eq '.' or $command eq '?' or $command eq '!') { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next($command)); |
| $formatter->{'container'}->add_end_sentence(1); |
| } elsif ($command eq ' ' or $command eq "\n" or $command eq "\t") { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next($no_brace_commands{$command})); |
| } else { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text($no_brace_commands{$command})); |
| } |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'today') { |
| my $today = $self->Texinfo::Common::expand_today(); |
| unshift @{$self->{'current_contents'}->[-1]}, $today; |
| } elsif (exists($brace_no_arg_commands{$root->{'cmdname'}})) { |
| my $text; |
| |
| if ($command eq 'dots' or $command eq 'enddots') { |
| # Don't use Unicode ellipsis character. |
| $text = '...'; |
| } else { |
| $text = Texinfo::Convert::Text::brace_no_arg_command($root, |
| $self->{'convert_text_options'}); |
| } |
| |
| # @AA{} should suppress an end sentence, @aa{} shouldn't. This |
| # is the case whether we are in @sc or not. |
| if ($formatter->{'upper_case'} |
| and $letter_no_arg_commands{$root->{'cmdname'}}) { |
| $text = _protect_sentence_ends($text); |
| $text = uc($text); |
| |
| if ($self->{'DEBUG'}) { |
| my $debug_text = $text; |
| $debug_text =~ s/\x08/!!/g; |
| print STDERR "accent markers:$debug_text\n"; |
| } |
| } |
| |
| if ($punctuation_no_arg_commands{$command}) { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next($text)); |
| $formatter->{'container'}->add_end_sentence(1); |
| } elsif ($command eq 'tie') { |
| $formatter->{'w'}++; |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->set_space_protection(1,undef)) |
| if ($formatter->{'w'} == 1); |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text($text)); |
| $formatter->{'w'}--; |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->set_space_protection(0,undef)) |
| if ($formatter->{'w'} == 0); |
| } else { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text($text)); |
| |
| # This is to have @TeX{}, for example, not to prevent end sentences. |
| if (!$letter_no_arg_commands{$command}) { |
| $formatter->{'container'}->allow_end_sentence(); |
| } |
| |
| if ($command eq 'dots') { |
| $formatter->{'container'}->remove_end_sentence(); |
| } |
| } |
| if ($formatter->{'var'} |
| or $formatter->{'font_type_stack'}->[-1]->{'monospace'}) { |
| $formatter->{'container'}->allow_end_sentence(); |
| } |
| return $result; |
| # commands with braces |
| } elsif ($accent_commands{$root->{'cmdname'}}) { |
| my $encoding; |
| if ($self->{'enable_encoding'}) { |
| $encoding = $self->{'output_encoding_name'}; |
| } |
| my $sc; |
| if ($formatter->{'upper_case'}) { |
| $sc = 1; |
| } |
| my $accented_text |
| = Texinfo::Convert::Text::text_accents($root, $encoding, $sc); |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text($accented_text)); |
| |
| my $accented_text_original; |
| if ($formatter->{'upper_case'}) { |
| $accented_text_original |
| = Texinfo::Convert::Text::text_accents($root, $encoding); |
| } |
| |
| if ($accented_text_original |
| and $accented_text_original !~ /[[:upper:]]/ |
| or $formatter->{'var'} |
| or $formatter->{'font_type_stack'}->[-1]->{'monospace'}) { |
| $formatter->{'container'}->allow_end_sentence(); |
| } |
| |
| # in case the text added ends with punctuation. |
| # If the text is empty (likely because of an error) previous |
| # punctuation will be cancelled, we don't want that. |
| $formatter->{'container'}->remove_end_sentence() |
| if ($accented_text ne ''); |
| return $result; |
| } elsif ($self->{'style_map'}->{$command} |
| or ($root->{'type'} and $root->{'type'} eq 'definfoenclose_command')) { |
| if ($code_style_commands{$command}) { |
| if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { |
| push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; |
| } else { |
| $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; |
| } |
| #$formatter->{'code'}++; |
| } elsif ($regular_font_style_commands{$command}) { |
| if ($formatter->{'font_type_stack'}->[-1]->{'monospace'}) { |
| push @{$formatter->{'font_type_stack'}}, {'monospace' => 0, |
| 'normal' => 1}; |
| } elsif ($formatter->{'font_type_stack'}->[-1]->{'normal'}) { |
| $formatter->{'font_type_stack'}->[-1]->{'normal'}++; |
| } |
| } |
| if ($no_punctation_munging_commands{$command}) { |
| push @{$formatter->{'frenchspacing_stack'}}, 'on'; |
| $formatter->{'container'}->set_space_protection(undef, |
| undef,undef,1); |
| } |
| if ($upper_case_commands{$command}) { |
| $formatter->{'upper_case'}++; |
| $formatter->{'var'}++ if ($command eq 'var'); |
| } |
| if ($command eq 'w') { |
| $formatter->{'w'}++; |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->set_space_protection(1,undef)) |
| if ($formatter->{'w'} == 1); |
| } |
| my ($text_before, $text_after); |
| if ($root->{'type'} and $root->{'type'} eq 'definfoenclose_command') { |
| $text_before = $root->{'extra'}->{'begin'}; |
| $text_after = $root->{'extra'}->{'end'}; |
| } elsif ($non_quoted_commands_when_nested{$command} |
| and $formatter->{'font_type_stack'}->[-1]->{'code_command'}) { |
| $text_before = ''; |
| $text_after = ''; |
| } elsif ($formatter->{'suppress_styles'} |
| and !$index_style_commands{$command}) { |
| $text_before = ''; |
| $text_after = ''; |
| } else { |
| $text_before = $self->{'style_map'}->{$command}->[0]; |
| $text_after = $self->{'style_map'}->{$command}->[1]; |
| } |
| # do this after determining $text_before/$text_after such that it |
| # doesn't impact the current command, but only commands nested within |
| if ($non_quoted_commands_when_nested{$command}) { |
| $formatter->{'font_type_stack'}->[-1]->{'code_command'}++; |
| } |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next($text_before, 1)) |
| if ($text_before ne ''); |
| if ($root->{'args'}) { |
| $result .= $self->_convert($root->{'args'}->[0]); |
| if ($root->{'cmdname'} eq 'strong' |
| and scalar (@{$root->{'args'}->[0]->{'contents'}}) |
| and $root->{'args'}->[0]->{'contents'}->[0]->{'text'} |
| and $root->{'args'}->[0]->{'contents'}->[0]->{'text'} =~ /^Note\s/i |
| and $self->{'output_format'} |
| and $self->{'output_format'} eq 'info') { |
| $self->line_warn($self->__( |
| "\@strong{Note...} produces a spurious cross-reference in Info; reword to avoid that"), |
| $root->{'line_nr'}); |
| } |
| } |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next($text_after, 1)) |
| if ($text_after ne ''); |
| if ($command eq 'w') { |
| $formatter->{'w'}--; |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->set_space_protection(0,undef)) |
| if ($formatter->{'w'} == 0); |
| } |
| if ($code_style_commands{$command}) { |
| #$formatter->{'code'}--; |
| $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; |
| $formatter->{'container'}->allow_end_sentence(); |
| pop @{$formatter->{'font_type_stack'}} |
| if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; |
| } elsif ($regular_font_style_commands{$command}) { |
| if ($formatter->{'font_type_stack'}->[-1]->{'normal'}) { |
| $formatter->{'font_type_stack'}->[-1]->{'normal'}--; |
| pop @{$formatter->{'font_type_stack'}} |
| if !$formatter->{'font_type_stack'}->[-1]->{'normal'}; |
| } |
| } |
| if ($non_quoted_commands_when_nested{$command}) { |
| #$formatter->{'code_command'}--; |
| $formatter->{'font_type_stack'}->[-1]->{'code_command'}--; |
| } |
| if ($no_punctation_munging_commands{$command}) { |
| pop @{$formatter->{'frenchspacing_stack'}}; |
| my $frenchspacing = 0; |
| $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); |
| $formatter->{'container'}->set_space_protection(undef, |
| undef, undef, $frenchspacing); |
| } |
| if ($upper_case_commands{$command}) { |
| $formatter->{'upper_case'}--; |
| if ($command eq 'var') { |
| $formatter->{'var'}--; |
| # Allow a following full stop to terminate a sentence. |
| $formatter->{'container'}->allow_end_sentence(); |
| } |
| } |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'image') { |
| $result = _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_pending_word(1)); |
| my ($image, $lines_count) = $self->_image($root); |
| _add_lines_count($self, $lines_count); |
| _add_text_count($self, $image); |
| if ($image ne '' and $formatter->{'type'} ne 'paragraph') { |
| $self->{'empty_lines_count'} = 0; |
| } |
| $result .= $image; |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'email') { |
| # nothing is output for email, instead the command is substituted. |
| my @email_contents; |
| if ($root->{'extra'} and $root->{'extra'}->{'brace_command_contents'}) { |
| my $name; |
| my $email; |
| if (scalar (@{$root->{'extra'}->{'brace_command_contents'}}) == 2 |
| and defined($root->{'extra'}->{'brace_command_contents'}->[-1])) { |
| $name = $root->{'extra'}->{'brace_command_contents'}->[1]; |
| } |
| if (defined($root->{'extra'}->{'brace_command_contents'}->[0])) { |
| $email = $root->{'extra'}->{'brace_command_contents'}->[0]; |
| } |
| my $prepended; |
| if ($name and $email) { |
| $prepended = $self->gdt('{name} @url{{email}}', |
| {'name' => $name, 'email' => $email}); |
| } elsif ($email) { |
| $prepended = $self->gdt('@url{{email}}', |
| {'email' => $email}); |
| } elsif ($name) { |
| $prepended = {'contents' => $name}; |
| } else { |
| return ''; |
| } |
| unshift @{$self->{'current_contents'}->[-1]}, $prepended; |
| } |
| return ''; |
| } elsif ($command eq 'uref' or $command eq 'url') { |
| if ($root->{'extra'} and $root->{'extra'}->{'brace_command_contents'}) { |
| if (scalar(@{$root->{'extra'}->{'brace_command_contents'}}) == 3 |
| and defined($root->{'extra'}->{'brace_command_contents'}->[2])) { |
| unshift @{$self->{'current_contents'}->[-1]}, |
| {'contents' => $root->{'extra'}->{'brace_command_contents'}->[2]}; |
| } elsif (defined($root->{'extra'}->{'brace_command_contents'}->[0])) { |
| # no mangling of --- and similar in url. |
| my $url = {'type' => '_code', |
| 'contents' => $root->{'extra'}->{'brace_command_contents'}->[0]}; |
| if (scalar(@{$root->{'extra'}->{'brace_command_contents'}}) == 2 |
| and defined($root->{'extra'}->{'brace_command_contents'}->[1])) { |
| my $prepended = $self->gdt('{text} ({url})', |
| {'text' => $root->{'extra'}->{'brace_command_contents'}->[1], |
| 'url' => $url }); |
| unshift @{$self->{'current_contents'}->[-1]}, $prepended; |
| } else { |
| my $prepended = $self->gdt('@t{<{url}>}', |
| {'url' => $url}); |
| unshift @{$self->{'current_contents'}->[-1]}, $prepended |
| } |
| } elsif (scalar(@{$root->{'extra'}->{'brace_command_contents'}}) == 2 |
| and defined($root->{'extra'}->{'brace_command_contents'}->[1])) { |
| unshift @{$self->{'current_contents'}->[-1]}, |
| {'contents' => $root->{'extra'}->{'brace_command_contents'}->[1]}; |
| } |
| } |
| return ''; |
| } elsif ($command eq 'footnote') { |
| $self->{'footnote_index'}++ unless ($self->{'multiple_pass'}); |
| my $formatted_footnote_number; |
| if ($self->get_conf('NUMBER_FOOTNOTES')) { |
| $formatted_footnote_number = $self->{'footnote_index'}; |
| } else { |
| $formatted_footnote_number = $NO_NUMBER_FOOTNOTE_SYMBOL; |
| } |
| push @{$self->{'pending_footnotes'}}, {'root' => $root, |
| 'number' => $self->{'footnote_index'}} |
| unless ($self->{'multiple_pass'}); |
| if (!$self->{'in_copying_header'}) { |
| $self->_error_outside_of_any_node($root); |
| } |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_next |
| ("($formatted_footnote_number)", 1)); |
| if ($self->get_conf('footnotestyle') eq 'separate' and $self->{'node'}) { |
| $result .= $self->_convert({'contents' => |
| [{'text' => ' ('}, |
| {'cmdname' => 'pxref', |
| 'extra' => {'brace_command_contents' => |
| [[@{$self->{'node'}->{'extra'}->{'node_content'}}, |
| {'text' => "-Footnote-$self->{'footnote_index'}"}]]}}, |
| {'text' => ')'}], |
| }); |
| } |
| return $result; |
| } elsif ($command eq 'anchor') { |
| $result = _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_pending_word()); |
| $result .= $self->_anchor($root); |
| return $result; |
| } elsif ($ref_commands{$command}) { |
| if ($root->{'extra'} and $root->{'extra'}->{'brace_command_contents'} |
| and scalar(@{$root->{'extra'}->{'brace_command_contents'}})) { |
| my @args = @{$root->{'extra'}->{'brace_command_contents'}}; |
| $args[0] = [{'text' => ''}] if (!defined($args[0])); |
| |
| # normalize node name, to get a ref with the right formatting |
| # NOTE as a consequence, the line numbers appearing in case of errors |
| # correspond to the node lines numbers, and not the @ref. |
| my $node_content; |
| if ($root->{'extra'} |
| and $root->{'extra'}->{'label'}) { |
| $node_content = $root->{'extra'}->{'label'}->{'extra'}->{'node_content'}; |
| } else { |
| $node_content = $args[0]; |
| } |
| |
| # if it a reference to a float with a label, $arg[1] is |
| # set to '$type $number' or '$number' if there is no type. |
| if (! defined($args[1]) |
| and $root->{'extra'} |
| and $root->{'extra'}->{'label'} |
| and $root->{'extra'}->{'label'}->{'cmdname'} |
| and $root->{'extra'}->{'label'}->{'cmdname'} eq 'float') { |
| my $float = $root->{'extra'}->{'label'}; |
| |
| my $name = $self->_float_type_number($float); |
| $args[1] = $name->{'contents'}; |
| } |
| if ($command eq 'inforef' and scalar(@args) == 3) { |
| $args[3] = $args[2]; |
| $args[2] = undef; |
| } |
| |
| # Treat cross-reference commands in a multitable cell as if they |
| # were surrounded by @w{ ... }, so the output will not be split across |
| # lines, leading text from other columns appearing to be part of the |
| # cross-reference. |
| my $in_multitable = 0; |
| if ($self->{'document_context'}->[-1]->{'in_multitable'}) { |
| $in_multitable = 1; |
| $formatter->{'w'}++; |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->set_space_protection(1,undef)) |
| if ($formatter->{'w'} == 1); |
| } |
| # Disallow breaks in runs of Chinese text in node names, because a |
| # break would be normalized to a single space by the Info reader, and |
| # the node wouldn't be found. |
| $formatter->{'container'} |
| ->set_space_protection(undef,undef,undef,undef,1); # double_width_no_break |
| |
| if ($command eq 'xref') { |
| $result = $self->_convert({'contents' => [{'text' => '*Note '}]}); |
| } else { |
| $result = $self->_convert({'contents' => [{'text' => '*note '}]}); |
| } |
| my $name; |
| if (defined($args[1])) { |
| $name = $args[1]; |
| } elsif (defined($args[2])) { |
| $name = $args[2]; |
| } |
| my $file; |
| if (defined($args[3])) { |
| $file = [{'text' => '('}, |
| {'type' => '_code', |
| 'contents' => $args[3]}, |
| {'text' => ')'},]; |
| } elsif (defined($args[4])) { |
| # add a () such that the node is considered to be external, |
| # even though the manual name is not known. |
| $file = [{'text' => '()'}]; |
| } |
| |
| if ($name) { |
| my $name_text = $self->_convert({'contents' => $name}); |
| # needed, as last word is added only when : is added below |
| my $name_text_checked = $name_text |
| .$self->{'formatters'}->[-1]->{'container'}->get_pending(); |
| my $quoting_required = 0; |
| if ($name_text_checked =~ /:/m) { |
| if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn(sprintf($self->__( |
| "\@%s cross-reference name should not contain `:'"), |
| $command), $root->{'line_nr'}); |
| } |
| if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { |
| $quoting_required = 1; |
| } |
| } |
| my $pre_quote = $quoting_required ? "\x{7f}" : ''; |
| my $post_quote = $pre_quote; |
| $name_text .= $self->_convert({'contents' => [ |
| {'text' => "$post_quote: "}]}); |
| $name_text =~ s/^(\s*)/$1$pre_quote/ if $pre_quote; |
| $result .= $name_text; |
| _count_added($self,$self->{'formatters'}[-1]{'container'}, |
| $pre_quote) |
| if $pre_quote; |
| |
| if ($file) { |
| $result .= $self->_convert({'contents' => $file}); |
| } |
| # node name |
| $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; |
| my $node_text = $self->_convert({'type' => '_code', |
| 'contents' => $node_content}); |
| delete $self->{'formatters'}->[-1]->{'suppress_styles'}; |
| |
| my $node_text_checked = $node_text |
| .$self->{'formatters'}->[-1]->{'container'}->get_pending(); |
| $quoting_required = 0; |
| if ($node_text_checked =~ /([,\t\.])/m ) { |
| if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn(sprintf($self->__( |
| "\@%s node name should not contain `%s'"), $command, $1), |
| $root->{'line_nr'}); |
| } |
| if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { |
| $quoting_required = 1; |
| } |
| } |
| $pre_quote = $quoting_required ? "\x{7f}" : ''; |
| $post_quote = $pre_quote; |
| if ($pre_quote) { |
| $node_text =~ s/^(\s*)/$1$pre_quote/; |
| _count_added($self,$self->{'formatters'}[-1]{'container'}, |
| $pre_quote); |
| } |
| $result .= $node_text; |
| _count_added($self, $self->{'formatters'}[-1]{'container'}, |
| $self->{'formatters'}->[-1]->{'container'}->add_next($post_quote)) |
| if $post_quote; |
| } else { # Label same as node specification |
| if ($file) { |
| $result .= $self->_convert({'contents' => $file}); |
| } |
| $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; |
| my $node_text = $self->_convert({'type' => '_code', |
| 'contents' => $node_content}); |
| delete $self->{'formatters'}->[-1]->{'suppress_styles'}; |
| |
| my $node_text_checked = $node_text |
| .$self->{'formatters'}->[-1]->{'container'}->get_pending(); |
| my $quoting_required = 0; |
| if ($node_text_checked =~ /:/m) { |
| if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn(sprintf($self->__( |
| "\@%s node name should not contain `:'"), $command), |
| $root->{'line_nr'}); |
| } |
| if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { |
| $quoting_required = 1; |
| } |
| } |
| my $pre_quote = $quoting_required ? "\x{7f}" : ''; |
| my $post_quote = $pre_quote; |
| $node_text .= $self->_convert({'contents' => [ |
| {'text' => "${post_quote}::"}]}); |
| _count_added($self,$self->{'formatters'}[-1]{'container'}, |
| $pre_quote) |
| if $pre_quote; |
| if ($pre_quote) { |
| # This is needed to get a pending word. We could use |
| # add_pending_word, but that would not include following |
| # punctuation in the word. |
| my $next = $self->{'current_contents'}->[-1]->[0]; |
| if ($next) { |
| $node_text .= $self->_convert($next); |
| shift @{$self->{'current_contents'}->[-1]}; |
| } |
| |
| $node_text =~ s/^(\s*)/$1$pre_quote/; |
| } |
| $result .= $node_text; |
| } |
| # we could use $formatter, but in case it was changed in _convert |
| # we play it safe. |
| my $pending = $result |
| .$self->{'formatters'}->[-1]->{'container'}->get_pending(); |
| |
| # If command is @xref, the punctuation must always follow the |
| # command, for other commands it may be in the argument, hence the |
| # use of $pending. |
| if ($name and ($command eq 'xref' |
| or ($pending !~ /[\.,]$/ and $pending !~ /::$/))) { |
| my $next = $self->{'current_contents'}->[-1]->[0]; |
| if (!($next and $next->{'text'} and $next->{'text'} =~ /^[\.,]/)) { |
| if ($command eq 'xref') { |
| if ($next and defined($next->{'text'}) and $next->{'text'} =~ /\S/) { |
| my $text = $next->{'text'}; |
| $text =~ s/^\s*//; |
| my $char = substr($text, 0, 1); |
| $self->line_warn(sprintf($self->__( |
| "`.' or `,' must follow \@xref, not %s"), |
| $char), $root->{'line_nr'}); |
| } else { |
| $self->line_warn($self->__("`.' or `,' must follow \@xref"), |
| $root->{'line_nr'}); |
| } |
| } |
| my @added = ({'text' => '.'}); |
| # the added dot do not end a sentence for pxref or ref. |
| push @added, {'cmdname' => ':'} if ($command ne 'xref'); |
| unshift @{$self->{'current_contents'}->[-1]}, @added; |
| } |
| } |
| |
| if ($in_multitable) { |
| $formatter->{'w'}--; |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->set_space_protection(0,undef)) |
| if ($formatter->{'w'} == 0); |
| } |
| $formatter->{'container'} |
| ->set_space_protection(undef,undef,undef,undef,0); # double_width_no_break |
| return $result; |
| } |
| return ''; |
| } elsif ($explained_commands{$command}) { |
| if ($root->{'extra'} and $root->{'extra'}->{'brace_command_contents'} |
| and defined($root->{'extra'}->{'brace_command_contents'}->[0])) { |
| # in abbr spaces never end a sentence. |
| my $argument; |
| if ($command eq 'abbr') { |
| $argument = {'type' => 'frenchspacing', |
| 'contents' => $root->{'extra'}->{'brace_command_contents'}->[0]}; |
| } else { |
| $argument = {'contents' => $root->{'extra'}->{'brace_command_contents'}->[0]}; |
| } |
| if (scalar (@{$root->{'extra'}->{'brace_command_contents'}}) == 2 |
| and defined($root->{'extra'}->{'brace_command_contents'}->[-1])) { |
| my $prepended = $self->gdt('{abbr_or_acronym} ({explanation})', |
| {'abbr_or_acronym' => $argument, |
| 'explanation' => |
| $root->{'extra'}->{'brace_command_contents'}->[-1]}); |
| #print STDERR "".Data::Dumper->Dump([$prepended])."\n"; |
| unshift @{$self->{'current_contents'}->[-1]}, $prepended; |
| return ''; |
| } else { |
| $result = $self->_convert($argument); |
| |
| # We want to permit an end of sentence, but not force it as @. does. |
| $formatter->{'container'}->allow_end_sentence(); |
| return $result; |
| } |
| } |
| return ''; |
| } elsif ($inline_commands{$command}) { |
| my $arg_index = 1; |
| if ($command eq 'inlinefmtifelse' |
| and (!$root->{'extra'}->{'format'} |
| or !$self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}})) { |
| $arg_index = 2; |
| } |
| if (scalar(@{$root->{'extra'}->{'brace_command_contents'}}) > $arg_index |
| and defined($root->{'extra'}->{'brace_command_contents'}->[$arg_index])) { |
| my $argument; |
| if ($command eq 'inlineraw') { |
| $argument->{'type'} = '_code'; |
| } |
| $argument->{'contents'} |
| = $root->{'extra'}->{'brace_command_contents'}->[$arg_index]; |
| unshift @{$self->{'current_contents'}->[-1]}, ($argument); |
| } |
| return ''; |
| } elsif ($command eq 'math') { |
| push @{$self->{'context'}}, 'math'; |
| if ($root->{'args'}) { |
| $result .= $self->_convert({'type' => 'frenchspacing', |
| 'contents' => [{'type' => '_code', |
| 'contents' => [$root->{'args'}->[0]]}]}); |
| } |
| my $old_context = pop @{$self->{'context'}}; |
| die if ($old_context ne 'math'); |
| return $result; |
| } elsif ($command eq 'titlefont') { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| $result = $self->convert_line ({'type' => 'frenchspacing', |
| 'contents' => [$root->{'args'}->[0]]}); |
| pop @{$self->{'count_context'}}; |
| $result = Texinfo::Convert::Text::heading({'level' => 0, |
| 'cmdname' => 'titlefont'}, $result, $self, |
| $self->get_conf('NUMBER_SECTIONS'), |
| ($self->{'format_context'}->[-1]->{'indent_level'}) * $indent_length); |
| $self->{'empty_lines_count'} = 0 unless ($result eq ''); |
| _add_text_count($self, $result); |
| _add_lines_count($self, 2); |
| return $result; |
| |
| } elsif ($command eq 'U') { |
| my $arg = $root->{'extra'}->{'brace_command_contents'} |
| ->[0]->[0]->{'text'}; |
| if (defined($arg) && $arg) { |
| # The general idea is to output UTF-8 if that has been |
| # explicitly given as the encoding, else simple ASCII. |
| # |
| # 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 chr(hex($arg)) is valid. Perl gives a warning |
| # and will not output UTF-8 for Unicode non-characters such as |
| # U+10FFFF. In this case, silently fall back to plain text, on |
| # the theory that the user wants something. |
| # |
| # Having an option to output binary bytes nevertheless is |
| # possible, but seems unlikely to be practically useful, so skip |
| # it until it gets requested. |
| my $res; |
| if ($self->{'to_utf8'}) { |
| # The warning about non-characters is only given when the code |
| # point is attempted to be output, not just manipulated. |
| # http://stackoverflow.com/questions/5127725/how-could-i-catch-an-unicode-non-character-warning |
| # |
| # Therefore, we have to try to output it within an eval. |
| # Since opening /dev/null or a temporary file means |
| # more system-dependent checks, use a string as our |
| # filehandle; this was introduced ca.2000, which should be old |
| # enough. We hope. |
| eval { |
| use warnings FATAL => qw(all); |
| my ($fh, $string); |
| open($fh, ">", \$string) || die "open(U string eval) failed: $!"; |
| binmode($fh, ":utf8") || die "binmode(U string eval) failed: $!"; |
| print $fh chr(hex("$arg")); |
| }; |
| if ($@) { |
| warn "\@U chr(hex($arg)) eval failed: $@\n" if ($self->{'DEBUG'}); |
| $res = "U+$arg"; # chr won't work |
| } else { |
| $res = chr(hex($arg)); # ok to call chr |
| } |
| } else { |
| $res = "U+$arg"; # not outputting UTF-8 |
| } |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text($res)); |
| } else { |
| $result = ''; # arg was not defined |
| } |
| return $result; |
| |
| } elsif ($command eq 'value') { |
| my $expansion = $self->gdt('@{No value for `{value}\'@}', |
| {'value' => $root->{'type'}}); |
| if ($formatter->{'_top_formatter'}) { |
| $expansion = {'type' => 'paragraph', |
| 'contents' => [$expansion]}; |
| } |
| $result .= $self->_convert($expansion); |
| # unshift @{$self->{'current_contents'}->[-1]}, $expansion; |
| #return ''; |
| return $result; |
| } elsif ($root->{'args'} and $root->{'args'}->[0] |
| and $root->{'args'}->[0]->{'type'} |
| and $root->{'args'}->[0]->{'type'} eq 'brace_command_arg') { |
| print STDERR "Unknown command with braces `$root->{'cmdname'}'\n" |
| if ($self->get_conf('VERBOSE') or $self->{'debug'}); |
| # block commands |
| } elsif (exists($block_commands{$root->{'cmdname'}})) { |
| # remark: |
| # cartouche group and raggedright -> nothing on format stack |
| |
| if ($menu_commands{$root->{'cmdname'}} and !$self->get_conf('SHOW_MENU')) { |
| return ''; |
| } |
| if ($self->{'preformatted_context_commands'}->{$root->{'cmdname'}} |
| or $root->{'cmdname'} eq 'float') { |
| if ($self->{'formatters'}->[-1]->{'type'} eq 'paragraph' |
| and $format_raw_commands{$root->{'cmdname'}}) { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_pending_word(1)); |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->end_line()); |
| } |
| push @{$self->{'context'}}, $root->{'cmdname'}; |
| } elsif ($flush_commands{$root->{'cmdname'}}) { |
| push @{$self->{'context'}}, $root->{'cmdname'}; |
| } elsif ($raw_commands{$root->{'cmdname'}}) { |
| if (!$self->{'formatters'}->[-1]->{'_top_formatter'}) { |
| # reuse the current formatter if not in top level |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_pending_word(1)); |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->end_line()); |
| } else { |
| # if in top level, the raw block command is turned into a |
| # simple preformatted command (alike @verbatim), to have a |
| # formatter container being created. |
| push @{$self->{'context'}}, $root->{'cmdname'}; |
| $self->{'format_context_commands'}->{$root->{'cmdname'}} = 1; |
| $self->{'preformatted_context_commands'}->{$root->{'cmdname'}} = 1; |
| } |
| } |
| |
| if ($self->{'format_context_commands'}->{$root->{'cmdname'}}) { |
| push @{$self->{'format_context'}}, |
| { 'cmdname' => $root->{'cmdname'}, |
| 'paragraph_count' => 0, |
| 'indent_level' => |
| $self->{'format_context'}->[-1]->{'indent_level'}, |
| }; |
| $self->{'format_context'}->[-1]->{'indent_level'}++ |
| if ($indented_commands{$root->{'cmdname'}}); |
| # open a preformatted container, if the command opening the |
| # preformatted context is not a classical preformatted |
| # command (ie if it is menu or verbatim, and not example or |
| # similar) |
| if ($self->{'preformatted_context_commands'}->{$root->{'cmdname'}} |
| and ! $preformatted_commands{$root->{'cmdname'}} |
| and ! $format_raw_commands{$root->{'cmdname'}}) { |
| $preformatted = $self->new_formatter('unfilled'); |
| push @{$self->{'formatters'}}, $preformatted; |
| } |
| } |
| if ($root->{'cmdname'} eq 'quotation' |
| or $root->{'cmdname'} eq 'smallquotation') { |
| if ($root->{'extra'} and $root->{'extra'}->{'block_command_line_contents'}) { |
| my $prepended = $self->gdt('@b{{quotation_arg}:} ', |
| {'quotation_arg' => $root->{'extra'}->{'block_command_line_contents'}->[0]}); |
| #print STDERR Data::Dumper->Dump([$prepended]); |
| $prepended->{'type'} = 'frenchspacing'; |
| $result .= $self->convert_line($prepended); |
| $self->{'text_element_context'}->[-1]->{'counter'} += |
| Texinfo::Convert::Unicode::string_width($result); |
| $self->{'empty_lines_count'} = 0 unless ($result eq ''); |
| } |
| } elsif ($menu_commands{$root->{'cmdname'}}) { |
| $result .= $self->_menu($root); |
| } elsif ($root->{'cmdname'} eq 'multitable') { |
| my $columnsize; |
| if ($root->{'extra'}->{'columnfractions'}) { |
| foreach my $fraction (@{$root->{'extra'}->{'columnfractions'}}) { |
| push @$columnsize, int($fraction * $self->{'text_element_context'}->[-1]->{'max'} +0.5); |
| } |
| } elsif ($root->{'extra'}->{'prototypes'}) { |
| foreach my $prototype (@{$root->{'extra'}->{'prototypes'}}) { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| my ($formatted_prototype) = $self->convert_line($prototype, |
| {'indent_length' => 0}); |
| pop @{$self->{'count_context'}}; |
| print STDERR " MULTITABLE_PROTO {$formatted_prototype}\n" |
| if ($self->{'debug'}); |
| push @$columnsize, |
| 2+Texinfo::Convert::Unicode::string_width($formatted_prototype); |
| } |
| } |
| print STDERR "MULTITABLE_SIZES @$columnsize\n" if ($columnsize |
| and $self->{'debug'}); |
| $self->{'format_context'}->[-1]->{'columns_size'} = $columnsize; |
| $self->{'format_context'}->[-1]->{'row_empty_lines_count'} |
| = $self->{'empty_lines_count'}; |
| $self->{'document_context'}->[-1]->{'in_multitable'}++; |
| } elsif ($root->{'cmdname'} eq 'float') { |
| $result .= _add_newline_if_needed($self); |
| if ($root->{'extra'} and $root->{'extra'}->{'normalized'}) { |
| $result .= $self->_anchor($root); |
| } |
| } |
| } elsif ($root->{'cmdname'} eq 'node') { |
| $self->{'node'} = $root; |
| $result .= $self->_node($root); |
| $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; |
| } elsif ($sectioning_commands{$root->{'cmdname'}}) { |
| if ($self->get_conf('setcontentsaftertitlepage') |
| and $root_commands{$root->{'cmdname'}} |
| and !$self->{'setcontentsaftertitlepage_done'}) { |
| my ($contents, $lines_count) |
| = $self->_contents($self->{'structuring'}->{'sectioning_root'}, |
| 'contents'); |
| if ($contents ne '') { |
| $contents .= "\n"; |
| $self->{'empty_lines_count'} = 1; |
| _add_text_count($self, $contents); |
| _add_lines_count($self, $lines_count+1); |
| } |
| $self->{'setcontentsaftertitlepage_done'} = 1; |
| $result .= $contents; |
| } |
| if ($self->get_conf('setshortcontentsaftertitlepage') |
| and $root_commands{$root->{'cmdname'}} |
| and !$self->{'setshortcontentsaftertitlepage_done'}) { |
| my ($contents, $lines_count) |
| = $self->_contents($self->{'structuring'}->{'sectioning_root'}, |
| 'shortcontents'); |
| if ($contents ne '') { |
| $contents .= "\n"; |
| $self->{'empty_lines_count'} = 1; |
| _add_text_count($self, $contents); |
| _add_lines_count($self, $lines_count+1); |
| } |
| |
| $self->{'setshortcontentsaftertitlepage_done'} = 1; |
| $result .= $contents; |
| } |
| # use settitle for empty @top |
| # ignore @part |
| my $contents; |
| if ($root->{'extra'}->{'misc_content'} |
| and @{$root->{'extra'}->{'misc_content'}} |
| and $root->{'cmdname'} ne 'part') { |
| $contents = $root->{'extra'}->{'misc_content'}; |
| } elsif ($root->{'cmdname'} eq 'top' |
| and $self->{'extra'}->{'settitle'} |
| and $self->{'extra'}->{'settitle'}->{'extra'} |
| and $self->{'extra'}->{'settitle'}->{'extra'}->{'misc_content'} |
| and @{$self->{'extra'}->{'settitle'}->{'extra'}->{'misc_content'}}) { |
| $contents = $self->{'extra'}->{'settitle'}->{'extra'}->{'misc_content'}; |
| } |
| |
| if ($contents) { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| my $heading = $self->convert_line({'type' => 'frenchspacing', |
| 'contents' => $contents}); |
| pop @{$self->{'count_context'}}; |
| # @* leads to an end of line, underlying appears on the line below |
| # over one line |
| my $heading_underlined = |
| Texinfo::Convert::Text::heading($root, $heading, $self, |
| $self->get_conf('NUMBER_SECTIONS'), |
| ($self->{'format_context'}->[-1]->{'indent_level'}) |
| * $indent_length); |
| $result .= _add_newline_if_needed($self); |
| $self->{'empty_lines_count'} = 0 unless ($heading_underlined eq ''); |
| _add_text_count($self, $heading_underlined); |
| $result .= $heading_underlined; |
| if ($heading_underlined ne '') { |
| _add_lines_count($self, 2); |
| $result .= _add_newline_if_needed($self); |
| } |
| } |
| $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; |
| } elsif (($root->{'cmdname'} eq 'item' or $root->{'cmdname'} eq 'itemx') |
| and $root->{'args'} and $root->{'args'}->[0] |
| and $root->{'args'}->[0]->{'type'} |
| and $root->{'args'}->[0]->{'type'} eq 'misc_line_arg') { |
| if ($root->{'extra'} and $root->{'extra'}->{'misc_content'}) { |
| |
| my $converted_tree = $self->_table_item_content_tree($root, |
| $root->{'extra'}->{'misc_content'}); |
| |
| $converted_tree->{'type'} = 'frenchspacing'; |
| $result = $self->convert_line($converted_tree, |
| {'indent_level' |
| => $self->{'format_context'}->[-1]->{'indent_level'} -1}); |
| if ($result ne '') { |
| $result = $self->ensure_end_of_line($result); |
| $self->{'empty_lines_count'} = 0; |
| } |
| } |
| } elsif ($root->{'cmdname'} eq 'item' and $root->{'parent'}->{'cmdname'} |
| and $item_container_commands{$root->{'parent'}->{'cmdname'}}) { |
| $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; |
| my $line = $self->new_formatter('line', |
| {'indent_length' => |
| ($self->{'format_context'}->[-1]->{'indent_level'} -1) |
| * $indent_length |
| + $item_indent_format_length{$root->{'parent'}->{'cmdname'}}}); |
| push @{$self->{'formatters'}}, $line; |
| if ($root->{'parent'}->{'cmdname'} eq 'enumerate') { |
| $result = _count_added($self, $line->{'container'}, |
| $line->{'container'}->add_next( |
| Texinfo::Common::enumerate_item_representation( |
| $root->{'parent'}->{'extra'}->{'enumerate_specification'}, |
| $root->{'extra'}->{'item_number'}) . '. ')); |
| } elsif ($root->{'parent'}->{'extra'}->{'block_command_line_contents'}) { |
| # this is the text prepended to items. |
| |
| $result = $self->_convert( |
| {'contents' => |
| [@{$root->{'parent'}->{'extra'}->{'block_command_line_contents'}->[0]}, |
| { 'text' => ' ' }] |
| }); |
| } |
| $result .= _count_added($self, $line->{'container'}, |
| $line->{'container'}->end()); |
| print STDERR " $root->{'parent'}->{'cmdname'}($root->{'extra'}->{'item_number'}) -> |$result|\n" |
| if ($self->{'debug'}); |
| pop @{$self->{'formatters'}}; |
| $self->{'text_element_context'}->[-1]->{'counter'} += |
| Texinfo::Convert::Unicode::string_width($result); |
| $self->{'empty_lines_count'} = 0 unless ($result eq ''); |
| # open a multitable cell |
| } elsif ($root->{'cmdname'} eq 'headitem' or $root->{'cmdname'} eq 'item' |
| or $root->{'cmdname'} eq 'tab') { |
| my $cell_width = $self->{'format_context'}->[-1]->{'columns_size'}->[$root->{'extra'}->{'cell_number'}-1]; |
| $self->{'format_context'}->[-1]->{'item_command'} = $root->{'cmdname'} |
| if ($root->{'cmdname'} ne 'tab'); |
| print STDERR "CELL [$root->{'extra'}->{'cell_number'}]: \@$root->{'cmdname'}. Width: $cell_width\n" |
| if ($self->{'debug'}); |
| die if (!defined($cell_width)); |
| $self->{'empty_lines_count'} |
| = $self->{'format_context'}->[-1]->{'row_empty_lines_count'}; |
| |
| push @{$self->{'format_context'}}, |
| { 'cmdname' => $root->{'cmdname'}, |
| 'paragraph_count' => 0, |
| 'indent_level' => 0 }; |
| push @{$self->{'text_element_context'}}, {'max' => $cell_width - 2 }; |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, |
| 'locations' => []}; |
| $cell = 1; |
| } elsif ($root->{'cmdname'} eq 'center') { |
| #my ($counts, $new_locations); |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, |
| 'locations' => []}; |
| $result = $self->convert_line ( |
| {'type' => 'frenchspacing', |
| 'contents' => $root->{'extra'}->{'misc_content'}}, |
| {'indent_length' => 0}); |
| if ($result ne '') { |
| $result = $self->ensure_end_of_line($result); |
| |
| $result = $self->_align_environment ($result, |
| $self->{'text_element_context'}->[-1]->{'max'}, 'center'); |
| $self->{'empty_lines_count'} = 0; |
| } else { |
| # it has to be done here, as it is done in _align_environment above |
| pop @{$self->{'count_context'}}; |
| } |
| $self->{'format_context'}->[-1]->{'paragraph_count'}++; |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'exdent') { |
| if ($self->{'preformatted_context_commands'}->{$self->{'context'}->[-1]}) { |
| $result = $self->convert_unfilled({'contents' => $root->{'extra'}->{'misc_content'}}, |
| {'indent_level' |
| => $self->{'format_context'}->[-1]->{'indent_level'} -1}); |
| } else { |
| $result = $self->convert_line({'contents' => $root->{'extra'}->{'misc_content'}}, |
| {'indent_level' |
| => $self->{'format_context'}->[-1]->{'indent_level'} -1}); |
| } |
| if ($result ne '') { |
| $result = $self->ensure_end_of_line($result); |
| $self->{'empty_lines_count'} = 0; |
| } |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'verbatiminclude') { |
| my $expansion = $self->Texinfo::Common::expand_verbatiminclude($root); |
| unshift @{$self->{'current_contents'}->[-1]}, $expansion |
| if ($expansion); |
| return ''; |
| } elsif ($root->{'cmdname'} eq 'insertcopying') { |
| if ($self->{'extra'} and $self->{'extra'}->{'copying'}) { |
| unshift @{$self->{'current_contents'}->[-1]}, |
| {'contents' => $self->{'extra'}->{'copying'}->{'contents'}}; |
| } |
| return ''; |
| } elsif ($root->{'cmdname'} eq 'printindex') { |
| $result = $self->_printindex($root); |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'listoffloats') { |
| my $lines_count = 0; |
| if ($root->{'extra'} and $root->{'extra'}->{'type'} |
| and defined($root->{'extra'}->{'type'}->{'normalized'}) |
| and $self->{'floats'} |
| and $self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}} |
| and @{$self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}}}) { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; |
| if (!$self->{'empty_lines_count'}) { |
| $result .= "\n"; |
| $lines_count++; |
| } |
| $result .= "* Menu:\n\n"; |
| $lines_count += 2; |
| foreach my $float (@{$self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}}}) { |
| next if (!defined($float->{'extra'}->{'block_command_line_contents'}->[1])); |
| my $float_label_text = $self->convert_line({'type' => '_code', |
| 'contents' |
| => $float->{'extra'}->{'block_command_line_contents'}->[1]}); |
| my $float_entry = $self->_float_type_number($float); |
| my $float_entry_text = ':'; |
| if (defined($float_entry)) { |
| $float_entry->{'type'} = 'frenchspacing'; |
| $float_entry_text = $self->convert_line($float_entry); |
| } |
| # no translation here, this is required Info format. |
| my $float_line = "* $float_entry_text: $float_label_text."; |
| my $line_width |
| = Texinfo::Convert::Unicode::string_width($float_line); |
| if ($line_width > $listoffloat_entry_length) { |
| $float_line .= "\n" . ' ' x $listoffloat_entry_length; |
| $lines_count++; |
| } else { |
| $float_line .= ' ' x ($listoffloat_entry_length - $line_width); |
| } |
| $line_width = $listoffloat_entry_length; |
| my $caption; |
| if ($float->{'extra'}->{'shortcaption'}) { |
| $caption = $float->{'extra'}->{'shortcaption'}; |
| } elsif ($float->{'extra'}->{'caption'}) { |
| $caption = $float->{'extra'}->{'caption'}; |
| } |
| if ($caption) { |
| $self->{'multiple_pass'} = 1; |
| push @{$self->{'context'}}, 'listoffloats'; |
| my $tree = {'contents' => $caption->{'args'}->[0]->{'contents'}}; |
| # the following does nothing since there are paragraphs within |
| # the shortcaption. |
| #if ($caption->{'cmdname'} eq 'shortcaption') { |
| # $tree->{'type'} = 'frenchspacing'; |
| #} |
| my $caption_text = $self->_convert($tree); |
| my $old_context = pop @{$self->{'context'}}; |
| delete $self->{'multiple_pass'}; |
| die if ($old_context ne 'listoffloats'); |
| while ($caption_text =~ s/^\s*(\p{Unicode::EastAsianWidth::InFullwidth}\s*|\S+\s*)//) { |
| my $new_word = $1; |
| $new_word =~ s/\n//g; |
| if ((Texinfo::Convert::Unicode::string_width($new_word) + |
| $line_width) > |
| ($self->{'text_element_context'}->[-1]->{'max'} - 3)) { |
| $float_line .= $listoffloat_append; |
| last; |
| } else { |
| $float_line .= $new_word; |
| $line_width += |
| Texinfo::Convert::Unicode::string_width($new_word); |
| } |
| } |
| } |
| $result .= $float_line. "\n"; |
| $lines_count++; |
| } |
| $result .= "\n"; |
| $lines_count++; |
| $self->{'empty_lines_count'} = 1; |
| pop @{$self->{'count_context'}}; |
| } |
| $self->{'format_context'}->[-1]->{'paragraph_count'}++; |
| _add_text_count($self, $result); |
| _add_lines_count($self, $lines_count); |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'sp') { |
| if ($root->{'extra'}->{'misc_args'}->[0]) { |
| # this useless copy avoids perl changing the type to integer! |
| my $sp_nr = $root->{'extra'}->{'misc_args'}->[0]; |
| for (my $i = 0; $i < $sp_nr; $i++) { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->end_line()); |
| } |
| |
| $self->{'empty_lines_count'} += $sp_nr; |
| delete $self->{'text_element_context'}->[-1]->{'counter'}; |
| } |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'contents') { |
| if (!defined($self->get_conf('setcontentsaftertitlepage')) |
| and $self->{'structuring'} |
| and $self->{'structuring'}->{'sectioning_root'}) { |
| my $lines_count; |
| ($result, $lines_count) |
| = $self->_contents($self->{'structuring'}->{'sectioning_root'}, |
| 'contents'); |
| _add_lines_count($self, $lines_count); |
| _add_text_count($self, $result); |
| } |
| return $result; |
| } elsif ($root->{'cmdname'} eq 'shortcontents' |
| or $root->{'cmdname'} eq 'summarycontents') { |
| if (!defined($self->get_conf('setshortcontentsaftertitlepage')) |
| and $self->{'structuring'} |
| and $self->{'structuring'}->{'sectioning_root'}) { |
| my $lines_count; |
| ($result, $lines_count) |
| = $self->_contents($self->{'structuring'}->{'sectioning_root'}, |
| 'shortcontents'); |
| _add_lines_count($self, $lines_count); |
| _add_text_count($self, $result); |
| } |
| return $result; |
| # all the @-commands that have an information for the formatting, like |
| # @paragraphindent, @frenchspacing... |
| } elsif ($informative_commands{$root->{'cmdname'}}) { |
| $self->_informative_command($root); |
| return ''; |
| } else { |
| $unknown_command = 1; |
| } |
| if ($unknown_command and !($root->{'extra'} |
| and $root->{'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{$root->{'cmdname'}} |
| and $root->{'cmdname'} =~ /x$/)) { |
| warn "Unhandled $root->{'cmdname'}\n"; |
| $result .= "!!!!!!!!! Unhandled $root->{'cmdname'} !!!!!!!!!\n"; |
| } |
| } |
| |
| # open 'type' constructs. |
| my $paragraph; |
| if ($root->{'type'}) { |
| if ($root->{'type'} eq 'paragraph') { |
| $self->{'empty_lines_count'} = 0; |
| my $conf; |
| # indent. Not first paragraph. |
| if ($self->{'debug'}) { |
| print STDERR "OPEN PARA ($self->{'format_context'}->[-1]->{'cmdname'}) " |
| . "cnt ". |
| (defined($self->{'text_element_context'}->[-1]->{'counter'}) ? |
| $self->{'text_element_context'}->[-1]->{'counter'} : 'UNDEF'). ' ' |
| . "para cnt $self->{'format_context'}->[-1]->{'paragraph_count'} " |
| . "fparaindent ".$self->get_conf('firstparagraphindent')." " |
| . "paraindent ".$self->get_conf('paragraphindent')."\n"; |
| } |
| if ($self->{'format_context'}->[-1]->{'cmdname'} eq '_top_format' |
| and $self->get_conf('paragraphindent') ne 'asis' |
| and $self->get_conf('paragraphindent') |
| and (($root->{'extra'} and $root->{'extra'}->{'indent'}) |
| or (!($root->{'extra'} and $root->{'extra'}->{'noindent'}) |
| and ($self->{'format_context'}->[-1]->{'paragraph_count'} |
| or $self->get_conf('firstparagraphindent') eq 'insert') |
| and !$self->{'text_element_context'}->[-1]->{'counter'}))) { |
| $conf->{'first_indent_length'} = $self->get_conf('paragraphindent'); |
| $conf->{'first_indent_length'} = 0 |
| if ($conf->{'first_indent_length'} eq 'none'); |
| } |
| $paragraph = $self->new_formatter('paragraph', $conf); |
| push @{$self->{'formatters'}}, $paragraph; |
| $self->{'format_context'}->[-1]->{'paragraph_count'}++; |
| if ($self->{'context'}->[-1] eq 'flushright') { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, |
| 'locations' => []}; |
| } |
| } elsif ($root->{'type'} eq 'preformatted' |
| or $root->{'type'} eq 'rawpreformatted') { |
| # if in a description reuse the main menu unfilled, to keep things |
| # simpler and avoid having to do a separate count. |
| if ($root->{'type'} eq 'rawpreformatted' |
| or !$root->{'parent'}->{'type'} |
| or $root->{'parent'}->{'type'} ne 'menu_entry_description') { |
| $preformatted = $self->new_formatter('unfilled'); |
| push @{$self->{'formatters'}}, $preformatted; |
| if ($self->{'context'}->[-1] eq 'flushright') { |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, |
| 'locations' => []}; |
| } |
| } |
| } elsif ($root->{'type'} eq 'def_line') { |
| if ($root->{'extra'} and $root->{'extra'}->{'def_args'} |
| and @{$root->{'extra'}->{'def_args'}}) { |
| my $arguments = Texinfo::Common::definition_arguments_content($root); |
| my $tree; |
| my $command; |
| if ($Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}) { |
| $command = $Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}; |
| } else { |
| $command = $root->{'extra'}->{'def_command'}; |
| } |
| my $name; |
| if ($root->{'extra'}->{'def_parsed_hash'}->{'name'}) { |
| $name = $root->{'extra'}->{'def_parsed_hash'}->{'name'}; |
| } else { |
| $name = ''; |
| } |
| |
| |
| if ($command eq 'deffn' |
| or $command eq 'defvr' |
| or $command eq 'deftp' |
| or (($command eq 'deftypefn' |
| or $command eq 'deftypevr') |
| and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { |
| if ($arguments) { |
| $tree = $self->gdt("\@tie{ }-- {category}: {name} {arguments}", { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name, |
| 'arguments' => $arguments}); |
| } else { |
| $tree = $self->gdt("\@tie{ }-- {category}: {name}", { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name}); |
| } |
| } elsif ($command eq 'deftypefn' |
| or $command eq 'deftypevr') { |
| if ($arguments) { |
| my $strings = { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name, |
| 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, |
| 'arguments' => $arguments}; |
| if ($self->get_conf('deftypefnnewline') eq 'on') { |
| $tree = $self->gdt("\@tie{ }-- {category}:\@*{type}\@*{name} {arguments}", |
| $strings); |
| } else { |
| $tree = $self->gdt("\@tie{ }-- {category}: {type} {name} {arguments}", |
| $strings); |
| } |
| } else { |
| my $strings = { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, |
| 'name' => $name}; |
| if ($self->get_conf('deftypefnnewline') eq 'on') { |
| $tree = $self->gdt("\@tie{ }-- {category}:\@*{type}\@*{name}", |
| $strings); |
| } else { |
| $tree = $self->gdt("\@tie{ }-- {category}: {type} {name}", |
| $strings); |
| } |
| } |
| } elsif ($command eq 'defcv' |
| or ($command eq 'deftypecv' |
| and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { |
| if ($arguments) { |
| $tree = $self->gdt("\@tie{ }-- {category} of {class}: {name} {arguments}", { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'arguments' => $arguments}); |
| } else { |
| $tree = $self->gdt("\@tie{ }-- {category} of {class}: {name}", { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'name' => $name}); |
| } |
| } elsif ($command eq 'defop' |
| or ($command eq 'deftypeop' |
| and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { |
| if ($arguments) { |
| $tree = $self->gdt("\@tie{ }-- {category} on {class}: {name} {arguments}", { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'arguments' => $arguments}); |
| } else { |
| $tree = $self->gdt("\@tie{ }-- {category} on {class}: {name}", { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'name' => $name}); |
| } |
| } elsif ($command eq 'deftypeop') { |
| if ($arguments) { |
| my $strings = { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, |
| 'arguments' => $arguments}; |
| if ($self->get_conf('deftypefnnewline') eq 'on') { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} on {class}:\@*{type}\@*{name} {arguments}", |
| $strings); |
| } else { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} on {class}: {type} {name} {arguments}", |
| $strings); |
| } |
| } else { |
| my $strings = { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'name' => $name}; |
| if ($self->get_conf('deftypefnnewline') eq 'on') { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} on {class}:\@*{type}\@*{name}", |
| $strings); |
| } else { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} on {class}: {type} {name}", |
| $strings); |
| } |
| } |
| } elsif ($command eq 'deftypecv') { |
| if ($arguments) { |
| my $strings = { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'name' => $name, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, |
| 'arguments' => $arguments}; |
| if ($self->get_conf('deftypefnnewline') eq 'on') { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} of {class}:\@*{type}\@*{name} {arguments}", |
| $strings); |
| } else { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} of {class}: {type} {name} {arguments}", |
| $strings); |
| } |
| } else { |
| my $strings = { |
| 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, |
| 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, |
| 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, |
| 'name' => $name}; |
| if ($self->get_conf('deftypefnnewline') eq 'on') { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} of {class}:\@*{type}\@*{name}", |
| $strings); |
| } else { |
| $tree |
| = $self->gdt("\@tie{ }-- {category} of {class}: {type} {name}", |
| $strings); |
| } |
| } |
| } |
| |
| my $def_paragraph = $self->new_formatter('paragraph', |
| { 'indent_length' => ($self->{'format_context'}->[-1]->{'indent_level'} -1) *$indent_length, |
| 'indent_length_next' => (1+$self->{'format_context'}->[-1]->{'indent_level'})*$indent_length}); |
| push @{$self->{'formatters'}}, $def_paragraph; |
| |
| $result .= $self->_convert({'type' => '_code', 'contents' => [$tree]}); |
| $result .= _count_added($self, $def_paragraph->{'container'}, |
| $def_paragraph->{'container'}->end()); |
| |
| pop @{$self->{'formatters'}}; |
| delete $self->{'text_element_context'}->[-1]->{'counter'}; |
| $self->{'empty_lines_count'} = 0; |
| print STDERR " --> $result" if ($self->{'debug'}); |
| } |
| } elsif ($root->{'type'} eq 'menu_entry') { |
| #my $menu_entry_internal_node; |
| #if ($root->{'extra'} and $root->{'extra'}->{'menu_entry_node'} |
| # and defined($root->{'extra'}->{'menu_entry_node'}->{'normalized'}) |
| # and !$root->{'extra'}->{'menu_entry_node'}->{'manual_content'} |
| # and $self->{'labels'} |
| # and $self->{'labels'}->{$root->{'extra'}->{'menu_entry_node'}->{'normalized'}}) { |
| # $menu_entry_internal_node |
| # = $self->{'labels'}->{$root->{'extra'}->{'menu_entry_node'}->{'normalized'}}; |
| #} |
| my $entry_name_seen = 0; |
| foreach my $arg (@{$root->{'args'}}) { |
| my ($pre_quote, $post_quote); |
| if ($arg->{'type'} eq 'menu_entry_node') { |
| $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; |
| my $node_text = $self->_convert({'type' => '_code', |
| 'contents' => $arg->{'contents'}}); |
| |
| delete $self->{'formatters'}->[-1]->{'suppress_styles'}; |
| $pre_quote = $post_quote = ''; |
| if ($entry_name_seen) { |
| if ($node_text =~ /([,\t]|\.\s)/) { |
| if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn(sprintf($self->__( |
| "menu entry node name should not contain `%s'"), $1), |
| $root->{'line_nr'}); |
| } |
| } |
| if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { |
| $pre_quote = $post_quote = "\x{7f}"; |
| } |
| } else { |
| if ($node_text =~ /:/) { |
| if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn($self->__( |
| "menu entry node name should not contain `:'"), |
| $root->{'line_nr'}); |
| } |
| if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { |
| $pre_quote = $post_quote = "\x{7f}"; |
| } |
| } |
| } |
| $result .= $pre_quote . $node_text . $post_quote; |
| } elsif ($arg->{'type'} eq 'menu_entry_name') { |
| my $entry_name = $self->_convert($arg); |
| $entry_name_seen = 1; |
| $pre_quote = $post_quote = ''; |
| if ($entry_name =~ /:/) { |
| if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { |
| $self->line_warn($self->__( |
| "menu entry name should not contain `:'"), |
| $root->{'line_nr'}); |
| } |
| if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { |
| $pre_quote = $post_quote = "\x{7f}"; |
| } |
| } |
| $result .= $pre_quote . $entry_name . $post_quote; |
| } else { |
| $result .= $self->_convert($arg); |
| } |
| } |
| $result = $self->ensure_end_of_line($result) |
| unless ($root->{'parent'}->{'type'} |
| and $root->{'parent'}->{'type'} eq 'preformatted'); |
| } elsif ($root->{'type'} eq 'frenchspacing') { |
| push @{$formatter->{'frenchspacing_stack'}}, 'on'; |
| $formatter->{'container'}->set_space_protection(undef, |
| undef,undef,1); |
| } elsif ($root->{'type'} eq '_code') { |
| #$formatter->{'code'}++; |
| if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { |
| push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; |
| } else { |
| $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; |
| } |
| push @{$formatter->{'frenchspacing_stack'}}, 'on'; |
| $formatter->{'container'}->set_space_protection(undef, |
| undef,undef,1); |
| } elsif ($root->{'type'} eq 'bracketed') { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text('{')); |
| } |
| } |
| |
| # The processing of contents is done here. |
| if ($root->{'contents'}) { |
| my @contents = @{$root->{'contents'}}; |
| push @{$self->{'current_contents'}}, \@contents; |
| push @{$self->{'current_roots'}}, $root; |
| while (@contents) { |
| my $content = shift @contents; |
| my $text = $self->_convert($content); |
| $self->{'empty_lines_count'} = 0 |
| if ($preformatted and $text =~ /\S/); |
| $result .= $text; |
| } |
| pop @{$self->{'current_contents'}}; |
| pop @{$self->{'current_roots'}}; |
| } |
| |
| # now closing. First, close types. |
| if ($root->{'type'}) { |
| if ($root->{'type'} eq 'frenchspacing') { |
| pop @{$formatter->{'frenchspacing_stack'}}; |
| my $frenchspacing = 0; |
| $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); |
| $formatter->{'container'}->set_space_protection(undef, |
| undef, undef, $frenchspacing); |
| } elsif ($root->{'type'} eq '_code') { |
| #$formatter->{'code'}--; |
| $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; |
| pop @{$formatter->{'font_type_stack'}} |
| if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; |
| pop @{$formatter->{'frenchspacing_stack'}}; |
| my $frenchspacing = 0; |
| $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); |
| $formatter->{'container'}->set_space_protection(undef, |
| undef, undef, $frenchspacing); |
| } elsif ($root->{'type'} eq 'bracketed') { |
| $result .= _count_added($self, $formatter->{'container'}, |
| $formatter->{'container'}->add_text('}')); |
| } elsif ($root->{'type'} eq 'row') { |
| my @cell_beginnings; |
| my @cell_lines; |
| my $cell_beginning = 0; |
| my $cell_idx = 0; |
| my $max_lines = 0; |
| my $indent_len = $indent_length |
| * $self->{'format_context'}->[-1]->{'indent_level'}; |
| foreach my $cell (@{$self->{'format_context'}->[-1]->{'row'}}) { |
| $cell_beginnings[$cell_idx] = $cell_beginning; |
| my $cell_width = $self->{'format_context'}->[-1]->{'columns_size'}->[$cell_idx]; |
| $cell_beginning += $cell_width +1; |
| $cell_lines[$cell_idx] = [ split /^/, $cell ]; |
| $max_lines = scalar(@{$cell_lines[$cell_idx]}) |
| if (scalar(@{$cell_lines[$cell_idx]}) > $max_lines); |
| $cell_idx++; |
| } |
| |
| $cell_idx = 0; |
| my $cell_updated_locations; |
| my @row_locations; |
| foreach my $cell_locations (@{$self->{'format_context'}->[-1]->{'row_counts'}}) { |
| foreach my $location (@{$cell_locations->{'locations'}}) { |
| next unless (defined($location->{'bytes'}) and defined($location->{'lines'})); |
| push @{$cell_updated_locations->[$cell_idx]->{$location->{'lines'}}}, |
| $location; |
| print STDERR "MULTITABLE anchor $location->{'root'}->{'extra'}->{'normalized'}: c $cell_idx, l $location->{'lines'} ($location->{'bytes'})\n" |
| if ($self->{'debug'}); |
| $max_lines = $location->{'lines'}+1 |
| if ($location->{'lines'}+1 > $max_lines); |
| } |
| push @row_locations, @{$cell_locations->{'locations'}}; |
| $cell_idx++; |
| } |
| |
| print STDERR "ROW, max_lines $max_lines, indent_len $indent_len\n" |
| if ($self->{'debug'}); |
| |
| # this is used to keep track of the last cell with content. |
| my $max_cell = scalar(@{$self->{'format_context'}->[-1]->{'row'}}); |
| my $bytes_count = 0; |
| my $line; |
| for (my $line_idx = 0; $line_idx < $max_lines; $line_idx++) { |
| my $line_width = $indent_len; |
| $line = ''; |
| # determine the last cell in the line, to fill spaces in |
| # cells preceding that cell on the line |
| my $last_cell = 0; |
| for (my $cell_idx = 0; $cell_idx < $max_cell; $cell_idx++) { |
| $last_cell = $cell_idx+1 if (defined($cell_lines[$cell_idx]->[$line_idx]) |
| or defined($cell_updated_locations->[$cell_idx]->{$line_idx})); |
| } |
| print STDERR " L(last_cell $last_cell): $line_idx\n" |
| if ($self->{'debug'}); |
| |
| for (my $cell_idx = 0; $cell_idx < $last_cell; $cell_idx++) { |
| my $cell_text = $cell_lines[$cell_idx]->[$line_idx]; |
| if (defined($cell_text)) { |
| chomp($cell_text); |
| if ($line eq '' and $cell_text ne '') { |
| $line = ' ' x $indent_len; |
| $bytes_count += $self->count_bytes($line); |
| } |
| print STDERR " C($cell_idx) `$cell_text'\n" if ($self->{'debug'}); |
| $line .= $cell_text; |
| $bytes_count += $self->count_bytes($cell_text); |
| $line_width += Texinfo::Convert::Unicode::string_width($cell_text); |
| } |
| if (defined($cell_updated_locations->[$cell_idx]->{$line_idx})) { |
| foreach my $location (@{$cell_updated_locations->[$cell_idx]->{$line_idx}}) { |
| print STDERR "MULTITABLE UPDATE ANCHOR (l $line_idx, c $cell_idx): $location->{'root'}->{'extra'}->{'normalized'}: $location->{'bytes'} -> $bytes_count\n" |
| if ($self->{'debug'}); |
| $location->{'bytes'} = $bytes_count; |
| } |
| } |
| if ($cell_idx+1 < $last_cell) { |
| if ($line_width < $indent_len + $cell_beginnings[$cell_idx+1]) { |
| if ($line eq '') { |
| $line = ' ' x $indent_len; |
| $bytes_count += $self->count_bytes($line); |
| } |
| my $spaces = ' ' x ($indent_len + $cell_beginnings[$cell_idx+1] - $line_width); |
| $line_width += Texinfo::Convert::Unicode::string_width($spaces); |
| $line .= $spaces; |
| $bytes_count += $self->count_bytes($spaces); |
| } |
| } |
| } |
| $line .= "\n"; |
| $bytes_count += $self->count_bytes("\n"); |
| $result .= $line; |
| } |
| if ($self->{'format_context'}->[-1]->{'item_command'} eq 'headitem') { |
| # at this point cell_beginning is at the beginning of |
| # the cell following the end of the table -> full width |
| my $line = ' ' x $indent_len . '-' x $cell_beginning . "\n"; |
| $bytes_count += $self->count_bytes($line); |
| $result .= $line; |
| $self->{'empty_lines_count'} = 0; |
| $max_lines++; |
| # there may be empty lines, in that case $line is undef, $max_lines == 0 |
| } elsif ($max_lines) { |
| if ($line eq "\n") { |
| $self->{'empty_lines_count'} = 1; |
| } else { |
| $self->{'empty_lines_count'} = 0; |
| } |
| } |
| $self->_update_locations_counts(\@row_locations); |
| push @{$self->{'count_context'}->[-1]->{'locations'}}, @row_locations; |
| $self->{'count_context'}->[-1]->{'bytes'} += $bytes_count; |
| $self->{'count_context'}->[-1]->{'lines'} += $max_lines; |
| $self->{'format_context'}->[-1]->{'row'} = []; |
| $self->{'format_context'}->[-1]->{'row_counts'} = []; |
| $self->{'format_context'}->[-1]->{'row_empty_lines_count'} |
| = $self->{'empty_lines_count'}; |
| } elsif ($root->{'type'} eq 'text_root') { |
| $self->{'text_before_first_node'} = $result; |
| } |
| } |
| # close paragraphs and preformatted |
| if ($paragraph) { |
| $result .= _count_added($self, $paragraph->{'container'}, |
| $paragraph->{'container'}->end()); |
| if ($self->{'context'}->[-1] eq 'flushright') { |
| $result = $self->_align_environment($result, |
| $self->{'text_element_context'}->[-1]->{'max'}, 'right'); |
| } |
| pop @{$self->{'formatters'}}; |
| delete $self->{'text_element_context'}->[-1]->{'counter'}; |
| } elsif ($preformatted) { |
| $result .= _count_added($self, $preformatted->{'container'}, |
| $preformatted->{'container'}->end()); |
| if ($result ne '') { |
| $result = $self->ensure_end_of_line($result); |
| } |
| if ($self->{'context'}->[-1] eq 'flushright') { |
| $result = $self->_align_environment ($result, |
| $self->{'text_element_context'}->[-1]->{'max'}, 'right'); |
| } |
| pop @{$self->{'formatters'}}; |
| # We assume that, upon closing the preformatted we are at the |
| # beginning of a line. |
| delete $self->{'text_element_context'}->[-1]->{'counter'}; |
| } |
| |
| # close commands |
| if ($root->{'cmdname'}) { |
| if ($root->{'cmdname'} eq 'float') { |
| if ($self->{'debug'}) { |
| my $type_texi = ''; |
| $type_texi = Texinfo::Convert::Texinfo::convert({'contents' => $root->{'extra'}->{'type'}->{'content'}}) |
| if ($root->{'extra'} and $root->{'extra'}->{'type'}->{'normalized'} ne ''); |
| my $number = ''; |
| $number = $root->{'number'} if (defined($root->{'number'})); |
| print STDERR "FLOAT: ($number) ($type_texi)\n"; |
| } |
| |
| if ($root->{'extra'} |
| and ($root->{'extra'}->{'type'}->{'normalized'} ne '' |
| or defined($root->{'number'}) |
| or $root->{'extra'}->{'caption'} or $root->{'extra'}->{'shortcaption'})) { |
| |
| $result .= _add_newline_if_needed($self); |
| my ($caption, $prepended) = Texinfo::Common::float_name_caption($self, |
| $root); |
| if ($prepended) { |
| #print STDERR "PREPENDED ".Data::Dumper->Dump([$prepended]); |
| $prepended->{'type'} = 'frenchspacing'; |
| my $float_number = $self->convert_line ($prepended); |
| $result .= $float_number; |
| $self->{'text_element_context'}->[-1]->{'counter'} += |
| Texinfo::Convert::Unicode::string_width($float_number); |
| $self->{'empty_lines_count'} = 0; |
| } |
| if ($caption) { |
| $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; |
| my $tree = $caption->{'args'}->[0]; |
| $result .= $self->_convert($tree); |
| } |
| } |
| } elsif (($root->{'cmdname'} eq 'quotation' |
| or $root->{'cmdname'} eq 'smallquotation') |
| and $root->{'extra'} and $root->{'extra'}->{'authors'}) { |
| foreach my $author (@{$root->{'extra'}->{'authors'}}) { |
| $result .= $self->_convert( |
| $self->gdt("\@center --- \@emph{{author}}\n", |
| {'author' => $author->{'extra'}->{'misc_content'}})); |
| } |
| } elsif (($root->{'cmdname'} eq 'multitable')) { |
| $self->{'document_context'}->[-1]->{'in_multitable'}--; |
| } |
| |
| # close the contexts and register the cells |
| if ($self->{'preformatted_context_commands'}->{$root->{'cmdname'}} |
| or $root->{'cmdname'} eq 'float') { |
| my $old_context = pop @{$self->{'context'}}; |
| die "Not a preformatted context: $old_context" |
| if (!$self->{'preformatted_context_commands'}->{$old_context} |
| and $old_context ne 'float'); |
| if ($old_context ne 'float' and !$menu_commands{$old_context}) { |
| $self->{'empty_lines_count'} = 0; |
| } |
| delete ($self->{'preformatted_context_commands'}->{$root->{'cmdname'}}) |
| unless ($default_preformatted_context_commands{$root->{'cmdname'}}); |
| } elsif ($flush_commands{$root->{'cmdname'}}) { |
| my $old_context = pop @{$self->{'context'}}; |
| die if (! $flush_commands{$old_context}); |
| } |
| |
| if ($self->{'format_context_commands'}->{$root->{'cmdname'}}) { |
| pop @{$self->{'format_context'}}; |
| delete ($self->{'format_context_commands'}->{$root->{'cmdname'}}) |
| unless ($default_format_context_commands{$root->{'cmdname'}}); |
| } elsif ($cell) { |
| pop @{$self->{'format_context'}}; |
| pop @{$self->{'text_element_context'}}; |
| push @{$self->{'format_context'}->[-1]->{'row'}}, $result; |
| _update_count_context($self); |
| my $cell_counts = pop @{$self->{'count_context'}}; |
| push @{$self->{'format_context'}->[-1]->{'row_counts'}}, $cell_counts; |
| $result = ''; |
| } |
| if ($advance_paragraph_count_commands{$root->{'cmdname'}}) { |
| $self->{'format_context'}->[-1]->{'paragraph_count'}++; |
| } |
| } |
| |
| return $result; |
| } |
| |
| sub indent_one_menu_descriptions($$) |
| { |
| my $self = shift; |
| my $menu = shift; |
| |
| foreach my $content (@{$menu->{'contents'}}) { |
| if ($content->{'type'} and $content->{'type'} eq 'menu_entry') { |
| my $result = ''; |
| my $node_seen = 0; |
| foreach my $arg (@{$content->{'args'}}) { |
| if ($arg->{'type'} eq 'menu_entry_node') { |
| $result .= $self->_convert({'type' => '_code', |
| 'contents' => $arg->{'contents'}}); |
| $node_seen = 1; |
| } else { |
| # the separator appearing after the node is modified |
| if ($arg->{'type'} eq 'menu_entry_separator' and $node_seen) { |
| $arg->{'text'} =~ s/\s*$//; |
| } |
| $result .= $self->_convert($arg); |
| if ($arg->{'type'} eq 'menu_entry_separator' and $node_seen) { |
| my $length = Texinfo::Convert::Unicode::string_width($result); |
| my $description_indent |
| = $self->get_conf('TEXINFO_COLUMN_FOR_DESCRIPTION'); |
| if ($length >= $description_indent) { |
| $arg->{'text'} .= ' '; |
| } else { |
| $arg->{'text'} .= ' ' x ($description_indent - $length); |
| } |
| last; |
| } elsif ($arg->{'type'} eq 'menu_entry_description') { |
| # This should never happen, but this is a safeguard for |
| # incorrect trees. |
| last; |
| } |
| } |
| } |
| #print STDERR "$result"; |
| } |
| } |
| } |
| |
| # FIXME document |
| sub indent_menu_descriptions($;$) |
| { |
| my $self = shift; |
| my $parser = shift; |
| |
| if (!defined($self)) { |
| # setup a converter for menu |
| if (!defined($parser)) { |
| return undef; |
| } |
| $self = Texinfo::Convert::Plaintext->converter({'parser' => $parser}); |
| } elsif (!defined($parser)) { |
| if (!defined($self->{'parser'})) { |
| return undef; |
| } |
| $parser = $self->{'parser'}; |
| } |
| |
| # setup the converter as if it was in a menu |
| my $cmdname = 'menu'; |
| push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, |
| 'locations' => []}; |
| push @{$self->{'context'}}, $cmdname; |
| push @{$self->{'format_context'}}, |
| { 'cmdname' => $cmdname, |
| 'paragraph_count' => 0, |
| 'indent_level' => |
| $self->{'format_context'}->[-1]->{'indent_level'}, |
| }; |
| my $preformatted = $self->new_formatter('unfilled'); |
| push @{$self->{'formatters'}}, $preformatted; |
| |
| if ($parser->{'info'} and $parser->{'info'}->{'unassociated_menus'}) { |
| foreach my $menu (@{$parser->{'info'}->{'unassociated_menus'}}) { |
| $self->indent_one_menu_descriptions($menu); |
| } |
| } |
| if ($parser->{'nodes'} and @{$parser->{'nodes'}}) { |
| foreach my $node (@{$parser->{'nodes'}}) { |
| if ($node->{'menus'}) { |
| foreach my $menu (@{$node->{'menus'}}) { |
| $self->indent_menu_descriptions($menu); |
| } |
| } |
| } |
| } |
| pop @{$self->{'formatters'}}; |
| pop @{$self->{'context'}}; |
| pop @{$self->{'format_context'}}; |
| pop @{$self->{'count_context'}}; |
| } |
| |
| 1; |
| |
| __END__ |
| # $Id: Plaintext.pm 6991 2016-02-06 12:16:13Z gavin $ |
| # Automatically generated from maintain/template.pod |
| |
| =head1 NAME |
| |
| Texinfo::Convert::Plaintext - Convert Texinfo tree to Plaintext |
| |
| =head1 SYNOPSIS |
| |
| my $converter |
| = Texinfo::Convert::Plaintext->converter({'parser' => $parser}); |
| |
| $converter->output($tree); |
| $converter->convert($tree); |
| $converter->convert_tree($tree); |
| |
| =head1 DESCRIPTION |
| |
| Texinfo::Convert::Plaintext converts a Texinfo tree to Plaintext. |
| |
| =head1 METHODS |
| |
| =over |
| |
| =item $converter = Texinfo::Convert::Plaintext->converter($options) |
| |
| Initialize converter from Texinfo to Plaintext. |
| |
| The I<$options> hash reference holds options for the converter. In |
| this option hash reference a parser object may be associated with the |
| I<parser> key. The other options should be configuration options |
| described in the Texinfo manual. Those options, when appropriate, |
| override the document content. |
| |
| See L<Texinfo::Convert::Converter> for more informations. |
| |
| =item $converter->output($tree) |
| |
| Convert a Texinfo tree I<$tree> and output the result in files as |
| described in the Texinfo manual. |
| |
| =item $result = $converter->convert($tree) |
| |
| Convert a Texinfo tree I<$tree> or tree portion and return |
| the resulting output. |
| |
| =item $result = $converter->convert_tree($tree) |
| |
| Convert a Texinfo tree portion I<$tree> and return the resulting |
| output. This function does not try to output a full document but only |
| portions. For a full document use C<convert>. |
| |
| =back |
| |
| =head1 AUTHOR |
| |
| Patrice Dumas, E<lt>pertusus@free.frE<gt> |
| |
| =head1 COPYRIGHT AND LICENSE |
| |
| Copyright 2015 Free Software Foundation, Inc. |
| |
| This library 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. |
| |
| =cut |