package LatexIndent::Wrap; # 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. # # See http://www.gnu.org/licenses/. # # Chris Hughes, 2017 # # For all communication, please visit: https://github.com/cmhughes/latexindent.pl use strict; use warnings; use Text::Wrap; use LatexIndent::Tokens qw/%tokens/; use LatexIndent::AlignmentAtAmpersand qw/get_column_width/; use LatexIndent::TrailingComments qw/$trailingCommentRegExp @trailingComments/; use LatexIndent::GetYamlSettings qw/%mainSettings %previouslyFoundSettings/; use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active $is_m_switch_active/; use LatexIndent::LogFile qw/$logger/; use LatexIndent::Verbatim qw/%verbatimStorage/; use Exporter qw/import/; our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 our @EXPORT_OK = qw/text_wrap text_wrap_comment_blocks/; our $sentenceCounter; sub text_wrap { my $self = shift; $logger->trace("*Text wrap regular expression construction: (see textWrapOptions: )") if $is_t_switch_active; # # textWrap Blocks FOLLOW # my $blocksFollow = q(); my $blocksFollowHash = \%{ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{blocksFollow} }; my $headingsRegEx = q(); foreach my $blocksFollowEachPart ( sort keys %{$blocksFollowHash} ) { last if ${$self}{modifyLineBreaksYamlName} eq 'sentence'; my $yesNo = $blocksFollowHash->{$blocksFollowEachPart}; if ($yesNo) { if ( $blocksFollowEachPart eq "par" ) { $blocksFollowEachPart = qr/\R?\\par(?![a-zA-Z])/s; } elsif ( $blocksFollowEachPart eq "blankLine" ) { $blocksFollowEachPart = qr/ (?:\A(?:$tokens{blanklines}\R)+) # the order of each of these | # is important, as (like always) the first (?:\G(?:$tokens{blanklines}\R)+) # thing to be matched will | # be accepted (?:(?:$tokens{blanklines}\h*\R)+) | \R{2,} | \G /sx; } elsif ( $blocksFollowEachPart eq "commentOnPreviousLine" ) { $blocksFollowEachPart = qr/(?:^\h*$trailingCommentRegExp\R?)+/m; } elsif ( $blocksFollowEachPart eq "verbatim" ) { $blocksFollowEachPart = qr/$tokens{verbatim}\d+$tokens{endOfToken}/; } elsif ( $blocksFollowEachPart eq "filecontents" ) { $blocksFollowEachPart = qr/$tokens{filecontents}\d+$tokens{endOfToken}/; } elsif ( $blocksFollowEachPart eq "headings" ) { $blocksFollowEachPart = q(); my %headingsLevels = %{ $mainSettings{indentAfterHeadings} }; while ( my ( $headingName, $headingInfo ) = each %headingsLevels ) { # check for a * in the name $headingName =~ s/\*/\\\*/g; # make the regex as # # headingName # # possible horizontal space # []? # possible optional argument # # possible horizontal space # {} # mandatory argument # $headingName = qr/\\$headingName\h*(?:\[[^]]*?\])?\h*\{[^}]*?\}\h*(?:\\label\{[^}]*?\})?/m; # stored for possible use with 'after' $headingsRegEx = $headingName . ( $headingsRegEx eq '' ? q() : "|$headingsRegEx" ); # put starred headings at the front of the regexp if ( $headingName =~ m/\*/ ) { $blocksFollowEachPart = $headingName . ( $blocksFollowEachPart eq '' ? q() : "|$blocksFollowEachPart" ); } else { $blocksFollowEachPart .= ( $blocksFollowEachPart eq '' ? q() : "|" ) . $headingName; } } } elsif ( $blocksFollowEachPart eq "other" ) { $blocksFollowEachPart = qr/$yesNo/x; } $blocksFollow .= ( $blocksFollow eq '' ? q() : "|" ) . qr/$blocksFollowEachPart/s; } } # if blankLine is not active from blocksFollow then we need to set up the # beginning of the string, but make sure that it is *not* followed by a # blank line token, or a blank line if ( !${ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{blocksFollow} }{blankLine} ) { $blocksFollow .= ( $blocksFollow eq '' ? q() : "|" ) . qr/ \G (?!$tokens{blanklines}) /sx; } # followed by 0 or more h-space and line breaks if ( ${ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{blocksFollow} }{commentOnPreviousLine} ) { $blocksFollow = ( $blocksFollow eq '' ? q() : qr/(?:$blocksFollow)(?:\h|\R)*(?:$trailingCommentRegExp\R\h*)?/s ); } else { $blocksFollow = ( $blocksFollow eq '' ? q() : qr/(?:$blocksFollow)(?:\h|\R)*/s ); } $logger->trace("textWrap blocks follow regexp:") if ( $is_tt_switch_active and ${$self}{modifyLineBreaksYamlName} ne 'sentence' ); $logger->trace($blocksFollow) if $is_tt_switch_active; # # textWrap Blocks BEGIN with # my $blocksBeginWith = q(); my $blocksBeginWithHash = \%{ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{blocksBeginWith} }; foreach my $blocksBeginWithEachPart ( sort keys %{$blocksBeginWithHash} ) { last if ${$self}{modifyLineBreaksYamlName} eq 'sentence'; my $yesNo = $blocksBeginWithHash->{$blocksBeginWithEachPart}; if ($yesNo) { if ( $blocksBeginWithEachPart eq "A-Z" ) { $logger->trace("textWrap Blocks BEGINS with capital letters (see textWrap:blocksBeginWith:A-Z)") if $is_t_switch_active; $blocksBeginWithEachPart = qr/(?!(?:$tokens{blanklines}|$tokens{verbatim}|$tokens{preamble}))[A-Z]/; } elsif ( $blocksBeginWithEachPart eq "a-z" ) { $logger->trace("textWrap Blocks BEGINS with lower-case letters (see textWrap:blocksBeginWith:a-z)") if $is_t_switch_active; $blocksBeginWithEachPart = qr/[a-z]/; } elsif ( $blocksBeginWithEachPart eq "0-9" ) { $logger->trace("textWrap Blocks BEGINS with numbers (see textWrap:blocksBeginWith:0-9)") if $is_t_switch_active; $blocksBeginWithEachPart = qr/[0-9]/; } elsif ( $blocksBeginWithEachPart eq "other" ) { $logger->trace( "textWrap Blocks BEGINS with other $yesNo (reg exp) (see textWrap:blocksBeginWith:other)") if $is_t_switch_active; $blocksBeginWithEachPart = qr/$yesNo/; } $blocksBeginWith .= ( $blocksBeginWith eq "" ? q() : "|" ) . $blocksBeginWithEachPart; } } $blocksBeginWith = qr/$blocksBeginWith/; # # textWrap Blocks END with # my $blocksEndBefore = q(); my $blocksEndBeforeHash = \%{ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{blocksEndBefore} }; foreach my $blocksEndBeforeEachPart ( sort keys %{$blocksEndBeforeHash} ) { last if ${$self}{modifyLineBreaksYamlName} eq 'sentence'; my $yesNo = $blocksEndBeforeHash->{$blocksEndBeforeEachPart}; if ($yesNo) { if ( $blocksEndBeforeEachPart eq "other" ) { $logger->trace("textWrap Blocks ENDS with other $yesNo (reg exp) (see textWrap:blocksEndBefore:other)") if $is_t_switch_active; $blocksEndBeforeEachPart = qr/\R?$yesNo/; } elsif ( $blocksEndBeforeEachPart eq "commentOnOwnLine" ) { $logger->trace( "textWrap Blocks ENDS with commentOnOwnLine (see textWrap:blocksEndBefore:commentOnOwnLine)") if $is_t_switch_active; $blocksEndBeforeEachPart = qr/^\h*$trailingCommentRegExp/m; } elsif ( $blocksEndBeforeEachPart eq "verbatim" ) { $logger->trace("textWrap Blocks ENDS with verbatim (see textWrap:blocksEndBefore:verbatim)") if $is_t_switch_active; $blocksEndBeforeEachPart = qr/$tokens{verbatim}\d/; } elsif ( $blocksEndBeforeEachPart eq "filecontents" ) { $logger->trace("textWrap Blocks ENDS with filecontents (see textWrap:blocksEndBefore:filecontents)") if $is_t_switch_active; $blocksEndBeforeEachPart = qr/$tokens{filecontents}/; } $blocksEndBefore .= ( $blocksEndBefore eq "" ? q() : "|" ) . $blocksEndBeforeEachPart; } } $blocksEndBefore = qr/$blocksEndBefore/; # # the OVERALL textWrap Blocks regexp # $logger->trace("Overall textWrap Blocks end with regexp:") if ( $is_tt_switch_active and ${$self}{modifyLineBreaksYamlName} ne 'sentence' ); $logger->trace($blocksEndBefore) if $is_tt_switch_active; # store the text wrap blocks my @textWrapBlockStorage = split( /($blocksFollow)/, ${$self}{body} ); @textWrapBlockStorage = split( /(\s*$blocksFollow+)/, ${$self}{body} ) if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{when} eq 'after'; # sentences need special treatment if ( ${$self}{modifyLineBreaksYamlName} eq 'sentence' ) { @textWrapBlockStorage = ( ${$self}{body} ); } # call the text wrapping routine my $columns = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{columns}; # vital Text::Wrap options $Text::Wrap::columns = $columns; $Text::Wrap::huge = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{huge}; $Text::Wrap::unexpand = 0; # all other Text::Wrap options not usually needed/helpful, but available $Text::Wrap::separator = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{separator} if ( ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{separator} ne '' ); $Text::Wrap::break = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{break} if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{break}; $Text::Wrap::unexpand = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{unexpand} if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{unexpand}; $Text::Wrap::tabstop = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{tabstop} if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{tabstop}; # only needed if when:after my $subsequentSpace = q(); # clear the body, which will be updated with newly wrapped body ${$self}{body} = q(); # text wrap block loop counter my $textWrapBlockCount = -1; # loop back through the text wrap block storage foreach my $textWrapBlockStorageValue (@textWrapBlockStorage) { # increment text wrap block loop counter $textWrapBlockCount++; if ( $textWrapBlockStorageValue =~ m/^\s*$blocksBeginWith/s or ${$self}{modifyLineBreaksYamlName} eq 'sentence' ) { # text wrap AFTER indentation needs to turn, for example (with columns: 100), # # before: # # \begin{abstract} # This is a very long sentence that should be wrapped at some point, which is the case but is is is is line width is not respected because of the indentation. # \end{abstract} # # after: # # \begin{abstract} # This is a very long sentence that should be wrapped at some point, which is the case but is is # is is line width is not respected because of the indentation. # \end{abstract} # ----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----| # 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 105 110 # # note: it respects the column width AND indentation # # see also: test-cases/text-wrap/issue-359*.tex # my $firstLineColumns = 0; if ( ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{when} eq 'after' ) { # reset columns $Text::Wrap::columns = $columns; # reset subsequent Space $subsequentSpace = q(); my $numberOfTABS = 0; # measure leading horizontal space if ( ${$self}{modifyLineBreaksYamlName} eq 'sentence' ) { my $thingToMeasure = ( split( /\R/, ${$self}{follows} ) )[-1]; $subsequentSpace = ( ${$self}{follows} ? " " x &get_column_width($thingToMeasure) : q() ); # possible tab adjustments $thingToMeasure = q() if ( not defined $thingToMeasure ); $numberOfTABS = () = $thingToMeasure =~ m/\t/g; for ( my $i = 1; $i <= $numberOfTABS; $i++ ) { $subsequentSpace =~ s/ / /; } } else { # note, in the above we have defined # # @textWrapBlockStorage = split( /(\s*$blocksFollow)/, ${$self}{body} ) # # so that both the *match* AND the *block* are stored, so # # ( split( /\R/, $textWrapBlockStorage[ $textWrapBlockCount - 1 ] ) )[-1] # # represents the *match* # # scenarios such as # # % comment goes here # text that isn't commented # # means that the *match* doesn't contain a linebreak # if ( scalar( split( /\R/, $textWrapBlockStorage[ $textWrapBlockCount - 1 ] ) ) == 1 and ( split( /\R/, $textWrapBlockStorage[ $textWrapBlockCount - 1 ] ) )[-1] =~ m/$trailingCommentRegExp/ ) { $subsequentSpace = q(); } else { my $thingToMeasure = ( split( /\R/, $textWrapBlockStorage[ $textWrapBlockCount - 1 ] ) )[-1]; $thingToMeasure =~ s/$tokens{blanklines}//; $thingToMeasure =~ s/$tokens{verbatim}\d+$tokens{endOfToken}//; $thingToMeasure =~ s/$trailingCommentRegExp//; $thingToMeasure =~ s/$headingsRegEx//; $thingToMeasure =~ s/\\end\{[^}]+?\}//; $subsequentSpace = ( $textWrapBlockCount == 0 ? q() : " " x &get_column_width($thingToMeasure) ); # possible tab adjustments $numberOfTABS = () = $thingToMeasure =~ m/\t/g; for ( my $i = 1; $i <= $numberOfTABS; $i++ ) { $subsequentSpace =~ s/ / /; } # check for custom indentRules see # # test-cases/text-wrap/issue-444.tex and friends # # https://github.com/cmhughes/latexindent.pl/issues/444 # # WARNING: this routine isn't perfect, it's *VERY DIFFICULT* # to figure out what a code block is without actually # finding the code block # # **known issue** with textWrap: after and indentRules # # the below will likely need to be built upon # my $name = $thingToMeasure; $name =~ s/\\begin//sg; $name =~ s/[^a-zA-Z\@0-9]//sg; # check that $name is within indentRules if ( $textWrapBlockCount > 0 and $name !~ m/^\h*$/ and defined $previouslyFoundSettings{textWrapAfter} and defined ${ $previouslyFoundSettings{textWrapAfter} }{$name} and ${ $previouslyFoundSettings{textWrapAfter} }{$name} =~ m/^\h*$/ ) { $firstLineColumns = $columns - length($subsequentSpace); $thingToMeasure =~ s/^\h*//; for ( my $i = 0; $i < &get_column_width($thingToMeasure); $i++ ) { $subsequentSpace =~ s/ //; } $subsequentSpace .= ${ $previouslyFoundSettings{textWrapAfter} }{$name}; } } } $Text::Wrap::columns = $columns - length($subsequentSpace); # possible tab adjustments for ( my $i = 1; $i <= $numberOfTABS; $i++ ) { $subsequentSpace =~ s/ /\t/; } } # LIMIT is one greater than the maximum number of times EXPR may be split my @textWrapBeforeEndWith = split( /($blocksEndBefore)/, $textWrapBlockStorageValue, 2 ); @textWrapBeforeEndWith = split( /(\s*$blocksEndBefore)/, $textWrapBlockStorageValue, 2 ) if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{when} eq 'after'; # sentences need special treatment if ( ${$self}{modifyLineBreaksYamlName} eq 'sentence' ) { @textWrapBeforeEndWith = (); } # if we have an occurrence of blocksEndBefore, then grab the stuff before it if ( scalar @textWrapBeforeEndWith > 1 ) { $textWrapBlockStorageValue = $textWrapBeforeEndWith[0]; } $logger->trace("TEXTWRAP BLOCK: $textWrapBlockStorageValue") if $is_tt_switch_active; # initiate the trailing comments storage my $trailingComments = q(); # about trailing comments # # - trailing comments that do *not* have leading space instruct the text # wrap routine to connect the lines *without* space # # - multiple trailing comments will be connected at the end of the text wrap block # # - the number of spaces between the end of the text wrap block and # the (possibly combined) trailing comments is determined by the # spaces (if any) at the end of the text wrap block # for trailing comments that # # do *NOT* have a leading space # do have a trailing line break # # then we *remove* the trailing line break while ( $textWrapBlockStorageValue =~ m|\H$trailingCommentRegExp\h*\R|s ) { $textWrapBlockStorageValue =~ s|(\H)($trailingCommentRegExp)\h*\R|$1$2|s; } # now we put all of the trailing comments together while ( $textWrapBlockStorageValue =~ m|$trailingCommentRegExp|s ) { $textWrapBlockStorageValue =~ s|($trailingCommentRegExp)||s; $trailingComments = $trailingComments . $1; } $trailingComments =~ s/\h{2,}/ /sg if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{multipleSpacesToSingle}; # determine if text wrapping will remove paragraph line breaks my $removeBlockLineBreaks = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{removeBlockLineBreaks}; # sentence remove line breaks is determined by removeSentenceLineBreaks if ( ${$self}{modifyLineBreaksYamlName} eq 'sentence' ) { $removeBlockLineBreaks = ${ $mainSettings{modifyLineBreaks}{oneSentencePerLine} }{removeSentenceLineBreaks}; } # remove internal line breaks $textWrapBlockStorageValue =~ s/\R(?!\Z)/ /sg if $removeBlockLineBreaks; # convert multiple spaces into single $textWrapBlockStorageValue =~ s/\h{2,}/ /sg if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{multipleSpacesToSingle}; # goal: get an accurate measurement of verbatim objects; # # example: # Lorem \verb!x+y! ipsum dolor sit amet # # is represented as # # Lorem LTXIN-TK-VERBATIM1-END ipsum dolor sit amet # # so we *measure* the verbatim token and replace it with # an appropriate-length string # # Lorem a2A41233rt ipsum dolor sit amet # # and then put the body back to # # Lorem LTXIN-TK-VERBATIM1-END ipsum dolor sit amet # # following the text wrapping my @putVerbatimBackIn; # check body for verbatim and get measurements if ( $textWrapBlockStorageValue =~ m/$tokens{verbatim}/s and ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{huge} eq "overflow" ) { # reference: https://stackoverflow.com/questions/10336660/in-perl-how-can-i-generate-random-strings-consisting-of-eight-hex-digits my @set = ( '0' .. '9', 'A' .. 'Z', 'a' .. 'z' ); # loop through verbatim objects while ( my ( $verbatimID, $child ) = each %verbatimStorage ) { my $verbatimThing = ${$child}{begin} . ${$child}{body} . ${$child}{end}; # if the object has line breaks, don't measure it next if $verbatimThing =~ m/\R/s; if ( $textWrapBlockStorageValue =~ m/$verbatimID/s ) { # measure length my $verbatimLength = &get_column_width($verbatimThing); # create temporary ID, and check that it is not contained in the body my $verbatimTmpID = join '' => map $set[ rand @set ], 1 .. $verbatimLength; while ( $textWrapBlockStorageValue =~ m/$verbatimTmpID/s ) { $verbatimTmpID = join '' => map $set[ rand @set ], 1 .. $verbatimLength; } # store for use after the text wrapping push( @putVerbatimBackIn, { origVerbatimID => $verbatimID, tmpVerbatimID => $verbatimTmpID } ); # make the substitution $textWrapBlockStorageValue =~ s/$verbatimID/$verbatimTmpID/s; } } } # perform the text wrap routine if ( $firstLineColumns > 0 ) { # when 'after' is active, and indentation rules specified, they need attention # # noAdditionalIndent *per-name* basis # indentRules *per-name* basis # noAdditionalIndentGlobal # indentRulesGlobal # # see # # test-cases/text-wrap/issue-444.tex # my $secondLineOnwardsColumns = $Text::Wrap::columns; # wrap the *first* line $Text::Wrap::columns = $firstLineColumns; $textWrapBlockStorageValue = wrap( '', '', $textWrapBlockStorageValue ); my @tmpBlock = split( /\R/, $textWrapBlockStorageValue ); my $firstLine = shift(@tmpBlock); # wrap the *subsequent* lines my $secondLineOnwards = join( " ", @tmpBlock ); $Text::Wrap::columns = $secondLineOnwardsColumns; $secondLineOnwards = wrap( '', '', $secondLineOnwards ); $textWrapBlockStorageValue = $firstLine . "\n" . $secondLineOnwards; } else { $textWrapBlockStorageValue = wrap( '', '', $textWrapBlockStorageValue ) if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{columns} > 0; } # if text wrap has happened *AFTER* indentation, # then we need to add the leading indentation # onto the lines of the text wrap block # # before: # # \begin{abstract} # This is a very long sentence that should be wrapped at some point, which is the case but is is # is is line width is not respected because of the indentation. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # \end{abstract} # # after: # # \begin{abstract} # This is a very long sentence that should be wrapped at some point, which is the case but is is # is is line width is not respected because of the indentation. # ^^^^ # \end{abstract} if ( ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{when} eq 'after' ) { # add leading indentation back in to the text-wrapped block $textWrapBlockStorageValue =~ s/^/$subsequentSpace/mg; # remove the very first leading space, which is already handled by blocks follow $textWrapBlockStorageValue =~ s/^\h*//s unless ${$self}{modifyLineBreaksYamlName} eq 'sentence'; } # # optionally wrap comments # if ( ${ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{comments} }{wrap} and $trailingComments ne '' ) { my $commentValue = q(); # split the trailing comments, and put the *values* together foreach ( split( /([0-9]+)$tokens{endOfToken}/, $trailingComments ) ) { next unless $_ =~ m/[0-9]/; $commentValue .= ${ $trailingComments[ $_ - 1 ] }{value}; } # leading comment space, e.g. # # % This is a very very long trailing comment that needs to be wrapped by latexindent to make sure the visual display is in a good shape. # ^^^^^ # $commentValue =~ m/^(\h+)/s; my $leadingCommentSpace = ( $1 ? $1 : ' ' ); # reset columns $Text::Wrap::columns = $columns - &get_column_width( ( split( /\R/, $textWrapBlockStorageValue ) )[-1] ); $commentValue = wrap( '', '', $commentValue ); # the 2nd and subsequent lines need further attention my @commentArray = ( split( /\R/, $commentValue ) ); my $firstLine = shift(@commentArray); my $secondCommentLineOnwards = join( " ", @commentArray ); $secondCommentLineOnwards =~ s/\h{2,}/ /sg; # assemble the new trailing comments, which will be wrapped after this routine as a comment block $trailingComments = "%" . $self->add_comment_symbol( value => $firstLine ); $trailingComments .= "\n$subsequentSpace%" . $self->add_comment_symbol( value => $leadingCommentSpace . $secondCommentLineOnwards ); } # append trailing comments from WITHIN the block $textWrapBlockStorageValue =~ s/\R?$/$trailingComments\n/s if ( $trailingComments ne '' ); # append blocksEndBefore and the stuff following it if ( scalar @textWrapBeforeEndWith > 1 ) { $textWrapBlockStorageValue .= $textWrapBeforeEndWith[1] . $textWrapBeforeEndWith[2]; } # put blank line tokens on their own lines, should only happen when the following is used: # blocksFollow: # blankLine: 0 while ( $textWrapBlockStorageValue =~ m/^\H.*?$tokens{blanklines}/m ) { $textWrapBlockStorageValue =~ s/^(\H.*?)$tokens{blanklines}/$1\n$tokens{blanklines}/m; } # remove surrounding spaces from blank line tokens $textWrapBlockStorageValue =~ s/^\h*$tokens{blanklines}\h*/$tokens{blanklines}/mg; # put verbatim back in $textWrapBlockStorageValue =~ s/${$_}{tmpVerbatimID}/${$_}{origVerbatimID}/s foreach (@putVerbatimBackIn); } # update the body ${$self}{body} .= $textWrapBlockStorageValue; } } sub text_wrap_comment_blocks { my $self = shift; # call the text wrapping routine my $columns = ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{columns}; # fail gracefully if columns == 0 if ($columns==0){ $logger->warn("* textWrapOptions: columns is set to 0, can't text wrap with this"); ${ ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{comments} }{wrap} = 0; return; } # # text wrap comment blocks # # # from: # # % first comment # % second comment # % third comment # % fourth comment # % fifth comment # # into: # # % first comment second comment # % third comment fourth comment # % fifth comment # ${$self}{body} =~ s&((?:^\h*$trailingCommentRegExp\R)+)& my @commentBlocks = $1; my $trailingComments = q(); # loop through comment blocks foreach my $commentBlock (@commentBlocks){ my $leadingHorizontalSpace = ($commentBlock =~ m|^(\h*\t*)|s? $1 : q()); my $numberOfTABS = () = $leadingHorizontalSpace =~ m/\t/g; $leadingHorizontalSpace =~ s/\t/ /g; my $commentValue = q(); $trailingComments = q(); # split the trailing comments, and put the *values* together foreach (split(/([0-9]+)$tokens{endOfToken}/,$commentBlock)){ next unless $_ =~ m/[0-9]/; $commentValue .= " ".${$trailingComments[$_-1]}{value}; } # very first space $commentValue =~ s|^\h||s; # leading space $commentValue =~ s|^(\h*)||s; my $leadingSpace = (${${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{comments}}{inheritLeadingSpace}? $1: ' ' ); $commentValue =~ s/\h{2,}/ /sg if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{multipleSpacesToSingle}; # reset columns $Text::Wrap::columns = $columns - length($leadingHorizontalSpace)-length($leadingSpace); # ^^ # for the space added after comment symbol below # tab adjustment for (my $i=1; $i<=$numberOfTABS;$i++){ $leadingHorizontalSpace =~ s/ /\t/; } # wrap the comments $commentValue = wrap( '', '', $commentValue ); # put them into storage $trailingComments .= $leadingHorizontalSpace."%".$self->add_comment_symbol(value=>$leadingSpace.$_)."\n" foreach (split( /\R/, $commentValue ) ); } $trailingComments; &xemg; # # text wrap multiple comments on a line # # from: # # % fifth comment% fourth comment% third comment% second comment% first comment # # into: # # % first comment second comment # % third comment fourth comment # % fifth comment # ${$self}{body} =~ s&((?:$trailingCommentRegExp\h*)+)& my @commentBlocks = $1; my $trailingComments = q(); # loop through comment blocks foreach my $commentBlock (@commentBlocks){ my $leadingHorizontalSpace = ($commentBlock =~ m|^(\h*\t*)|s? $1 : q()); my $numberOfTABS = () = $leadingHorizontalSpace =~ m/\t/g; $leadingHorizontalSpace =~ s/\t/ /g; my $commentValue = q(); $trailingComments = q(); # split the trailing comments, and put the *values* together foreach (split(/([0-9]+)$tokens{endOfToken}/,$commentBlock)){ next unless $_ =~ m/[0-9]/; $commentValue .= " ".${$trailingComments[$_-1]}{value}; } # very first space $commentValue =~ s|^\h||s; # leading space $commentValue =~ s|^(\h*)||s; my $leadingSpace = (${${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{comments}}{inheritLeadingSpace}? $1: ' ' ); $commentValue =~ s/\h{2,}/ /sg if ${ $mainSettings{modifyLineBreaks}{textWrapOptions} }{multipleSpacesToSingle}; # reset columns $Text::Wrap::columns = $columns - length($leadingHorizontalSpace)-length($leadingSpace); # ^^ # for the space added after comment symbol below # tab adjustment for (my $i=1; $i<=$numberOfTABS;$i++){ $leadingHorizontalSpace =~ s/ /\t/; } # wrap the comments $commentValue = wrap( '', '', $commentValue ); # put them into storage $trailingComments .= $leadingHorizontalSpace."%".$self->add_comment_symbol(value=>$leadingSpace.$_)."\n" foreach (split( /\R/, $commentValue ) ); } $trailingComments; &xemg; } 1;