package LatexIndent::AlignmentAtAmpersand; # 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-2025 # # For all communication, please visit: https://github.com/cmhughes/latexindent.pl use strict; use warnings; use Data::Dumper; use Exporter qw/import/; use List::Util qw/max min sum/; #----------- use LatexIndent::Blocks qw/$mSwitchOnlyTrailing $allCodeBlocks/; use LatexIndent::GetYamlSettings qw/%mainSetting %previouslyFoundSetting/; use LatexIndent::LogFile qw/$logger/; use LatexIndent::ModifyLineBreaks qw/_mlb_line_break_token_adjust/; use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active %switch/; use LatexIndent::Tokens qw/%tokens/; use LatexIndent::TrailingComments qw/$trailingCommentRegExp @trailingComments/; use LatexIndent::Verbatim qw/%verbatimStorage/; our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 our @EXPORT_OK = qw/align_at_ampersand _align_mark_down_block double_back_slash_else main_formatting individual_padding multicolumn_padding multicolumn_pre_check multicolumn_post_check dont_measure hidden_child_cell_row_width hidden_child_row_width get_column_width/; our $alignmentBlockCounter; our @cellStorage; # two-dimensional storage array containing the cell information our @formattedBody; # array for the new body our @minMultiColSpan; our @maxColumnWidth; our @maxDelimiterWidth; our @hiddenChildrenStorage; our $hiddenChildCount = 0; sub _align_mark_down_block { # aligned block within mark down # # %* \begin{tabular} # 1 & 2 & 3 & 4 \\ # 5 & & 6 & \\ # %* \end{tabular} # my $self = shift; my @tcMarkDownToBeReturned; foreach (@trailingComments) { next unless ${$_}{alignMarkUp}; next unless ${$self}{body} =~ m/${$_}{id}/s; ${$self}{body} =~ s/${$_}{id}/${$_}{value}/s; push( @tcMarkDownToBeReturned, { id => ${$_}{id}, value => ${$_}{value} } ); } $logger->info("*alignment mark-up routine %*\\begin{}...\\end{}") if $is_t_switch_active; $logger->trace("unpacking mark-up comments for this routine") if $is_t_switch_active; while ( my ( $alignmentBlock, $yesno ) = each %{ $mainSetting{lookForAlignDelims} } ) { if ( ref $yesno eq "HASH" ) { $yesno = ( defined ${$yesno}{delims} ) ? ${$yesno}{delims} : 1; } if ($yesno) { $logger->trace("looking for %*\\begin\{$alignmentBlock\} environments") if $is_t_switch_active; my $alignmentRegExp = qr/ (\h*) ( (?!<\\)%\*\h* \\begin\{ ($alignmentBlock) # environment name captured into $2 \} # \begin{alignmentBlock} statement captured into $1 ) ( .*? # non-greedy match (body) into $3 \R # a line break ) \h* # possible horizontal spaces ( (?!<\\)%\* # %* \h* # possible horizontal spaces \\end\{\3\} # \end{alignmentBlock} statement captured into $4 ) /smx; ${$self}{body} =~ s/$alignmentRegExp/ my $indentation = ($1?$1:q()); my $begin = $2; my $name = $3; my $body = $4; my $end = $5; # create a new Environment object my $alignmentBlockObj = LatexIndent::AlignmentAtAmpersand->new( body=>$body, name=>$name, modifyLineBreaksYamlName=>"environments", ); # log file output $logger->trace("*found: marked up alignment block %*\\begin{$name}") if $is_t_switch_active; # align at ampersand routine $alignmentBlockObj->yaml_get_indentation_settings_for_this_object; $alignmentBlockObj->align_at_ampersand ; # body update $body = ${$alignmentBlockObj}{body}; $body =~ s"^"$indentation"mg; $body =~ s"^$indentation""s; my $addedIndentation = ${$alignmentBlockObj}{indentation}; # add indentation $body =~ s"^"$addedIndentation"mg; $body =~ s"^$addedIndentation""s; $body =~ s"\s*$"\n"s; $begin.$body.$end; /xseg; } else { $logger->trace("*not* looking for $alignmentBlock as $alignmentBlock:$yesno") if $is_t_switch_active; } } $logger->trace("*alignment mark-up routine: replacing comments") if $is_t_switch_active; my $notPrecededByRegExp = qr/${${$mainSetting{fineTuning}}{trailingComments}}{notPrecededBy}/; foreach (@tcMarkDownToBeReturned) { ${$self}{body} =~ s/$notPrecededByRegExp%\Q${$_}{value}\E/%${$_}{id}/s; } return; } sub yaml_modify_line_breaks_settings { return; } sub align_at_ampersand { my $self = shift; my %input = @_; $logger->trace("*alignAtAmpersand routine for ${$self}{name} (type: ${$self}{modifyLineBreaksYamlName})") if $is_t_switch_active; # some blocks may contain verbatim to be measured ${$self}{measureVerbatim} = ( ${$self}{body} =~ m/$tokens{verbatim}/ ? 1 : 0 ); # m-switch only for double back slash # DBSStartsOnOwnLine # DBSFinishesWithLineBreak if ($is_m_switch_active) { my $DBSStartsOnOwnLine; my $DBSFinishesWithLineBreak; if ( ${$self}{modifyLineBreaksYamlName} =~ m/Arguments/s ) { $DBSStartsOnOwnLine = ${$self}{DBSStartsOnOwnLine}; $DBSFinishesWithLineBreak = ${$self}{DBSFinishesWithLineBreak}; } else { my $commandStorageName = ${$self}{name} . ${$self}{modifyLineBreaksYamlName}; $DBSStartsOnOwnLine = ( defined ${ $previouslyFoundSetting{$commandStorageName} }{DBSStartsOnOwnLine} ? ${ $previouslyFoundSetting{$commandStorageName} }{DBSStartsOnOwnLine} : 0 ); $DBSFinishesWithLineBreak = ( defined ${ $previouslyFoundSetting{$commandStorageName} }{DBSFinishesWithLineBreak} ? ${ $previouslyFoundSetting{$commandStorageName} }{DBSFinishesWithLineBreak} : 0 ); } $self->double_back_slash_else( $DBSStartsOnOwnLine, $DBSFinishesWithLineBreak ) if ( $DBSStartsOnOwnLine or $DBSFinishesWithLineBreak ); } # abandon the routine if there are no line breaks return unless ${$self}{body} =~ m/\R/s; # check for line breaks at end of begin # example: # # \begin{align*} 1&2\\ # 3&4\\ # \end{align*} # # stores '\begin{align*} ' as the begin statement (note the trailing h-space) # # note: check ${$self}{begin} as well because of poly-switch adjustment (see pmatrix2-mod1.tex) ${$self}{linebreaksAtEnd}{begin} = ( ${$self}{body} =~ m/\A\h*\R/s ? 1 : 0 ); if ( $is_m_switch_active and defined ${$self}{begin} and !${$self}{linebreaksAtEnd}{begin} ) { ${$self}{linebreaksAtEnd}{begin} = ( ${$self}{begin} =~ m/\h*\R$/s ? 1 : 0 ); } if ( !${ ${$self}{linebreaksAtEnd} }{begin} ) { ${$self}{body} =~ s/\A(\h*)//s; ${$self}{begin} .= ( $1 ? $1 : q() ); } # check for line breaks at end of body ${$self}{linebreaksAtEnd}{body} = ( ${$self}{body} =~ m/\h*\R$/s ? 1 : 0 ); # find and store \\ [ but only temporarily; we need to do this before the hidden children routine if ( ${$self}{lookForChildCodeBlocks} ) { my @DBSstorage = (); ${$self}{body} =~ s/(\\\\\h+\[)/ $hiddenChildCount++; my $output = $tokens{alignmentBlock}.$hiddenChildCount.$tokens{endOfToken}; push(@DBSstorage,{id=>$output, value=>$1 }); $output;/sgex; # find and store hidden children ${$self}{body} =~ s/ (? $allCodeBlocks )/ # store the code block my $anyCodeBlock = $+{ALLCODEBLOCKS}; # remove leading space $anyCodeBlock =~ s"^(\s*)""s; my $leadingSpace = ($1?$1:q()); $anyCodeBlock =~ s"(\s*)$""s; my $trailingSpace = ($1?$1:q()); my $output = $anyCodeBlock; # check for internal line breaks or \\ if ($anyCodeBlock =~ m"\R"s or $anyCodeBlock =~m"(${$self}{doubleBackSlash})"s or $anyCodeBlock =~m"${$self}{delimiterRegEx}"s){ $hiddenChildCount++; $output = $tokens{alignmentBlock}.$hiddenChildCount.$tokens{endOfToken}; push(@hiddenChildrenStorage,{id=>$output,value=>$anyCodeBlock}); $logger->trace("*found: hidden child in alignment block") if $is_t_switch_active; ${$self}{measureHiddenChildren} = 1; } $output = $leadingSpace.$output.$trailingSpace; $output;/sgex; # put DBS with h-space [ back in foreach (@DBSstorage) { ${$self}{body} =~ s/${$_}{id}/${$_}{value}/s; } } my $maximumNumberOfAmpersands = 0; # clear the global arrays @formattedBody = (); @cellStorage = (); @minMultiColSpan = (); @maxColumnWidth = (); @maxDelimiterWidth = (); # maximum column widths my @maximumColumnWidths; my $rowCounter = -1; my $columnCounter = -1; $logger->trace("*dontMeasure routine, row mode") if ( ${$self}{dontMeasure} and $is_t_switch_active ); # delimiters regex, default is: # # (?dont_measure( mode => "row", row => $_ ) if ${$self}{dontMeasure}; # remove \\ and anything following it my $endPiece = q(); if ( $_ =~ m/(\\\\.*)/ ) { if ( ${$self}{alignFinalDoubleBackSlash} ) { $logger->trace("alignFinalDoubleBackSlash active for ${$self}{name}") if ($is_t_switch_active); # for example, if we want: # # Name & \shortstack{Hi \\ Lo} \\