1# Plaintext.pm: output tree as text with filling. 2# 3# Copyright 2010-2020 Free Software Foundation, Inc. 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 3 of the License, 8# or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17# 18# Original author: Patrice Dumas <pertusus@free.fr> 19 20package Texinfo::Convert::Plaintext; 21 22use 5.00405; 23 24# See comment at start of HTML.pm 25use if $] >= 5.012, feature => qw(unicode_strings); 26 27use strict; 28 29use Texinfo::Convert::Converter; 30use Texinfo::Common; 31use Texinfo::Convert::Texinfo; 32use Texinfo::Convert::Paragraph; 33 34use Texinfo::Convert::Text; 35 36require Exporter; 37use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 38@ISA = qw(Texinfo::Convert::Converter); 39 40# Some extra initialization for the first time this module is loaded. 41# This could be done in a UNITCHECK block, introduced in Perl 5.10. 42our $module_loaded = 0; 43sub import { 44 if (!$module_loaded) { 45 Texinfo::XSLoader::override( 46 "Texinfo::Convert::Plaintext::_process_text_internal", 47 "Texinfo::MiscXS::process_text"); 48 $module_loaded = 1; 49 } 50 # The usual import method 51 goto &Exporter::import; 52} 53 54%EXPORT_TAGS = ( 'all' => [ qw( 55 convert 56 output 57) ] ); 58 59@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); 60 61@EXPORT = qw( 62); 63 64$VERSION = '6.8'; 65 66# misc commands that are of use for formatting. 67my %formatting_misc_commands = %Texinfo::Convert::Text::formatting_misc_commands; 68 69my $NO_NUMBER_FOOTNOTE_SYMBOL = '*'; 70 71my @informative_global_commands = ('paragraphindent', 'firstparagraphindent', 72'frenchspacing', 'documentencoding', 'footnotestyle', 'documentlanguage', 73'contents', 'shortcontents', 'summarycontents', 'deftypefnnewline'); 74 75my %informative_commands; 76foreach my $informative_command (@informative_global_commands) { 77 $informative_commands{$informative_command} = 1; 78} 79 80my %no_brace_commands = %Texinfo::Common::no_brace_commands; 81my %brace_no_arg_commands; 82foreach my $command (keys (%Texinfo::Common::brace_commands)) { 83 $brace_no_arg_commands{$command} = 1 84 if ($Texinfo::Common::brace_commands{$command} eq '0'); 85} 86my %accent_commands = %Texinfo::Common::accent_commands; 87my %misc_commands = %Texinfo::Common::misc_commands; 88my %sectioning_commands = %Texinfo::Common::sectioning_commands; 89my %def_commands = %Texinfo::Common::def_commands; 90my %ref_commands = %Texinfo::Common::ref_commands; 91my %block_commands = %Texinfo::Common::block_commands; 92my %menu_commands = %Texinfo::Common::menu_commands; 93my %root_commands = %Texinfo::Common::root_commands; 94my %preformatted_commands = %Texinfo::Common::preformatted_commands; 95my %math_commands = %Texinfo::Common::math_commands; 96my %explained_commands = %Texinfo::Common::explained_commands; 97my %inline_format_commands = %Texinfo::Common::inline_format_commands; 98my %inline_commands = %Texinfo::Common::inline_commands; 99my %item_container_commands = %Texinfo::Common::item_container_commands; 100my %raw_commands = %Texinfo::Common::raw_commands; 101my %format_raw_commands = %Texinfo::Common::format_raw_commands; 102my %code_style_commands = %Texinfo::Common::code_style_commands; 103my %regular_font_style_commands = %Texinfo::Common::regular_font_style_commands; 104my %preformatted_code_commands = %Texinfo::Common::preformatted_code_commands; 105my %default_index_commands = %Texinfo::Common::default_index_commands; 106my %letter_no_arg_commands = %Texinfo::Common::letter_no_arg_commands; 107 108foreach my $kept_command(keys (%informative_commands), 109 keys (%default_index_commands), 110 'verbatiminclude', 'insertcopying', 111 'listoffloats', 'printindex', ) { 112 $formatting_misc_commands{$kept_command} = 1; 113} 114 115foreach my $def_command (keys(%def_commands)) { 116 $formatting_misc_commands{$def_command} = 1 if ($misc_commands{$def_command}); 117} 118 119# There are 6 stacks that define the context. 120# context: relevant for alignement of text. Set in math, footnote, 121# listoffloats, flush_commands, preformatted_context_commands 122# (preformatted + menu + verbatim), and raw commands if 123# on top level. 124# format_context: used for the count of paragraphs and for the indentation. 125# Set in footnote, for all commands relevant for indenting, like 126# @*table, @itemize, @enumerate, preformatted commands, 127# @*quotation, @def*, and also menu commands, @flushleft, 128# @flushright, @float, in multitable cell and raw commands if at 129# toplevel. 130# text_element_context: for the max columns and the counter in the line 131# position (although the counter in the line is taken over by 132# the formatter once a formatter is opened). 133# Set in footnote and in multitable cells. 134# formatters: a formatter environment has stacks for formatting context. 135# Also holds a container, an objects that does the counting 136# of columns, actual indentation. In general, it is better not 137# to have formatters in parallel, but it may happen. 138# count_context: holds the bytes count, the lines count and the location 139# of the commands that have their byte count or lines count 140# recorded. It is set for out of document formatting to avoid 141# counting some converted text, but it is also set when it has 142# to be modified afterwards, for aligned commands or multitable 143# cells for example. 144# document_context: Used to keep track if we are in a multitable. 145 146# formatters have their own stack 147# in container 148# 'upper_case' 149# 'var' 150# 'font_type_stack' 151# 152# paragraph number incremented with paragraphs, center, listoffloats 153# and block commands except: html and such, group, raggedright, menu*, float 154 155my %default_preformatted_context_commands = (%preformatted_commands, 156 %format_raw_commands); 157foreach my $preformatted_command ('verbatim', keys(%menu_commands)) { 158 $default_preformatted_context_commands{$preformatted_command} = 1; 159} 160 161my %block_math_commands; 162foreach my $block_math_command (keys(%math_commands)) { 163 if (exists($block_commands{$block_math_command})) { 164 $block_math_commands{$block_math_command} = 1; 165 } 166} 167 168my %ignored_misc_commands; 169foreach my $misc_command (keys(%misc_commands)) { 170 $ignored_misc_commands{$misc_command} = 1 171 unless ($formatting_misc_commands{$misc_command}); 172} 173 174my %ignored_commands = %ignored_misc_commands; 175foreach my $ignored_brace_commands ('caption', 'shortcaption', 176 'hyphenation', 'sortas') { 177 $ignored_commands{$ignored_brace_commands} = 1; 178} 179 180my %item_indent_format_length = ('enumerate' => 2, 181 'itemize' => 3, 182 'table' => 0, 183 'vtable' => 0, 184 'ftable' => 0, 185 ); 186 187my $indent_length = 5; 188 189my %indented_commands; 190foreach my $indented_command (keys(%item_indent_format_length), 191 keys(%preformatted_commands), 'quotation', 'smallquotation', 192 'indentedblock', 'smallindentedblock', 193 keys(%def_commands)) { 194 $indented_commands{$indented_command} = 1 195 if exists($block_commands{$indented_command}); 196} 197 198my %default_format_context_commands = %indented_commands; 199 200foreach my $non_indented('format', 'smallformat') { 201 delete $indented_commands{$non_indented}; 202} 203 204# FIXME should keys(%math_brace_commands) be added here? 205# How can this be tested? 206foreach my $format_context_command (keys(%menu_commands), 'verbatim', 207 'flushleft', 'flushright', 'multitable', 'float') { 208 $default_format_context_commands{$format_context_command} = 1; 209} 210 211my %flush_commands = ( 212 'flushleft' => 1, 213 'flushright' => 1 214); 215 216# commands that leads to advancing the paragraph number. This is mostly 217# used to determine the first line, in fact. 218my %advance_paragraph_count_commands; 219foreach my $command (keys(%block_commands)) { 220 next if ($menu_commands{$command} 221 or $block_commands{$command} eq 'raw'); 222 $advance_paragraph_count_commands{$command} = 1; 223} 224 225# group and raggedright do more than not advancing para, they should also 226# be transparent with respect to paragraph number counting. 227foreach my $not_advancing_para ('group', 'raggedright', 228 'titlepage', 'copying', 'documentdescription', 'float') { 229 delete $advance_paragraph_count_commands{$not_advancing_para}; 230} 231 232foreach my $advancing_para('center', 'verbatim', 'listoffloats') { 233 $advance_paragraph_count_commands{$advancing_para} = 1; 234} 235 236foreach my $ignored_block_commands ('ignore', 'macro', 'rmacro', 'copying', 237 'documentdescription', 'titlepage', 'direntry') { 238 $ignored_commands{$ignored_block_commands} = 1; 239} 240 241my %punctuation_no_arg_commands; 242foreach my $punctuation_command('enddots', 'exclamdown', 'questiondown') { 243 $punctuation_no_arg_commands{$punctuation_command} = 1; 244} 245 246my %upper_case_commands = ( 247 'sc' => 1, 248 'var' => 1 249); 250 251my %ignorable_space_types; 252foreach my $type ('empty_line_after_command', 253 'empty_spaces_after_command', 'spaces_at_end', 254 'empty_spaces_before_argument', 'empty_spaces_before_paragraph', 255 'empty_spaces_after_close_brace') { 256 $ignorable_space_types{$type} = 1; 257} 258 259my %ignored_types; 260foreach my $type ('preamble', 261 'preamble_before_setfilename') { 262 $ignored_types{$type} = 1; 263} 264 265my %ignorable_types = %ignorable_space_types; 266foreach my $ignored_type(keys(%ignored_types)) { 267 $ignorable_types{$ignored_type} = 1; 268} 269 270# All those commands run with the text. 271my %style_map = ( 272 'strong' => '*', 273 'dfn' => '"', 274 'emph' => '_', 275); 276 277foreach my $command (keys(%style_map)) { 278 $style_map{$command} = [$style_map{$command}, $style_map{$command}]; 279} 280 281# math is special 282my @asis_commands = ('asis', 'w', 'b', 'i', 'sc', 't', 'r', 283 'slanted', 'sansserif', 'var', 'verb', 'clicksequence', 284 'headitemfont', 'dmn'); 285 286foreach my $asis_command (@asis_commands) { 287 $style_map{$asis_command} = ['', '']; 288} 289 290my @quoted_commands = ('cite', 'code', 'command', 'env', 'file', 'kbd', 291 'option', 'samp', 'indicateurl'); 292 293# %non_quoted_commands_when_nested have no quote when in code command contexts 294my %non_quoted_commands_when_nested; 295 296# Quotes are reset in converter_initialize and unicode quotes are used 297# if @documentencoding utf-8 is used. 298foreach my $quoted_command (@quoted_commands) { 299 $style_map{$quoted_command} = ["'", "'"]; 300 if ($code_style_commands{$quoted_command}) { 301 $non_quoted_commands_when_nested{$quoted_command} = 1; 302 } 303} 304# always quoted even when nested 305delete $non_quoted_commands_when_nested{'samp'}; 306delete $non_quoted_commands_when_nested{'indicateurl'}; 307 308$style_map{'key'} = ['<', '>']; 309$style_map{'sub'} = ['_{', '}']; 310$style_map{'sup'} = ['^{', '}']; 311 312# Commands producing styles that are output in node names and index entries. 313my %index_style_commands; 314for my $index_style_command ('strong', 'emph', 'sub', 'sup', 'key') { 315 $index_style_commands{$index_style_command} = 1; 316} 317 318 319# in those commands, there is no addition of double space after a dot. 320# math is special 321my %no_punctation_munging_commands; 322foreach my $command ('var', 'cite', 'dmn', keys(%code_style_commands)) { 323 $no_punctation_munging_commands{$command} = 1; 324} 325 326my %defaults = ( 327 'ENABLE_ENCODING' => 1, 328 'FORMAT_MENU' => 'nomenu', 329 #'EXTENSION' => 'info', 330 'EXTENSION' => 'txt', 331 #'USE_SETFILENAME_EXTENSION' => 1, 332 'INFO_SPECIAL_CHARS_WARNING' => 1, 333 334 #'OUTFILE' => undef, 335 'OUTFILE' => '-', 336 'SUBDIR' => undef, 337 'documentlanguage' => undef, 338 339 'output_format' => '', 340 'USE_NODES' => 1, 341); 342 343sub push_top_formatter($$) 344{ 345 my ($self, $top_context) = @_; 346 347 push @{$self->{'context'}}, $top_context; 348 push @{$self->{'format_context'}}, { 349 'cmdname' => '_top_format', 350 'indent_level' => 0, 351 'paragraph_count' => 0, 352 }; 353 push @{$self->{'text_element_context'}}, { 354 'max' => $self->{'fillcolumn'} 355 }; 356 push @{$self->{'document_context'}}, { 357 'in_multitable' => 0 358 }; 359 360 # This is not really meant to be used, as contents should open 361 # their own formatters, however it happens that there is some text 362 # outside any content that needs to be formatted, as @sp for example. 363 push @{$self->{'formatters'}}, $self->new_formatter('line'); 364 $self->{'formatters'}->[-1]->{'_top_formatter'} = 1; 365} 366 367 368my %contents_commands = ( 369 'contents' => 1, 370 'shortcontents' => 1, 371 'summarycontents' => 1, 372); 373 374sub converter_defaults($$) 375{ 376 return %defaults; 377} 378 379sub converter_global_commands($) 380{ 381 return @informative_global_commands; 382} 383 384sub converter_initialize($) 385{ 386 my $self = shift; 387 388 $self->{'context'} = []; 389 $self->{'format_context'} = []; 390 $self->{'empty_lines_count'} = undef; 391 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, 392 'locations' => []}; 393 394 %{$self->{'ignored_types'}} = %ignored_types; 395 %{$self->{'ignorable_space_types'}} = %ignorable_space_types; 396 %{$self->{'ignored_commands'}} = %ignored_commands; 397 # this is dynamic because raw formats may either be full commands if 398 # isolated, or simple text if in a paragraph 399 %{$self->{'format_context_commands'}} = %default_format_context_commands; 400 %{$self->{'preformatted_context_commands'}} 401 = %default_preformatted_context_commands; 402 $self->{'footnote_index'} = 0; 403 $self->{'pending_footnotes'} = []; 404 405 foreach my $format (keys(%format_raw_commands)) { 406 $self->{'ignored_commands'}->{$format} = 1 407 unless ($self->{'expanded_formats_hash'}->{$format}); 408 } 409 410 %{$self->{'style_map'}} = %style_map; 411 $self->{'convert_text_options'} 412 = {Texinfo::Common::_convert_text_options($self)}; 413 414 if ($self->get_conf('ENABLE_ENCODING') and $self->get_conf('OUTPUT_ENCODING_NAME') 415 and $self->get_conf('OUTPUT_ENCODING_NAME') eq 'utf-8') { 416 # cache this to avoid redoing calls to get_conf 417 $self->{'to_utf8'} = 1; 418 if (!$self->{'extra'}->{'documentencoding'}) { 419 # Do not use curly quotes or some other unnecessary non-ASCII characters 420 # if '@documentencoding UTF-8' is not given. 421 $self->{'convert_text_options'}->{'no_extra_unicode'} = 1; 422 } else { 423 foreach my $quoted_command (@quoted_commands) { 424 # Directed single quotes 425 $self->{'style_map'}->{$quoted_command} = ["\x{2018}", "\x{2019}"]; 426 } 427 # Directed double quotes 428 $self->{'style_map'}->{'dfn'} = ["\x{201C}", "\x{201D}"]; 429 } 430 } 431 if (defined($self->get_conf('OPEN_QUOTE_SYMBOL'))) { 432 foreach my $quoted_command (@quoted_commands) { 433 $self->{'style_map'}->{$quoted_command}->[0] 434 = $self->get_conf('OPEN_QUOTE_SYMBOL'); 435 } 436 } 437 if (defined($self->get_conf('CLOSE_QUOTE_SYMBOL'))) { 438 foreach my $quoted_command (@quoted_commands) { 439 $self->{'style_map'}->{$quoted_command}->[1] 440 = $self->get_conf('CLOSE_QUOTE_SYMBOL'); 441 } 442 } 443 if ($self->get_conf('FILLCOLUMN')) { 444 $self->{'fillcolumn'} = $self->get_conf('FILLCOLUMN'); 445 # else it's already set via the defaults 446 } 447 # This needs to be here to take into account $self->{'fillcolumn'}. 448 $self->push_top_formatter('_Root_context'); 449 # some caching to avoid calling get_conf 450 if ($self->get_conf('OUTPUT_PERL_ENCODING')) { 451 $self->{'output_perl_encoding'} = $self->get_conf('OUTPUT_PERL_ENCODING'); 452 } else { 453 $self->{'output_perl_encoding'} = ''; 454 } 455 $self->{'enable_encoding'} = $self->get_conf('ENABLE_ENCODING'); 456 $self->{'output_encoding_name'} = $self->get_conf('OUTPUT_ENCODING_NAME'); 457 $self->{'debug'} = $self->get_conf('DEBUG'); 458 459 return $self; 460} 461 462sub _count_context_bug_message($$$) 463{ 464 my ($self, $precision, $element) = @_; 465 466 if (scalar(@{$self->{'count_context'}}) != 1) { 467 my $element_text; 468 if ($element) { 469 $element_text 470 = Texinfo::Structuring::_print_element_command_texi($element); 471 } else { 472 $element_text = ''; 473 } 474 $self->_bug_message("Too much count_context ${precision}(". 475 scalar(@{$self->{'count_context'}}). "): ". $element_text, $element); 476 die; 477 } 478} 479 480sub _convert_element($$) 481{ 482 my ($self, $element) = @_; 483 484 my $result = ''; 485 $result .= $self->_convert($element); 486 $self->_count_context_bug_message('', $element); 487 $result .= $self->_footnotes($element); 488 $self->_count_context_bug_message('footnotes ', $element); 489 490 return $result; 491} 492 493sub convert($$) 494{ 495 my ($self, $root) = @_; 496 497 my $result = ''; 498 499 my $elements = Texinfo::Structuring::split_by_node($root); 500 $self->{'empty_lines_count'} = 1; 501 if (!defined($elements)) { 502 $result = $self->_convert($root); 503 $self->_count_context_bug_message('no element '); 504 my $footnotes = $self->_footnotes(); 505 $self->_count_context_bug_message('no element footnotes '); 506 $result .= $footnotes; 507 } else { 508 foreach my $node (@$elements) { 509 my $node_text = _convert_element($self, $node); 510 $result .= $node_text; 511 } 512 } 513 514 return $result; 515} 516 517sub convert_tree($$) 518{ 519 my ($self, $root) = @_; 520 521 $self->{'empty_lines_count'} = 1; 522 my $result; 523 if ($root->{'type'} and $root->{'type'} eq 'element') { 524 $result = _convert_element($self, $root); 525 } else { 526 $result = $self->_convert($root); 527 } 528 return $result; 529} 530 531my $end_sentence = quotemeta('.?!'); 532my $after_punctuation = quotemeta('"\')]'); 533 534sub _protect_sentence_ends ($) { 535 my $text = shift; 536 # Avoid suppressing end of sentence, by inserting a control character 537 # in front of the full stop. The choice of BS for this is arbitrary. 538 $text =~ s/(?<=[^[:upper:]]) 539 (?=[$end_sentence][$after_punctuation]*(?:\s|$)) 540 /\x08/gx; 541 542 # Also insert a control character at end of string, to protect a full stop 543 # that may follow later. 544 545 #$text =~ s/(?<=[^[:upper:]][$after_punctuation]*)$/\x08/; 546 # Perl doesn't support "variable length lookbehind" 547 548 $text = reverse $text; 549 $text =~ s/^(?=[$after_punctuation]* 550 (?:[^[:upper:]\s]|[\x{202f}\x{00a0}])) 551 /\x08/x; 552 $text = reverse $text; 553 554 return $text; 555} 556 557sub _process_text_internal { 558 my ($text) = @_; 559 560 $text =~ s/---/\x{1F}/g; 561 $text =~ s/--/-/g; 562 $text =~ s/\x{1F}/--/g; 563 $text =~ s/``/"/g; 564 $text =~ s/\'\'/"/g; 565 $text =~ s/`/'/g; 566 567 return $text; 568} 569 570# Convert ``, '', `, ', ---, -- in $COMMAND->{'text'} to their output, 571# possibly coverting to upper case as well. 572sub _process_text($$$) 573{ 574 my ($self, $command, $context) = @_; 575 576 my $text = $command->{'text'}; 577 578 if ($context->{'upper_case'} 579 or $self->{'formatters'}[-1]->{'var'}) { 580 $text = _protect_sentence_ends($text); 581 $text = uc($text); 582 } 583 584 if ($self->{'to_utf8'} 585 and $self->{'extra'}->{'documentencoding'}) { 586 return Texinfo::Convert::Unicode::unicode_text($text, 587 $context->{'font_type_stack'}->[-1]->{'monospace'}); 588 } elsif (!$context->{'font_type_stack'}->[-1]->{'monospace'}) { 589 return _process_text_internal($text); 590 } 591 return $text; 592} 593 594sub new_formatter($$;$) 595{ 596 my ($self, $type, $conf) = @_; 597 598 my $first_indent_length = $conf->{'first_indent_length'}; 599 delete $conf->{'first_indent_length'}; 600 601 my $container; 602 my $container_conf = { 603 'max' => $self->{'text_element_context'}->[-1]->{'max'}, 604 'indent_level' => $self->{'format_context'}->[-1]->{'indent_level'}, 605 }; 606 $container_conf->{'frenchspacing'} = 1 607 if ($self->{'conf'}->{'frenchspacing'} eq 'on'); 608 $container_conf->{'counter'} 609 = $self->{'text_element_context'}->[-1]->{'counter'} 610 if (defined($self->{'text_element_context'}->[-1]->{'counter'})); 611 $container_conf->{'DEBUG'} = 1 if ($self->{'debug'}); 612 if ($conf) { 613 foreach my $key (keys(%$conf)) { 614 $container_conf->{$key} = $conf->{$key}; 615 } 616 } 617 my $indent = $container_conf->{'indent_length'}; 618 $indent = $indent_length*$container_conf->{'indent_level'} 619 if (!defined($indent)); 620 621 if ($first_indent_length) { 622 $container_conf->{'indent_length'} = $first_indent_length; 623 $container_conf->{'indent_length_next'} = $indent; 624 } else { 625 $container_conf->{'indent_length'} = $indent; 626 } 627 628 if ($type eq 'line') { 629 $container_conf->{'max'} = 10000001; 630 $container_conf->{'keep_end_lines'} = 1; 631 $container_conf->{'no_final_newline'} = 1; 632 $container_conf->{'add_final_space'} = 1; 633 634 $container = Texinfo::Convert::Paragraph->new($container_conf); 635 } elsif ($type eq 'paragraph') { 636 $container = Texinfo::Convert::Paragraph->new($container_conf); 637 } elsif ($type eq 'unfilled') { 638 $container_conf->{'max'} = 10000000; 639 $container_conf->{'ignore_columns'} = 1; 640 $container_conf->{'keep_end_lines'} = 1; 641 $container_conf->{'frenchspacing'} = 1; 642 $container_conf->{'unfilled'} = 1; 643 $container_conf->{'no_final_newline'} = 1; 644 645 $container = Texinfo::Convert::Paragraph->new($container_conf); 646 } else { 647 die "Unknown container type $type\n"; 648 } 649 650 if ($flush_commands{$self->{'context'}->[-1]}) { 651 set_space_protection($container, undef, 1, 1); 652 } 653 654 my $formatter = {'container' => $container, 'upper_case' => 0, 655 'font_type_stack' => [{}], 656 'w' => 0, 'type' => $type, 657 'frenchspacing_stack' => [$self->{'conf'}->{'frenchspacing'}], 658 'suppress_styles' => $conf->{'suppress_styles'}}; 659 660 if ($type eq 'unfilled') { 661 foreach my $context (reverse(@{$self->{'context'}})) { 662 if ($menu_commands{$context}) { 663 last; 664 } elsif ($preformatted_code_commands{$context} 665 or $format_raw_commands{$context} 666 or $math_commands{$context}) { 667 $formatter->{'font_type_stack'}->[-1]->{'monospace'} = 1; 668 $formatter->{'font_type_stack'}->[-1]->{'code_command'} = 1 669 if ($preformatted_code_commands{$context} 670 or $math_commands{$context}); 671 last; 672 } 673 } 674 } 675 return $formatter; 676} 677 678sub convert_line($$;$) 679{ 680 my ($self, $converted, $conf) = @_; 681 my $formatter = $self->new_formatter('line', $conf); 682 push @{$self->{'formatters'}}, $formatter; 683 my $text = $self->_convert($converted); 684 $text .= _count_added($self, $formatter->{'container'}, 685 Texinfo::Convert::Paragraph::end($formatter->{'container'})); 686 pop @{$self->{'formatters'}}; 687 return $text; 688} 689 690sub convert_unfilled($$;$) 691{ 692 my ($self, $converted, $conf) = @_; 693 my $formatter = $self->new_formatter('unfilled', $conf); 694 $formatter->{'font_type_stack'}->[-1]->{'monospace'} = 1; 695 push @{$self->{'formatters'}}, $formatter; 696 my $result = $self->_convert($converted); 697 $result .= _count_added($self, $formatter->{'container'}, 698 Texinfo::Convert::Paragraph::end($formatter->{'container'})); 699 pop @{$self->{'formatters'}}; 700 return $result; 701} 702 703sub count_bytes($$) 704{ 705 my ($self, $string) = @_; 706 707 return Texinfo::Common::count_bytes($self, $string, 708 $self->{'output_perl_encoding'}); 709} 710 711sub _add_text_count($$) 712{ 713 my ($self, $text) = @_; 714 if (!$self->{'count_context'}->[-1]->{'pending_text'}) { 715 $self->{'count_context'}->[-1]->{'pending_text'} = ''; 716 } 717 $self->{'count_context'}->[-1]->{'pending_text'} .= $text; 718} 719 720sub _add_lines_count($$) 721{ 722 my ($self, $lines_count) = @_; 723 $self->{'count_context'}->[-1]->{'lines'} += $lines_count; 724} 725 726# Update $SELF->{'count_context'}->[-1]->{'bytes'} by counting the text that 727# hasn't been counted yet. It is faster to count the text all together than 728# piece by piece in _add_text_count. 729sub _update_count_context($) 730{ 731 my $self = shift; 732 if ($self->{'count_context'}->[-1]->{'pending_text'}) { 733 $self->{'count_context'}->[-1]->{'bytes'} += 734 Texinfo::Common::count_bytes($self, 735 $self->{'count_context'}->[-1]->{'pending_text'}, 736 $self->{'output_perl_encoding'}); 737 $self->{'count_context'}->[-1]->{'pending_text'} = ''; 738 } 739} 740 741# Save the line and byte offset of $ROOT. 742sub _add_location($$) 743{ 744 my ($self, $root) = @_; 745 my $location = { 'lines' => $self->{'count_context'}->[-1]->{'lines'} }; 746 push @{$self->{'count_context'}->[-1]->{'locations'}}, $location; 747 if (!($root->{'extra'} and $root->{'extra'}->{'index_entry'})) { 748 _update_count_context($self); 749 $location->{'bytes'} = $self->{'count_context'}->[-1]->{'bytes'}; 750 $location->{'root'} = $root; 751 } else { 752 $location->{'index_entry'} = $root; 753 } 754 return $location; 755} 756 757sub _add_image($$$$;$) 758{ 759 my ($self, $root, $lines_count, $image_width, $no_align) = @_; 760 761 push @{$self->{'count_context'}->[-1]->{'images'}}, { 762 'lines' => $self->{'count_context'}->[-1]->{'lines'}, 763 'lines_count' => $lines_count, 764 'image_width' => $image_width, 765 'no_align' => $no_align, 766 # may be used for debugging? 767 #'_ref' => $root, 768 }; 769} 770 771sub _count_added($$$) 772{ 773 my ($self, $container, $text) = @_; 774 775 my $count_context = $self->{'count_context'}->[-1]; 776 $count_context->{'lines'} 777 += Texinfo::Convert::Paragraph::end_line_count($container); 778 779 if (!defined $count_context->{'pending_text'}) { 780 $count_context->{'pending_text'} = ''; 781 } 782 $count_context->{'pending_text'} .= $text; 783 return $text; 784} 785 786sub _update_locations_counts($$) 787{ 788 my ($self, $locations) = @_; 789 790 _update_count_context($self); 791 foreach my $location (@$locations) { 792 $location->{'bytes'} += $self->{'count_context'}->[-1]->{'bytes'} 793 if (defined($location->{'bytes'})); 794 $location->{'lines'} += $self->{'count_context'}->[-1]->{'lines'} 795 if (defined($location->{'lines'})); 796 } 797} 798 799sub _add_newline_if_needed($) { 800 my $self = shift; 801 if (defined($self->{'empty_lines_count'}) 802 and $self->{'empty_lines_count'} == 0) { 803 _add_text_count($self, "\n"); 804 _add_lines_count($self, 1); 805 $self->{'empty_lines_count'} = 1; 806 return "\n"; 807 } 808 return ''; 809} 810 811my $footnote_indent = 3; 812sub _footnotes($;$) 813{ 814 my ($self, $element) = @_; 815 816 $element = undef if ($element and $element->{'extra'}->{'no_node'}); 817 818 my $result = ''; 819 if (scalar(@{$self->{'pending_footnotes'}})) { 820 $result .= _add_newline_if_needed($self); 821 if ($self->get_conf('footnotestyle') eq 'end' or !defined($element)) { 822 my $footnotes_header = " ---------- Footnotes ----------\n\n"; 823 $result .= $footnotes_header; 824 _add_text_count($self, $footnotes_header); 825 _add_lines_count($self, 2); 826 $self->{'empty_lines_count'} = 1; 827 } else { 828 829 my $node_contents = [@{$element->{'extra'}->{'node'}->{'extra'}->{'node_content'}}, 830 {'text' => '-Footnotes'}]; 831 my $footnotes_node = { 832 'cmdname' => 'node', 833 'node_up' => $element->{'extra'}->{'node'}, 834 'extra' => {'node_content' => $node_contents } 835 }; 836 $result .= $self->_node($footnotes_node); 837 $self->{'node'} = $footnotes_node; 838 } 839 while (@{$self->{'pending_footnotes'}}) { 840 my $footnote = shift (@{$self->{'pending_footnotes'}}); 841 842 # If nested within another footnote and footnotestyle is separate, 843 # the element here will be the parent element and not the footnote 844 # element, while the pxref will point to the name with the 845 # footnote node taken into account. Not really problematic as 846 # nested footnotes are not right. 847 if ($element) { 848 my $node_contents = [@{$element->{'extra'}->{'node'}->{'extra'}->{'node_content'}}, 849 {'text' => "-Footnote-$footnote->{'number'}"}]; 850 $self->_add_location({'cmdname' => 'anchor', 851 'extra' => {'node_content' => $node_contents } 852 }); 853 } 854 # this pushes on 'context', 'formatters', 'format_context', 855 # 'text_element_context' and 'document_context' 856 $self->push_top_formatter('footnote'); 857 my $formatted_footnote_number; 858 if ($self->get_conf('NUMBER_FOOTNOTES')) { 859 $formatted_footnote_number = $footnote->{'number'}; 860 } else { 861 $formatted_footnote_number = $NO_NUMBER_FOOTNOTE_SYMBOL; 862 } 863 my $footnote_text = ' ' x $footnote_indent 864 . "($formatted_footnote_number) "; 865 $result .= $footnote_text; 866 $self->{'text_element_context'}->[-1]->{'counter'} += 867 Texinfo::Convert::Unicode::string_width($footnote_text); 868 _add_text_count($self, $footnote_text); 869 $self->{'empty_lines_count'} = 0; 870 871 $result .= $self->_convert($footnote->{'root'}->{'args'}->[0]); 872 $result .= _add_newline_if_needed($self); 873 874 my $old_context = pop @{$self->{'context'}}; 875 die if ($old_context ne 'footnote'); 876 pop @{$self->{'formatters'}}; 877 pop @{$self->{'format_context'}}; 878 pop @{$self->{'text_element_context'}}; 879 pop @{$self->{'document_context'}}; 880 } 881 } 882 $self->{'footnote_index'} = 0; 883 884 return $result; 885} 886 887sub _compute_spaces_align_line($$$;$) 888{ 889 my ($line_width, $max_column, $direction, $no_align) = @_; 890 891 my $spaces_prepended; 892 if ($line_width >= $max_column or $no_align) { 893 $spaces_prepended = 0; 894 } elsif ($direction eq 'center') { 895 # if no int we may end up with floats... 896 $spaces_prepended = int(($max_column -1 - $line_width) /2); 897 } else { 898 $spaces_prepended = ($max_column -1 - $line_width); 899 } 900 return $spaces_prepended; 901} 902 903sub _align_lines($$$$$$) 904{ 905 my ($self, $text, $max_column, $direction, $locations, $images) = @_; 906 907 my $result = ''; 908 909 my $updated_locations = {}; 910 if ($locations and @$locations) { 911 foreach my $location (@$locations) { 912 next unless (defined($location->{'bytes'})); 913 push @{$updated_locations->{$location->{'lines'}}}, $location; 914 } 915 } 916 my $images_marks = {}; 917 if ($images and @$images) { 918 foreach my $image (@$images) { 919 if ($image->{'lines_count'} > 1) { 920 if (!$images_marks->{$image->{'lines'}}) { 921 $images_marks->{$image->{'lines'}} = $image; 922 }# else { 923 # Happens in Info with the special construct as, in that 924 # case, there are no lines! So no error... 925 # $self->_bug_message("more than one image with lines on $image->{'lines'}"); 926 # in that case, the $image->{'lines'} is not in sync with the 927 # lines count. So the second image will be treated as simple text. 928 #} 929 } 930 } 931 } 932 933 my $bytes_count = 0; 934 my $delta_bytes = 0; 935 my $line_index = 0; 936 my $image; 937 my $image_lines_count; 938 my $image_prepended_spaces; 939 foreach my $line (split /^/, $text) { 940 my $line_bytes_begin = 0; 941 my $line_bytes_end = 0; 942 my $removed_line_bytes_end = 0; 943 my $removed_line_bytes_begin = 0; 944 945 my ($new_image, $new_image_prepended_spaces); 946 if ($images_marks->{$line_index}) { 947 $new_image = $images_marks->{$line_index}; 948 $image_lines_count = 0; 949 $new_image_prepended_spaces 950 = _compute_spaces_align_line($new_image->{'image_width'}, $max_column, 951 $direction, $new_image->{'no_align'}); 952 if (!defined($image)) { 953 $image = $new_image; 954 $image_prepended_spaces = $new_image_prepended_spaces; 955 $new_image = undef; 956 } 957 } 958 959 my $orig_line; 960 if (!$image) { 961 my $chomped = chomp($line); 962 # for debugging. 963 $orig_line = $line; 964 $removed_line_bytes_end -= count_bytes($self, $chomped); 965 $line =~ s/^(\s*)//; 966 $removed_line_bytes_begin -= count_bytes($self, $1); 967 $line =~ s/(\s*)$//; 968 $removed_line_bytes_end -= count_bytes($self, $1); 969 my $line_width = Texinfo::Convert::Unicode::string_width($line); 970 if ($line_width == 0) { 971 $result .= "\n"; 972 $line_bytes_end += count_bytes($self, "\n"); 973 $bytes_count += count_bytes($self, "\n"); 974 } else { 975 my $spaces_prepended 976 = _compute_spaces_align_line($line_width, $max_column, $direction); 977 $result .= ' ' x$spaces_prepended . $line ."\n"; 978 $line_bytes_begin += count_bytes($self, ' ' x$spaces_prepended); 979 $line_bytes_end += count_bytes($self, "\n"); 980 $bytes_count += $line_bytes_begin + $line_bytes_end 981 + count_bytes($self, $line); 982 } 983 } else { 984 $image_lines_count++; 985 my $prepended_spaces = $image_prepended_spaces; 986 # adjust if there is something else that the image on the first or 987 # last line. The adjustment is approximate. 988 if (($image_lines_count == 1 or $image_lines_count == $image->{'lines_count'}) 989 and Texinfo::Convert::Unicode::string_width($line) > $image->{'image_width'}) { 990 $prepended_spaces 991 -= Texinfo::Convert::Unicode::string_width($line) - $image->{'image_width'}; 992 $prepended_spaces = 0 if ($prepended_spaces < 0); 993 } 994 $result .= ' ' x$prepended_spaces . $line; 995 $line_bytes_begin += count_bytes($self, ' ' x$prepended_spaces); 996 $bytes_count += $line_bytes_begin + count_bytes($self, $line); 997 if ($new_image) { 998 $image = $new_image; 999 $image_prepended_spaces = $new_image_prepended_spaces; 1000 } elsif ($image_lines_count == $image->{'lines_count'}) { 1001 $image = undef; 1002 $image_lines_count = undef; 1003 $image_prepended_spaces = undef; 1004 } 1005 } 1006 1007 if ($updated_locations->{$line_index}) { 1008 foreach my $location (@{$updated_locations->{$line_index}}) { 1009 $location->{'bytes'} += $line_bytes_begin + $removed_line_bytes_begin 1010 + $delta_bytes; 1011 } 1012 } 1013 $delta_bytes += $line_bytes_begin + $line_bytes_end 1014 + $removed_line_bytes_begin + $removed_line_bytes_end; 1015 $line_index++; 1016 } 1017 return ($result, $bytes_count); 1018} 1019 1020sub _align_environment($$$$) 1021{ 1022 my ($self, $result, $max, $align) = @_; 1023 1024 _update_count_context($self); 1025 my $counts = pop @{$self->{'count_context'}}; 1026 my $bytes_count; 1027 ($result, $bytes_count) = $self->_align_lines($result, $max, 1028 $align, $counts->{'locations'}, $counts->{'images'}); 1029 $self->_update_locations_counts($counts->{'locations'}); 1030 $self->{'count_context'}->[-1]->{'bytes'} += $bytes_count; 1031 $self->{'count_context'}->[-1]->{'lines'} += $counts->{'lines'}; 1032 push @{$self->{'count_context'}->[-1]->{'locations'}}, 1033 @{$counts->{'locations'}}; 1034 return $result; 1035} 1036 1037sub _contents($$$) 1038{ 1039 my ($self, $section_root, $contents_or_shortcontents) = @_; 1040 1041 my $contents = 1 if ($contents_or_shortcontents eq 'contents'); 1042 1043 # no sections 1044 return ('', 0) if (!$section_root or !$section_root->{'section_childs'}); 1045 my $root_level = $section_root->{'section_childs'}->[0]->{'level'}; 1046 foreach my $top_section(@{$section_root->{'section_childs'}}) { 1047 $root_level = $top_section->{'level'} 1048 if ($top_section->{'level'} < $root_level); 1049 } 1050 1051 my $result = ''; 1052 my $lines_count = 0; 1053 # This is done like that because the tree may not be well formed if 1054 # there is a @part after a @chapter for example. 1055 foreach my $top_section (@{$section_root->{'section_childs'}}) { 1056 my $section = $top_section; 1057 SECTION: 1058 while ($section) { 1059 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 1060 my $section_title_tree; 1061 if (defined($section->{'number'}) 1062 and ($self->get_conf('NUMBER_SECTIONS') 1063 or !defined($self->get_conf('NUMBER_SECTIONS')))) { 1064 if ($section->{'cmdname'} eq 'appendix' and $section->{'level'} == 1) { 1065 $section_title_tree = $self->gdt('Appendix {number} {section_title}', 1066 {'number' => {'text' => $section->{'number'}}, 1067 'section_title' 1068 => {'contents' => $section->{'args'}->[0]->{'contents'}}}); 1069 } else { 1070 $section_title_tree = $self->gdt('{number} {section_title}', 1071 {'number' => {'text' => $section->{'number'}}, 1072 'section_title' 1073 => {'contents' => $section->{'args'}->[0]->{'contents'}}}); 1074 } 1075 } else { 1076 $section_title_tree = {'contents' => $section->{'args'}->[0]->{'contents'}}; 1077 } 1078 my $section_title = $self->convert_line( 1079 {'contents' => [$section_title_tree], 1080 'type' => 'frenchspacing'}); 1081 pop @{$self->{'count_context'}}; 1082 my $text = $section_title; 1083 chomp ($text); 1084 $text .= "\n"; 1085 my $repeat_count = 2 * ($section->{'level'} - ($root_level+1)); 1086 ($result .= (' ' x $repeat_count)) if $repeat_count > 0; 1087 $result .= $text; 1088 $lines_count++; 1089 if ($section->{'section_childs'} 1090 and ($contents or $section->{'level'} < $root_level+1)) { 1091 $section = $section->{'section_childs'}->[0]; 1092 } elsif ($section->{'section_next'}) { 1093 last if ($section eq $top_section); 1094 $section = $section->{'section_next'}; 1095 } else { 1096 last if ($section eq $top_section); 1097 while ($section->{'section_up'}) { 1098 $section = $section->{'section_up'}; 1099 last SECTION if ($section eq $top_section); 1100 if ($section->{'section_next'}) { 1101 $section = $section->{'section_next'}; 1102 last; 1103 } 1104 } 1105 } 1106 } 1107 } 1108 return ($result, $lines_count); 1109} 1110 1111sub _menu($$) 1112{ 1113 my ($self, $menu_command) = @_; 1114 1115 if ($menu_command->{'cmdname'} eq 'menu') { 1116 my $result = "* Menu:\n\n"; 1117 _add_text_count($self, $result); 1118 _add_lines_count($self, 2); 1119 if ($self->{'node'}) { 1120 $self->{'seenmenus'}->{$self->{'node'}} = 1; 1121 } 1122 return $result; 1123 } else { 1124 return ''; 1125 } 1126} 1127 1128sub _printindex($$) 1129{ 1130 my ($self, $printindex) = @_; 1131 return $self->_printindex_formatted($printindex); 1132} 1133 1134sub _normalize_top_node($) 1135{ 1136 my $node = shift; 1137 return Texinfo::Common::normalize_top_node_name($node); 1138} 1139 1140# convert and cache a node name. $NODE is a node element. 1141sub _node_line($$) 1142{ 1143 my ($self, $node) = @_; 1144 if (!$self->{'node_lines_text'}->{$node}) { 1145 my $node_text = {'type' => '_code', 1146 'contents' => $node->{'extra'}->{'node_content'}}; 1147 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 1148 $self->{'node_lines_text'}->{$node}->{'text'} 1149 = _normalize_top_node($self->convert_line($node_text, 1150 {'suppress_styles' => 1})); 1151 _update_count_context($self); 1152 my $end_context = pop @{$self->{'count_context'}}; 1153 $self->{'node_lines_text'}->{$node}->{'count'} 1154 = $end_context->{'bytes'}; 1155 } 1156 return ($self->{'node_lines_text'}->{$node}->{'text'}, 1157 $self->{'node_lines_text'}->{$node}->{'count'}); 1158} 1159 1160my $index_length_to_node = 41; 1161 1162sub _printindex_formatted($$;$) 1163{ 1164 my ($self, $printindex, $in_info) = @_; 1165 1166 my $index_name; 1167 1168 if ($printindex->{'extra'} and $printindex->{'extra'}->{'misc_args'} 1169 and defined($printindex->{'extra'}->{'misc_args'}->[0])) { 1170 $index_name = $printindex->{'extra'}->{'misc_args'}->[0]; 1171 } else { 1172 return ''; 1173 } 1174 1175 # this is not redone for each index, only once 1176 if (!defined($self->{'index_entries'}) and $self->{'parser'}) { 1177 1178 my $index_names = $self->{'parser'}->indices_information(); 1179 my $merged_index_entries 1180 = Texinfo::Structuring::merge_indices($index_names); 1181 $self->{'index_entries'} 1182 = Texinfo::Structuring::sort_indices($self->{'parser'}, 1183 $merged_index_entries, $index_names); 1184 $self->{'index_names'} = $index_names; 1185 } 1186 if (!$self->{'index_entries'} or !$self->{'index_entries'}->{$index_name} 1187 or ! @{$self->{'index_entries'}->{$index_name}}) { 1188 return ''; 1189 } 1190 1191 my $result = ''; 1192 $result .= _add_newline_if_needed($self); 1193 if ($in_info) { 1194 my $info_printindex_magic = "\x{00}\x{08}[index\x{00}\x{08}]\n"; 1195 $result .= $info_printindex_magic; 1196 _add_text_count($self, $info_printindex_magic); 1197 _add_lines_count($self, 1); 1198 } 1199 my $heading = "* Menu:\n\n"; 1200 1201 $result .= $heading; 1202 _add_text_count($self, $heading); 1203 _add_lines_count($self, 2); 1204 1205 # first determine the line numbers for the spacing of their formatting 1206 my %line_nrs; 1207 my %entry_nodes; 1208 my $max_index_line_nr_string_length = 0; 1209 my %ignored_entries; 1210 foreach my $entry (@{$self->{'index_entries'}->{$index_name}}) { 1211 my $line_nr; 1212 1213 if (defined ($self->{'index_entries_line_location'}->{$entry->{'command'}})) { 1214 $line_nr = $self->{'index_entries_line_location'}->{$entry->{'command'}}->{'lines'}; 1215 # ignore index entries in special regions that haven't been seen 1216 } elsif ($entry->{'region'}) { 1217 $ignored_entries{$entry} = 1; 1218 next; 1219 } 1220 1221 my $node; 1222 # priority given to the location determined dynamically as the 1223 # index entry may be in footnote. 1224 if (defined($self->{'index_entries_line_location'}->{$entry->{'command'}}->{'node'})) { 1225 $node = $self->{'index_entries_line_location'}->{$entry->{'command'}}->{'node'}; 1226 } elsif (defined($entry->{'node'})) { 1227 $node = $entry->{'node'}; 1228 } 1229 $entry_nodes{$entry} = $node; 1230 if (!defined($node)) { 1231 $line_nr = 0; 1232 } elsif($in_info) { 1233 $line_nr = 3 if (defined($line_nr) and $line_nr < 3); 1234 $line_nr = 4 if (!defined($line_nr)); 1235 } else { 1236 $line_nr = 0 if (!defined($line_nr)); 1237 } 1238 my $index_line_nr_string_length = 1239 Texinfo::Convert::Unicode::string_width($line_nr); 1240 $max_index_line_nr_string_length = $index_line_nr_string_length 1241 if ($max_index_line_nr_string_length < $index_line_nr_string_length); 1242 $line_nrs{$entry} = $line_nr; 1243 } 1244 1245 # this is used to count entries that are the same 1246 my %entry_counts = (); 1247 1248 foreach my $entry (@{$self->{'index_entries'}->{$index_name}}) { 1249 next if ($ignored_entries{$entry}); 1250 my $entry_tree = {'contents' => $entry->{'content'}}; 1251 if ($entry->{'in_code'}) { 1252 $entry_tree->{'type'} = '_code'; 1253 } else { 1254 $entry_tree->{'type'} = 'frenchspacing'; 1255 } 1256 my $entry_text = ''; 1257 1258 my $formatter = $self->new_formatter('line', 1259 {'indent' => 0, 'suppress_styles' => 1}); 1260 push @{$self->{'formatters'}}, $formatter; 1261 $entry_text = $self->_convert($entry_tree); 1262 $entry_text .= $self->convert_index_subentries($entry); 1263 $entry_text .= _count_added($self, $formatter->{'container'}, 1264 Texinfo::Convert::Paragraph::end($formatter->{'container'})); 1265 pop @{$self->{'formatters'}}; 1266 1267 next if ($entry_text !~ /\S/); 1268 1269 # FIXME protect instead 1270 if ($entry_text =~ /:/ and $self->get_conf('INDEX_SPECIAL_CHARS_WARNING')) { 1271 $self->line_warn (sprintf(__("Index entry in \@%s with : produces invalid Info: %s"), 1272 $entry->{'index_at_command'}, 1273 Texinfo::Convert::Texinfo::convert($entry_tree)), 1274 $entry->{'command'}->{'line_nr'}); 1275 } 1276 1277 my $entry_nr = ''; 1278 if (!defined($entry_counts{$entry_text})) { 1279 $entry_counts{$entry_text} = 0; 1280 } else { 1281 $entry_counts{$entry_text}++; 1282 $entry_nr = ' <'.$entry_counts{$entry_text}.'>'; 1283 _add_text_count($self, $entry_nr); 1284 } 1285 my $entry_line = "* $entry_text${entry_nr}: "; 1286 _add_text_count($self, "* ".": "); 1287 #_add_text_count($self, $entry_line); 1288 1289 my $line_width = Texinfo::Convert::Unicode::string_width($entry_line); 1290 my $entry_line_addition = ''; 1291 if ($line_width < $index_length_to_node) { 1292 my $spaces = ' ' x ($index_length_to_node - $line_width); 1293 $entry_line_addition .= $spaces; 1294 _add_text_count($self, $spaces); 1295 } 1296 my $node = $entry_nodes{$entry}; 1297 1298 if (!defined($node)) { 1299 # cache the transformation to text and byte counting, as 1300 # it is likeky that there is more than one such entry 1301 if (!$self->{'outside_of_any_node_text'}) { 1302 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 1303 my $node_text = $self->gdt('(outside of any node)'); 1304 $self->{'outside_of_any_node_text'}->{'text'} 1305 = $self->convert_line($node_text); 1306 _update_count_context($self); 1307 my $end_context = pop @{$self->{'count_context'}}; 1308 $self->{'outside_of_any_node_text'}->{'count'} 1309 = $end_context->{'bytes'}; 1310 } 1311 $entry_line_addition .= $self->{'outside_of_any_node_text'}->{'text'}; 1312 $self->{'count_context'}->[-1]->{'bytes'} 1313 += $self->{'outside_of_any_node_text'}->{'count'}; 1314 # FIXME when outside of sectioning commands this message was already 1315 # done by the Parser. 1316 # Warn, only once. 1317 if (!$self->{'index_entries_no_node'}->{$entry}) { 1318 $self->line_warn(sprintf(__("entry for index `%s' outside of any node"), 1319 $index_name), $entry->{'command'}->{'line_nr'}); 1320 $self->{'index_entries_no_node'}->{$entry} = 1; 1321 } 1322 } else { 1323 my ($node_line, $byte_count) = $self->_node_line($node); 1324 $entry_line_addition .= $node_line; 1325 $self->{'count_context'}->[-1]->{'bytes'} += $byte_count; 1326 } 1327 $entry_line_addition .= '.'; 1328 _add_text_count($self, '.'); 1329 1330 $entry_line .= $entry_line_addition; 1331 $result .= $entry_line; 1332 1333 my $line_nr = $line_nrs{$entry}; 1334 my $line_nr_spaces = sprintf("%${max_index_line_nr_string_length}d", $line_nr); 1335 my $line_part = "(line ${line_nr_spaces})"; 1336 $line_width += Texinfo::Convert::Unicode::string_width($entry_line_addition); 1337 my $line_part_width = Texinfo::Convert::Unicode::string_width($line_part); 1338 if ($line_width + $line_part_width +1 > $self->{'fillcolumn'}) { 1339 $line_part = "\n" . ' ' x ($self->{'fillcolumn'} - $line_part_width) 1340 . "$line_part\n"; 1341 _add_lines_count($self, 1); 1342 } else { 1343 $line_part 1344 = ' ' x ($self->{'fillcolumn'} - $line_part_width - $line_width) 1345 . "$line_part\n"; 1346 } 1347 _add_lines_count($self, 1); 1348 _add_text_count($self, $line_part); 1349 $result .= $line_part; 1350 } 1351 1352 $result .= "\n"; 1353 _add_text_count($self, "\n"); 1354 _add_lines_count($self, 1); 1355 1356 return $result; 1357} 1358 1359 1360sub _node($$) 1361{ 1362 my $self = shift; 1363 my $node = shift; 1364 1365 return ''; 1366} 1367 1368# no error in plaintext 1369sub _error_outside_of_any_node($$) 1370{ 1371 my $self = shift; 1372 my $root = shift; 1373} 1374 1375sub _anchor($$) 1376{ 1377 my $self = shift; 1378 my $anchor = shift; 1379 1380 if (!($self->{'multiple_pass'} or $self->{'in_copying_header'})) { 1381 $self->_add_location($anchor); 1382 $self->_error_outside_of_any_node($anchor); 1383 } 1384 return ''; 1385} 1386 1387my $listoffloat_entry_length = 41; 1388my $listoffloat_append = '...'; 1389 1390sub ensure_end_of_line($$) 1391{ 1392 my ($self, $text) = @_; 1393 1394 my $chomped = chomp ($text); 1395 if ($chomped) { 1396 $self->{'count_context'}->[-1]->{'bytes'} -= count_bytes($self, $chomped); 1397 $self->{'count_context'}->[-1]->{'lines'} -= 1; 1398 } 1399 $text .= "\n"; 1400 $self->{'text_element_context'}->[-1]->{'counter'} = 0; 1401 _add_text_count($self, "\n"); 1402 _add_lines_count($self, 1); 1403 return $text; 1404} 1405 1406sub _image_text($$$) 1407{ 1408 my ($self, $root, $basefile) = @_; 1409 1410 my $txt_file = $self->Texinfo::Common::locate_include_file($basefile.'.txt'); 1411 if (!defined($txt_file)) { 1412 return undef; 1413 } else { 1414 my $filehandle = do { local *FH }; 1415 if (open ($filehandle, $txt_file)) { 1416 my $enc = $root->{'extra'}->{'input_perl_encoding'}; 1417 binmode($filehandle, ":encoding($enc)") 1418 if ($enc); 1419 my $result = ''; 1420 my $max_width = 0; 1421 while (<$filehandle>) { 1422 my $width = Texinfo::Convert::Unicode::string_width($_); 1423 if ($width > $max_width) { 1424 $max_width = $width; 1425 } 1426 $result .= $_; 1427 } 1428 # remove last end of line 1429 chomp ($result); 1430 if (!close ($filehandle)) { 1431 $self->document_warn(sprintf(__("error on closing image text file %s: %s"), 1432 $txt_file, $!)); 1433 } 1434 return ($result, $max_width); 1435 } else { 1436 $self->line_warn(sprintf(__("\@image file `%s' unreadable: %s"), 1437 $txt_file, $!), $root->{'line_nr'}); 1438 } 1439 } 1440 return undef; 1441} 1442 1443sub _image_formatted_text($$$$) 1444{ 1445 my ($self, $root, $basefile, $text) = @_; 1446 1447 my $result; 1448 if (defined($text)) { 1449 $result = $text; 1450 } elsif (defined($root->{'args'}->[3]) 1451 and @{$root->{'args'}->[3]->{'contents'}}) { 1452 $result = '[' .Texinfo::Convert::Text::convert( 1453 {'contents' => $root->{'args'}->[3]->{'contents'}}, 1454 $self->{'convert_text_options'}) .']'; 1455 } else { 1456 $self->line_warn(sprintf(__( 1457 "could not find \@image file `%s.txt' nor alternate text"), 1458 $basefile), $root->{'line_nr'}); 1459 $result = '['.$basefile.']'; 1460 } 1461 return $result; 1462} 1463 1464sub _image($$) 1465{ 1466 my ($self, $root) = @_; 1467 1468 if (defined($root->{'args'}->[0]) 1469 and @{$root->{'args'}->[0]->{'contents'}}) { 1470 my $basefile = Texinfo::Convert::Text::convert( 1471 {'contents' => $root->{'args'}->[0]->{'contents'}}, 1472 {'code' => 1, %{$self->{'convert_text_options'}}}); 1473 my ($text, $width) = $self->_image_text($root, $basefile); 1474 my $result = $self->_image_formatted_text($root, $basefile, $text); 1475 my $lines_count = ($result =~ tr/\n/\n/); 1476 if (!defined($width)) { 1477 $width = Texinfo::Convert::Unicode::string_width($result); 1478 } 1479 # the last line is part of the image but do not have a new line, 1480 # so 1 is added to $lines_count to have the number of lines of 1481 # the image 1482 $self->_add_image($root, $lines_count+1, $width); 1483 return ($result, $lines_count); 1484 } 1485 return ('', 0); 1486} 1487 1488sub _get_form_feeds($) 1489{ 1490 my $form_feeds = shift; 1491 $form_feeds =~ s/^[^\f]*//; 1492 $form_feeds =~ s/[^\f]$//; 1493 return $form_feeds; 1494} 1495 1496sub _convert($$); 1497 1498# Convert the Texinfo tree under $ROOT to plain text. 1499sub _convert($$) 1500{ 1501 my ($self, $root) = @_; 1502 1503 my $formatter = $self->{'formatters'}->[-1]; 1504 1505 if (($root->{'type'} and $self->{'ignored_types'}->{$root->{'type'}}) 1506 or ($root->{'cmdname'} 1507 and ($self->{'ignored_commands'}->{$root->{'cmdname'}} 1508 or ($inline_commands{$root->{'cmdname'}} 1509 and $root->{'cmdname'} ne 'inlinefmtifelse' 1510 and (($inline_format_commands{$root->{'cmdname'}} 1511 and (!$root->{'extra'}->{'format'} 1512 or !$self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}})) 1513 or (!$inline_format_commands{$root->{'cmdname'}} 1514 and !defined($root->{'extra'}->{'expand_index'}))))))) { 1515 return ''; 1516 } 1517 my $result = ''; 1518 1519 my $type = $root->{'type'}; 1520 my $command = $root->{'cmdname'}; 1521 1522 # in ignorable spaces, keep only form feeds. 1523 if ($type and $self->{'ignorable_space_types'}->{$type} 1524 and ($type ne 'empty_spaces_before_paragraph' 1525 or $self->get_conf('paragraphindent') ne 'asis')) { 1526 if ($root->{'text'} =~ /\f/) { 1527 $result = _get_form_feeds($root->{'text'}); 1528 } 1529 _add_text_count($self, $result); 1530 return $result; 1531 } 1532 1533 # First handle empty lines. This has to be done before the handling 1534 # of text below to be sure that an empty line is always processed 1535 # especially 1536 if ($type and ($type eq 'empty_line' 1537 or $type eq 'after_description_line')) { 1538 delete $self->{'text_element_context'}->[-1]->{'counter'}; 1539 $self->{'empty_lines_count'}++; 1540 if ($self->{'empty_lines_count'} <= 1 1541 or $self->{'preformatted_context_commands'}->{$self->{'context'}->[-1]}) { 1542 $result = ""; 1543 if ($root->{'text'} =~ /\f/) { 1544 $result .= _get_form_feeds($root->{'text'}); 1545 _add_text_count($self, $result); 1546 } 1547 $result .= _count_added($self, $formatter->{'container'}, 1548 add_text($formatter->{'container'}, "\n")); 1549 return $result; 1550 } else { 1551 return ''; 1552 } 1553 } 1554 1555 # process text 1556 if (defined($root->{'text'})) { 1557 if (!$type or $type ne 'untranslated') { 1558 if (!$formatter->{'_top_formatter'}) { 1559 if ($type and ($type eq 'raw' 1560 or $type eq 'last_raw_newline')) { 1561 $result = _count_added($self, $formatter->{'container'}, 1562 add_next($formatter->{'container'}, $root->{'text'})); 1563 } else { 1564 my $text = _process_text($self, $root, $formatter); 1565 $result = _count_added($self, $formatter->{'container'}, 1566 add_text ($formatter->{'container'}, $text)); 1567 } 1568 return $result; 1569 # the following is only possible if paragraphindent is set to asis 1570 } elsif ($type and $type eq 'empty_spaces_before_paragraph') { 1571 _add_text_count($self, $root->{'text'}); 1572 return $root->{'text'}; 1573 # ignore text outside of any format, but warn if ignored text not empty 1574 } elsif ($root->{'text'} =~ /\S/) { 1575 $self->_bug_message("ignored text not empty `$root->{'text'}'", $root); 1576 return ''; 1577 } else { 1578 # miscellaneous top-level whitespace - possibly after an @image 1579 return _count_added($self, $formatter->{'container'}, 1580 add_text($formatter->{'container'}, $root->{'text'})); 1581 } 1582 } else { 1583 my $tree = $self->gdt($root->{'text'}); 1584 my $converted = _convert($self, $tree); 1585 return $converted; 1586 } 1587 } 1588 1589 if ($root->{'extra'}) { 1590 if ($root->{'extra'}->{'missing_argument'} 1591 and (!$root->{'contents'} or !@{$root->{'contents'}})) { 1592 return ''; 1593 } 1594 } 1595 1596 if ($root->{'extra'} and $root->{'extra'}->{'index_entry'} 1597 and !$self->{'multiple_pass'} and !$self->{'in_copying_header'}) { 1598 my $location = $self->_add_location($root); 1599 # remove a 'lines' from $location if at the very end of a node 1600 # since it will lead to the next node otherwise. 1601 if ($command and $command =~ /index/) { 1602 my $following_not_empty; 1603 my @parents = @{$self->{'current_roots'}}; 1604 my @parent_contents = @{$self->{'current_contents'}}; 1605 while (@parents) { 1606 my $parent = pop @parents; 1607 my $parent_content = pop @parent_contents; 1608 if ($parent->{'type'} and $parent->{'type'} eq 'paragraph') { 1609 $following_not_empty = 1; 1610 last; 1611 } 1612 foreach my $following_content (@$parent_content) { 1613 unless (($following_content->{'type'} 1614 and ($following_content->{'type'} eq 'empty_line' 1615 or $ignorable_types{$following_content->{'type'}})) 1616 or ($following_content->{'cmdname'} 1617 and ($following_content->{'cmdname'} eq 'c' 1618 or $following_content->{'cmdname'} eq 'comment'))) { 1619 $following_not_empty = 1; 1620 last; 1621 } 1622 } 1623 last if $following_not_empty; 1624 if ($parent->{'cmdname'} and $root_commands{$parent->{'cmdname'}}) { 1625 last; 1626 } 1627 } 1628 if (! $following_not_empty) { 1629 $location->{'lines'}--; 1630 } 1631 } 1632 # this covers the special case for index entry not associated with a 1633 # node but seen. this will be an index entry in @copying, 1634 # in @insertcopying. 1635 # This also covers the case of an index entry in a node added by a 1636 # @footnote with footnotestyle separate. 1637 if ($self->{'node'}) { 1638 $location->{'node'} = $self->{'node'}; 1639 } 1640 $self->{'index_entries_line_location'}->{$root} = $location; 1641 } 1642 1643 my $cell; 1644 my $preformatted; 1645 if ($command) { 1646 my $unknown_command; 1647 if (defined($no_brace_commands{$command})) { 1648 if ($command eq ':') { 1649 remove_end_sentence($formatter->{'container'}); 1650 return ''; 1651 } elsif ($command eq '*') { 1652 $result = _count_added($self, $formatter->{'container'}, 1653 add_pending_word($formatter->{'container'})); 1654 $result .= _count_added($self, $formatter->{'container'}, 1655 end_line($formatter->{'container'})); 1656 } elsif ($command eq '.' or $command eq '?' or $command eq '!') { 1657 $result .= _count_added($self, $formatter->{'container'}, 1658 add_next($formatter->{'container'}, $command)); 1659 add_end_sentence($formatter->{'container'}, 1); 1660 } elsif ($command eq ' ' or $command eq "\n" or $command eq "\t") { 1661 $result .= _count_added($self, $formatter->{'container'}, 1662 add_next($formatter->{'container'}, $no_brace_commands{$command})); 1663 } else { 1664 $result .= _count_added($self, $formatter->{'container'}, 1665 add_text($formatter->{'container'}, $no_brace_commands{$command})); 1666 } 1667 return $result; 1668 } elsif ($command eq 'today') { 1669 my $today = $self->Texinfo::Common::expand_today(); 1670 unshift @{$self->{'current_contents'}->[-1]}, $today; 1671 } elsif (exists($brace_no_arg_commands{$command})) { 1672 my $text; 1673 1674 if ($command eq 'dots' or $command eq 'enddots') { 1675 # Don't use Unicode ellipsis character. 1676 $text = '...'; 1677 } else { 1678 $text = Texinfo::Convert::Text::brace_no_arg_command($root, 1679 $self->{'convert_text_options'}); 1680 } 1681 1682 # @AA{} should suppress an end sentence, @aa{} shouldn't. This 1683 # is the case whether we are in @sc or not. 1684 if ($formatter->{'upper_case'} 1685 and $letter_no_arg_commands{$command}) { 1686 $text = _protect_sentence_ends($text); 1687 $text = uc($text); 1688 } 1689 1690 if ($punctuation_no_arg_commands{$command}) { 1691 $result .= _count_added($self, $formatter->{'container'}, 1692 add_next($formatter->{'container'}, $text)); 1693 add_end_sentence($formatter->{'container'}, 1); 1694 } elsif ($command eq 'tie') { 1695 $formatter->{'w'}++; 1696 set_space_protection($formatter->{'container'}, 1, undef) 1697 if ($formatter->{'w'} == 1); 1698 $result .= _count_added($self, $formatter->{'container'}, 1699 add_text($formatter->{'container'}, $text)); 1700 $formatter->{'w'}--; 1701 set_space_protection($formatter->{'container'}, 0, undef) 1702 if ($formatter->{'w'} == 0); 1703 } else { 1704 $result .= _count_added($self, $formatter->{'container'}, 1705 add_text($formatter->{'container'}, $text)); 1706 1707 # This is to have @TeX{}, for example, not to prevent end sentences. 1708 if (!$letter_no_arg_commands{$command}) { 1709 allow_end_sentence($formatter->{'container'}); 1710 } 1711 1712 if ($command eq 'dots') { 1713 remove_end_sentence($formatter->{'container'}); 1714 } 1715 } 1716 if ($formatter->{'var'} 1717 or $formatter->{'font_type_stack'}->[-1]->{'monospace'}) { 1718 allow_end_sentence($formatter->{'container'}); 1719 } 1720 return $result; 1721 # commands with braces 1722 } elsif ($accent_commands{$command}) { 1723 my $encoding; 1724 if ($self->{'enable_encoding'}) { 1725 $encoding = $self->{'output_encoding_name'}; 1726 } 1727 my $sc; 1728 if ($formatter->{'upper_case'}) { 1729 $sc = 1; 1730 } 1731 my $accented_text 1732 = Texinfo::Convert::Text::text_accents($root, $encoding, $sc); 1733 $result .= _count_added($self, $formatter->{'container'}, 1734 add_text($formatter->{'container'}, $accented_text)); 1735 1736 my $accented_text_original; 1737 if ($formatter->{'upper_case'}) { 1738 $accented_text_original 1739 = Texinfo::Convert::Text::text_accents($root, $encoding); 1740 } 1741 1742 if ($accented_text_original 1743 and $accented_text_original !~ /[[:upper:]]/ 1744 or $formatter->{'var'} 1745 or $formatter->{'font_type_stack'}->[-1]->{'monospace'}) { 1746 allow_end_sentence($formatter->{'container'}); 1747 } 1748 1749 # in case the text added ends with punctuation. 1750 # If the text is empty (likely because of an error) previous 1751 # punctuation will be cancelled, we don't want that. 1752 remove_end_sentence($formatter->{'container'}) 1753 if ($accented_text ne ''); 1754 return $result; 1755 } elsif ($self->{'style_map'}->{$command} 1756 or ($root->{'type'} and $root->{'type'} eq 'definfoenclose_command')) { 1757 if ($code_style_commands{$command}) { 1758 if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { 1759 push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; 1760 } else { 1761 $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; 1762 } 1763 } elsif ($regular_font_style_commands{$command}) { 1764 if ($formatter->{'font_type_stack'}->[-1]->{'monospace'}) { 1765 push @{$formatter->{'font_type_stack'}}, {'monospace' => 0, 1766 'normal' => 1}; 1767 } elsif ($formatter->{'font_type_stack'}->[-1]->{'normal'}) { 1768 $formatter->{'font_type_stack'}->[-1]->{'normal'}++; 1769 } 1770 } 1771 if ($no_punctation_munging_commands{$command}) { 1772 push @{$formatter->{'frenchspacing_stack'}}, 'on'; 1773 set_space_protection($formatter->{'container'}, undef, 1774 undef,undef,1); 1775 } 1776 if ($upper_case_commands{$command}) { 1777 $formatter->{'upper_case'}++; 1778 $formatter->{'var'}++ if ($command eq 'var'); 1779 } 1780 if ($command eq 'w') { 1781 $formatter->{'w'}++; 1782 set_space_protection($formatter->{'container'}, 1,undef) 1783 if ($formatter->{'w'} == 1); 1784 } 1785 my ($text_before, $text_after); 1786 if ($root->{'type'} and $root->{'type'} eq 'definfoenclose_command') { 1787 $text_before = $root->{'extra'}->{'begin'}; 1788 $text_after = $root->{'extra'}->{'end'}; 1789 } elsif ($non_quoted_commands_when_nested{$command} 1790 and $formatter->{'font_type_stack'}->[-1]->{'code_command'}) { 1791 $text_before = ''; 1792 $text_after = ''; 1793 } elsif ($formatter->{'suppress_styles'} 1794 and !$index_style_commands{$command}) { 1795 $text_before = ''; 1796 $text_after = ''; 1797 } else { 1798 $text_before = $self->{'style_map'}->{$command}->[0]; 1799 $text_after = $self->{'style_map'}->{$command}->[1]; 1800 } 1801 # do this after determining $text_before/$text_after such that it 1802 # doesn't impact the current command, but only commands nested within 1803 if ($non_quoted_commands_when_nested{$command}) { 1804 $formatter->{'font_type_stack'}->[-1]->{'code_command'}++; 1805 } 1806 $result .= _count_added($self, $formatter->{'container'}, 1807 add_next($formatter->{'container'}, $text_before, 1)) 1808 if ($text_before ne ''); 1809 if ($root->{'args'}) { 1810 $result .= _convert($self, $root->{'args'}->[0]); 1811 if ($command eq 'strong' 1812 and scalar (@{$root->{'args'}->[0]->{'contents'}}) 1813 and $root->{'args'}->[0]->{'contents'}->[0]->{'text'} 1814 and $root->{'args'}->[0]->{'contents'}->[0]->{'text'} =~ /^Note\s/i 1815 and $self->{'output_format'} 1816 and $self->{'output_format'} eq 'info') { 1817 $self->line_warn(__( 1818 "\@strong{Note...} produces a spurious cross-reference in Info; reword to avoid that"), 1819 $root->{'line_nr'}); 1820 } 1821 } 1822 $result .= _count_added($self, $formatter->{'container'}, 1823 add_next($formatter->{'container'}, $text_after, 1)) 1824 if ($text_after ne ''); 1825 if ($command eq 'w') { 1826 $formatter->{'w'}--; 1827 set_space_protection($formatter->{'container'},0,undef) 1828 if ($formatter->{'w'} == 0); 1829 } 1830 if ($code_style_commands{$command}) { 1831 $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; 1832 allow_end_sentence($formatter->{'container'}); 1833 pop @{$formatter->{'font_type_stack'}} 1834 if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; 1835 } elsif ($regular_font_style_commands{$command}) { 1836 if ($formatter->{'font_type_stack'}->[-1]->{'normal'}) { 1837 $formatter->{'font_type_stack'}->[-1]->{'normal'}--; 1838 pop @{$formatter->{'font_type_stack'}} 1839 if !$formatter->{'font_type_stack'}->[-1]->{'normal'}; 1840 } 1841 } 1842 if ($non_quoted_commands_when_nested{$command}) { 1843 #$formatter->{'code_command'}--; 1844 $formatter->{'font_type_stack'}->[-1]->{'code_command'}--; 1845 } 1846 if ($no_punctation_munging_commands{$command}) { 1847 pop @{$formatter->{'frenchspacing_stack'}}; 1848 my $frenchspacing = 0; 1849 $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); 1850 set_space_protection($formatter->{'container'}, undef, 1851 undef, undef, $frenchspacing); 1852 } 1853 if ($upper_case_commands{$command}) { 1854 $formatter->{'upper_case'}--; 1855 if ($command eq 'var') { 1856 $formatter->{'var'}--; 1857 # Allow a following full stop to terminate a sentence. 1858 allow_end_sentence($formatter->{'container'}); 1859 } 1860 } 1861 return $result; 1862 } elsif ($command eq 'image') { 1863 $result = _count_added($self, $formatter->{'container'}, 1864 add_pending_word($formatter->{'container'}, 1)); 1865 # add an empty word so that following spaces aren't lost 1866 add_next($formatter->{'container'},''); 1867 my ($image, $lines_count) = $self->_image($root); 1868 _add_lines_count($self, $lines_count); 1869 _add_text_count($self, $image); 1870 if ($image ne '' and $formatter->{'type'} ne 'paragraph') { 1871 $self->{'empty_lines_count'} = 0; 1872 } 1873 $result .= $image; 1874 return $result; 1875 } elsif ($command eq 'email') { 1876 # nothing is output for email, instead the command is substituted. 1877 my @email_contents; 1878 if ($root->{'args'}) { 1879 my $name; 1880 my $email; 1881 if (scalar (@{$root->{'args'}}) == 2 1882 and defined($root->{'args'}->[1]) 1883 and @{$root->{'args'}->[1]->{'contents'}}) { 1884 $name = $root->{'args'}->[1]->{'contents'}; 1885 } 1886 if (defined($root->{'args'}->[0]) 1887 and @{$root->{'args'}->[0]->{'contents'}}) { 1888 $email = $root->{'args'}->[0]->{'contents'}; 1889 } 1890 my $prepended; 1891 if ($name and $email) { 1892 $prepended = $self->gdt('{name} @url{{email}}', 1893 {'name' => $name, 'email' => $email}); 1894 } elsif ($email) { 1895 $prepended = $self->gdt('@url{{email}}', 1896 {'email' => $email}); 1897 } elsif ($name) { 1898 $prepended = {'contents' => $name}; 1899 } else { 1900 return ''; 1901 } 1902 unshift @{$self->{'current_contents'}->[-1]}, $prepended; 1903 } 1904 return ''; 1905 } elsif ($command eq 'uref' or $command eq 'url') { 1906 if ($root->{'args'}) { 1907 if (scalar(@{$root->{'args'}}) == 3 1908 and defined($root->{'args'}->[2]) 1909 and @{$root->{'args'}->[2]->{'contents'}}) { 1910 unshift @{$self->{'current_contents'}->[-1]}, 1911 {'contents' => $root->{'args'}->[2]->{'contents'}}; 1912 } elsif (@{$root->{'args'}->[0]->{'contents'}}) { 1913 # no mangling of --- and similar in url. 1914 my $url = {'type' => '_code', 1915 'contents' => $root->{'args'}->[0]->{'contents'}}; 1916 if (scalar(@{$root->{'args'}}) == 2 1917 and defined($root->{'args'}->[1]) 1918 and @{$root->{'args'}->[1]->{'contents'}}) { 1919 my $prepended = $self->gdt('{text} ({url})', 1920 {'text' => $root->{'args'}->[1]->{'contents'}, 1921 'url' => $url }); 1922 unshift @{$self->{'current_contents'}->[-1]}, $prepended; 1923 } else { 1924 my $prepended = $self->gdt('@t{<{url}>}', 1925 {'url' => $url}); 1926 unshift @{$self->{'current_contents'}->[-1]}, $prepended 1927 } 1928 } elsif (scalar(@{$root->{'args'}}) == 2 1929 and defined($root->{'args'}->[1]) 1930 and @{$root->{'args'}->[1]->{'contents'}}) { 1931 unshift @{$self->{'current_contents'}->[-1]}, 1932 {'contents' => $root->{'args'}->[1]->{'contents'}}; 1933 } 1934 } 1935 return ''; 1936 } elsif ($command eq 'footnote') { 1937 $self->{'footnote_index'}++ unless ($self->{'multiple_pass'}); 1938 my $formatted_footnote_number; 1939 if ($self->get_conf('NUMBER_FOOTNOTES')) { 1940 $formatted_footnote_number = $self->{'footnote_index'}; 1941 } else { 1942 $formatted_footnote_number = $NO_NUMBER_FOOTNOTE_SYMBOL; 1943 } 1944 push @{$self->{'pending_footnotes'}}, {'root' => $root, 1945 'number' => $self->{'footnote_index'}} 1946 unless ($self->{'multiple_pass'}); 1947 if (!$self->{'in_copying_header'}) { 1948 $self->_error_outside_of_any_node($root); 1949 } 1950 $result .= _count_added($self, $formatter->{'container'}, 1951 add_next($formatter->{'container'}, 1952 "($formatted_footnote_number)", 1)); 1953 if ($self->get_conf('footnotestyle') eq 'separate' and $self->{'node'}) { 1954 $result .= _convert($self, {'contents' => 1955 [{'text' => ' ('}, 1956 {'cmdname' => 'pxref', 1957 'args' => [ 1958 {'type' => 'brace_command_arg', 1959 'contents' => [ 1960 @{$self->{'node'}->{'extra'}->{'node_content'}}, 1961 {'text' => "-Footnote-$self->{'footnote_index'}"} 1962 ] 1963 } 1964 ] 1965 }, 1966 {'text' => ')'}], 1967 }); 1968 } 1969 return $result; 1970 } elsif ($command eq 'anchor') { 1971 $result = _count_added($self, $formatter->{'container'}, 1972 add_pending_word($formatter->{'container'})); 1973 $result .= $self->_anchor($root); 1974 return $result; 1975 } elsif ($ref_commands{$command}) { 1976 if (scalar(@{$root->{'args'}})) { 1977 my @args; 1978 for my $a (@{$root->{'args'}}) { 1979 if (defined $a->{'contents'} and @{$a->{'contents'}}) { 1980 push @args, $a->{'contents'}; 1981 } else { 1982 push @args, undef; 1983 } 1984 } 1985 $args[0] = [{'text' => ''}] if (!defined($args[0])); 1986 1987 # normalize node name, to get a ref with the right formatting 1988 # NOTE as a consequence, the line numbers appearing in case of errors 1989 # correspond to the node lines numbers, and not the @ref. 1990 my $node_content; 1991 if ($root->{'extra'} 1992 and $root->{'extra'}->{'label'}) { 1993 $node_content = $root->{'extra'}->{'label'}->{'extra'}->{'node_content'}; 1994 } else { 1995 $node_content = $args[0]; 1996 } 1997 1998 # if it a reference to a float with a label, $arg[1] is 1999 # set to '$type $number' or '$number' if there is no type. 2000 if (! defined($args[1]) 2001 and $root->{'extra'} 2002 and $root->{'extra'}->{'label'} 2003 and $root->{'extra'}->{'label'}->{'cmdname'} 2004 and $root->{'extra'}->{'label'}->{'cmdname'} eq 'float') { 2005 my $float = $root->{'extra'}->{'label'}; 2006 2007 my $name = $self->_float_type_number($float); 2008 $args[1] = $name->{'contents'}; 2009 } 2010 if ($command eq 'inforef' and scalar(@args) == 3) { 2011 $args[3] = $args[2]; 2012 $args[2] = undef; 2013 } 2014 2015 # Treat cross-reference commands in a multitable cell as if they 2016 # were surrounded by @w{ ... }, so the output will not be split across 2017 # lines, leading text from other columns appearing to be part of the 2018 # cross-reference. 2019 my $in_multitable = 0; 2020 if ($self->{'document_context'}->[-1]->{'in_multitable'}) { 2021 $in_multitable = 1; 2022 $formatter->{'w'}++; 2023 set_space_protection($formatter->{'container'}, 1, undef) 2024 if ($formatter->{'w'} == 1); 2025 } 2026 # Disallow breaks in runs of Chinese text in node names, because a 2027 # break would be normalized to a single space by the Info reader, and 2028 # the node wouldn't be found. 2029 set_space_protection($formatter->{'container'}, 2030 undef, undef, undef, undef, 1); 2031 2032 if ($command eq 'xref') { 2033 $result = _convert($self, {'contents' => [{'text' => '*Note '}]}); 2034 } else { 2035 $result = _convert($self, {'contents' => [{'text' => '*note '}]}); 2036 } 2037 my $name; 2038 if (defined($args[1])) { 2039 $name = $args[1]; 2040 } elsif (defined($args[2])) { 2041 $name = $args[2]; 2042 } 2043 my $file; 2044 if (defined($args[3])) { 2045 $file = [{'text' => '('}, 2046 {'type' => '_code', 2047 'contents' => $args[3]}, 2048 {'text' => ')'},]; 2049 } elsif (defined($args[4])) { 2050 # add a () such that the node is considered to be external, 2051 # even though the manual name is not known. 2052 $file = [{'text' => '()'}]; 2053 } 2054 2055 if ($name) { 2056 my $name_text = _convert($self, {'contents' => $name}); 2057 # needed, as last word is added only when : is added below 2058 my $name_text_checked = $name_text 2059 .get_pending($self->{'formatters'}->[-1]->{'container'}); 2060 my $quoting_required = 0; 2061 if ($name_text_checked =~ /:/m) { 2062 if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { 2063 $self->line_warn(sprintf(__( 2064 "\@%s cross-reference name should not contain `:'"), 2065 $command), $root->{'line_nr'}); 2066 } 2067 if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { 2068 $quoting_required = 1; 2069 } 2070 } 2071 my $pre_quote = $quoting_required ? "\x{7f}" : ''; 2072 my $post_quote = $pre_quote; 2073 $name_text .= _convert($self, {'contents' => [ 2074 {'text' => "$post_quote: "}]}); 2075 $name_text =~ s/^(\s*)/$1$pre_quote/ if $pre_quote; 2076 $result .= $name_text; 2077 _count_added($self,$self->{'formatters'}[-1]{'container'}, 2078 $pre_quote) 2079 if $pre_quote; 2080 2081 if ($file) { 2082 $result .= _convert($self, {'contents' => $file}); 2083 } 2084 # node name 2085 $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; 2086 2087 my $maybe_file 2088 = get_pending($self->{'formatters'}->[-1]->{'container'}); 2089 my $node_text = _convert($self, {'type' => '_code', 2090 'contents' => $node_content}); 2091 delete $self->{'formatters'}->[-1]->{'suppress_styles'}; 2092 2093 my $node_text_checked = $node_text 2094 .get_pending($self->{'formatters'}->[-1]->{'container'}); 2095 $maybe_file =~ s/^\s*//; 2096 $maybe_file = quotemeta $maybe_file; 2097 $node_text_checked =~ s/^\s*$maybe_file//; 2098 $quoting_required = 0; 2099 if ($node_text_checked =~ /([,\t\.])/m ) { 2100 if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { 2101 $self->line_warn(sprintf(__( 2102 "\@%s node name should not contain `%s'"), $command, $1), 2103 $root->{'line_nr'}); 2104 } 2105 if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { 2106 $quoting_required = 1; 2107 } 2108 } 2109 $pre_quote = $quoting_required ? "\x{7f}" : ''; 2110 $post_quote = $pre_quote; 2111 if ($pre_quote) { 2112 $node_text =~ s/^(\s*)/$1$pre_quote/; 2113 _count_added($self,$self->{'formatters'}[-1]{'container'}, 2114 $pre_quote); 2115 } 2116 $result .= $node_text; 2117 _count_added($self, $self->{'formatters'}[-1]{'container'}, 2118 add_next($self->{'formatters'}->[-1]->{'container'}, $post_quote)) 2119 if $post_quote; 2120 } else { # Label same as node specification 2121 if ($file) { 2122 $result .= _convert($self, {'contents' => $file}); 2123 } 2124 $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; 2125 my $node_text = _convert($self, {'type' => '_code', 2126 'contents' => $node_content}); 2127 delete $self->{'formatters'}->[-1]->{'suppress_styles'}; 2128 2129 my $node_text_checked = $node_text 2130 .get_pending($self->{'formatters'}->[-1]->{'container'}); 2131 my $quoting_required = 0; 2132 if ($node_text_checked =~ /:/m) { 2133 if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { 2134 $self->line_warn(sprintf(__( 2135 "\@%s node name should not contain `:'"), $command), 2136 $root->{'line_nr'}); 2137 } 2138 if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { 2139 $quoting_required = 1; 2140 } 2141 } 2142 my $pre_quote = $quoting_required ? "\x{7f}" : ''; 2143 my $post_quote = $pre_quote; 2144 $node_text .= _convert($self, {'contents' => [ 2145 {'text' => "${post_quote}::"}]}); 2146 _count_added($self,$self->{'formatters'}[-1]{'container'}, 2147 $pre_quote) 2148 if $pre_quote; 2149 if ($pre_quote) { 2150 # This is needed to get a pending word. We could use 2151 # add_pending_word, but that would not include following 2152 # punctuation in the word. 2153 my $next = $self->{'current_contents'}->[-1]->[0]; 2154 if ($next) { 2155 $node_text .= _convert($self, $next); 2156 shift @{$self->{'current_contents'}->[-1]}; 2157 } 2158 2159 $node_text =~ s/^(\s*)/$1$pre_quote/; 2160 } 2161 $result .= $node_text; 2162 } 2163 # we could use $formatter, but in case it was changed in _convert 2164 # we play it safe. 2165 my $pending = $result 2166 .get_pending($self->{'formatters'}->[-1]->{'container'}); 2167 2168 # If command is @xref, the punctuation must always follow the 2169 # command, for other commands it may be in the argument, hence the 2170 # use of $pending. 2171 # FIXME: is @xref really special here? 2172 if ($name and ($command eq 'xref' 2173 or ($pending !~ /[\.,]$/ and $pending !~ /::$/))) { 2174 my $next = $self->{'current_contents'}->[-1]->[0]; 2175 if (!($next and $next->{'text'} and $next->{'text'} =~ /^[\.,]/)) { 2176 if ($command eq 'xref') { 2177 if ($next and defined($next->{'text'}) and $next->{'text'} =~ /\S/) { 2178 my $text = $next->{'text'}; 2179 $text =~ s/^\s*//; 2180 my $char = substr($text, 0, 1); 2181 $self->line_warn(sprintf(__( 2182 "`.' or `,' must follow \@xref, not %s"), 2183 $char), $root->{'line_nr'}); 2184 } else { 2185 $self->line_warn(__("`.' or `,' must follow \@xref"), 2186 $root->{'line_nr'}); 2187 } 2188 } 2189 my @added = ({'text' => '.'}); 2190 # The added full stop does not end a sentence. Info readers will 2191 # have a chance of guessing correctly whether the full stop was 2192 # added by whether it is followed by 2 spaces (although this 2193 # doesn't help at the end of a line). 2194 push @added, {'cmdname' => ':'}; 2195 unshift @{$self->{'current_contents'}->[-1]}, @added; 2196 } 2197 } 2198 2199 if ($in_multitable) { 2200 $formatter->{'w'}--; 2201 set_space_protection($formatter->{'container'}, 0, undef) 2202 if ($formatter->{'w'} == 0); 2203 } 2204 set_space_protection($formatter->{'container'}, 2205 undef,undef,undef,undef,0); # double_width_no_break 2206 return $result; 2207 } 2208 return ''; 2209 } elsif ($explained_commands{$command}) { 2210 if ($root->{'args'} 2211 and defined($root->{'args'}->[0]) 2212 and @{$root->{'args'}->[0]->{'contents'}}) { 2213 # in abbr spaces never end a sentence. 2214 my $argument; 2215 if ($command eq 'abbr') { 2216 $argument = {'type' => 'frenchspacing', 2217 'contents' => $root->{'args'}->[0]->{'contents'}}; 2218 } else { 2219 $argument = { 'contents' => $root->{'args'}->[0]->{'contents'}}; 2220 } 2221 if (scalar (@{$root->{'args'}}) == 2 2222 and defined($root->{'args'}->[-1]) 2223 and @{$root->{'args'}->[-1]->{'contents'}}) { 2224 my $prepended = $self->gdt('{abbr_or_acronym} ({explanation})', 2225 {'abbr_or_acronym' => $argument, 2226 'explanation' => $root->{'args'}->[-1]->{'contents'}}); 2227 unshift @{$self->{'current_contents'}->[-1]}, $prepended; 2228 return ''; 2229 } else { 2230 $result = _convert($self, $argument); 2231 2232 # We want to permit an end of sentence, but not force it as @. does. 2233 allow_end_sentence($formatter->{'container'}); 2234 return $result; 2235 } 2236 } 2237 return ''; 2238 } elsif ($inline_commands{$command}) { 2239 my $arg_index = 1; 2240 if ($command eq 'inlinefmtifelse' 2241 and (!$root->{'extra'}->{'format'} 2242 or !$self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}})) { 2243 $arg_index = 2; 2244 } 2245 if (scalar(@{$root->{'args'}}) > $arg_index 2246 and defined($root->{'args'}->[$arg_index]) 2247 and @{$root->{'args'}->[$arg_index]->{'contents'}}) { 2248 my $argument; 2249 if ($command eq 'inlineraw') { 2250 $argument->{'type'} = '_code'; 2251 } 2252 $argument->{'contents'} = $root->{'args'}->[$arg_index]->{'contents'}; 2253 unshift @{$self->{'current_contents'}->[-1]}, ($argument); 2254 } 2255 return ''; 2256 # condition should actually be that the $command is inline 2257 } elsif ($math_commands{$command} and not exists($block_commands{$command})) { 2258 push @{$self->{'context'}}, $command; 2259 if ($root->{'args'}) { 2260 $result .= _convert($self, {'type' => 'frenchspacing', 2261 'contents' => [{'type' => '_code', 2262 'contents' => [$root->{'args'}->[0]]}]}); 2263 } 2264 my $old_context = pop @{$self->{'context'}}; 2265 die if ($old_context ne $command); 2266 return $result; 2267 } elsif ($command eq 'titlefont') { 2268 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 2269 $result = $self->convert_line ({'type' => 'frenchspacing', 2270 'contents' => [$root->{'args'}->[0]]}); 2271 pop @{$self->{'count_context'}}; 2272 $result = Texinfo::Convert::Text::heading({'level' => 0, 2273 'cmdname' => 'titlefont'}, $result, $self, 2274 $self->get_conf('NUMBER_SECTIONS'), 2275 ($self->{'format_context'}->[-1]->{'indent_level'}) * $indent_length); 2276 $result =~ s/\n$//; # final newline has its own tree element 2277 $self->{'empty_lines_count'} = 0 unless ($result eq ''); 2278 _add_text_count($self, $result); 2279 _add_lines_count($self, 1); 2280 return $result; 2281 2282 } elsif ($command eq 'U') { 2283 my $arg; 2284 if ($root->{'args'} 2285 and $root->{'args'}->[0] 2286 and $root->{'args'}->[0]->{'contents'} 2287 and $root->{'args'}->[0]->{'contents'}->[0] 2288 and $root->{'args'}->[0]->{'contents'}->[0]->{'text'}) { 2289 $arg = $root->{'args'}->[0]->{'contents'}->[0]->{'text'}; 2290 } 2291 if ($arg) { 2292 # Syntactic checks on the value were already done in Parser.pm, 2293 # but we have one more thing to test: since this is the one 2294 # place where we might output actual UTF-8 binary bytes, we have 2295 # to check that chr(hex($arg)) is valid. Perl gives a warning 2296 # and will not output UTF-8 for Unicode non-characters such as 2297 # U+10FFFF. In this case, silently fall back to plain text, on 2298 # the theory that the user wants something. 2299 my $res; 2300 if ($self->{'to_utf8'}) { 2301 my $error = 0; 2302 # The warning about non-characters is only given when the code 2303 # point is attempted to be output, not just manipulated. 2304 # http://stackoverflow.com/questions/5127725/how-could-i-catch-an-unicode-non-character-warning 2305 # 2306 # Therefore, we have to try to output it within an eval. 2307 # Since opening /dev/null or a temporary file means 2308 # more system-dependent checks, use a string as our 2309 # filehandle. 2310 eval { 2311 use warnings FATAL => qw(all); 2312 my ($fh, $string); 2313 open($fh, ">", \$string) || die "open(U string eval) failed: $!"; 2314 binmode($fh, ":utf8") || die "binmode(U string eval) failed: $!"; 2315 print $fh chr(hex("$arg")); 2316 }; 2317 if ($@) { 2318 warn "\@U chr(hex($arg)) eval failed: $@\n" if ($self->{'DEBUG'}); 2319 $error = 1; 2320 } elsif (hex($arg) > 0x10FFFF) { 2321 # The check above appears not to work in older versions of perl, 2322 # so check the argument is not greater the maximum Unicode code 2323 # point. 2324 $error = 1; 2325 } 2326 if ($error) { 2327 $res = "U+$arg"; 2328 } else { 2329 $res = chr(hex($arg)); # ok to call chr 2330 } 2331 } else { 2332 $res = "U+$arg"; # not outputting UTF-8 2333 } 2334 $result .= _count_added($self, $formatter->{'container'}, 2335 add_text($formatter->{'container'}, $res)); 2336 } else { 2337 $result = ''; # arg was not defined 2338 } 2339 return $result; 2340 2341 } elsif ($command eq 'value') { 2342 my $expansion = $self->gdt('@{No value for `{value}\'@}', 2343 {'value' => $root->{'type'}}); 2344 if ($formatter->{'_top_formatter'}) { 2345 $expansion = {'type' => 'paragraph', 2346 'contents' => [$expansion]}; 2347 } 2348 $result .= _convert($self, $expansion); 2349 # unshift @{$self->{'current_contents'}->[-1]}, $expansion; 2350 #return ''; 2351 return $result; 2352 } elsif ($root->{'args'} and $root->{'args'}->[0] 2353 and $root->{'args'}->[0]->{'type'} 2354 and $root->{'args'}->[0]->{'type'} eq 'brace_command_arg') { 2355 # block commands 2356 } elsif (exists($block_commands{$command})) { 2357 # remark: 2358 # cartouche group and raggedright -> nothing on format stack 2359 2360 if ($menu_commands{$command} and $self->get_conf('FORMAT_MENU') eq 'nomenu') { 2361 return ''; 2362 } 2363 if ($self->{'preformatted_context_commands'}->{$command} 2364 or $command eq 'float') { 2365 if ($self->{'formatters'}->[-1]->{'type'} eq 'paragraph' 2366 and $format_raw_commands{$command}) { 2367 $result .= _count_added($self, $formatter->{'container'}, 2368 add_pending_word($formatter->{'container'}, 1)); 2369 $result .= _count_added($self, $formatter->{'container'}, 2370 end_line($formatter->{'container'})); 2371 } 2372 push @{$self->{'context'}}, $command; 2373 } elsif ($flush_commands{$command}) { 2374 push @{$self->{'context'}}, $command; 2375 } elsif ($raw_commands{$command} or $block_math_commands{$command}) { 2376 if (!$self->{'formatters'}->[-1]->{'_top_formatter'}) { 2377 # reuse the current formatter if not in top level 2378 $result .= _count_added($self, $formatter->{'container'}, 2379 add_pending_word($formatter->{'container'}, 1)); 2380 $result .= _count_added($self, $formatter->{'container'}, 2381 end_line($formatter->{'container'})); 2382 } else { 2383 # if in top level, the raw block command is turned into a 2384 # simple preformatted command (alike @verbatim), to have a 2385 # formatter container being created. 2386 push @{$self->{'context'}}, $command; 2387 $self->{'format_context_commands'}->{$command} = 1; 2388 $self->{'preformatted_context_commands'}->{$command} = 1; 2389 } 2390 } 2391 2392 if ($self->{'format_context_commands'}->{$command}) { 2393 push @{$self->{'format_context'}}, 2394 { 'cmdname' => $command, 2395 'paragraph_count' => 0, 2396 'indent_level' => 2397 $self->{'format_context'}->[-1]->{'indent_level'}, 2398 }; 2399 $self->{'format_context'}->[-1]->{'indent_level'}++ 2400 if ($indented_commands{$command}); 2401 # open a preformatted container, if the command opening the 2402 # preformatted context is not a classical preformatted 2403 # command (ie if it is menu or verbatim, and not example or 2404 # similar) 2405 if ($self->{'preformatted_context_commands'}->{$command} 2406 and ! $preformatted_commands{$command} 2407 and ! $format_raw_commands{$command}) { 2408 $preformatted = $self->new_formatter('unfilled'); 2409 push @{$self->{'formatters'}}, $preformatted; 2410 } 2411 } 2412 if ($command eq 'quotation' 2413 or $command eq 'smallquotation') { 2414 if ($root->{'args'} and $root->{'args'}->[0] 2415 and $root->{'args'}->[0]->{'contents'} 2416 and @{$root->{'args'}->[0]->{'contents'}}) { 2417 my $prepended = $self->gdt('@b{{quotation_arg}:} ', 2418 {'quotation_arg' => $root->{'args'}->[0]->{'contents'}}); 2419 $prepended->{'type'} = 'frenchspacing'; 2420 $result .= $self->convert_line($prepended); 2421 $self->{'text_element_context'}->[-1]->{'counter'} += 2422 Texinfo::Convert::Unicode::string_width($result); 2423 $self->{'empty_lines_count'} = 0 unless ($result eq ''); 2424 } 2425 } elsif ($menu_commands{$command}) { 2426 $result .= $self->_menu($root); 2427 } elsif ($command eq 'multitable') { 2428 my $columnsize; 2429 if ($root->{'extra'}->{'columnfractions'}) { 2430 foreach my $fraction (@{$root->{'extra'}->{'columnfractions'} 2431 ->{'extra'}->{'misc_args'}}) { 2432 push @$columnsize, int($fraction * $self->{'text_element_context'}->[-1]->{'max'} +0.5); 2433 } 2434 } elsif ($root->{'extra'}->{'prototypes'}) { 2435 foreach my $prototype (@{$root->{'extra'}->{'prototypes'}}) { 2436 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 2437 my ($formatted_prototype) = $self->convert_line($prototype, 2438 {'indent_length' => 0}); 2439 pop @{$self->{'count_context'}}; 2440 push @$columnsize, 2441 2+Texinfo::Convert::Unicode::string_width($formatted_prototype); 2442 } 2443 } 2444 $self->{'format_context'}->[-1]->{'columns_size'} = $columnsize; 2445 $self->{'format_context'}->[-1]->{'row_empty_lines_count'} 2446 = $self->{'empty_lines_count'}; 2447 $self->{'document_context'}->[-1]->{'in_multitable'}++; 2448 } elsif ($command eq 'float') { 2449 $result .= _add_newline_if_needed($self); 2450 if ($root->{'extra'} and $root->{'extra'}->{'node_content'}) { 2451 $result .= $self->_anchor($root); 2452 } 2453 } 2454 } elsif ($command eq 'node') { 2455 $self->{'node'} = $root; 2456 $result .= $self->_node($root); 2457 $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; 2458 } elsif ($sectioning_commands{$command}) { 2459 # use settitle for empty @top 2460 # ignore @part 2461 my $contents; 2462 if ($root->{'args'}->[0]->{'contents'} 2463 and @{$root->{'args'}->[0]->{'contents'}} 2464 and $command ne 'part') { 2465 $contents = $root->{'args'}->[0]->{'contents'}; 2466 } elsif ($command eq 'top' 2467 and $self->{'extra'}->{'settitle'} 2468 and $self->{'extra'}->{'settitle'}->{'args'} 2469 and @{$self->{'extra'}->{'settitle'}->{'args'}} 2470 and $self->{'extra'}->{'settitle'}->{'args'}->[0]->{'contents'} 2471 and @{$self->{'extra'}->{'settitle'}->{'args'}->[0]->{'contents'}}) { 2472 $contents = $self->{'extra'}->{'settitle'}->{'args'}->[0]->{'contents'}; 2473 } 2474 2475 if ($contents) { 2476 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 2477 my $heading = $self->convert_line({'type' => 'frenchspacing', 2478 'contents' => $contents}); 2479 pop @{$self->{'count_context'}}; 2480 # @* leads to an end of line, underlying appears on the line below 2481 # over one line 2482 my $heading_underlined = 2483 Texinfo::Convert::Text::heading($root, $heading, $self, 2484 $self->get_conf('NUMBER_SECTIONS'), 2485 ($self->{'format_context'}->[-1]->{'indent_level'}) 2486 * $indent_length); 2487 $result .= _add_newline_if_needed($self); 2488 $self->{'empty_lines_count'} = 0 unless ($heading_underlined eq ''); 2489 _add_text_count($self, $heading_underlined); 2490 $result .= $heading_underlined; 2491 if ($heading_underlined ne '') { 2492 _add_lines_count($self, 2); 2493 $result .= _add_newline_if_needed($self); 2494 } 2495 } 2496 $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; 2497 } elsif (($command eq 'item' or $command eq 'itemx') 2498 and $root->{'args'} and $root->{'args'}->[0] 2499 and $root->{'args'}->[0]->{'type'} 2500 and $root->{'args'}->[0]->{'type'} eq 'line_arg') { 2501 if ($root->{'args'} and @{$root->{'args'}} 2502 and $root->{'args'}->[0]->{'contents'}) { 2503 2504 my $converted_tree = $self->_table_item_content_tree($root, 2505 $root->{'args'}->[0]->{'contents'}); 2506 2507 $converted_tree->{'type'} = 'frenchspacing'; 2508 $result = $self->convert_line($converted_tree, 2509 {'indent_level' 2510 => $self->{'format_context'}->[-1]->{'indent_level'} -1}); 2511 if ($result ne '') { 2512 $result = $self->ensure_end_of_line($result); 2513 $self->{'empty_lines_count'} = 0; 2514 } 2515 } 2516 } elsif ($command eq 'item' and $root->{'parent'}->{'cmdname'} 2517 and $item_container_commands{$root->{'parent'}->{'cmdname'}}) { 2518 $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; 2519 my $line = $self->new_formatter('line', 2520 {'indent_length' => 2521 ($self->{'format_context'}->[-1]->{'indent_level'} -1) 2522 * $indent_length 2523 + $item_indent_format_length{$root->{'parent'}->{'cmdname'}}}); 2524 push @{$self->{'formatters'}}, $line; 2525 if ($root->{'parent'}->{'cmdname'} eq 'enumerate') { 2526 $result = _count_added($self, $line->{'container'}, 2527 add_next($line->{'container'}, 2528 Texinfo::Common::enumerate_item_representation( 2529 $root->{'parent'}->{'extra'}->{'enumerate_specification'}, 2530 $root->{'extra'}->{'item_number'}) . '. ')); 2531 } elsif ($root->{'parent'}->{'args'} 2532 and $root->{'parent'}->{'args'}->[0]) { 2533 # this is the text prepended to items. 2534 2535 $result = _convert($self, $root->{'parent'}->{'args'}->[0]); 2536 $result .= _convert($self, { 'text' => ' ' }); 2537 } 2538 $result .= _count_added($self, $line->{'container'}, 2539 Texinfo::Convert::Paragraph::end($line->{'container'})); 2540 pop @{$self->{'formatters'}}; 2541 $self->{'text_element_context'}->[-1]->{'counter'} += 2542 Texinfo::Convert::Unicode::string_width($result); 2543 $self->{'empty_lines_count'} = 0 unless ($result eq ''); 2544 # open a multitable cell 2545 } elsif ($command eq 'headitem' or $command eq 'item' 2546 or $command eq 'tab') { 2547 my $cell_width = $self->{'format_context'}->[-1]->{'columns_size'}->[$root->{'extra'}->{'cell_number'}-1]; 2548 $self->{'format_context'}->[-1]->{'item_command'} = $command 2549 if ($command ne 'tab'); 2550 die if (!defined($cell_width)); 2551 $self->{'empty_lines_count'} 2552 = $self->{'format_context'}->[-1]->{'row_empty_lines_count'}; 2553 2554 push @{$self->{'format_context'}}, 2555 { 'cmdname' => $command, 2556 'paragraph_count' => 0, 2557 'indent_level' => 0 }; 2558 push @{$self->{'text_element_context'}}, {'max' => $cell_width - 2 }; 2559 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, 2560 'locations' => []}; 2561 $cell = 1; 2562 } elsif ($command eq 'center') { 2563 #my ($counts, $new_locations); 2564 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, 2565 'locations' => []}; 2566 $result = $self->convert_line ( 2567 {'type' => 'frenchspacing', 2568 'contents' => $root->{'args'}->[0]->{'contents'}}, 2569 {'indent_length' => 0}); 2570 if ($result ne '') { 2571 $result = $self->ensure_end_of_line($result); 2572 2573 $result = $self->_align_environment ($result, 2574 $self->{'text_element_context'}->[-1]->{'max'}, 'center'); 2575 $self->{'empty_lines_count'} = 0; 2576 } else { 2577 # it has to be done here, as it is done in _align_environment above 2578 pop @{$self->{'count_context'}}; 2579 } 2580 $self->{'format_context'}->[-1]->{'paragraph_count'}++; 2581 return $result; 2582 } elsif ($command eq 'exdent') { 2583 if ($self->{'preformatted_context_commands'}->{$self->{'context'}->[-1]}) { 2584 $result = $self->convert_unfilled({'contents' => $root->{'args'}->[0]->{'contents'}}, 2585 {'indent_level' 2586 => $self->{'format_context'}->[-1]->{'indent_level'} -1}); 2587 } else { 2588 $result = $self->convert_line({'contents' => $root->{'args'}->[0]->{'contents'}}, 2589 {'indent_level' 2590 => $self->{'format_context'}->[-1]->{'indent_level'} -1}); 2591 } 2592 if ($result ne '') { 2593 $result = $self->ensure_end_of_line($result); 2594 $self->{'empty_lines_count'} = 0; 2595 } 2596 return $result; 2597 } elsif ($command eq 'verbatiminclude') { 2598 my $expansion = $self->Texinfo::Common::expand_verbatiminclude($root); 2599 unshift @{$self->{'current_contents'}->[-1]}, $expansion 2600 if ($expansion); 2601 return ''; 2602 } elsif ($command eq 'insertcopying') { 2603 if ($self->{'extra'} and $self->{'extra'}->{'copying'}) { 2604 unshift @{$self->{'current_contents'}->[-1]}, 2605 {'contents' => $self->{'extra'}->{'copying'}->{'contents'}}; 2606 } 2607 return ''; 2608 } elsif ($command eq 'printindex') { 2609 $result = $self->_printindex($root); 2610 return $result; 2611 } elsif ($command eq 'listoffloats') { 2612 my $lines_count = 0; 2613 if ($root->{'extra'} and $root->{'extra'}->{'type'} 2614 and defined($root->{'extra'}->{'type'}->{'normalized'}) 2615 and $self->{'floats'} 2616 and $self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}} 2617 and @{$self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}}}) { 2618 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0}; 2619 if (!$self->{'empty_lines_count'}) { 2620 $result .= "\n"; 2621 $lines_count++; 2622 } 2623 $result .= "* Menu:\n\n"; 2624 $lines_count += 2; 2625 foreach my $float (@{$self->{'floats'}->{$root->{'extra'}->{'type'}->{'normalized'}}}) { 2626 next if !$float->{'args'} or !$float->{'args'}->[1] 2627 or !@{$float->{'args'}->[1]->{'contents'}}; 2628 my $float_label_text = $self->convert_line({'type' => '_code', 2629 'contents' => $float->{'args'}->[1]->{'contents'}}); 2630 my $float_entry = $self->_float_type_number($float); 2631 my $float_entry_text = ':'; 2632 if (defined($float_entry)) { 2633 $float_entry->{'type'} = 'frenchspacing'; 2634 $float_entry_text = $self->convert_line($float_entry); 2635 } 2636 # no translation here, this is required Info format. 2637 my $float_line = "* $float_entry_text: $float_label_text."; 2638 my $line_width 2639 = Texinfo::Convert::Unicode::string_width($float_line); 2640 if ($line_width > $listoffloat_entry_length) { 2641 $float_line .= "\n" . ' ' x $listoffloat_entry_length; 2642 $lines_count++; 2643 } else { 2644 $float_line .= ' ' x ($listoffloat_entry_length - $line_width); 2645 } 2646 $line_width = $listoffloat_entry_length; 2647 my $caption; 2648 if ($float->{'extra'}->{'shortcaption'}) { 2649 $caption = $float->{'extra'}->{'shortcaption'}; 2650 } elsif ($float->{'extra'}->{'caption'}) { 2651 $caption = $float->{'extra'}->{'caption'}; 2652 } 2653 if ($caption) { 2654 $self->{'multiple_pass'} = 1; 2655 push @{$self->{'context'}}, 'listoffloats'; 2656 my $tree = {'contents' => $caption->{'args'}->[0]->{'contents'}}; 2657 # the following does nothing since there are paragraphs within 2658 # the shortcaption. 2659 #if ($caption->{'cmdname'} eq 'shortcaption') { 2660 # $tree->{'type'} = 'frenchspacing'; 2661 #} 2662 my $caption_text = _convert($self, $tree); 2663 my $old_context = pop @{$self->{'context'}}; 2664 delete $self->{'multiple_pass'}; 2665 die if ($old_context ne 'listoffloats'); 2666 while ($caption_text =~ s/^\s*(\p{Unicode::EastAsianWidth::InFullwidth}\s*|\S+\s*)//) { 2667 my $new_word = $1; 2668 $new_word =~ s/\n//g; 2669 if ((Texinfo::Convert::Unicode::string_width($new_word) + 2670 $line_width) > 2671 ($self->{'text_element_context'}->[-1]->{'max'} - 3)) { 2672 $float_line .= $listoffloat_append; 2673 last; 2674 } else { 2675 $float_line .= $new_word; 2676 $line_width += 2677 Texinfo::Convert::Unicode::string_width($new_word); 2678 } 2679 } 2680 } 2681 $result .= $float_line. "\n"; 2682 $lines_count++; 2683 } 2684 $result .= "\n"; 2685 $lines_count++; 2686 $self->{'empty_lines_count'} = 1; 2687 pop @{$self->{'count_context'}}; 2688 } 2689 $self->{'format_context'}->[-1]->{'paragraph_count'}++; 2690 _add_text_count($self, $result); 2691 _add_lines_count($self, $lines_count); 2692 return $result; 2693 } elsif ($command eq 'sp') { 2694 if ($root->{'extra'}->{'misc_args'}->[0]) { 2695 $result = _count_added($self, $formatter->{'container'}, 2696 add_pending_word($formatter->{'container'})); 2697 # this useless copy avoids perl changing the type to integer! 2698 my $sp_nr = $root->{'extra'}->{'misc_args'}->[0]; 2699 for (my $i = 0; $i < $sp_nr; $i++) { 2700 $result .= _count_added($self, $formatter->{'container'}, 2701 end_line($formatter->{'container'})); 2702 } 2703 2704 $self->{'empty_lines_count'} += $sp_nr; 2705 delete $self->{'text_element_context'}->[-1]->{'counter'}; 2706 } 2707 return $result; 2708 } elsif ($command eq 'contents') { 2709 if ($self->{'structuring'} 2710 and $self->{'structuring'}->{'sectioning_root'}) { 2711 my $lines_count; 2712 ($result, $lines_count) 2713 = $self->_contents($self->{'structuring'}->{'sectioning_root'}, 2714 'contents'); 2715 _add_lines_count($self, $lines_count); 2716 _add_text_count($self, $result); 2717 } 2718 return $result; 2719 } elsif ($command eq 'shortcontents' 2720 or $command eq 'summarycontents') { 2721 if ($self->{'structuring'} 2722 and $self->{'structuring'}->{'sectioning_root'}) { 2723 my $lines_count; 2724 ($result, $lines_count) 2725 = $self->_contents($self->{'structuring'}->{'sectioning_root'}, 2726 'shortcontents'); 2727 _add_lines_count($self, $lines_count); 2728 _add_text_count($self, $result); 2729 } 2730 return $result; 2731 # all the @-commands that have an information for the formatting, like 2732 # @paragraphindent, @frenchspacing... 2733 } elsif ($informative_commands{$command}) { 2734 $self->_informative_command($root); 2735 return ''; 2736 } else { 2737 $unknown_command = 1; 2738 } 2739 if ($unknown_command 2740 and !($root->{'extra'} 2741 and ($root->{'extra'}->{'index_entry'} 2742 or $root->{'extra'}->{'seeentry'} 2743 or $root->{'extra'}->{'seealso'})) 2744 # commands like def*x are not processed above, since only the def_line 2745 # associated is processed. If they have no name and no category they 2746 # are not considered as index entries either so they have a specific 2747 # condition 2748 and !($def_commands{$command} 2749 and $command =~ /x$/)) { 2750 warn "Unhandled $command\n"; 2751 $result .= "!!!!!!!!! Unhandled $command !!!!!!!!!\n"; 2752 } 2753 } 2754 2755 # open 'type' constructs. 2756 my $paragraph; 2757 if ($root->{'type'}) { 2758 if ($root->{'type'} eq 'paragraph') { 2759 $self->{'empty_lines_count'} = 0; 2760 my $conf; 2761 # indent. Not first paragraph. 2762 if ($self->{'format_context'}->[-1]->{'cmdname'} eq '_top_format' 2763 and $self->get_conf('paragraphindent') ne 'asis' 2764 and $self->get_conf('paragraphindent') 2765 and (($root->{'extra'} and $root->{'extra'}->{'indent'}) 2766 or (!($root->{'extra'} and $root->{'extra'}->{'noindent'}) 2767 and ($self->{'format_context'}->[-1]->{'paragraph_count'} 2768 or $self->get_conf('firstparagraphindent') eq 'insert') 2769 and !$self->{'text_element_context'}->[-1]->{'counter'}))) { 2770 $conf->{'first_indent_length'} = $self->get_conf('paragraphindent'); 2771 $conf->{'first_indent_length'} = 0 2772 if ($conf->{'first_indent_length'} eq 'none'); 2773 } 2774 $paragraph = $self->new_formatter('paragraph', $conf); 2775 push @{$self->{'formatters'}}, $paragraph; 2776 $self->{'format_context'}->[-1]->{'paragraph_count'}++; 2777 if ($self->{'context'}->[-1] eq 'flushright') { 2778 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, 2779 'locations' => []}; 2780 } 2781 } elsif ($root->{'type'} eq 'preformatted' 2782 or $root->{'type'} eq 'rawpreformatted') { 2783 # if in a description reuse the main menu unfilled, to keep things 2784 # simpler and avoid having to do a separate count. 2785 if ($root->{'type'} eq 'rawpreformatted' 2786 or !$root->{'parent'}->{'type'} 2787 or $root->{'parent'}->{'type'} ne 'menu_entry_description') { 2788 $preformatted = $self->new_formatter('unfilled'); 2789 push @{$self->{'formatters'}}, $preformatted; 2790 if ($self->{'context'}->[-1] eq 'flushright') { 2791 push @{$self->{'count_context'}}, {'lines' => 0, 'bytes' => 0, 2792 'locations' => []}; 2793 } 2794 } 2795 } elsif ($root->{'type'} eq 'def_line') { 2796 if ($root->{'extra'} and $root->{'extra'}->{'def_parsed_hash'} 2797 and %{$root->{'extra'}->{'def_parsed_hash'}}) { 2798 my $arguments = Texinfo::Common::definition_arguments_content($root); 2799 my $tree; 2800 my $command; 2801 if ($Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}) { 2802 $command = $Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}; 2803 } else { 2804 $command = $root->{'extra'}->{'def_command'}; 2805 } 2806 my $name; 2807 if ($root->{'extra'}->{'def_parsed_hash'}->{'name'}) { 2808 $name = $root->{'extra'}->{'def_parsed_hash'}->{'name'}; 2809 } else { 2810 $name = ''; 2811 } 2812 2813 if ($command eq 'deffn' 2814 or $command eq 'defvr' 2815 or $command eq 'deftp' 2816 or (($command eq 'deftypefn' 2817 or $command eq 'deftypevr') 2818 and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { 2819 if ($arguments) { 2820 $tree = $self->gdt("\@tie{}-- {category}: {name} {arguments}", { 2821 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2822 'name' => $name, 2823 'arguments' => $arguments}); 2824 } else { 2825 $tree = $self->gdt("\@tie{}-- {category}: {name}", { 2826 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2827 'name' => $name}); 2828 } 2829 } elsif ($command eq 'deftypefn' 2830 or $command eq 'deftypevr') { 2831 if ($arguments) { 2832 my $strings = { 2833 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2834 'name' => $name, 2835 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, 2836 'arguments' => $arguments}; 2837 if ($self->get_conf('deftypefnnewline') eq 'on') { 2838 $tree = $self->gdt("\@tie{}-- {category}:\@*{type}\@*{name} {arguments}", 2839 $strings); 2840 } else { 2841 $tree = $self->gdt("\@tie{}-- {category}: {type} {name} {arguments}", 2842 $strings); 2843 } 2844 } else { 2845 my $strings = { 2846 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2847 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, 2848 'name' => $name}; 2849 if ($self->get_conf('deftypefnnewline') eq 'on') { 2850 $tree = $self->gdt("\@tie{}-- {category}:\@*{type}\@*{name}", 2851 $strings); 2852 } else { 2853 $tree = $self->gdt("\@tie{}-- {category}: {type} {name}", 2854 $strings); 2855 } 2856 } 2857 } elsif ($command eq 'defcv' 2858 or ($command eq 'deftypecv' 2859 and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { 2860 if ($arguments) { 2861 $tree = $self->gdt("\@tie{}-- {category} of {class}: {name} {arguments}", { 2862 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2863 'name' => $name, 2864 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2865 'arguments' => $arguments}); 2866 } else { 2867 $tree = $self->gdt("\@tie{}-- {category} of {class}: {name}", { 2868 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2869 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2870 'name' => $name}); 2871 } 2872 } elsif ($command eq 'defop' 2873 or ($command eq 'deftypeop' 2874 and !$root->{'extra'}->{'def_parsed_hash'}->{'type'})) { 2875 if ($arguments) { 2876 $tree = $self->gdt("\@tie{}-- {category} on {class}: {name} {arguments}", { 2877 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2878 'name' => $name, 2879 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2880 'arguments' => $arguments}); 2881 } else { 2882 $tree = $self->gdt("\@tie{}-- {category} on {class}: {name}", { 2883 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2884 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2885 'name' => $name}); 2886 } 2887 } elsif ($command eq 'deftypeop') { 2888 if ($arguments) { 2889 my $strings = { 2890 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2891 'name' => $name, 2892 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2893 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, 2894 'arguments' => $arguments}; 2895 if ($self->get_conf('deftypefnnewline') eq 'on') { 2896 $tree 2897 = $self->gdt("\@tie{}-- {category} on {class}:\@*{type}\@*{name} {arguments}", 2898 $strings); 2899 } else { 2900 $tree 2901 = $self->gdt("\@tie{}-- {category} on {class}: {type} {name} {arguments}", 2902 $strings); 2903 } 2904 } else { 2905 my $strings = { 2906 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2907 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, 2908 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2909 'name' => $name}; 2910 if ($self->get_conf('deftypefnnewline') eq 'on') { 2911 $tree 2912 = $self->gdt("\@tie{}-- {category} on {class}:\@*{type}\@*{name}", 2913 $strings); 2914 } else { 2915 $tree 2916 = $self->gdt("\@tie{}-- {category} on {class}: {type} {name}", 2917 $strings); 2918 } 2919 } 2920 } elsif ($command eq 'deftypecv') { 2921 if ($arguments) { 2922 my $strings = { 2923 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2924 'name' => $name, 2925 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2926 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, 2927 'arguments' => $arguments}; 2928 if ($self->get_conf('deftypefnnewline') eq 'on') { 2929 $tree 2930 = $self->gdt("\@tie{}-- {category} of {class}:\@*{type}\@*{name} {arguments}", 2931 $strings); 2932 } else { 2933 $tree 2934 = $self->gdt("\@tie{}-- {category} of {class}: {type} {name} {arguments}", 2935 $strings); 2936 } 2937 } else { 2938 my $strings = { 2939 'category' => $root->{'extra'}->{'def_parsed_hash'}->{'category'}, 2940 'type' => $root->{'extra'}->{'def_parsed_hash'}->{'type'}, 2941 'class' => $root->{'extra'}->{'def_parsed_hash'}->{'class'}, 2942 'name' => $name}; 2943 if ($self->get_conf('deftypefnnewline') eq 'on') { 2944 $tree 2945 = $self->gdt("\@tie{}-- {category} of {class}:\@*{type}\@*{name}", 2946 $strings); 2947 } else { 2948 $tree 2949 = $self->gdt("\@tie{}-- {category} of {class}: {type} {name}", 2950 $strings); 2951 } 2952 } 2953 } 2954 2955 my $def_paragraph = $self->new_formatter('paragraph', 2956 { 'indent_length' => ($self->{'format_context'}->[-1]->{'indent_level'} -1) *$indent_length, 2957 'indent_length_next' => (1+$self->{'format_context'}->[-1]->{'indent_level'})*$indent_length, 2958 'suppress_styles' => 1 2959 }); 2960 push @{$self->{'formatters'}}, $def_paragraph; 2961 2962 $result .= _convert($self, {'type' => '_code', 'contents' => [$tree]}); 2963 $result .= _count_added($self, $def_paragraph->{'container'}, 2964 Texinfo::Convert::Paragraph::end($def_paragraph->{'container'})); 2965 2966 pop @{$self->{'formatters'}}; 2967 delete $self->{'text_element_context'}->[-1]->{'counter'}; 2968 $self->{'empty_lines_count'} = 0; 2969 } 2970 } elsif ($root->{'type'} eq 'menu_entry') { 2971 my $entry_name_seen = 0; 2972 foreach my $arg (@{$root->{'args'}}) { 2973 my ($pre_quote, $post_quote); 2974 if ($arg->{'type'} eq 'menu_entry_node') { 2975 $self->{'formatters'}->[-1]->{'suppress_styles'} = 1; 2976 2977 # Flush a leading space 2978 $result .= _count_added($self, $formatter->{'container'}, 2979 add_pending_word($formatter->{'container'}, 1)); 2980 2981 my $node_text = _convert($self, {'type' => '_code', 2982 'contents' => $arg->{'contents'}}); 2983 2984 $node_text .= _count_added($self, $formatter->{'container'}, 2985 add_pending_word($formatter->{'container'}, 1)); 2986 delete $self->{'formatters'}->[-1]->{'suppress_styles'}; 2987 $pre_quote = $post_quote = ''; 2988 if ($entry_name_seen) { 2989 if ($node_text =~ /([,\t]|\.\s)/) { 2990 if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { 2991 $self->line_warn(sprintf(__( 2992 "menu entry node name should not contain `%s'"), $1), 2993 $root->{'line_nr'}); 2994 } 2995 } 2996 if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { 2997 $pre_quote = $post_quote = "\x{7f}"; 2998 } 2999 } else { 3000 if ($node_text =~ /:/) { 3001 if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { 3002 $self->line_warn(__( 3003 "menu entry node name should not contain `:'"), 3004 $root->{'line_nr'}); 3005 } 3006 if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { 3007 $pre_quote = $post_quote = "\x{7f}"; 3008 } 3009 } 3010 } 3011 $result .= $pre_quote . $node_text . $post_quote; 3012 } elsif ($arg->{'type'} eq 'menu_entry_name') { 3013 my $entry_name = _convert($self, $arg); 3014 my $formatter = $self->{'formatters'}->[-1]; 3015 $entry_name .= _count_added($self, 3016 $formatter->{'container'}, 3017 add_pending_word($formatter->{'container'}, 1)); 3018 $entry_name_seen = 1; 3019 $pre_quote = $post_quote = ''; 3020 if ($entry_name =~ /:/) { 3021 if ($self->get_conf('INFO_SPECIAL_CHARS_WARNING')) { 3022 $self->line_warn(__( 3023 "menu entry name should not contain `:'"), 3024 $root->{'line_nr'}); 3025 } 3026 if ($self->get_conf('INFO_SPECIAL_CHARS_QUOTE')) { 3027 $pre_quote = $post_quote = "\x{7f}"; 3028 } 3029 } 3030 $result .= $pre_quote . $entry_name . $post_quote; 3031 } else { 3032 $result .= _convert($self, $arg); 3033 } 3034 } 3035 3036 # If we are nested inside an @example, a 'menu_entry_description' may not 3037 # have been processed yet, and we need to output any pending spaces 3038 # before 'end_line' throws them away. The argument to 'add_pending_word' 3039 # does this. 3040 if ($root->{'parent'}->{'type'} 3041 and $root->{'parent'}->{'type'} eq 'preformatted') { 3042 $result .= _count_added($self, 3043 $formatter->{'container'}, 3044 add_pending_word($formatter->{'container'}, 1)); 3045 } else { 3046 $result .= _count_added($self, 3047 $formatter->{'container'}, 3048 add_pending_word($formatter->{'container'})); 3049 end_line($formatter->{'container'}); 3050 $result = $self->ensure_end_of_line($result) ; 3051 } 3052 3053 } elsif ($root->{'type'} eq 'frenchspacing') { 3054 push @{$formatter->{'frenchspacing_stack'}}, 'on'; 3055 set_space_protection($formatter->{'container'}, undef, undef, undef, 1); 3056 } elsif ($root->{'type'} eq '_code') { 3057 if (!$formatter->{'font_type_stack'}->[-1]->{'monospace'}) { 3058 push @{$formatter->{'font_type_stack'}}, {'monospace' => 1}; 3059 } else { 3060 $formatter->{'font_type_stack'}->[-1]->{'monospace'}++; 3061 } 3062 push @{$formatter->{'frenchspacing_stack'}}, 'on'; 3063 set_space_protection($formatter->{'container'}, undef, undef, undef, 1); 3064 } elsif ($root->{'type'} eq 'bracketed') { 3065 $result .= _count_added($self, $formatter->{'container'}, 3066 add_text($formatter->{'container'}, '{')); 3067 } 3068 } 3069 3070 # The processing of contents is done here. 3071 if ($root->{'contents'}) { 3072 my @contents = @{$root->{'contents'}}; 3073 push @{$self->{'current_contents'}}, \@contents; 3074 push @{$self->{'current_roots'}}, $root; 3075 while (@contents) { 3076 my $content = shift @contents; 3077 my $text = _convert($self, $content); 3078 $self->{'empty_lines_count'} = 0 3079 if ($preformatted and $text =~ /\S/); 3080 $result .= $text; 3081 } 3082 pop @{$self->{'current_contents'}}; 3083 pop @{$self->{'current_roots'}}; 3084 } 3085 3086 # now closing. First, close types. 3087 if ($root->{'type'}) { 3088 if ($root->{'type'} eq 'frenchspacing') { 3089 pop @{$formatter->{'frenchspacing_stack'}}; 3090 my $frenchspacing = 0; 3091 $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); 3092 set_space_protection($formatter->{'container'}, undef, 3093 undef, undef, $frenchspacing); 3094 } elsif ($root->{'type'} eq '_code') { 3095 $formatter->{'font_type_stack'}->[-1]->{'monospace'}--; 3096 pop @{$formatter->{'font_type_stack'}} 3097 if !$formatter->{'font_type_stack'}->[-1]->{'monospace'}; 3098 pop @{$formatter->{'frenchspacing_stack'}}; 3099 my $frenchspacing = 0; 3100 $frenchspacing = 1 if ($formatter->{'frenchspacing_stack'}->[-1] eq 'on'); 3101 set_space_protection($formatter->{'container'}, undef, 3102 undef, undef, $frenchspacing); 3103 } elsif ($root->{'type'} eq 'bracketed') { 3104 $result .= _count_added($self, $formatter->{'container'}, 3105 add_text($formatter->{'container'}, '}')); 3106 } elsif ($root->{'type'} eq 'row') { 3107 my @cell_beginnings; 3108 my @cell_lines; 3109 my $cell_beginning = 0; 3110 my $cell_idx = 0; 3111 my $max_lines = 0; 3112 my $indent_len = $indent_length 3113 * $self->{'format_context'}->[-1]->{'indent_level'}; 3114 foreach my $cell (@{$self->{'format_context'}->[-1]->{'row'}}) { 3115 $cell_beginnings[$cell_idx] = $cell_beginning; 3116 my $cell_width = $self->{'format_context'}->[-1]->{'columns_size'}->[$cell_idx]; 3117 $cell_beginning += $cell_width +1; 3118 $cell_lines[$cell_idx] = [ split /^/, $cell ]; 3119 $max_lines = scalar(@{$cell_lines[$cell_idx]}) 3120 if (scalar(@{$cell_lines[$cell_idx]}) > $max_lines); 3121 $cell_idx++; 3122 } 3123 3124 $cell_idx = 0; 3125 my $cell_updated_locations; 3126 my @row_locations; 3127 foreach my $cell_locations (@{$self->{'format_context'}->[-1]->{'row_counts'}}) { 3128 foreach my $location (@{$cell_locations->{'locations'}}) { 3129 next unless (defined($location->{'bytes'}) and defined($location->{'lines'})); 3130 push @{$cell_updated_locations->[$cell_idx]->{$location->{'lines'}}}, 3131 $location; 3132 $max_lines = $location->{'lines'}+1 3133 if ($location->{'lines'}+1 > $max_lines); 3134 } 3135 push @row_locations, @{$cell_locations->{'locations'}}; 3136 $cell_idx++; 3137 } 3138 3139 # this is used to keep track of the last cell with content. 3140 my $max_cell = scalar(@{$self->{'format_context'}->[-1]->{'row'}}); 3141 my $bytes_count = 0; 3142 my $line; 3143 for (my $line_idx = 0; $line_idx < $max_lines; $line_idx++) { 3144 my $line_width = $indent_len; 3145 $line = ''; 3146 # determine the last cell in the line, to fill spaces in 3147 # cells preceding that cell on the line 3148 my $last_cell = 0; 3149 for (my $cell_idx = 0; $cell_idx < $max_cell; $cell_idx++) { 3150 $last_cell = $cell_idx+1 if (defined($cell_lines[$cell_idx]->[$line_idx]) 3151 or defined($cell_updated_locations->[$cell_idx]->{$line_idx})); 3152 } 3153 3154 for (my $cell_idx = 0; $cell_idx < $last_cell; $cell_idx++) { 3155 my $cell_text = $cell_lines[$cell_idx]->[$line_idx]; 3156 if (defined($cell_text)) { 3157 chomp($cell_text); 3158 if ($line eq '' and $cell_text ne '') { 3159 $line = ' ' x $indent_len; 3160 $bytes_count += count_bytes($self, $line); 3161 } 3162 $line .= $cell_text; 3163 $bytes_count += count_bytes($self, $cell_text); 3164 $line_width += Texinfo::Convert::Unicode::string_width($cell_text); 3165 } 3166 if (defined($cell_updated_locations->[$cell_idx]->{$line_idx})) { 3167 foreach my $location (@{$cell_updated_locations->[$cell_idx]->{$line_idx}}) { 3168 $location->{'bytes'} = $bytes_count; 3169 } 3170 } 3171 if ($cell_idx+1 < $last_cell) { 3172 if ($line_width < $indent_len + $cell_beginnings[$cell_idx+1]) { 3173 if ($line eq '') { 3174 $line = ' ' x $indent_len; 3175 $bytes_count += count_bytes($self, $line); 3176 } 3177 my $spaces = ' ' x ($indent_len + $cell_beginnings[$cell_idx+1] - $line_width); 3178 $line_width += Texinfo::Convert::Unicode::string_width($spaces); 3179 $line .= $spaces; 3180 $bytes_count += count_bytes($self, $spaces); 3181 } 3182 } 3183 } 3184 $line .= "\n"; 3185 $bytes_count += count_bytes($self, "\n"); 3186 $result .= $line; 3187 } 3188 if ($self->{'format_context'}->[-1]->{'item_command'} eq 'headitem') { 3189 # at this point cell_beginning is at the beginning of 3190 # the cell following the end of the table -> full width 3191 my $line = ' ' x $indent_len . '-' x $cell_beginning . "\n"; 3192 $bytes_count += count_bytes($self, $line); 3193 $result .= $line; 3194 $self->{'empty_lines_count'} = 0; 3195 $max_lines++; 3196 # there may be empty lines, in that case $line is undef, $max_lines == 0 3197 } elsif ($max_lines) { 3198 if ($line eq "\n") { 3199 $self->{'empty_lines_count'} = 1; 3200 } else { 3201 $self->{'empty_lines_count'} = 0; 3202 } 3203 } 3204 $self->_update_locations_counts(\@row_locations); 3205 push @{$self->{'count_context'}->[-1]->{'locations'}}, @row_locations; 3206 $self->{'count_context'}->[-1]->{'bytes'} += $bytes_count; 3207 $self->{'count_context'}->[-1]->{'lines'} += $max_lines; 3208 $self->{'format_context'}->[-1]->{'row'} = []; 3209 $self->{'format_context'}->[-1]->{'row_counts'} = []; 3210 $self->{'format_context'}->[-1]->{'row_empty_lines_count'} 3211 = $self->{'empty_lines_count'}; 3212 } elsif ($root->{'type'} eq 'text_root') { 3213 $self->{'text_before_first_node'} = $result; 3214 } 3215 } 3216 # close paragraphs and preformatted 3217 if ($paragraph) { 3218 $result .= _count_added($self, $paragraph->{'container'}, 3219 Texinfo::Convert::Paragraph::end($paragraph->{'container'})); 3220 if ($self->{'context'}->[-1] eq 'flushright') { 3221 $result = $self->_align_environment($result, 3222 $self->{'text_element_context'}->[-1]->{'max'}, 'right'); 3223 } 3224 pop @{$self->{'formatters'}}; 3225 delete $self->{'text_element_context'}->[-1]->{'counter'}; 3226 } elsif ($preformatted) { 3227 $result .= _count_added($self, $preformatted->{'container'}, 3228 Texinfo::Convert::Paragraph::end($preformatted->{'container'})); 3229 if ($result ne '') { 3230 $result = $self->ensure_end_of_line($result); 3231 } 3232 if ($self->{'context'}->[-1] eq 'flushright') { 3233 $result = $self->_align_environment ($result, 3234 $self->{'text_element_context'}->[-1]->{'max'}, 'right'); 3235 } 3236 pop @{$self->{'formatters'}}; 3237 # We assume that, upon closing the preformatted we are at the 3238 # beginning of a line. 3239 delete $self->{'text_element_context'}->[-1]->{'counter'}; 3240 } 3241 3242 # close commands 3243 if ($command) { 3244 if ($command eq 'float') { 3245 if ($root->{'extra'} 3246 and ($root->{'extra'}->{'type'}->{'normalized'} ne '' 3247 or defined($root->{'number'}) 3248 or $root->{'extra'}->{'caption'} or $root->{'extra'}->{'shortcaption'})) { 3249 3250 $result .= _add_newline_if_needed($self); 3251 my ($caption, $prepended) = Texinfo::Common::float_name_caption($self, 3252 $root); 3253 if ($prepended) { 3254 $prepended->{'type'} = 'frenchspacing'; 3255 my $float_number = $self->convert_line ($prepended); 3256 $result .= $float_number; 3257 $self->{'text_element_context'}->[-1]->{'counter'} += 3258 Texinfo::Convert::Unicode::string_width($float_number); 3259 $self->{'empty_lines_count'} = 0; 3260 } 3261 if ($caption) { 3262 $self->{'format_context'}->[-1]->{'paragraph_count'} = 0; 3263 my $tree = $caption->{'args'}->[0]; 3264 $result .= _convert($self, $tree); 3265 } 3266 } 3267 } elsif (($command eq 'quotation' 3268 or $command eq 'smallquotation') 3269 and $root->{'extra'} and $root->{'extra'}->{'authors'}) { 3270 foreach my $author (@{$root->{'extra'}->{'authors'}}) { 3271 $result .= _convert($self, 3272 $self->gdt("\@center --- \@emph{{author}}\n", 3273 {'author' => $author->{'args'}->[0]->{'contents'}})); 3274 } 3275 } elsif (($command eq 'multitable')) { 3276 $self->{'document_context'}->[-1]->{'in_multitable'}--; 3277 } elsif ($root_commands{$command} 3278 and $sectioning_commands{$command} 3279 and $command ne 'part') { 3280 # add menu if missing 3281 my $node = $self->{'node'}; 3282 my $automatic_directions; 3283 if ($node and defined($node->{'extra'}->{'nodes_manuals'})) { 3284 $automatic_directions = 3285 (scalar(@{$node->{'extra'}->{'nodes_manuals'}}) == 1); 3286 } 3287 if ($node and $automatic_directions 3288 and !$self->{'seenmenus'}->{$node}) { 3289 $self->{'seenmenus'}->{$node} = 1; 3290 my $menu_node = Texinfo::Structuring::new_complete_node_menu(undef, $node); 3291 if ($menu_node) { 3292 my $menu_text = $self->_convert($menu_node); 3293 if ($menu_text) { 3294 $result .= $menu_text; 3295 } 3296 } 3297 } 3298 } 3299 3300 # close the contexts and register the cells 3301 if ($self->{'preformatted_context_commands'}->{$command} 3302 or $command eq 'float') { 3303 my $old_context = pop @{$self->{'context'}}; 3304 die "Not a preformatted context: $old_context" 3305 if (!$self->{'preformatted_context_commands'}->{$old_context} 3306 and $old_context ne 'float'); 3307 if ($old_context ne 'float' and !$menu_commands{$old_context}) { 3308 $self->{'empty_lines_count'} = 0; 3309 } 3310 delete ($self->{'preformatted_context_commands'}->{$command}) 3311 unless ($default_preformatted_context_commands{$command}); 3312 } elsif ($flush_commands{$command}) { 3313 my $old_context = pop @{$self->{'context'}}; 3314 die if (! $flush_commands{$old_context}); 3315 } 3316 3317 if ($self->{'format_context_commands'}->{$command}) { 3318 pop @{$self->{'format_context'}}; 3319 delete ($self->{'format_context_commands'}->{$command}) 3320 unless ($default_format_context_commands{$command}); 3321 } elsif ($cell) { 3322 pop @{$self->{'format_context'}}; 3323 pop @{$self->{'text_element_context'}}; 3324 push @{$self->{'format_context'}->[-1]->{'row'}}, $result; 3325 _update_count_context($self); 3326 my $cell_counts = pop @{$self->{'count_context'}}; 3327 push @{$self->{'format_context'}->[-1]->{'row_counts'}}, $cell_counts; 3328 $result = ''; 3329 } 3330 if ($advance_paragraph_count_commands{$command}) { 3331 $self->{'format_context'}->[-1]->{'paragraph_count'}++; 3332 } 3333 } 3334 3335 return $result; 3336} 3337 33381; 3339 3340__END__ 3341# $Id$ 3342# Automatically generated from maintain/template.pod 3343 3344=head1 NAME 3345 3346Texinfo::Convert::Plaintext - Convert Texinfo tree to Plaintext 3347 3348=head1 SYNOPSIS 3349 3350 my $converter 3351 = Texinfo::Convert::Plaintext->converter({'parser' => $parser}); 3352 3353 $converter->output($tree); 3354 $converter->convert($tree); 3355 $converter->convert_tree($tree); 3356 3357=head1 DESCRIPTION 3358 3359Texinfo::Convert::Plaintext converts a Texinfo tree to Plaintext. 3360 3361=head1 METHODS 3362 3363=over 3364 3365=item $converter = Texinfo::Convert::Plaintext->converter($options) 3366 3367Initialize converter from Texinfo to Plaintext. 3368 3369The I<$options> hash reference holds options for the converter. In 3370this option hash reference a parser object may be associated with the 3371I<parser> key. The other options should be configuration options 3372described in the Texinfo manual. Those options, when appropriate, 3373override the document content. 3374 3375See L<Texinfo::Convert::Converter> for more informations. 3376 3377=item $converter->output($tree) 3378 3379Convert a Texinfo tree I<$tree> and output the result in files as 3380described in the Texinfo manual. 3381 3382=item $result = $converter->convert($tree) 3383 3384Convert a Texinfo tree I<$tree> or tree portion and return 3385the resulting output. 3386 3387=item $result = $converter->convert_tree($tree) 3388 3389Convert a Texinfo tree portion I<$tree> and return the resulting 3390output. This function does not try to output a full document but only 3391portions. For a full document use C<convert>. 3392 3393=back 3394 3395=head1 AUTHOR 3396 3397Patrice Dumas, E<lt>pertusus@free.frE<gt> 3398 3399=cut 3400