<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># Paragraph.pm: handle paragraph text.
#
# Copyright 2010, 2011, 2012 Free Software Foundation, Inc.
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.
# 
# Original author: Patrice Dumas &lt;pertusus@free.fr&gt;

# this module has nothing Texinfo specific.  In contrast with existing
# modules Text::Wrap, Text::Format, it keeps a state of the paragraph 
# and wait for text to be feed.

package Texinfo::Convert::Paragraph;

use 5.006;
use strict;

use Unicode::EastAsianWidth;
use Carp qw(cluck);

# initialize a paragraph object.
sub new($;$)
{
  my $class = shift;
  my $conf = shift;
  my $self = {'max' =&gt; 72, 'indent_length' =&gt; 0, 'counter' =&gt; 0, 
              'word_counter' =&gt; 0, 'space' =&gt; '', 'frenchspacing' =&gt; 0,
              'lines_counter' =&gt; 0, 'end_line_count' =&gt; 0};
  if (defined($conf)) {
    foreach my $key (keys(%$conf)) {
      $self-&gt;{$key} = $conf-&gt;{$key};
    }
  }
  bless $self, $class;
}

# for debugging
sub dump($)
{
  my $self = shift;
  my $word = 'UNDEF';
  my $underlying = '';
  if (defined($self-&gt;{'word'})) {
    $word = $self-&gt;{'word'};
    if ($self-&gt;{'word'} ne $self-&gt;{'underlying_word'}) {
      $underlying = ", underlying_word: $self-&gt;{'underlying_word'},";
    }
  }
  my $end_sentence = 'UNDEF';
  $end_sentence = $self-&gt;{'end_sentence'} if (defined($self-&gt;{'end_sentence'}));
  print STDERR "para ($self-&gt;{'counter'}+$self-&gt;{'word_counter'}) word: $word, space `$self-&gt;{'space'}'${underlying} end_sentence: $end_sentence\n"; 
}

sub _cut_line($)
{
  my $paragraph = shift;
  return '' if ($paragraph-&gt;{'ignore_columns'});
  return $paragraph-&gt;_end_line();
}

sub end_line_count($)
{
  my $paragraph = shift;
  return $paragraph-&gt;{'end_line_count'};
}

sub end_line($)
{
  my $paragraph = shift;
  $paragraph-&gt;{'end_line_count'} = 0;
  return $paragraph-&gt;_end_line();
}

# end a line.
sub _end_line($)
{
  my $paragraph = shift;
  $paragraph-&gt;{'counter'} = 0;
  $paragraph-&gt;{'space'} = '';
  if (defined($paragraph-&gt;{'indent_length_next'})) {
    $paragraph-&gt;{'indent_length'} = $paragraph-&gt;{'indent_length_next'};
    delete $paragraph-&gt;{'indent_length_next'};        
  }
  $paragraph-&gt;{'lines_counter'}++;
  $paragraph-&gt;{'end_line_count'}++;
  print STDERR "END_LINE\n" if ($paragraph-&gt;{'DEBUG'});
  return "\n";
}

sub get_pending($)
{
  my $paragraph = shift;
  my $result = '';
  if ($paragraph-&gt;{'space'}) {
    $result .= $paragraph-&gt;{'space'};
  }
  if (defined($paragraph-&gt;{'word'})) {
    $result .= $paragraph-&gt;{'word'};
  }
  return $result;
}

sub add_pending_word($;$)
{
  my $paragraph = shift;
  my $add_spaces = shift;
  $paragraph-&gt;{'end_line_count'} = 0;
  return $paragraph-&gt;_add_pending_word($add_spaces);
}

# put a pending word and spaces in the result string.
sub _add_pending_word($;$)
{
  my $paragraph = shift;
  my $add_spaces = shift;
  my $result = '';

  if (defined($paragraph-&gt;{'word'}) or $add_spaces) {
    if ($paragraph-&gt;{'indent_length'} &gt; $paragraph-&gt;{'counter'}) {
      $result .= ' ' x ($paragraph-&gt;{'indent_length'} - $paragraph-&gt;{'counter'});
      $paragraph-&gt;{'counter'} = $paragraph-&gt;{'indent_length'};
      print STDERR "INDENT($paragraph-&gt;{'counter'}+$paragraph-&gt;{'word_counter'})\n" 
                   if ($paragraph-&gt;{'DEBUG'});
    } elsif ($paragraph-&gt;{'space'}) {
      $result .= $paragraph-&gt;{'space'};
      $paragraph-&gt;{'counter'} += length($paragraph-&gt;{'space'});
      print STDERR "ADD_SPACES($paragraph-&gt;{'counter'}+$paragraph-&gt;{'word_counter'})\n" 
         if ($paragraph-&gt;{'DEBUG'});
      
    }
    if (defined($paragraph-&gt;{'word'})) {
      $result .= $paragraph-&gt;{'word'};
      $paragraph-&gt;{'counter'} += $paragraph-&gt;{'word_counter'};
      print STDERR "ADD_WORD[$paragraph-&gt;{'word'}]+$paragraph-&gt;{'word_counter'} ($paragraph-&gt;{'counter'})\n"
        if ($paragraph-&gt;{'DEBUG'});
      $paragraph-&gt;{'word'} = undef;
      $paragraph-&gt;{'underlying_word'} = undef;
      $paragraph-&gt;{'word_counter'} = 0;
    }
    $paragraph-&gt;{'space'} = '';
  }
  return $result;
}

# end a paragraph
sub end($)
{
  my $paragraph = shift;
  $paragraph-&gt;{'end_line_count'} = 0;
  print STDERR "PARA END\n" if ($paragraph-&gt;{'DEBUG'});
  my $result = $paragraph-&gt;_add_pending_word();
  if ($paragraph-&gt;{'counter'} != 0) {
    $result .= "\n"; 
    $paragraph-&gt;{'lines_counter'}++;
    $paragraph-&gt;{'end_line_count'}++;
  }
  return $result;
}

sub add_next($;$$$$)
{
  my $paragraph = shift;
  my $word = shift;
  my $space = shift;
  my $end_sentence = shift;
  my $transparent = shift;
  $paragraph-&gt;{'end_line_count'} = 0;
  return $paragraph-&gt;_add_next($word, undef, $space, $end_sentence, 
                               $transparent);
}

# add a word and/or spaces and end of sentence.
sub _add_next($;$$$$$)
{
  my $paragraph = shift;
  my $word = shift;
  my $underlying_word = shift;
  my $space = shift;
  my $end_sentence = shift;
  my $transparent = shift;
  my $result = '';

  $underlying_word = $word if (!defined($underlying_word));

  if (defined($word)) {
    if (!defined($paragraph-&gt;{'word'})) {
      $paragraph-&gt;{'word'} = '';
      $paragraph-&gt;{'underlying_word'} = '';
      if ($paragraph-&gt;{'end_sentence'} 
           and $paragraph-&gt;{'end_sentence'} &gt; 0
           and !$paragraph-&gt;{'frenchspacing'}
           and $paragraph-&gt;{'counter'} != 0 and $paragraph-&gt;{'space'}) {
        # do not to double space if there are leading spaces in word
        if ($word !~ /^\s/) {
          #$paragraph-&gt;{'space'} = '  ';
          $paragraph-&gt;{'space'} .= ' ' x (2 - length($paragraph-&gt;{'space'}));
        }
        delete $paragraph-&gt;{'end_sentence'};
      }
    }
    
    $paragraph-&gt;{'word'} .= $word;
    $paragraph-&gt;{'underlying_word'} .= $underlying_word unless($transparent);
    if ($word =~ /\n/) {
      $result .= $paragraph-&gt;{'space'};
      $paragraph-&gt;{'space'} = '';
      $result .= $paragraph-&gt;{'word'};
      $paragraph-&gt;_end_line();
      $paragraph-&gt;{'word_counter'} = 0;
      $paragraph-&gt;{'word'} = undef;
      $paragraph-&gt;{'underlying_word'} = undef;
    } else {
      $paragraph-&gt;{'word_counter'} += length($word);
    }
    if ($paragraph-&gt;{'DEBUG'}) {
      my $para_word = 'UNDEF';;
      if (defined($paragraph-&gt;{'word'})) {
        $para_word = $paragraph-&gt;{'word'};
      }
      my $para_underlying_word = 'UNDEF';;
      if (defined($paragraph-&gt;{'underlying_word'})) {
        $para_underlying_word = $paragraph-&gt;{'word'};
      }

      print STDERR "WORD+ $word -&gt; $para_word\n";
      print STDERR "UNDERLYING_WORD+ $underlying_word -&gt; $para_underlying_word\n";
    }
    # The $paragraph-&gt;{'counter'} != 0 is here to avoid having an
    # additional line output when the text is longer than the max.
    if ($paragraph-&gt;{'counter'} != 0 and 
        $paragraph-&gt;{'counter'} + $paragraph-&gt;{'word_counter'} + 
           length($paragraph-&gt;{'space'}) &gt; $paragraph-&gt;{'max'}) {
      $result .= $paragraph-&gt;_cut_line();
    }
  }
  if (defined($space)) {
    if ($paragraph-&gt;{'protect_spaces'}) {
      $result .= $paragraph-&gt;_add_text($space);
    } else {
      $result .= $paragraph-&gt;_add_pending_word();
      $paragraph-&gt;{'space'} = $space;
      if ($paragraph-&gt;{'counter'} + length($paragraph-&gt;{'space'}) 
                      &gt; $paragraph-&gt;{'max'}) {
        $result .= $paragraph-&gt;_cut_line();
      }
    }
  }
  if (defined($end_sentence)) {
    $paragraph-&gt;{'end_sentence'} = $end_sentence;
  }
  return $result;
}

sub add_underlying_text($$)
{
  my $paragraph = shift;
  my $underlying_text = shift;
  if (defined($underlying_text)) {
    $paragraph-&gt;{'underlying_word'} = ''
       if (!defined($paragraph-&gt;{'underlying_word'}));
    $paragraph-&gt;{'underlying_word'} .= $underlying_text;
  }
}

sub inhibit_end_sentence($)
{
  my $paragraph = shift;
  $paragraph-&gt;{'end_sentence'} = 0;
}

sub set_space_protection($$;$$$)
{
  my $paragraph = shift;
  my $space_protection = shift;
  my $ignore_columns = shift;
  my $keep_end_lines = shift;
  my $frenchspacing = shift;
  $paragraph-&gt;{'protect_spaces'} = $space_protection 
    if defined($space_protection);
  $paragraph-&gt;{'ignore_columns'} = $ignore_columns
    if defined($ignore_columns);
  $paragraph-&gt;{'keep_end_lines'} = $keep_end_lines
    if defined($keep_end_lines);
  if (!$paragraph-&gt;{'frenchspacing'} and $frenchspacing
    and $paragraph-&gt;{'end_sentence'} and $paragraph-&gt;{'counter'} != 0 
    and $paragraph-&gt;{'space'} and !defined($paragraph-&gt;{'word'})) {
    $paragraph-&gt;{'space'} .= ' ' x (2 - length($paragraph-&gt;{'space'}));
    print STDERR "SWITCH frenchspacing end sentence space\n" 
       if ($paragraph-&gt;{'DEBUG'});
    delete $paragraph-&gt;{'end_sentence'};
  }
  $paragraph-&gt;{'frenchspacing'} = $frenchspacing
    if defined($frenchspacing);
  # begin a word, to have something even if empty
  if ($space_protection) {
    $paragraph-&gt;_add_next('');
  }
  return '';
}

my $end_sentence_character = quotemeta('.?!');
my $after_punctuation_characters = quotemeta('"\')]');

# wrap a text.
sub add_text($$;$)
{
  my $paragraph = shift;
  my $text = shift;
  my $underlying_text = shift;
  $underlying_text = $text if (!defined($underlying_text));
  $paragraph-&gt;{'end_line_count'} = 0;
  my $result = '';

  while ($text ne '') {
    if ($paragraph-&gt;{'DEBUG'}) {
      my $word = 'UNDEF';
      $word = $paragraph-&gt;{'word'} if (defined($paragraph-&gt;{'word'}));
      print STDERR "p ($paragraph-&gt;{'counter'}+$paragraph-&gt;{'word_counter'}) s `"._print_escaped_spaces($paragraph-&gt;{'space'})."', w `$word'\n";
      #print STDERR "TEXT: "._print_escaped_spaces($text)."|\n"
    }
    # \x{202f}\x{00a0} are non breaking spaces
    if ($text =~ s/^([^\S\x{202f}\x{00a0}]+)//) {
      my $spaces = $1;
      $underlying_text =~ s/^([^\S\x{202f}\x{00a0}]+)//;
      print STDERR "SPACES($paragraph-&gt;{'counter'}) `"._print_escaped_spaces($spaces)."'\n" if ($paragraph-&gt;{'DEBUG'});
      #my $added_word = $paragraph-&gt;{'word'};
      if ($paragraph-&gt;{'protect_spaces'}) {
        $paragraph-&gt;{'word'} .= $spaces;
        $paragraph-&gt;{'underlying_word'} .= $spaces;
        $paragraph-&gt;{'word_counter'} += length($spaces);
        #$paragraph-&gt;{'space'} .= $spaces;
        if ($paragraph-&gt;{'word'} =~ s/\n/ /g 
           and !$paragraph-&gt;{'frenchspacing'} 
           and $paragraph-&gt;{'end_sentence'}
           and $paragraph-&gt;{'end_sentence'} &gt; 0) {
          $paragraph-&gt;{'word'} =~ /(\s*)$/;
          if (length($1) &lt; 2) {
            #$paragraph-&gt;{'word'} =~ s/(\s*)$/  /;
            #$paragraph-&gt;{'underlying_word'} =~ s/(\s*)$/  /;
            #my $removed = $1;
            #$paragraph-&gt;{'word_counter'} += length('  ') - length($removed);
            my $added = ' ' x (2 - length($1));
            $paragraph-&gt;{'word'} .= $added;
            $paragraph-&gt;{'word'} =~ /(\s*)$/;
            my $end_spaces = $1;
            $paragraph-&gt;{'underlying_word'} =~ s/(\s*)$/$end_spaces/;
            $paragraph-&gt;{'word_counter'} += length($added);
          }
        }
        # The $paragraph-&gt;{'counter'} != 0 is here to avoid having an
        # additional line output when the text is longer than the max.
        if ($paragraph-&gt;{'counter'} != 0 and 
            $paragraph-&gt;{'counter'} + $paragraph-&gt;{'word_counter'} + 
               length($paragraph-&gt;{'space'}) &gt; $paragraph-&gt;{'max'}) {
          $result .= $paragraph-&gt;_cut_line();
        }
      } else {
        $result .= $paragraph-&gt;_add_pending_word();
        if ($paragraph-&gt;{'counter'} != 0) {
          if (!$paragraph-&gt;{'frenchspacing'} 
              and $paragraph-&gt;{'end_sentence'} 
              and $paragraph-&gt;{'end_sentence'} &gt; 0) {
            if (length($paragraph-&gt;{'space'}) &gt;= 1 or length($spaces) &gt; 1) {
              # more than one space, we can make sure tht there are only 
              # 2 spaces
              my $all_spaces = substr($paragraph-&gt;{'space'} . $spaces, 0, 2);
              $all_spaces =~ s/[\n\r]/ /g;
              $all_spaces .= ' ' x (2 - length($all_spaces));
              $paragraph-&gt;{'space'} = $all_spaces;
              delete $paragraph-&gt;{'end_sentence'};
            } else {
              # if there is only one space, we let it accumulate
              my $new_space = $spaces;
              $new_space =~ s/^[\n\r]/ /;
              $paragraph-&gt;{'space'} = $new_space;
            }
          } else {
            my $new_space = substr($spaces, 0, 1);
            $new_space =~ s/^[\n\r]/ /;
            $paragraph-&gt;{'space'} = $new_space;
          }
        }
      }
      #print STDERR "delete END_SENTENCE($paragraph-&gt;{'end_sentence'}): spaces\n" 
      #  if (defined($paragraph-&gt;{'end_sentence'}) and $paragraph-&gt;{'DEBUG'});
      #delete $paragraph-&gt;{'end_sentence'};
      if ($paragraph-&gt;{'counter'} + length($paragraph-&gt;{'space'}) 
                      &gt; $paragraph-&gt;{'max'}) {
        $result .= $paragraph-&gt;_cut_line();
      }
      if ($spaces =~ /\n/ and $paragraph-&gt;{'keep_end_lines'}) {
        $result .= $paragraph-&gt;_end_line();
      }
    } elsif ($text =~ s/^(\p{InFullwidth})//) {
      my $added = $1;
      $underlying_text =~ s/^(\p{InFullwidth})//;
      my $underlying_added = $1;
      
      print STDERR "EAST_ASIAN\n" if ($paragraph-&gt;{'DEBUG'});
      if (!defined($paragraph-&gt;{'word'})) {
        $paragraph-&gt;{'word'} = '';
        $paragraph-&gt;{'underlying_word'} = '';
      }
      $paragraph-&gt;{'word'} .= $added;
      $paragraph-&gt;{'underlying_word'} .= $underlying_added; 
      $paragraph-&gt;{'word_counter'} += 2;
      if ($paragraph-&gt;{'counter'} != 0 and
          $paragraph-&gt;{'counter'} + $paragraph-&gt;{'word_counter'} 
                               &gt; $paragraph-&gt;{'max'}) {
        $result .= $paragraph-&gt;_cut_line();
      }
      $result .= $paragraph-&gt;_add_pending_word();
      delete $paragraph-&gt;{'end_sentence'};
      $paragraph-&gt;{'space'} = '';
    } elsif ($text =~ s/^(([^\s\p{InFullwidth}]|[\x{202f}\x{00a0}])+)//) {
      my $added_word = $1;
      $underlying_text =~ s/^(([^\s\p{InFullwidth}]|[\x{202f}\x{00a0}])+)//;
      my $underlying_added_word = $1;

      $result .= $paragraph-&gt;_add_next($added_word, $underlying_added_word);

      # now check if it is considered as an end of sentence
      if (defined($paragraph-&gt;{'end_sentence'})
          and $underlying_added_word =~ /^[$after_punctuation_characters]*$/) {
        # do nothing in the case of a continuation of after_punctuation_characters
      } elsif ($paragraph-&gt;{'underlying_word'} =~ /[$end_sentence_character][$after_punctuation_characters]*$/
           and $paragraph-&gt;{'underlying_word'} !~ /[[:upper:]][$end_sentence_character$after_punctuation_characters]*$/) {
        if ($paragraph-&gt;{'frenchspacing'}) {
          $paragraph-&gt;{'end_sentence'} = -1;
        } else {
          $paragraph-&gt;{'end_sentence'} = 1;
        }
        print STDERR "END_SENTENCE\n" if ($paragraph-&gt;{'DEBUG'});
      } else {
        delete $paragraph-&gt;{'end_sentence'};
        print STDERR "delete END_SENTENCE($paragraph-&gt;{'end_sentence'}): text\n" 
          if (defined($paragraph-&gt;{'end_sentence'}) and $paragraph-&gt;{'DEBUG'});
      }
    } else {
      # Some characters are not handled by the cases above.
      # For example, it happened for strange caracters that seems to be
      # some special spaces.  It is a bit strange since the cases above 
      # include a possibility and the complement.  Maybe a character 
      # invalid in a given encoding?
      #die "Unknown caracter leading $text";
      last;
    }
  }
  return $result;
}

# for debug
sub _print_escaped_spaces($)
{
  my $spaces = shift;
  my $result = '';
  foreach my $pos (0 .. length($spaces)-1) {
    my $char = substr($spaces, $pos, 1);
    if ($char eq ' ') {
      $result .= $char;
    } elsif ($char =~ /[\f\n]/) {
      $char =~ s/\f/\\f/;
      $char =~ s/\n/\\n/;
      $result .= $char;
    } elsif ($char =~ /\s/) {
      if (ord($char) &lt;= hex(0xFFFF)) {
        $result .= '\x'.sprintf("%04x",ord($char));
      } else {
        $result .= '\x'.sprintf("%06x",ord($char));
      }
    } else {
      $result .= $char;
    }
  }
  return $result;
}

1;
</pre></body></html>