These three routines (given inside a simple utility program) perform full justification of text by expanding the spaces between words until the line meets the specified width.

Each routine uses a different method, which basically means they use a different starting point within the line and work out from there. I think that middle_out() looks the best, followed closely by right_to_left(). Your call, though.

These routines are simple. They do not do any reflowing and if the lines start out longer than the width given, they just pass it through unmodified. Update: If you want reflowing, just pass the text through Text::Wrap first.

Significate Update: I posted a new version as a reply below. It simplifies the code in the method somewhat and fixes a few bugs related to getting the options. More info below. :-)

#!/usr/bin/perl -wn BEGIN { $LINE_WIDTH = 75; $METHOD = 'middle_out'; $CONDENSE = 1; use Getopt::Long; Getopt::Long::Configure('bundling'); GetOptions( 'condense!' => \$CONDENSE, 'm|middle' => sub { $METHOD = 'middle_out' }, 'l|left' => sub { $METHOD = 'left_to_right' }, 'r|right' => sub { $METHOD = 'right_to_left' }, 'w|width=i' => \$LINE_WIDTH, 'help' => sub { my $self = $0; $self =~ s/^.*\///; die << "USAGE"; Usage: $self [OPTION]... [FILE]... Fully justifies a stream of text using one of three different methods. --condense condense multiple spaces before justifying (de +fault) --nocondense -m, --middle use the middle-out method (default) -l, --left use the left-to-right method -r, --right use the right-to-left method -w, --width=WIDTH set the line-width to WIDTH (default is 75) --help display this help and exit The different methods describe where $self starts expanding spaces to get lines to be the appropriate width. For example, middle-out starts in the middle of the line, expanding spaces in both directions. The middle-out method usually looks best, although right-to-left is some- times better. USAGE }, ); } if($CONDENSE) { s/^ +| +$//g; s/(?<!\. ) {2,}/ /g; } if(defined $last_line) { if($last_line !~ /^$/ and $_ !~ /^$/) { $last_line = $METHOD->($last_line, $LINE_WIDTH); } print $last_line; } $last_line = $_; END { print $last_line if defined $last_line } sub middle_out { my $line = shift; my $width = shift; while(length($line) < $width) { my $pr = my $pf = int(length($line) / 2); # Start from the mid +dle until($pr == -1 and $pf == -1) { $pr = rindex $line, ' ', $pr; if($pr > -1) { substr $line, $pr, 1, ' '; --$pr; # Skip the space that was there. ++$pf; ++$pf; # Adjust $pf to match what's really the +re now last unless(length($line) < $width); } $pf = index $line, ' ', $pf; if($pf > -1) { substr $line, $pf, 1, ' '; ++$pf; ++$pf; # Skip the two spaces we just made last unless(length($line) < $width); } } } return $line; } sub right_to_left { my $line = shift; my $width = shift; while(length($line) < $width) { my $pr = rindex $line, ' '; while($pr >= 0 and length($line) < $width) { substr $line, $pr, 1, ' '; --$pr; # Skip the space that was there $pr = rindex $line, ' ', $pr; } } return $line; } sub left_to_right { my $line = shift; my $width = shift; my $width = shift; while(length($line) < $width) { my $pr = rindex $line, ' '; while($pr >= 0 and length($line) < $width) { substr $line, $pr, 1, ' '; --$pr; # Skip the space that was there $pr = rindex $line, ' ', $pr; } } return $line; } sub left_to_right { my $line = shift; my $width = shift; while(length($line) < $width) { my $pf = index $line, ' '; while($pf >= 0 and length($line) < $width) { substr $line, $pf, 1, ' '; ++$pf; # Skip the space that was there ++$pf; # Skip the extra space we just put in $pf = index $line, ' ', $pf; } } return $line; }

Replies are listed 'Best First'.
(bbfu) (Version 2) Re: Full justification - 3 methods
by bbfu (Curate) on Apr 13, 2001 at 05:30 UTC

    I decided to write this in C to see how easy/hard it would be. It turned out to not be that hard but made me realize that the methods could be simplified by not having them work directly on the strings (lines). Instead, I made an "expansion map," held in an array.

    Each index in the array corresponds to the respective space in the string (ie, index 0 is the first space, etc) and holds a number that tells us how much we should expand that space. So if, for example, index 5 held the number 2, we would replace the sixth space in the string with two spaces.

    This simplifies the methods because we no longer have to account for a string of ever expanding length, plus we don't end up re-copying half of the string repeatedly.

    The basic idea behind the methods remain the same. Using a "space table" does introduce one side-effect, however. The middle-out method bases it's starting position on the middle of the table instead of the middle of the string. In almost all instances, this will not create a noticable difference, though.

    If you're interested in seeing the C version and how the code compares to the Perl (it's just about twice as long as the Perl; mostly due to setup for things like getopt_long and emulating perl's -n option, as well as not having an easy way to count or expand the spaces), it's available at http://www.exnoctum.f2s.com/just_c.c. I'm interested in what people think of both versions. =)

    This version also fixes a few minor bugs related to balancing of spaces in the middle-out method, as well as one or two minor issues with the user options (ie, I added short-forms of --condense and --nocondense).

Re: Full justification - 3 methods
by tinman (Curate) on Apr 06, 2001 at 01:54 UTC

    ++ for the methods, bbfu

    As a point of interest, doesn't Text::Wrap do something similar ? I've used Text::Wrap once or twice for formatting text.

      Text::Wrap does not. Text::Format does do full justification (along with quite a bit of other stuff) but I wrote these to try out and compare the different methods, as well as just for the simplicity (and because it was fun, and I could, and no one was there to stop me, so :-P).

      Besides, Text::Format isn't bundled. =) At any rate, it was mostly just an exercise that I thought I'd share.

      bbfu
      Seasons don't fear The Reaper.
      Nor do the wind, the sun, and the rain.
      We can be like they are.