]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
Apply big patch (patch-20) from Roland Huebner.
authorJim Meyering <jim@meyering.net>
Thu, 5 Dec 1996 04:33:33 +0000 (04:33 +0000)
committerJim Meyering <jim@meyering.net>
Thu, 5 Dec 1996 04:33:33 +0000 (04:33 +0000)
src/pr.c

index 2883ec0e0ac624bab27a862a531768e185aa0987..7e20e3c388d1df257ccc0013ddfd159bc831e65f 100644 (file)
--- a/src/pr.c
+++ b/src/pr.c
 
    Options:
 
-   +PAGE       Begin output at page PAGE of the output.
+   +FIRST_PAGE[:LAST_PAGE]
+               begin [stop] printing with page FIRST_[LAST_]PAGE
 
    -COLUMN     Produce output that is COLUMN columns wide and print
                columns down.
+                Balance columns on the last page is automatically set.
 
    -a          Print columns across rather than down.  The input
                one
                four
 
    -b          Balance columns on the last page.
+                -b is no longer an independent option. It's always used
+               together with -COLUMN (unless -a is used) to get a
+               consistent formulation with "FF set by hand" in input
+               files. Each formfeed found terminates the number of lines
+               to be read with the actual page. The situation for
+               printing columns down is equivalent to that on the last
+               page. So we need a balancing.
+
+               We do not yet eliminate source text dealing with -COLUMN
+               only. Tune this if it proved to be a satisfactory
+               procedure.
+
+               Keeping -b as an underground option guarantees some
+               downward compatibility. Utilities using pr with -b
+               (a most frequently used form) still work as usual.
 
    -c          Print unprintable characters as control prefixes.
                Control-g is printed as ^G.
 
    -F
    -f          Use formfeeds instead of newlines to separate pages.
+               A three line HEADER is used, no TRAILER (without -f
+               both HEADER and TRAILER are made of five lines).
 
-   -h header   Replace the filename in the header with the string HEADER.
+   -h HEADER   Replace the filename in the header with the string HEADER.
+                Checking and left-hand-side truncation of the length of the
+                standard and custom header string. A centered header is used.
+                The format of date and time has been shortened
+               to  yy-mm-dd HH:MM  to give place to a maximal filename
+               information.
+                -h ""  now prints a blank line header. -h"" shows an error.
 
    -i[c[k]]    Replace spaces with tabs on output.  Optional argument
                C is the output tab character.  (Default is `\t'.)  Optional
                argument K is the output tab character's width.  (Default
                is 8.)
 
+  -j            Merge full lines, turns off -w line truncation, no column
+                alignment, -s[STRING] sets separators, works with all
+               column options (-COLUMN | -a -COLUMN | -m).
+
    -l lines    Set the page length to LINES.  Default is 66.
 
-   -m          Print files in parallel.
+   -m          Print files in parallel; pad_across_to align columns;
+               truncate lines and print separator strings;
+               Do it also with empty columns to get a continuous line
+               numbering and column marking by separators throughout
+               the whole merged file.
+
+               Empty pages in some input files produce empty columns
+               [marked by separators] in the common pages. Completely
+               empty common pages show no column separators at all.
+
+               The layout of a merged page is ruled by the largest form
+               feed distance of the single pages at that page. Shorter
+               columns will be filled up with empty lines.
+
+               Together with -j option join lines of full length and
+               in addition set separators when -s option is used.
 
    -n[c[k]]    Precede each column with a line number.
                (With parallel files, precede each line with a line
                number.)  Optional argument C is the character to print
                after each number.  (Default `\t'.)  Optional argument
-               K is the number of digits per line number.  (Default 5.)
+               k is the number of digits per line number.  (Default 5.)
+               Default counting starts with 1st line of input file.
+
+  -N number    Start counting with  number  at 1st line of first page
+               printed.
 
    -o offset   Offset each line with a margin OFFSET spaces wide.
                Total page width is the size of this offset plus the
 
    -r          Ignore files that can't be opened.
 
-   -s[c]       Separate each line with a character.  Optional argument C is
-               the character to be used.  Default is `\t'.
+  -s[STRING]   Separate columns by any string STRING.
+                Don't use -s "STRING".
+               without -s: default separator 'space' s used,
+               same as -s" ",
+               with -s only: no separator is used, same as -s"".
+               Quotes should be used with blanks and some shell active
+               characters.
+
+   -t          Do not print headers or footers but retain form feeds
+               set in input files (some page layout is not changed).
 
-   -t          Do not print headers or footers.
+   -T          Do not print headers or footers, eliminate form feeds
+               in input files.
 
    -v          Print unprintable characters as escape sequences.
                Control-G becomes \007.
 
-   -w width    Set the page width to WIDTH characters. */
+   -w width    Set the page width to WIDTH characters.
+               (In pr versions newer than 1.14 -s option does no longer
+               affect -w option.)
+
+               With/without  -w width  the header line is truncated.
+               Default is 72 characters.
+               With  -w width  text lines will be truncated, unless -j is
+               used. Together with one of the column options
+               (-COLUMN| -a -COLUMN| -m) column alignment is used.
+               Without  -w PAGE_WIDTH
+               - but with one of the column options default truncation of
+                 72 characters is used (to keep downward compatibility
+                 and to simplify most frequently met column tasks).
+                 Column alignment and column separators are used.
+               - and without any of the column options no line truncation
+                 is used (to keep downward compatibility and to meet most
+                 frequent tasks). That's equivalent to  -w 72 -j .
+*/
 \f
 
 #include <config.h>
 #include <time.h>
 #include "system.h"
 #include "error.h"
+#include "xstrtol.h"
+
+#if HAVE_LIMITS_H
+#include <limits.h>
+#endif
 
 char *xmalloc ();
 char *xrealloc ();
 
+#ifndef UINT_MAX
+#define UINT_MAX ((unsigned int) ~(unsigned int) 0)
+#endif
+
+#ifndef INT_MAX
+#define INT_MAX ((int) (UINT_MAX >> 1))
+#endif
+
 #ifndef TRUE
 #define TRUE   1
 #define FALSE  0
@@ -156,46 +243,63 @@ char *xrealloc ();
    numbered            True means precede this column with a line number. */
 
 struct COLUMN
-{
-  FILE *fp;                    /* Input stream for this column. */
-  char *name;                  /* File name. */
-  enum
   {
-    OPEN,
-    ON_HOLD,                   /* Hit a form feed. */
-    CLOSED
-  } status;                    /* Status of the file pointer. */
-  int (*print_func) ();                /* Func to print lines in this col. */
-  void (*char_func) ();                /* Func to print/store chars in this col. */
-  int current_line;            /* Index of current place in line_vector. */
-  int lines_stored;            /* Number of lines stored in buff. */
-  int lines_to_print;          /* No. lines stored or space left on page. */
-  int start_position;          /* Horizontal position of first char. */
-  int numbered;
-};
+    FILE *fp;                  /* Input stream for this column. */
+    char *name;                        /* File name. */
+    enum
+      {
+       OPEN,
+       FF_FOUND,               /* used with -b option, set with \f, changed
+                                  to ON_HOLD after print_header */
+       ON_HOLD,                /* Hit a form feed. */
+       CLOSED
+      }
+    status;                    /* Status of the file pointer. */
+    int (*print_func) ();      /* Func to print lines in this col. */
+    void (*char_func) ();      /* Func to print/store chars in this col. */
+    int current_line;          /* Index of current place in line_vector. */
+    int lines_stored;          /* Number of lines stored in buff. */
+    int lines_to_print;                /* No. lines stored or space left on page. */
+    int start_position;                /* Horizontal position of first char. */
+    int numbered;
+    int full_page_printed;     /* True means printed without a FF found. */
+
+    /* p->full_page_printed  controls a special case of "FF set by hand":
+       True means a full page has been printed without FF found. To avoid an
+       additional empty page we have to ignore a FF immediately following in
+       the next line. */
+  };
 
 typedef struct COLUMN COLUMN;
 
 #define NULLCOL (COLUMN *)0
 
 static int char_to_clump __P ((int c));
-static int read_line __P ((COLUMN *p));
+static int read_line __P ((COLUMN * p));
 static int print_page __P ((void));
-static int print_stored __P ((COLUMN *p));
-static int open_file __P ((char *name, COLUMN *p));
+static int print_stored __P ((COLUMN * p));
+static int open_file __P ((char *name, COLUMN * p));
 static int skip_to_page __P ((int page));
+static void print_header __P ((void));
+static void pad_across_to __P ((int position));
+static void number __P ((COLUMN * p));
 static void getoptarg __P ((char *arg, char switch_char, char *character, int *number));
 static void usage __P ((int status));
 static void print_files __P ((int number_of_files, char **av));
+static void init_parameters __P ((int number_of_files));
 static void init_header __P ((char *filename, int desc));
 static void init_store_cols __P ((void));
 static void store_columns __P ((void));
 static void balance __P ((int total_stored));
 static void store_char __P ((int c));
 static void pad_down __P ((int lines));
-static void read_rest_of_line __P ((COLUMN *p));
+static void read_rest_of_line __P ((COLUMN * p));
+static void skip_read __P ((COLUMN * p, int column_number));
 static void print_char __P ((int c));
 static void cleanup __P ((void));
+static void first_last_page __P ((char *pages));
+static void print_sep_string __P ((void));
+static void separator_string (char *optarg_S);
 
 /* The name under which this program was invoked. */
 char *program_name;
@@ -205,7 +309,7 @@ static COLUMN *column_vector;
 
 /* When printing a single file in multiple downward columns,
    we store the leftmost columns contiguously in buff.
-   To print a line from buff, get the index of the first char
+   To print a line from buff, get the index of the first character
    from line_vector[i], and print up to line_vector[i + 1]. */
 static char *buff;
 
@@ -233,13 +337,33 @@ static int *end_vector;
 /* (-m) True means we're printing multiple files in parallel. */
 static int parallel_files = FALSE;
 
+/* (-m) True means a line starts with some empty columns (some files
+   already CLOSED or ON_HOLD) which we have to align. */
+static int align_empty_cols;
+
+/* (-m) True means we have not yet found any printable column in a line.
+   align_empty_cols = TRUE  has to be maintained. */
+static int empty_line;
+
+/* (-m) False means printable column output precedes a form feed found.
+   Column align is done only once. No additional action with that form
+   feed.
+   True means we found only a form feed in a column. Maybe we have to do
+   some column align with that form feed. */
+static int FF_only;
+
 /* (-[0-9]+) True means we're given an option explicitly specifying
    number of columns.  Used to detect when this option is used with -m. */
 static int explicit_columns = FALSE;
 
-/* (-t) True means we're printing headers and footers. */
+/* (-t|-T) False means we aren't printing headers and footers. */
 static int extremities = TRUE;
 
+/* (-t) True means we retain all FF set by hand in input files.
+   False is set with -T option. */
+static int keep_FF = FALSE;
+static int print_a_FF = FALSE;
+
 /* True means we need to print a header as soon as we know we've got input
    to print after it. */
 static int print_a_header;
@@ -261,6 +385,9 @@ static int print_across_flag = FALSE;
 static int storing_columns = TRUE;
 
 /* (-b) True means balance columns on the last page as Sys V does. */
+/* That's no longer an independent option. With storing_columns = TRUE
+   balance_columns = TRUE is used too (s. function init_parameters).
+   We get a consistent formulation with "FF set by hand" in input files. */
 static int balance_columns = FALSE;
 
 /* (-l) Number of lines on a page, including header and footer lines. */
@@ -276,7 +403,15 @@ static int lines_per_footer = 5;
    the margin. */
 static int chars_per_line = 72;
 
-/* Number of characters in a column.  Based on the gutter and page widths. */
+/* (-w) True means we truncate lines longer than chars_per_column. */
+static int truncate_lines = FALSE;
+
+/* (-j) True means we join lines without any line truncation. -j
+   dominates -w option. */
+static int join_lines = FALSE;
+
+/* Number of characters in a column.  Based on col_sep_length and
+   page width. */
 static int chars_per_column;
 
 /* (-e) True means convert tabs to spaces on input. */
@@ -303,11 +438,6 @@ static int chars_per_output_tab = 8;
    if necessary to get to output_position + spaces_not_printed. */
 static int spaces_not_printed;
 
-/* Number of spaces between columns (though tabs can be used when possible to
-   use up the equivalent amount of space).  Not sure if this is worth making
-   a flag for.  BSD uses 0, Sys V uses 1.  Sys V looks better. */
-static int chars_per_gutter = 1;
-
 /* (-o) Number of spaces in the left margin (tabs used when possible). */
 static int chars_per_margin = 0;
 
@@ -338,8 +468,9 @@ static int failed_opens = 0;
 /* (-NNN) Number of columns of text to print. */
 static int columns = 1;
 
-/* (+NNN) Page number on which to begin printing. */
+/* (+NNN:MMM) Page numbers on which to begin and stop printing. */
 static int first_page_number = 1;
+static int last_page_number = 0;
 
 /* Number of files open (not closed, not on hold). */
 static int files_ready_to_read = 0;
@@ -350,16 +481,16 @@ static int page_number;
 /* Current line number.  Displayed when -n flag is specified.
 
    When printing files in parallel (-m flag), line numbering is as follows:
-   1   foo     goo     moo
-   2   hoo     too     zoo
+   1    foo     goo     moo
+   2    hoo     too     zoo
 
    When printing files across (-a flag), ...
-   1   foo     2       moo     3       goo
-   4   hoo     3       too     6       zoo
+   1    foo     2       moo     3       goo
+   4    hoo     3       too     6       zoo
 
    Otherwise, line numbering is as follows:
-   1   foo     3       goo     5       too
-   2   moo     4       hoo     6       zoo */
+   1    foo     3       goo     5       too
+   2    moo     4       hoo     6       zoo */
 static int line_number;
 
 /* (-n) True means lines should be preceded by numbers. */
@@ -368,6 +499,19 @@ static int numbered_lines = FALSE;
 /* (-n) Character which follows each line number. */
 static char number_separator = '\t';
 
+/* (-n) line counting starts with 1st line of input file (not with 1st
+   line of 1st page printed). */
+static int line_count = 1;
+
+/* (-n) True means counting of skipped lines starts with 1st line of
+   input file. False means -N option is used in addition, counting of
+   skipped lines not required. */
+static int skip_count = TRUE;
+
+/* (-N) Counting starts with start_line_number = NUMBER at 1st line of
+   first page printed, usually not 1st page of input file. */
+static int start_line_num = 1;
+
 /* (-n) Width in characters of a line number. */
 static int chars_per_number = 5;
 
@@ -397,11 +541,15 @@ static int total_files = 0;
 /* (-r) True means don't complain if we can't open a file. */
 static int ignore_failed_opens = FALSE;
 
-/* (-s) True means we separate columns with a specified character. */
-static int use_column_separator = FALSE;
+/* (-s) True means we separate columns with a specified string.
+   -s option does not affect line truncation nor column alignment. */
+static int use_col_separator = FALSE;
 
-/* Character used to separate columns if the the -s flag has been specified. */
-static char column_separator = '\t';
+/* String used to separate columns if the -s option has been specified.
+   Default value with -s is a space. */
+static char *col_sep_string;
+static int col_sep_length = 0;
+static char *column_separator = " ";
 
 /* Number of separator characters waiting to be printed as soon as we
    know that we have any input remaining to be printed. */
@@ -423,19 +571,27 @@ static char *header;
 
 static int *clump_buff;
 
-/* True means we truncate lines longer than chars_per_column. */
-static int truncate_lines = FALSE;
-
 /* If nonzero, display usage information and exit.  */
 static int show_help;
 
 /* If nonzero, print the version on standard output then exit.  */
 static int show_version;
 
+/* True means we read the line no. lines_per_body in skip_read
+   called by skip_to_page. That variable controls the coincidence of a
+   "FF set by hand" and "full_page_printed", see above the definition of
+   structure COLUMN. */
+static int last_line = FALSE;
+
+/* If nonzero, print a non-variable date and time with the header
+   -h HEADER using pr test-suite */
+static int test_suite;
+
 static struct option const long_options[] =
 {
   {"help", no_argument, &show_help, 1},
   {"version", no_argument, &show_version, 1},
+  {"test", no_argument, &test_suite, 1},
   {0, 0, 0, 0}
 };
 
@@ -452,11 +608,66 @@ cols_ready_to_print (void)
   n = 0;
   for (q = column_vector, i = 0; i < columns; ++q, ++i)
     if (q->status == OPEN ||
+       q->status == FF_FOUND ||        /* With -b: To print a header only */
        (storing_columns && q->lines_stored > 0 && q->lines_to_print > 0))
       ++n;
   return n;
 }
 
+/* Estimate first_ / last_page_number
+   using option +FIRST_PAGE:LAST_PAGE */
+
+static void
+first_last_page (char *pages)
+{
+  char *str1;
+
+  if (*pages == ':')
+    {
+      error (0, 0, _("invalid range of page numbers: `%s'"), pages);
+      usage (2);
+    }
+
+  str1 = strchr (pages, ':');
+  if (str1 != NULL)
+    *str1 = '\0';
+
+  {
+    long int tmp_long;
+    if (xstrtol (pages, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+       || tmp_long <= 0 || tmp_long > INT_MAX)
+      error (EXIT_FAILURE, 0, _("invalid starting page number: `%s'"),
+            pages);
+    first_page_number = (int) tmp_long;
+  }
+
+  if (str1 == NULL)
+    return;
+
+  {
+    long int tmp_long;
+    if (xstrtol (str1 + 1, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+       || tmp_long <= 0 || tmp_long > INT_MAX)
+      error (EXIT_FAILURE, 0, _("invalid ending page number: `%s'"),
+            str1 + 1);
+    last_page_number = (int) tmp_long;
+  }
+
+  if (first_page_number > last_page_number)
+    error (EXIT_FAILURE, 0,
+          _("starting page number is larger than ending page number"));
+}
+
+/* Estimate length of col_sep_string with option -s[STRING] */
+
+static void
+separator_string (char *optarg_S)
+{
+  col_sep_length = (int) strlen (optarg_S);
+  col_sep_string = (char *) xmalloc (col_sep_length + 1);
+  strcpy (col_sep_string, optarg_S);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -478,22 +689,16 @@ main (int argc, char **argv)
   while (1)
     {
       c = getopt_long (argc, argv,
-                      "-0123456789abcde::fFh:i::l:mn::o:rs::tvw:",
+                      "-0123456789abcde::fFh:i::jl:mn::N:o:rs::tTvw:",
                       long_options, (int *) 0);
-      if (c == 1)              /* Non-option argument. */
+      if (c == 1)              /* Non-option argument. */
        {
          char *s;
          s = optarg;
          if (*s == '+')
            {
              ++s;
-             if (!ISDIGIT (*s))
-               {
-                 error (0, 0, _("`+' requires a numeric argument"));
-                 usage (2);
-               }
-             /* FIXME: use strtol */
-             first_page_number = atoi (s);
+             first_last_page (s);
            }
          else
            {
@@ -526,7 +731,7 @@ main (int argc, char **argv)
 
       switch (c)
        {
-       case 0: /* getopt long option */
+       case 0:         /* getopt long option */
          break;
 
        case 'a':
@@ -564,9 +769,19 @@ main (int argc, char **argv)
          /* Could check tab width > 0. */
          tabify_output = TRUE;
          break;
-       case 'l':
-         lines_per_page = atoi (optarg);
+       case 'j':
+         join_lines = TRUE;
          break;
+       case 'l':
+         {
+           long int tmp_long;
+           if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+               || tmp_long <= 0 || tmp_long > INT_MAX)
+             error (EXIT_FAILURE, 0,
+                 _("`-l PAGE_LENGTH' invalid number of lines: `%s'"), optarg);
+           lines_per_page = (int) tmp_long;
+           break;
+         }
        case 'm':
          parallel_files = TRUE;
          storing_columns = FALSE;
@@ -577,37 +792,57 @@ main (int argc, char **argv)
            getoptarg (optarg, 'n', &number_separator,
                       &chars_per_number);
          break;
+       case 'N':
+         skip_count = FALSE;
+         {
+           long int tmp_long;
+           if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+               || tmp_long > INT_MAX)
+             error (EXIT_FAILURE, 0,
+             _("`-N NUMBER' invalid starting line number: `%s'"), optarg);
+           start_line_num = (int) tmp_long;
+           break;
+         }
        case 'o':
-         chars_per_margin = atoi (optarg);
-         break;
+         {
+           long int tmp_long;
+           if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+               || tmp_long <= 0 || tmp_long > INT_MAX)
+             error (EXIT_FAILURE, 0,
+                    _("`-o MARGIN' invalid line offset: `%s'"), optarg);
+           chars_per_margin = (int) tmp_long;
+           break;
+         }
        case 'r':
          ignore_failed_opens = TRUE;
          break;
        case 's':
-         use_column_separator = TRUE;
+         use_col_separator = TRUE;
          if (optarg)
-           {
-             char *s;
-             s = optarg;
-             column_separator = *s;
-             if (*++s)
-               {
-                 fprintf (stderr, _("\
-%s: extra characters in the argument to the `-s' option: `%s'\n"),
-                          program_name, s);
-                 usage (2);
-               }
-           }
+           separator_string (optarg);
          break;
        case 't':
          extremities = FALSE;
+         keep_FF = TRUE;
+         break;
+       case 'T':
+         extremities = FALSE;
+         keep_FF = FALSE;
          break;
        case 'v':
          use_esc_sequence = TRUE;
          break;
        case 'w':
-         chars_per_line = atoi (optarg);
-         break;
+         truncate_lines = TRUE;
+         {
+           long int tmp_long;
+           if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+               || tmp_long <= 0 || tmp_long > INT_MAX)
+             error (EXIT_FAILURE, 0,
+                _("`-w PAGE_WIDTH' invalid column number: `%s'"), optarg);
+           chars_per_line = (int) tmp_long;
+           break;
+         }
        default:
          usage (2);
          break;
@@ -625,13 +860,13 @@ main (int argc, char **argv)
 
   if (parallel_files && explicit_columns)
     error (EXIT_FAILURE, 0,
-  _("Cannot specify number of columns when printing in parallel."));
+        _("Cannot specify number of columns when printing in parallel."));
 
   if (parallel_files && print_across_flag)
     error (EXIT_FAILURE, 0,
-  _("Cannot specify both printing across and printing in parallel."));
+       _("Cannot specify both printing across and printing in parallel."));
 
-  for ( ; optind < argc; optind++)
+  for (; optind < argc; optind++)
     {
       file_names[n_files++] = argv[optind];
     }
@@ -648,7 +883,7 @@ main (int argc, char **argv)
       else
        {
          int i;
-         for (i=0; i<n_files; i++)
+         for (i = 0; i < n_files; i++)
            print_files (1, &file_names[i]);
        }
     }
@@ -660,7 +895,7 @@ main (int argc, char **argv)
   if (ferror (stdout) || fclose (stdout) == EOF)
     error (EXIT_FAILURE, errno, _("write error"));
   if (failed_opens > 0)
-    exit(1);
+    exit (EXIT_FAILURE);
   exit (EXIT_SUCCESS);
 }
 
@@ -677,15 +912,16 @@ getoptarg (char *arg, char switch_char, char *character, int *number)
     *character = *arg++;
   if (*arg)
     {
-      if (ISDIGIT (*arg))
-       *number = atoi (arg);
-      else
+      long int tmp_long;
+      if (xstrtol (arg, NULL, 10, &tmp_long, NULL) != LONGINT_OK
+         || tmp_long <= 0 || tmp_long > INT_MAX)
        {
-         fprintf (stderr, _("\
-%s: extra characters in the argument to the `-%c' option: `%s'\n"),
-                  program_name, switch_char, arg);
+         error (0, 0,
+                _("`-%c' extra characters or invalid number in the argument: `%s'"),
+                switch_char, arg);
          usage (2);
        }
+      *number = (int) tmp_long;
     }
 }
 \f
@@ -696,9 +932,18 @@ init_parameters (int number_of_files)
 {
   int chars_used_by_number = 0;
 
+  if (use_form_feed)
+    {
+      lines_per_header = 3;
+      lines_per_footer = 0;
+    }
+
   lines_per_body = lines_per_page - lines_per_header - lines_per_footer;
   if (lines_per_body <= 0)
-    extremities = FALSE;
+    {
+      extremities = FALSE;
+      keep_FF = TRUE;
+    }
   if (extremities == FALSE)
     lines_per_body = lines_per_page;
 
@@ -713,20 +958,35 @@ init_parameters (int number_of_files)
   if (parallel_files)
     columns = number_of_files;
 
+  /* One file, multi columns down: -b option is set to get a consistent
+     formulation with "FF set by hand" in input files. */
+  if (storing_columns)
+    balance_columns = TRUE;
+
   /* Tabification is assumed for multiple columns. */
   if (columns > 1)
     {
-      if (!use_column_separator)
-       truncate_lines = TRUE;
+      if (!use_col_separator)
+       {
+         col_sep_string = column_separator;
+         col_sep_length = 1;
+         use_col_separator = TRUE;
+       }
 
+      truncate_lines = TRUE;
       untabify_input = TRUE;
       tabify_output = TRUE;
     }
   else
     storing_columns = FALSE;
 
+  /* -j dominates -w in any case */
+  if (join_lines)
+    truncate_lines = FALSE;
+
   if (numbered_lines)
     {
+      line_count = start_line_num;
       if (number_separator == input_tab_char)
        {
          number_width = chars_per_number +
@@ -742,7 +1002,7 @@ init_parameters (int number_of_files)
     }
 
   chars_per_column = (chars_per_line - chars_used_by_number -
-                     (columns - 1) * chars_per_gutter) / columns;
+                     (columns - 1) * col_sep_length) / columns;
 
   if (chars_per_column < 1)
     error (EXIT_FAILURE, 0, _("page width too narrow"));
@@ -751,8 +1011,7 @@ init_parameters (int number_of_files)
     {
       if (number_buff != (char *) 0)
        free (number_buff);
-      number_buff = (char *)
-       xmalloc (2 * chars_per_number * sizeof (char));
+      number_buff = (char *) xmalloc (2 * chars_per_number);
     }
 
   /* Pick the maximum between the tab width and the width of an
@@ -769,7 +1028,10 @@ init_parameters (int number_of_files)
    With multiple files, each column p has a different p->fp.
    With single files, each column p has the same p->fp.
    Return 1 if (number_of_files > 0) and no files can be opened,
-   0 otherwise.  */
+   0 otherwise.
+
+   With each column/file p, p->full_page_printed is initialized,
+   see also open_file.  */
 
 static int
 init_fps (int number_of_files, char **av)
@@ -815,6 +1077,7 @@ init_fps (int number_of_files, char **av)
          p->fp = stdin;
          have_read_stdin = TRUE;
          p->status = OPEN;
+         p->full_page_printed = FALSE;
          ++total_files;
          init_header ("", -1);
        }
@@ -826,6 +1089,7 @@ init_fps (int number_of_files, char **av)
          p->name = firstname;
          p->fp = firstfp;
          p->status = OPEN;
+         p->full_page_printed = FALSE;
        }
     }
   files_ready_to_read = total_files;
@@ -846,7 +1110,7 @@ init_funcs (void)
 
   h = chars_per_margin;
 
-  if (use_column_separator)
+  if (!truncate_lines)
     h_next = ANYWHERE;
   else
     {
@@ -859,6 +1123,10 @@ init_funcs (void)
        h_next = h + chars_per_column;
     }
 
+  /* Enlarge p->start_position of first column to use the same form of
+     padding_not_printed with all columns. */
+  h = h + col_sep_length;
+
   /* This loop takes care of all but the rightmost column. */
 
   for (p = column_vector, i = 1; i < columns; ++p, ++i)
@@ -880,18 +1148,18 @@ init_funcs (void)
       p->numbered = numbered_lines && (!parallel_files || i == 1);
       p->start_position = h;
 
-      /* If we're using separators, all start_positions are
+      /* If we don't truncate lines, all start_positions are
          ANYWHERE, except the first column's start_position when
          using a margin. */
 
-      if (use_column_separator)
+      if (!truncate_lines)
        {
          h = ANYWHERE;
          h_next = ANYWHERE;
        }
       else
        {
-         h = h_next + chars_per_gutter;
+         h = h_next + col_sep_length;
          h_next = h + chars_per_column;
        }
     }
@@ -915,10 +1183,13 @@ init_funcs (void)
   p->start_position = h;
 }
 \f
-/* Open a file.  Return nonzero if successful, zero if failed. */
+/* Open a file.  Return nonzero if successful, zero if failed.
+
+   With each file p, p->full_page_printed is initialized,
+   see also init_fps. */
 
 static int
-open_file (char *name, COLUMN *p)
+open_file (char *name, COLUMN * p)
 {
   if (!strcmp (name, "-"))
     {
@@ -939,6 +1210,7 @@ open_file (char *name, COLUMN *p)
       return 0;
     }
   p->status = OPEN;
+  p->full_page_printed = FALSE;
   ++total_files;
   return 1;
 }
@@ -949,7 +1221,7 @@ open_file (char *name, COLUMN *p)
    the status of all columns in the column list to reflect the close. */
 
 static void
-close_file (COLUMN *p)
+close_file (COLUMN * p)
 {
   COLUMN *q;
   int i;
@@ -988,16 +1260,22 @@ close_file (COLUMN *p)
    status of all columns in the column list. */
 
 static void
-hold_file (COLUMN *p)
+hold_file (COLUMN * p)
 {
   COLUMN *q;
   int i;
 
   if (!parallel_files)
     for (q = column_vector, i = columns; i; ++q, --i)
-      q->status = ON_HOLD;
+      {
+       if (storing_columns)
+         q->status = FF_FOUND;
+       else
+         q->status = ON_HOLD;
+      }
   else
     p->status = ON_HOLD;
+
   p->lines_to_print = 0;
   --files_ready_to_read;
 }
@@ -1017,6 +1295,9 @@ reset_status (void)
        p->status = OPEN;
        files_ready_to_read++;
       }
+
+  if (storing_columns)
+    files_ready_to_read = 1;
 }
 \f
 /* Print a single file, or multiple files in parallel.
@@ -1049,14 +1330,14 @@ print_files (int number_of_files, char **av)
 
   init_funcs ();
 
-  line_number = 1;
+  line_number = line_count;
   while (print_page ())
     ;
 }
 \f
-/* Generous estimate of number of characters taken up by "Jun  7 00:08 " and
-   "Page NNNNN". */
-#define CHARS_FOR_DATE_AND_PAGE        50
+/* Estimate the number of characters taken up by a short format date and
+   time:  "yy-mm-dd HH:MM"  and:  "Page NNNN". */
+#define CHARS_FOR_DATE_AND_PAGE        23
 
 /* Initialize header information.
    If DESC is non-negative, it is a file descriptor open to
@@ -1064,48 +1345,83 @@ print_files (int number_of_files, char **av)
 
    Allocate space for a header string,
    Determine the time, insert file name or user-specified string.
-
-   It might be nice to have a "blank headers" option, since
-   pr -h "" still prints the date and page number. */
+   Make use of a centered header with left-hand-side truncation marked by
+   a '*` in front, if necessary. */
 
 static void
 init_header (char *filename, int desc)
 {
-  int chars_per_header;
+  int chars_per_middle, chars_free, lhs_blanks, rhs_blanks;
   char *f = filename;
-  char *t, *middle;
+  char *no_middle = "";
+  char *header_text, *fmt, *t_buf;
+  struct tm *tmptr;
   struct stat st;
+  char *datim = "- Date/Time --";
+
+  fmt = "%y-%m-%d %H:%M";      /* date/time short format */
 
   if (filename == 0)
     f = "";
 
-  /* If parallel files or standard input, use current time. */
-  if (desc < 0 || !strcmp (filename, "-") || fstat (desc, &st))
-    st.st_mtime = time ((time_t *) 0);
-  t = ctime (&st.st_mtime);
+  if (header != (char *) 0)
+    free (header);
+  header = (char *) xmalloc (chars_per_line + 1);
 
-  t[16] = '\0';                        /* Mark end of month and time string. */
-  t[24] = '\0';                        /* Mark end of year string. */
+  if (!standard_header && *custom_header == '\0')
+    sprintf (header, "%s", " ");       /* blank line header */
+  else
+    {
+      /* If parallel files or standard input, use current time. */
+      if (desc < 0 || !strcmp (filename, "-") || fstat (desc, &st))
+       st.st_mtime = time ((time_t *) 0);
 
-  middle = standard_header ? f : custom_header;
+      {
+       size_t t_buf_size = 15;
+       t_buf = (char *) xmalloc (t_buf_size);
+       tmptr = localtime (&st.st_mtime);
+       strftime (t_buf, t_buf_size, fmt, tmptr);
+      }
 
-  chars_per_header = strlen (middle) + CHARS_FOR_DATE_AND_PAGE + 1;
-  if (header != (char *) 0)
-    free (header);
-  header = (char *) xmalloc (chars_per_header * sizeof (char));
+      chars_per_middle = chars_per_line - CHARS_FOR_DATE_AND_PAGE;
+      if (chars_per_middle < 3)
+        {
+         header_text = no_middle;      /* Nothing free for a heading */
+         lhs_blanks = 1;
+         rhs_blanks = 1;
+       }
+      else
+       {
+         header_text = standard_header ? f : custom_header;
+         chars_free = chars_per_middle - (int) strlen (header_text);
+         if (chars_free > 1)
+           {
+             lhs_blanks = chars_free / 2;      /* text not truncated */
+             rhs_blanks = chars_free - lhs_blanks;
+           }
+         else
+           {                   /* lhs truncation */
+             header_text = header_text - chars_free + 2;
+             *header_text = '*';
+             lhs_blanks = 1;
+             rhs_blanks = 1;
+           }
+       }
 
-  sprintf (header, _("%s %s  %s Page"), &t[4], &t[20], middle);
+      sprintf (header, _("%s%*s%s%*sPage"), (test_suite ? datim : t_buf),
+              lhs_blanks, " ", header_text, rhs_blanks, " ");
+    }
 }
 \f
 /* Set things up for printing a page
 
    Scan through the columns ...
-     Determine which are ready to print
-       (i.e., which have lines stored or open files)
-     Set p->lines_to_print appropriately
-       (to p->lines_stored if we're storing, or lines_per_body
-       if we're reading straight from the file)
-     Keep track of this total so we know when to stop printing */
+   Determine which are ready to print
+   (i.e., which have lines stored or open files)
+   Set p->lines_to_print appropriately
+   (to p->lines_stored if we're storing, or lines_per_body
+   if we're reading straight from the file)
+   Keep track of this total so we know when to stop printing */
 
 static void
 init_page (void)
@@ -1148,16 +1464,37 @@ init_page (void)
        p->lines_to_print = 0;
 }
 
+/* Align empty columns and print separators.
+   Empty columns will be formed by files with status ON_HOLD or CLOSED
+   when printing multiple files in parallel. */
+
+static void
+align_column (COLUMN * p)
+{
+  padding_not_printed = p->start_position;
+  if (padding_not_printed - col_sep_length > 0)
+    {
+      pad_across_to (padding_not_printed - col_sep_length);
+      padding_not_printed = ANYWHERE;
+    }
+
+  if (use_col_separator)
+    print_sep_string ();
+
+  if (p->numbered)
+    number (p);
+}
+
 /* Print one page.
 
    As long as there are lines left on the page and columns ready to print,
-     Scan across the column list
-       if the column has stored lines or the file is open
-         pad to the appropriate spot
-         print the column
+   Scan across the column list
+   if the column has stored lines or the file is open
+   pad to the appropriate spot
+   print the column
    pad the remainder of the page with \n or \f as requested
    reset the status of all files -- any files which where on hold because
-     of formfeeds are now put back into the lineup. */
+   of formfeeds are now put back into the lineup. */
 
 static int
 print_page (void)
@@ -1197,28 +1534,49 @@ print_page (void)
       spaces_not_printed = 0;
       separators_not_printed = 0;
       pad_vertically = FALSE;
+      align_empty_cols = FALSE;
+      empty_line = TRUE;
 
       for (j = 1, p = column_vector; j <= columns; ++j, ++p)
        {
          input_position = 0;
-         if (p->lines_to_print > 0)
+         if (p->lines_to_print > 0 || p->status == FF_FOUND)
            {
+             FF_only = FALSE;
              padding_not_printed = p->start_position;
-
              if (!(p->print_func) (p))
                read_rest_of_line (p);
              pv |= pad_vertically;
 
-             if (use_column_separator)
-               ++separators_not_printed;
-
              --p->lines_to_print;
              if (p->lines_to_print <= 0)
                {
                  if (cols_ready_to_print () <= 0)
                    break;
                }
+
+             /* File p changed its status to ON_HOLD or CLOSED */
+             if (parallel_files && p->status != OPEN)
+               {
+                 if (empty_line)
+                   align_empty_cols = TRUE;
+                 else if (p->status == CLOSED ||
+                          (p->status == ON_HOLD && FF_only))
+                   align_column (p);
+               }
+           }
+         else if (parallel_files)
+           {
+             /* File status ON_HOLD or CLOSED */
+             if (empty_line)
+               align_empty_cols = TRUE;
+             else
+               align_column (p);
            }
+
+         /* We need it also with an empty column */
+         if (use_col_separator)
+           ++separators_not_printed;
        }
 
       if (pad_vertically)
@@ -1234,10 +1592,23 @@ print_page (void)
        }
     }
 
+  if (lines_left_on_page == 0)
+    for (j = 1, p = column_vector; j <= columns; ++j, ++p)
+      if (p->status == OPEN)
+       p->full_page_printed = TRUE;
+
   pad_vertically = pv;
 
   if (pad_vertically && extremities)
     pad_down (lines_left_on_page + lines_per_footer);
+  else if (keep_FF && print_a_FF)
+    {
+      putchar ('\f');
+      print_a_FF = FALSE;
+    }
+
+  if (last_page_number && page_number > last_page_number)
+    return FALSE;              /* Stop printing with LAST_PAGE */
 
   reset_status ();             /* Change ON_HOLD to OPEN. */
 
@@ -1248,6 +1619,8 @@ print_page (void)
 
    This is necessary when printing multiple columns from a single file.
    Lines are stored consecutively in buff, separated by '\0'.
+
+   The following doesn't apply any longer - any tuning possible?
    (We can't use a fixed offset since with the '-s' flag lines aren't
    truncated.)
 
@@ -1272,22 +1645,22 @@ init_store_cols (void)
 
   if (buff != (char *) 0)
     free (buff);
-  buff_allocated = use_column_separator ? 2 * chars_if_truncate
+  buff_allocated = use_col_separator ? 2 * chars_if_truncate
     : chars_if_truncate;       /* Tune this. */
-  buff = (char *) xmalloc (buff_allocated * sizeof (char));
+  buff = (char *) xmalloc (buff_allocated);
 }
 
 /* Store all but the rightmost column.
    (Used when printing a single file in multiple downward columns)
 
    For each column
-     set p->current_line to be the index in line_vector of the
-       first line in the column
-     For each line in the column
-       store the line in buff
-       add to line_vector the index of the line's first char
-    buff_start is the index in buff of the first character in the
-     current line. */
+   set p->current_line to be the index in line_vector of the
+   first line in the column
+   For each line in the column
+   store the line in buff
+   add to line_vector the index of the line's first char
+   buff_start is the index in buff of the first character in the
+   current line. */
 
 static void
 store_columns (void)
@@ -1375,7 +1748,7 @@ store_char (int c)
 }
 
 static void
-number (COLUMN *p)
+number (COLUMN * p)
 {
   int i;
   char *s;
@@ -1440,7 +1813,7 @@ pad_down (int lines)
    to print or store its characters. */
 
 static void
-read_rest_of_line (COLUMN *p)
+read_rest_of_line (COLUMN * p)
 {
   register int c;
   FILE *f = p->fp;
@@ -1449,6 +1822,74 @@ read_rest_of_line (COLUMN *p)
     {
       if (c == '\f')
        {
+         if ((c = getc (f)) != '\n')
+           ungetc (c, f);
+         if (keep_FF)
+           print_a_FF = TRUE;
+         hold_file (p);
+         break;
+       }
+      else if (c == EOF)
+       {
+         close_file (p);
+         break;
+       }
+    }
+}
+
+/* Read a line with skip_to_page.
+
+   Read from the current column's file until an end of line is
+   hit.  Used when we read full lines to skip pages.
+   With skip_to_page we have to check for FF-coincidence which is done
+   in function read_line otherwise.
+   Count lines of skipped pages to find the line number of 1st page
+   printed relative to 1st line of input file (start_line_num). */
+
+static void
+skip_read (COLUMN * p, int column_number)
+{
+  register int c;
+  FILE *f = p->fp;
+  int i, single_ff = FALSE;
+  COLUMN *q;
+
+  /* Read 1st character in a line or any character succeeding a FF */
+  if ((c = getc (f)) == '\f' && p->full_page_printed)
+    /* A FF-coincidence with a previous full_page_printed.
+       To avoid an additional empty page, eliminate the FF */
+    if ((c = getc (f)) == '\n')
+      c = getc (f);
+
+  p->full_page_printed = FALSE;
+
+  /* 1st character a FF means a single FF without any printable
+     characters. Don't count it as a line with -n option. */
+  if (c == '\f')
+    single_ff = TRUE;
+
+  /* Preparing for a FF-coincidence: Maybe we finish that page
+     without a FF found */
+  if (last_line)
+    p->full_page_printed = TRUE;
+
+  while (c != '\n')
+    {
+      if (c == '\f')
+       {
+         /* No FF-coincidence possible,
+            no catching up of a FF-coincidence with next page */
+         if (last_line)
+           {
+             if (!parallel_files)
+               for (q = column_vector, i = columns; i; ++q, --i)
+                 q->full_page_printed = FALSE;
+             else
+               p->full_page_printed = FALSE;
+           }
+
+         if ((c = getc (f)) != '\n')
+           ungetc (c, f);
          hold_file (p);
          break;
        }
@@ -1457,7 +1898,12 @@ read_rest_of_line (COLUMN *p)
          close_file (p);
          break;
        }
+      c = getc (f);
     }
+
+  if (skip_count)
+    if ((!parallel_files || column_number == 1) && !single_ff)
+      ++line_count;
 }
 \f
 /* If we're tabifying output,
@@ -1474,7 +1920,7 @@ print_white_space (void)
   register int goal = h_old + spaces_not_printed;
 
   while (goal - h_old > 1
-         && (h_new = pos_after_tab (chars_per_output_tab, h_old)) <= goal)
+        && (h_new = pos_after_tab (chars_per_output_tab, h_old)) <= goal)
     {
       putchar (output_tab_char);
       h_old = h_new;
@@ -1489,20 +1935,32 @@ print_white_space (void)
 /* Print column separators.
 
    We keep a count until we know that we'll be printing a line,
-   then print_separators() is called. */
+   then print_sep_string() is called. */
 
 static void
-print_separators (void)
+print_sep_string ()
 {
+  char *s;
+  int l = col_sep_length;
+
+  s = col_sep_string;
+
+  if (spaces_not_printed > 0)
+    print_white_space ();
+
   for (; separators_not_printed > 0; --separators_not_printed)
-    print_char (column_separator);
+    {
+      while (l-- > 0)
+       putchar (*s++);
+      output_position += col_sep_length;
+    }
 }
 
 /* Print (or store, depending on p->char_func) a clump of N
    characters. */
 
 static void
-print_clump (COLUMN *p, int n, int *clump)
+print_clump (COLUMN * p, int n, int *clump)
 {
   while (n--)
     (p->char_func) (*clump++);
@@ -1510,6 +1968,8 @@ print_clump (COLUMN *p, int n, int *clump)
 
 /* Print a character.
 
+   Update the following comment: process-char hasn't been used any
+   longer.
    If we're tabifying, all tabs have been converted to spaces by
    process_char().  Keep a count of consecutive spaces, and when
    a nonspace is encountered, call print_white_space() to print the
@@ -1550,12 +2010,23 @@ skip_to_page (int page)
 
   for (n = 1; n < page; ++n)
     {
-      for (i = 1; i <= lines_per_body; ++i)
+      for (i = 1; i < lines_per_body; ++i)
        {
          for (j = 1, p = column_vector; j <= columns; ++j, ++p)
-           read_rest_of_line (p);
+           if (p->status == OPEN)
+             skip_read (p, j);
        }
+      last_line = TRUE;
+      for (j = 1, p = column_vector; j <= columns; ++j, ++p)
+       if (p->status == OPEN)
+         skip_read (p, j);
+
+      if (storing_columns)     /* change FF_FOUND to ON_HOLD */
+       for (j = 1, p = column_vector; j <= columns; ++j, ++p)
+         p->status = ON_HOLD;
+
       reset_status ();
+      last_line = FALSE;
     }
   return files_ready_to_read > 0;
 }
@@ -1575,7 +2046,10 @@ print_header (void)
   pad_across_to (chars_per_margin);
   print_white_space ();
 
-  fprintf (stdout, "%s %d\n\n\n", header, page_number++);
+  if (!standard_header && *custom_header == '\0')
+    fprintf (stdout, "%s\n\n\n", header);
+  else
+    fprintf (stdout, "%s%5d\n\n\n", header, page_number++);
 
   print_a_header = FALSE;
   output_position = 0;
@@ -1587,37 +2061,57 @@ print_header (void)
    (We may hit EOF, \n, or \f)
 
    Once we know we have a line,
-     set pad_vertically = TRUE, meaning it's safe
-       to pad down at the end of the page, since we do have a page.
-       print a header if needed.
-     pad across to padding_not_printed if needed.
-     print any separators which need to be printed.
-     print a line number if it needs to be printed.
+   set pad_vertically = TRUE, meaning it's safe
+   to pad down at the end of the page, since we do have a page.
+   print a header if needed.
+   pad across to padding_not_printed if needed.
+   print any separators which need to be printed.
+   print a line number if it needs to be printed.
 
    Print the clump which corresponds to the first character.
 
    Enter a loop and keep printing until an end of line condition
-     exists, or until we exceed chars_per_column.
+   exists, or until we exceed chars_per_column.
 
    Return FALSE if we exceed chars_per_column before reading
-     an end of line character, TRUE otherwise. */
+   an end of line character, TRUE otherwise. */
 
 static int
-read_line (COLUMN *p)
+read_line (COLUMN * p)
 {
   register int c, chars;
   int last_input_position;
+  int j, k;
+  COLUMN *q;
 
-#ifdef lint  /* Suppress `used before initialized' warning.  */
+/* Suppress `used before initialized' warning. */
+#ifdef lint
   chars = 0;
 #endif
 
+  /* read 1st character in each line or any character succeeding a FF: */
   c = getc (p->fp);
 
   last_input_position = input_position;
+
+  if (c == '\f' && p->full_page_printed)
+    if ((c = getc (p->fp)) == '\n')
+      c = getc (p->fp);
+  p->full_page_printed = FALSE;
+
   switch (c)
     {
     case '\f':
+      if ((c = getc (p->fp)) != '\n')
+       ungetc (c, p->fp);
+      FF_only = TRUE;
+      if (print_a_header && !storing_columns)
+       {
+         pad_vertically = TRUE;
+         print_header ();
+       }
+      else if (keep_FF)
+       print_a_FF = TRUE;
       hold_file (p);
       return TRUE;
     case EOF:
@@ -1639,22 +2133,41 @@ read_line (COLUMN *p)
     {
       pad_vertically = TRUE;
 
-      if (print_a_header)
+      if (print_a_header && !storing_columns)
        print_header ();
 
-      if (padding_not_printed != ANYWHERE)
+      if (parallel_files && align_empty_cols)
        {
-         pad_across_to (padding_not_printed);
+         /* We have to align empty columns at the beginning of a line. */
+         k = separators_not_printed;
+         separators_not_printed = 0;
+         for (j = 1, q = column_vector; j <= k; ++j, ++q)
+           {
+             align_column (q);
+             separators_not_printed += 1;
+           }
+         padding_not_printed = p->start_position;
+         if (truncate_lines)
+           spaces_not_printed = chars_per_column;
+         else
+           spaces_not_printed = 0;
+         align_empty_cols = FALSE;
+       }
+
+      if (padding_not_printed - col_sep_length > 0)
+       {
+         pad_across_to (padding_not_printed - col_sep_length);
          padding_not_printed = ANYWHERE;
        }
 
-      if (use_column_separator)
-       print_separators ();
+      if (use_col_separator)
+       print_sep_string ();
     }
 
   if (p->numbered)
     number (p);
 
+  empty_line = FALSE;
   if (c == '\n')
     return TRUE;
 
@@ -1669,6 +2182,10 @@ read_line (COLUMN *p)
        case '\n':
          return TRUE;
        case '\f':
+         if ((c = getc (p->fp)) != '\n')
+           ungetc (c, p->fp);
+         if (keep_FF)
+           print_a_FF = TRUE;
          hold_file (p);
          return TRUE;
        case EOF:
@@ -1690,16 +2207,24 @@ read_line (COLUMN *p)
 
 /* Print a line from buff.
 
-   If this function has been called, we know we have something to
-   print.  Therefore we set pad_vertically to TRUE, print
-   a header if necessary, pad across if necessary, and print
-   separators if necessary.
+   If this function has been called, we know we have "something to
+   print". But it remains to be seen whether we have a real text page
+   or an empty page (a single form feed) with/without a header only.
+   Therefore first we set pad_vertically to TRUE and print a header
+   if necessary.
+   If FF_FOUND and we are using -t|-T option we omit any newline by
+   setting pad_vertically to FALSE (see print_page).
+   Otherwise we pad across if necessary, print separators if necessary
+   and text of COLUMN *p.
 
    Return TRUE, meaning there is no need to call read_rest_of_line. */
 
 static int
-print_stored (COLUMN *p)
+print_stored (COLUMN * p)
 {
+  COLUMN *q;
+  int i;
+
   int line = p->current_line++;
   register char *first = &buff[line_vector[line]];
   register char *last = &buff[line_vector[line + 1]];
@@ -1709,20 +2234,36 @@ print_stored (COLUMN *p)
   if (print_a_header)
     print_header ();
 
-  if (padding_not_printed != ANYWHERE)
+  if (p->status == FF_FOUND)
+    {
+      for (i = 1, q = column_vector; i <= columns; ++i, ++q)
+       q->status = ON_HOLD;
+      if (column_vector->lines_to_print <= 0)
+       {
+         if (!extremities)
+           pad_vertically = FALSE;
+         return TRUE;          /* print a header only */
+       }
+    }
+
+  if (padding_not_printed - col_sep_length > 0)
     {
-      pad_across_to (padding_not_printed);
+      pad_across_to (padding_not_printed - col_sep_length);
       padding_not_printed = ANYWHERE;
     }
 
-  if (use_column_separator)
-    print_separators ();
+  if (use_col_separator)
+    print_sep_string ();
 
   while (first != last)
     print_char (*first++);
 
   if (spaces_not_printed == 0)
-    output_position = p->start_position + end_vector[line];
+    {
+      output_position = p->start_position + end_vector[line];
+      if (p->start_position - col_sep_length == chars_per_margin)
+       output_position -= col_sep_length;
+    }
 
   return TRUE;
 }
@@ -1855,33 +2396,54 @@ usage (int status)
 Usage: %s [OPTION]... [FILE]...\n\
 "),
              program_name);
+
       printf (_("\
 Paginate or columnate FILE(s) for printing.\n\
 \n\
-  +PAGE             begin printing with page PAGE\n\
-  -COLUMN           produce COLUMN-column output and print columns down\n\
-  -a                print columns across rather than down\n\
-  -b                balance columns on the last page\n\
+  +FIRST_PAGE[:LAST_PAGE]
+                    begin [stop] printing with page FIRST_[LAST_]PAGE\n\
+  -COLUMN           produce COLUMN-column output and print columns down,\n\
+                    unless -a is used. Balance number of lines in the\n\
+                    columns on each page.\n\
+  -a                print columns across rather than down, used together\n\
+                    with -COLUMN\n\
   -c                use hat notation (^G) and octal backslash notation\n\
   -d                double space the output\n\
   -e[CHAR[WIDTH]]   expand input CHARs (TABs) to tab WIDTH (8)\n\
-  -f, -F            simulate formfeed with newlines on output\n\
-  -h HEADER         use HEADER instead of filename in page headers\n\
+  -F, -f            use form feeds instead of newlines to separate pages\n\
+                    (by a 3-line page header with -f or a 5-line header\n\
+                    and trailer without -f)\n\
+  -h HEADER         use a centered HEADER instead of filename in page headers\n\
+                    with long headers left-hand-side truncation may occur\n\
+                    -h \"\" prints a blank line. Don't use -h\"\"\n\
   -i[CHAR[WIDTH]]   replace spaces with CHARs (TABs) to tab WIDTH (8)\n\
+  -j                merge full lines, turns off -w line truncation, no column\n\
+                    alignment, -s[STRING] sets separators\n\
   -l PAGE_LENGTH    set the page length to PAGE_LENGTH (66) lines\n\
-  -m                print all files in parallel, one in each column\n\
+                    (default number of lines of text 56, with -f 63)\n\
+  -m                print all files in parallel, one in each column,\n\
+                    truncate lines, but join lines of full length with -j\n\
   -n[SEP[DIGITS]]   number lines, use DIGITS (5) digits, then SEP (TAB)\n\
+                    default counting starts with 1st line of input file\n\
+  -N NUMBER         start counting with NUMBER at 1st line of first\n\
+                    page printed (see +FIRST_PAGE)\n\
   -o MARGIN         offset each line with MARGIN spaces (do not affect -w)\n\
   -r                inhibit warning when a file cannot be opened\n\
-  -s[SEP]           separate columns by character SEP (TAB)\n\
-  -t                inhibit 5-line page headers and trailers\n\
+  -s[STRING]        separate columns by an optional STRING\n\
+                    don't use -s \"STRING\" \n\
+                    without -s: default sep. \'space\' used, same as -s\" \"\n\
+                    -s only: no separator used, same as -s\"\" \n\
+  -t                inhibit page headers and trailers\n\
+  -T                inhibit page headers and trailers, eliminate any page\n\
+                    layout by form feeds set in input files\n\
   -v                use octal backslash notation\n\
-  -w PAGE_WIDTH     set page width to PAGE_WIDTH (72) columns\n\
-      --help        display this help and exit\n\
-      --version     output version information and exit\n\
+  -w PAGE_WIDTH     set page width to PAGE_WIDTH (72) columns, truncate\n\
+                    lines (see also -j option)\n\
+  --help            display this help and exit\n\
+  --version         output version information and exit\n\
 \n\
--t implied by -l N when N < 10.  Without -s, columns are separated by\n\
-spaces.  With no FILE, or when FILE is -, read standard input.\n\
+-T implied by -l nn when nn <= 10 or <= 3 with -f. With no FILE, or when\n\
+FILE is -, read standard input.\n\
 "));
       puts (_("\nReport bugs to textutils-bugs@gnu.ai.mit.edu"));
     }