[alt.sources] pfm -- print files in multiple columns

lee@sq.sq.com (Liam R. E. Quin) (12/15/89)

I tried twice to mail this to ++Brandon for comp.sources.misc, and got a
week's silence plus a bounce, so here it is... 
[Brandon, if you see this, it might be nice to archive it...]

pfm is rather like pr, except that
* it atomatically works out how many columns of text will fit on each page,
  on a page-by-page basis, without truncating.

* it can do some fancy stuff with headers/footers, and you can tell it to
  make the input a pipe (for example calling "vis", "cat -v" or whatever for
  each input file).

You will need this if you want to try the ascii to postscript filter I
am posting in the next article...

It used to be called pf, but I typed "pf" too often by mistake, so I
renamed it to pfm.
If you are using the version that was posted to net.sources in 1985 or so,
you will find this one has some minor bugs removed and is noticeably
faster on slow systems [suns, mips etc :-) :-)].

Lee     ---  Liam Russell Quin ---    lee@sq.com


#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  Makefile pfm.1 pfm.c
# Wrapped by lee@sq on Thu Dec 14 16:10:00 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(2595 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# $Header: /u/lee/src/pfm/RCS/Makefile,v 1.5 89/12/11 14:05:58 lee Exp $
X#
X# Makefile for pf.c
X# pf.c was written by Crispin Goswell;
X# minor bug fixes and makefile by Russell Quin,
X# who subsequently changed his name to Liam Quin.
X#
X# make, or make all -- makes the program
X# make tidy -- remove all generated intermediate files (including pfm.c)
X# make clean -- remove all generated files
X#
X# CFLAGS=-s -O
CFLAGS=-O
PROGS=pfm
DOCS=pfm.1
LINTFLAGS=-a -b -c -h -x
X
BIN=/usr/local/bin
MANDIR=/usr/local/man
X
RM=/bin/rm -f # for "make clean" and "make tidy"
CO=co # rcs check out program, if you have it
X
CLEAN=clean
MAKE=make
SHELL=/bin/sh
X
default:
X	@echo make $(PROGS) -- make the program
X	@echo make lint -- run lint on the source for $(PROGS)
X	@echo make install -- make $(PROGS), install them, $(MAKE) clean
X	@echo make tidy -- clean up without deleting: $(PROGS)
X	@echo make clean -- remove all generated files
X	@echo make all -- make: $(PROGS) tidy
X	@echo make rcsclean -- $(RM) non-writable files also in RCS
X	@false # force an error
X
X# you might want to remove the "tidy" for debugging...
all: $(PROGS) $(DOCS) tidy
X
install: $(PROGS) $(DOCS)
X	for i in $(PROGS); do \
X	    mv $$i $(BIN) ; \
X	done ; \
X	for i in $(DOCS); do \
X	    mv $$i $(MANDIR) ; \
X	done
X	$(MAKE) $(CLEAN)
X
lint: pfm.lint
X
pfm.lint: pfm.c
X	lint $(LINTFLAGS) pfm.c > pfm.lint 2>&1
X
X# making pfm depend on Makefile forces a recompile when CFLAGS change.
pfm: pfm.c Makefile
X	$(CC) $(CFLAGS) pfm.c -o pfm
X
X# make tidy removes all generated files, including pfm.c if there is
X# an RCS directory and pfm.c is not writeable.
X# you have been warned!
tidy: rcsclean
X	$(RM) *.o core tags
X
clean: tidy
X	$(RM) $(PROGS)
X
X# remove files that are also in the RCS directory
rcsclean:
X	-if test -d RCS; then \
X	    for i in `ls RCS` ; \
X	    do \
X		f=`basename $$i ,v` ; \
X		if test ! -w $$f ; \
X		then	$(RM) $$f ; \
X			echo Removed $$f; \
X		fi ; \
X	    done ; \
X	fi
X
X# if there are more programs, use a .SUFFIX rule instead here:
pfm.1: RCS/pfm.c,v
X	$(CO) pfm.1
X	chmod -w pfm.1 # to ensure that it gets removed
X
pfm.c: RCS/pfm.c,v
X	$(CO) pfm.c
X	chmod -w pfm.c # to ensure that it gets removed
X	ctags pfm.c
X
X#
X# $Log:	Makefile,v $
X# Revision 1.5  89/12/11  14:05:58  lee
X# CFLAGS change
X# 
X# Revision 1.4  89/12/03  20:58:43  lee
X# added tidy to default make.
X# 
X# Revision 1.3  89/12/03  20:55:07  lee
X# added make install entry, plus improved portability of rcsclean by
X# adding a chmod.
X# 
X# Revision 1.2  89/12/03  20:50:59  lee
X# Added code to remove rcs files.
X# 
X# Revision 1.1  89/12/03  20:36:34  lee
X# Initial revision
X# 
X#
END_OF_FILE
if test 2595 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'pfm.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pfm.1'\"
else
echo shar: Extracting \"'pfm.1'\" \(5220 characters\)
sed "s/^X//" >'pfm.1' <<'END_OF_FILE'
X.\" $Header: /u/lee/src/pfm/RCS/pfm.1,v 1.4 89/12/14 16:05:03 lee Exp Locker: lee $
X.TH PFM 1
X.UC 4
X.SH NAME
pfm \- print file with automatic multi-columning
X.SH SYNOPSIS
X.B pfm
X[ option ] ...
X[ file ] ...
X.SH DESCRIPTION
X.I Pfm
produces a printed listing of one or more
X.I files.
If there are no file arguments, or if a file name is \-
X.I pfm
prints its standard input.
X.PP
Options apply to all following files:
X.TP
X.B \-d
Don't break a page for each file \- continue in next column.
X.TP
X.B \-e
XEnsure all the columns on a page are of equal width.
X.TP
X.B \-f
Produce true action for formfeeds, i.e. move to next page. The default
action is to move to the top of the next column.
X.TP
X.B \-F
Specify footer information \- see note which follows options.
X.TP
X.B \-H
Specify header information \- see note which follows options.
X.TP
X.B \-h
Produces a list of options.
X.TP
X.B \-j
Causes the text to be justified to the right hand column (old behavior).
X.TP
X.BI \-l n
Set the length of the page to
X.I n
X(the default is 66 lines).
X.TP
X.BI \-m n
Set the maximum number of columns to use to
X.I n
X(default is the page
width).
X.TP
X.BI \-N n
Number each output line, allowing space for at least
X.I n
digits in the line number (larger numbers will print correctly, but will
intrude into the text area).
The default for
X.I N
is 4 digits.
X.TP
X.BI \-n n
Number each output line, but reset the numbers to one at the start of each
new file.
The optional argument
X.I n
is treated in the same way as that for the
X.B \-N
option.
X.TP
X.BI \-p cmd
XFilter input for each file through the given command.
Any number of
X.B %f
sequences may occur in
X.IR cmd ,
and will be replaced by the file name each time
the command is used.
This option has no effect when printing from standard input,
although in that case a pipe can be used directly from the shell.
X.TP
X.BI \-s n
Set the minimum width to separate columns by to
X.I n
X(the default is 1).
X.TP
X.BI \-t n
Set the width to which tabs are expanded to
X.I n
X(the default is 8).
X.TP
X.BI \-w n
Set the width of the page to
X.I n
X(the default is 132 positions).
X.PP
The header and footer options
X.B \-BH
and
X.B \-BF
respectively, allow
specification as follows:
X.LP
Mere inclusion indicates that headers and footers are required.
X.LP
XEither can be followed immediately by one of
X.BR l ,
X.BR c ,
or
X.B r
which mean that the next argument will be the
X.IR left ,
X.I centre
or
X.I right
header or footer respectively.
The
X.B s
specification,
X.I immediately
followed by a number, will give the vertical size of the header
or footer,
X.br
e.g.
X.B \-Fs3
indicates that footers are to be placed in three lines at the
bottom of the page.
X.PP
Within any header or footer, the following sequences are undertood:
X.TP
X.B %%
is replaced by a literal
X.B %
sign.
X.TP
X.B %l
X(lower-case ell) is replaced by the line number of the first line of
text on the page (counting the first line in that file as line one).
X.TP
X.B %L
is replaced by the line number of the first line of text on the page.
This number is not reset at the start of each new input file.
X.TP
X.B %n
is replaced by the line number of the
X.I last
line of text on the page, i.e., the bottom line in the right-most column,
numbered within the file.
X.TP
X.B %N
is replaced by the line number of the last line on the page.
This number is not reset at the start of each new input file.
X.TP
X.B %p
is preplaced by the current page number within the current file.
X.TP
X.B %P
is replaced by the current output page number.
This number is not reset at the start of each new input file.
X.TP
X.B %s
is replaced by the name of the file that was being printed at the
X.I start
of this page.
X.TP
X.B %e
is replaced by the name of the file that was being printed at the
X.I end
of the current page.
X.P
Any other character after a
X.BR % -sign
in a header or footer prints as itself, and a warning is generated.
X.LP
The three headers or footers are placed on a single line in the middle of
the vertical space.
X.SH NOTE
X.I Pfm
knows about control characters and escape sequences of length two:
it assumes these to print in zero width and will print them even if the line
has been truncated because it is longer than a page.
These means that bold will get turned off properly when the line is too long.
To prevent this behavior,
put the input through
X.I colrm
first.
X.SH AUTHOR
Crispin Goswell
X.br
Modified slightly and distrinbuted (with permission) by Liam Quin.
X.SH "SEE ALSO"
cat(1), colrm(1), pr(1)
X.SH BUGS
Too many options.
X.br
A more general syntax for headers and line numbers would help, but
the program would to begin to turn into a text formatter.
X.br
No way to print a list of all the filenames on the page in the header.
This would affect the number of columns prnted on the page if the filenames
were long, however.
X.\"
X.\" $Log:	pfm.1,v $
X.\" Revision 1.4  89/12/14  16:05:03  lee
X.\" added new options.
X.\" Fixed Log comment string.
X.\" 
X.\" Revision 1.3  89/12/14  16:04:25  lee
X.\" fixed font changes
X.\" 
X.\" Revision 1.2  89/12/14  12:05:34  lee
X.\" tidied up a little for distribution.
X.\" Fixed a bug in -p option (thanks to Mark Brader for commenting on and
X.\" improving my solution)
X.\" 
X.\" Revision 1.1  89/12/03  20:36:50  lee
X.\" Initial revision
X.\" 
X.\"
END_OF_FILE
if test 5220 -ne `wc -c <'pfm.1'`; then
    echo shar: \"'pfm.1'\" unpacked with wrong size!
fi
# end of 'pfm.1'
fi
if test -f 'pfm.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pfm.c'\"
else
echo shar: Extracting \"'pfm.c'\" \(20126 characters\)
sed "s/^X//" >'pfm.c' <<'END_OF_FILE'
X/* pfm.c -- print files in multiple columns
X * originally written by Crispin Goswell, from an idea we had at
X * Warwick University, England, in 1983
X *
X * Modified by me (Liam Quin) to use the new "getopt()" routine
X * and to be a little more portable now that the world's no longer
X * all a vax :-) :-)  In the process, I did massive re-indenting.
X *
X * I have also renamed it from pf to pfm, as I type "pf" too often by
X * mistake for pr or ps...
X *
X * Liam Quin, liam@sq.com, December 1989
X *
X * $Header: /u/lee/src/pfm/RCS/pfm.c,v 1.6 89/12/14 16:05:25 lee Exp $
X *
X * (see the end of this file for the RCS log)
X */
X
X#ifdef BSD
X# define strrchr rindex
X#endif
X
X#include <stdio.h>
X#include <ctype.h> /* for isprint() */
X#include <malloc.h>
X
X#define Malloc(n) malloc((unsigned)(n))
X#define Realloc(s, n) realloc(s, (unsigned)(n))
X
X#define EOL '\0'
X#define TW(s) (s ? Title(s, (FILE *) 0) : 0)
X
X#ifndef STREQ
X# define STREQ(henry,utzoo) ((*(henry)== *(utzoo)) && !strcmp(henry, utzoo))
X#endif
X
int width = 132, length = 66, sep   = 1, justify = 0, bufsiz, headers = 0,
X    equal = 0,   dont   = 0,  ncols = 0, form = 0, tabsize = 8, maxcols = 0;
X
int widest, eof, this_col, feed, *col_width;
typedef struct {
X    long File;
X    long Overall;
X} t_Lines;
X
t_Lines *first_lines, *last_lines;
X
extern char *malloc(), *strcpy(), *strcat();
X
char *getline();
char ***cols = 0;
X
char *left_top = 0, *centre_top = 0, *right_top = 0;
char *left_bot = 0, *centre_bot = 0, *right_bot = 0;
int top_space = -1, bot_space = -1;
int NumberLines = 0; /* an evil bletch I put there */
int NumberWidth = 6;
char *PipeCmd = 0;
X
char *progname = "pfm";
X
X/* Things that can be included in titles: */
char *FilePage = 0;
char *DocPage = 0;
char *ThisName;
int NameLen;
long DocLine = 0L;
long FileLine = 0L;
char **names;
X
main(argc, argv)
X    int argc;
X    char **argv;
X{
X    extern char *strrchr();
X    char *arg;
X
X    /* determine program name for error reporting */
X    arg = strrchr(argv[0], '/');
X    if (arg != (char *) 0) {
X	arg++; /* step over the final / */
X    }
X    if (!arg || !*arg) arg = argv[0]; /* best we can do (we're not on Unix?) */
X    progname = arg;
X
X    for (++argv; argc > 1; argc--, argv++) {
X	if (*(arg = *argv) != '-' || *++arg == EOL) {
X	    break;
X	} else switch (*arg) {
X	    case 'm': maxcols = atoi(++arg); break;
X	    case 'j': justify = 1; break; /* removes spaces to right */
X	    case 't': tabsize = atoi(++arg); break;
X	    case 'f': form = 1; break;		/* request true form-feed */
X	    case 'w': width = atoi(++arg); break;
X	    case 'l': length = atoi(++arg); break;
X	    case 'N': /* number throughout input */
X		NumberLines = 1;
X		/* fall through */
X	    case 'n': /* reset on file boundary */
X		NumberLines++; /* i.e., 1 or 2 */
X		if (isdigit(arg[1])) {
X		   NumberWidth = atoi(++arg);
X		}
X		break;
X	    case 'p':
X		PipeCmd = argv[1]; --argc; ++argv;
X		break;
X	    case 's': sep = atoi(++arg); break;
X					    /* minimum column separation */
X	    case 'e': equal = 1; break;	    /* request equal width columns */
X	    case 'd': dont = 1; break;	/* don't break a page on \f */
X	    case 'H':
X		headers = 1;
X		if (header(++arg, argv[1])) {
X		    argv++; argc--;
X		}
X		break;
X	    case 'F':
X		headers = 1;
X		if (footer(++arg, argv[1])) {
X		    argv++; argc--;
X		}
X	        break;
X
X	    default:
X		if ((*argv)[1] != 'h')
X		    fprintf(stderr, "%s: unknown option `%s'\n",
X							progname, *argv );
X		fprintf(stderr, "\
Valid options (with defaults if appropriate) are:-\n\
X\n\
X    -d	    don't break a page between files - break column instead\n\
X    -e	    equal width columns\n\
X    -f	    true form feeds - cause a new page instead of new column\n\
X    -h	    produces this listing and exit\n\
X    -F	    produce footers\n\
X    -H	    produce headers\n\
X    -j	    remove spaces to the right (original behavior)\n\
X    -l66    length of paper\n\
X    -m	    maximum number of columns (defaults to paper width)\n\
X    -n4     number lines in each file, allow 4 digits (default)\n\
X    -N4     number lines in the output, allowing 4 digits (default)\n\
X    -p cmd  pipe the input for each file through cmd\n\
X    -s1	    minimum separation of columns\n\
X    -t8	    tab size\n\
X    -w132   width of paper\n\
X    -x	    produces this explanation and exit\n\
X\n\
X-H and -F are normally turned off and can be appended by the following args\n\
X\n\
X    l	    following argument is left header\n\
X    c	    following argument is centre header\n\
X    r	    following argument is right header\n\
X    s3	    number of lines used for headers\n\
X\n\
X");
X		    exit (1);
X	}
X    }
X    if (maxcols == 0) maxcols = width;
X    if (sep) {
X	if (maxcols > width / (sep + 1)) maxcols = width / (sep + 1);
X    }
X    if (maxcols < 1) {
X	fprintf(stderr,
X		    "%s: No colums (each %d apart) fit on a %d-wide page\n",
X							progname, sep, width);
X	exit(1);
X    }
X    if (top_space < 0) top_space = 3;
X    if (bot_space < 0) bot_space = 3;
X    if (tabsize <= 0 || length <= 0 || width <= 0)
X	fprintf(stderr,
X	    "%s: non-positive or non-numeric flag (-t, -l or -w)\n", progname),
X	exit(1);
X    /*NOSTRICT*/ cols = (char ***) Malloc((maxcols + 1) * sizeof(char **));
X    /*NOSTRICT*/ col_width = (int *) Malloc((maxcols+1) * sizeof(int));
X    /*NOSTRICT*/ first_lines = (t_Lines *) Malloc((maxcols+1)*sizeof(t_Lines));
X    /*NOSTRICT*/ last_lines = (t_Lines *) Malloc((maxcols+1)*sizeof(t_Lines));
X    /*NOSTRICT*/ names = (char **) Malloc((maxcols + 1) * sizeof(char *));
X
X    if (!cols || !col_width || !first_lines || !last_lines || !names) {
X	fprintf(stderr, "%s: not enough memory to allocate buffers\n",
X								progname);
X	exit(0);
X    }
X
X    if (argc == 1) {
X#ifdef unix
X	/*NONPORT*/
X	*argv = "-", argc = 2;
X#else
X	this needs fixing, sorry -- Liam. ;;;;
X#endif
X    }
X
X    if (headers) {
X	if (length <= top_space + bot_space) {
X	    fprintf(stderr, "%s: page too short for headers\n", progname);
X	    exit(1);
X	} else {
X	    length -= top_space + bot_space;
X	}
X    }
X
X    for (; argc > 1; argc--, argv++) {
X	FILE *in;
X
X	ThisName = (*argv);
X	DocLine = 0;
X
X	if (**argv == '-' && (*argv)[1] == EOL) {
X	    in = stdin;
X	    ThisName = "-";
X	    NameLen = 1; /* strlen("-"); */
X	} else if (PipeCmd) {
X	    /* this might be a misfeature, I'm not sure */
X	    int len = strlen(PipeCmd + strlen(ThisName) + 1);
X	    char *buf = Malloc(len);
X	    char *p, *q;
X	    int NameIncluded = 0;
X
X	    NameLen = strlen(ThisName);
X	    if (!buf) {
X		fprintf(stderr, "%s: %s: -p: no memory for pipe \"%s\"\n",
X						progname, ThisName, PipeCmd);
X		exit(1);
X	    }
X	    for (p = PipeCmd, q = buf; *p; q++, p++) {
X		if (*p == '%' && p[1] == 'f') {
X		    ++NameIncluded;
X		    if (q - buf + NameLen >= len - 3) {
X			int where = q - buf;
X
X			if (!(buf = Realloc(buf, len += NameLen + 3))) {
X			    fprintf(stderr, "%s: RAM shortag\n", progname);
X			    exit(1);
X			}
X			q = &buf[where];
X		    }
X		    (void) strcpy(q, ThisName);
X		    q += strlen(q);
X		    ++p; /* skip over the f in "%f" */
X		} else {
X		    if (q - buf >= len - 2) {
X			int where = q - buf;
X
X			if (!(buf = Realloc(buf, len += 10 + NameLen))) {
X			    fprintf(stderr, "%s: RAM shorta\n", progname);
X			    exit(1);
X			}
X			q = &buf[where]; /* in case buf changed */
X		    }
X		    *q = *p;
X		}
X	    }
X	    *q = '\0';
X	    if (!NameIncluded) {
X		if (q - buf >= len - NameLen - 3) {
X		    if (!(buf = Realloc(buf, len += 10 + NameLen))) {
X			fprintf(stderr, "%s: %s: no RAM for pipe\n",
X						    progname, ThisName);
X			exit(1);
X		    }
X		}
X		(void) strcpy(q, " < ");
X		(void) strcat(q, ThisName);
X	    }
X
X	    if ((in = popen(buf, "r")) == (FILE *) 0) {
X		fprintf(stderr, "%s: %s: -p: can't open input pipe \"%s\"\n",
X						    progname, ThisName, buf);
X		exit(1);
X	    }
X	    /** fprintf(stderr, "[[%s]]\n", buf); **/
X	    (void) free(buf);
X	} else {
X	    NameLen = strlen(ThisName);
X	    if ((in = fopen(ThisName, "r")) == (FILE *) 0) {
X		fprintf (stderr, "%s: cannot open '%s'\n", progname, ThisName);
X		exit(1);
X	    }
X	}
X	DocPage = 0;
X	readfrom(in);
X	if (in != stdin) {
X	    if (PipeCmd) (void) pclose(in);
X	    else (void) fclose(in);
X	}
X	if (!dont) {
X	    output_page();
X	}
X    }
X    if (ncols > 0) {
X	output_page();
X    }
X}
X
header(ch, s)
X    char *ch, *s;
X{
X    switch (*ch) {
X	case 'l': left_top = s; break;
X	case 'c': centre_top = s; break;
X	case 'r': right_top = s; break;
X	case 's':
X	    if (*++ch) {
X		top_space = atoi(ch);
X		return 0;
X	    } else {
X		top_space = atoi(s);
X		return 1;
X	    }
X	case '\0': return 0;
X	
X	default:
X	    fprintf(stderr, "%s: -H not followed by %c, not l, c, r or s\n",
X								progname, *ch);
X	    exit(1);
X     }
X    return 1;
X}
X
footer(ch, s)
X    char *ch, *s;
X{
X    switch (*ch) {
X	case 'l': left_bot = s; break;
X	case 'c': centre_bot = s; break;
X	case 'r': right_bot = s; break;
X	case 's':
X	    if (*++ch) {
X		bot_space = atoi(ch);
X		return 0;
X	    } else {
X		bot_space = atoi(s);
X		return 1;
X	    }
X	case '\0': return 0;
X	
X	default:
X	    fprintf(stderr, "%s: -F not followed by %c, not l, c, r or s\n",
X								progname, *ch);
X	    exit(1);
X     }
X    return 1;
X}
X
readfrom(f)
X    FILE *f;
X{
X    eof = 0;
X
X    /* TODO: read_col should return EOF on EOF, & eliminate "eof" */
X    while(!eof) {
X	read_col(f, ncols++);
X	if (width_so_far() > width || form && feed || ncols >= maxcols) {
X	    output_page();
X	}
X    }
X}
X
read_col(f, n)
X    FILE *f;
X    int n;
X{
X    int line;
X    char **this = cols[n] = (char **) Malloc(length * sizeof(char *));
X
X    if (cols[n] == (char **) 0) {
X	fprintf(stderr, "%s: no memory for colmuns in \"%s\"\n",
X							progname, ThisName);
X	exit(1);
X    }
X
X    this_col = n;
X    feed = 0;
X    col_width[n] = 0;
X
X    first_lines[n].File = 0L;
X    first_lines[n].Overall = 0L;
X
X    for (line = 0; line < length; line++) {
X	if ((this[line] = getline(f)) != (char *) 0) {
X	    if (!first_lines[n].File) { /* read the first line... */
X		first_lines[n].File = DocLine;
X		first_lines[n].Overall = FileLine;
X		if ((names[n] = Malloc(NameLen + 1)) == (char *) 0) {
X		    fprintf(stderr, "%s: running out of memory\n", progname);
X		    /* but try to struggle on */
X		} else {
X		    (void) strcpy(names[n], ThisName);
X		}
X	    }
X	}
X    }
X
X    last_lines[n].File = DocLine;
X    last_lines[n].Overall = FileLine;
X}
X
X#define addch(buf, b, c) \
X    ((&(*buf)[bufsiz] == *b) ? AddCh(buf, b, c) : (*(*(b))++ = (c)))
X
AddCh(buf, b, c)
X    char **buf;
X    char **b;
X    int c;
X{
X    extern char *realloc();
X    
X    if (&(*buf)[bufsiz] == *b) {
X	(*buf) = Realloc((*buf), bufsiz + width);
X	*b = &(*buf)[bufsiz];
X	bufsiz += width;
X    }
X    *(*b)++ = c;
X}
X
char *
getline(f)
X    FILE *f;
X{
X    int w = 0, m = 0;
X    int *cw = &col_width[this_col];
X    int c;
X    char *r, *b;
X    char *RealStart;
X
X    if (eof || feof(f)) {
X	eof = 1;
X	return NULL;
X    }
X    if (feed) {
X	return NULL;
X    } else {
X	if ((RealStart = b = r = Malloc(width)) == (char *) 0) {
X	    fprintf(stderr, "%s: out of memory reading \"%s\"\n",
X							progname, ThisName);
X	    exit(1);
X	}
X
X	if (NumberLines) {
X	    (void) sprintf(r, "%*d", NumberWidth,
X			    (NumberLines == 1) ? DocLine + 1 : FileLine + 1);
X	    while (*b) {
X		b++;
X	    }
X	    addch(&r, &b, ' ');
X	    RealStart = b;
X	}
X	while ((c = getc(f)) != '\n' && c != '\f' && c != EOF) {
X	    switch (c) {
X	    case '\r':
X		if (m < w) m = w;
X		w = 0;
X		addch(&r, &b, c);
X		break;
X	    case '\b':
X		if (m < w) m = w;
X		w--;
X		addch(&r, &b, c);
X		break;
X	    case '\t':
X		do {
X		    ++w;
X		    addch(&r, &b, ' ');
X		} while (w % tabsize);
X		break;
X
X	    /* special case: don't count ESCc as a character, in case
X	     * there are printer escape sequences like START BOLD.
X	     */
X
X	    case '\033':
X		if (m < w) m = w;
X		addch(&r, &b, c);
X		--w;
X		break;
X	    default:
X		addch(&r, &b, c);
X		if (isprint(c)) {
X		    ++w;
X		}
X		break;
X	    }
X	}
X
X	addch(&r, &b, EOL);
X
X	if (c == EOF) {
X	    eof = 1;
X	} else {
X	    feed |= (c == '\f');
X	}
X
X	w = esclength(r);
X	if (w > width) w = width;
X    }
X
X    if (*cw < w) *cw = w;
X
X    if (eof && r - 1 <= RealStart) {
X	(void) free(r);
X	return (char *) 0;
X    } else {
X	DocLine++;
X	FileLine++;
X	return r;
X    }
X}
X
width_so_far()
X{
X    register int i, sum = 0;
X
X    widest = 0;
X    for (i = 0; i < ncols; i++) {
X	int cwi = col_width[i];
X
X	sum += cwi;
X	if (cwi > widest) widest = cwi;
X    }
X    sum = (equal ? widest * ncols : sum) + (ncols - justify - 1) * sep;
X    return(sum);
X}
X
int LastColOnPage;
X
output_page()
X{
X    int used = 0, n = 0, new, real_sep, extra, line, col;
X    
X    ++DocPage;
X    ++FilePage;
X
X    if (ncols <= 0) {
X	return;
X    }
X
X    --ncols;
X    if (width_so_far() > width)	{ /* NOTE: recomputes "widest" */
X	fprintf(stderr, "%s: internal error calculating width", progname);
X	exit(1);
X    }
X    ++ncols;
X
X    /* work out the number of columns to print, and also the
X     * umber of used columns (so as to be able to deduce the separation)
X     */
X    for (n = 0; n < ncols; n++)	{ /* n is number of columns to print */
X	if ((new = used + (equal ? widest : col_width[n]) + sep) >
X			    width + sep || equal && col_width[n] > widest) {
X	    break;
X	} else {
X	    used = new;
X	}
X    }
X
X    if (n > 1) {
X	real_sep = sep + (width + sep - used) / (n - justify),
X	extra = (width + sep - used) % (n - justify);
X    }
X
X    LastColOnPage = n; /* hack hack */
X    
X    if (headers) {
X	int ltw = TW(left_top);
X	int ctw = TW(centre_top);
X	int rtw = TW(right_top);
X
X	space((top_space - 1)/ 2, '\n');
X	if (top_space) {
X	    if (left_top && *left_top) (void) Title(left_top, stdout);
X	    space((width - ctw) / 2 - ltw, ' ');
X	    if (centre_top && *centre_top) (void) Title(centre_top, stdout);
X	    space((width - ctw) / 2 - rtw, ' ');
X	    if (right_top && *right_top) (void) Title(right_top, stdout);
X	}
X	space(top_space / 2 + 1, '\n');
X    }
X    
X    for (line = 0; line < length; line++) {
X	for (col = 0; col < n; col++) {
X	    char *text = cols[col][line];
X	    putline((text != NULL ? text : ""),
X		     (col == n ? 0 : equal ? widest : col_width[col]),
X		     col + 1 != n);
X	    if (text != NULL) {
X		free(text);
X	    }
X	    if (col + 1 < n) {
X		space(real_sep + (col < extra ? 1 : 0), ' ');
X	    } else {
X		putchar('\n');
X	    }
X	 }
X    }
X
X    for (col = 0; col < n; col++) {
X	if (cols[col]) != (char **) 0) {
X	    /*NOSTRICT*/ (void) free((char *) cols[col]);
X	    cols[col] = (char **) 0;
X	}
X	if (names[col]) {
X	    (void) free(names[col]);
X	    names[col] = (char *) 0;
X	}
X    }
X    for(col = n; col < ncols; col++) {
X	cols[col-n] = cols[col];
X	col_width[col-n] = col_width[col];
X	first_lines[col-n].File = first_lines[col].File;
X	first_lines[col-n].Overall = first_lines[col].Overall;
X	last_lines[col-n].File = last_lines[col].File;
X	last_lines[col-n].Overall = last_lines[col].Overall;
X	if (names[col-n] != (char *) 0) {
X	    /* "not reached", because already freed above... */
X	    (void) free(names[col-n]);
X	}
X	names[col-n] = names[n];
X    }
X    ncols -= n;
X    
X    /* feet */
X    if (headers) { /* should be "if footers"? */
X	int ctw = TW(centre_bot);
X	int ltw = TW(left_bot);
X	int rtw = TW(right_bot);
X
X	space(bot_space / 2, '\n');
X	if (bot_space) {
X	    if (left_bot && *left_bot) (void) Title(left_bot, stdout);
X	    space((width - ctw) / 2 - ltw, ' ');
X	    if (centre_bot && *centre_bot) (void) Title(centre_bot, stdout);
X	    space((width - ctw) / 2 - rtw, ' ');
X	    if (right_bot && *right_bot) (void) Title(right_bot, stdout);
X	}
X	space((bot_space + 1) / 2, '\n');
X    }
X}
X
X/* Maybe should be able to use tabs? */
space(n, c)
X{
X    register int i;
X
X    for (i = 0; i < n; i++)
X	putchar(c);
X}
X
putline(text, Width, pad)
X    char *text;
X    int Width, pad;
X{
X    register char *p;
X    int col = 0;
X    
X    for (p = text; *p; p++) {
X	switch (*p) {
X	case '\033':
X	    putchar(*p);
X	    if (*++p) putchar(*p);
X	    else --p;
X	    break;
X		
X	case '\b':
X	    if (col > 0) {
X		if (col <= Width) {
X		    putchar(*p);
X		}
X		--col;
X	    }
X	    break;
X	case '\r':
X	    col = col > Width ? Width : col;
X	    /* why not putchar (\r) here? -- Lee */
X	    while (col > 0) {
X		--col;
X		putchar('\b');
X	    }
X	    break;
X	default:
X	    /* only allow control characters beyond the margin */
X	    if (col >= 0 && col < Width || !isprint(*p)) {
X		putchar(*p);
X	    }
X	    if (isprint(*p)) ++col;
X	    break;
X	}
X    }
X    if (pad) {
X	while (col++ < Width) {
X	    putchar(' ');
X	}
X    }
X}
X
int
Title(string, fd)
X    char *string;
X    FILE *fd;
X{
X    register char *p = string;
X    static char *buf = 0;
X    char *q;
X
X    /* Print string with variable interpolations..
X     * Currently available:
X     * %f -- filename at start of page
X     * %s -- filename at end of page
X     * %p -- page number within file
X     * %P -- page number within entire output
X     * %l -- line number within file (at start of page)
X     * %L -- line number within entire output (at start of page)
X     * %n -- line number within file (at end of page)
X     * %N -- line number within entire output (at end of page)
X     * %% - print a % sign
X     */
X    
X    if (!buf) {
X	if ((buf = Malloc(width + 1)) == (char *) 0) {
X	    fprintf(stderr, "%s: not enough memory for titles\n", progname);
X	    return 0;
X	}
X    }
X
X    for (q = buf; p && *p; p++) {
X	if (*p == '%') {
X	    switch (*++p) {
X	    case '\0': /* err on the side of silent robustness... */
X		--p; /* so we fail the loop test...*/
X		/* fall through */
X	    case '%':
X		*q++ = '%';
X		break;
X	    case 'l':
X		(void) sprintf(q, "%ld", first_lines[0].File);
X		while(*q) q++;
X		break;
X	    case 'L':
X		(void) sprintf(q, "%ld", first_lines[0].Overall);
X		while(*q) q++;
X		break;
X	    case 'n':
X		(void) sprintf(q, "%ld", last_lines[LastColOnPage-1].File);
X		while(*q) q++;
X		break;
X	    case 'N':
X		(void) sprintf(q, "%ld", last_lines[LastColOnPage-1].Overall);
X		while(*q) q++;
X		break;
X	    case 'P':
X		(void) sprintf(q, "%d", FilePage);
X		while(*q) q++;
X		break;
X	    case 'p':
X		(void) sprintf(q, "%d", DocPage);
X		while(*q) q++;
X		break;
X	    case 's':
X		if (names[0]) {
X		    (void) strcpy(q, names[0]);
X		    while(*q) q++;
X		}
X		break;
X	    case 'e':
X		if (names[LastColOnPage - 1]) {
X		    (void) sprintf(q, names[LastColOnPage - 1]);
X		    while(*q) q++;
X		}
X		break;
X	    default:
X		fprintf(stderr, "%s: warning: unknown escape %%%c in title\n",
X							    progname, *p);
X		*p = '?'; /* for next time... */
X	     /* fall through: */
X	    case '?': /* %? -- silently swallowed */
X		break;
X	    }
X	} else {
X	    *q++ = (*p);
X	}
X    }
X    *q = '\0';
X
X    if (q == buf) return 0;
X
X    if (fd != (FILE *) 0) {
X	(void) fputs(buf, fd);
X    }
X
X    return esclength(buf);
X}
X
X/* esclength -- the width of a string when printed, taking into account
X * any embedded escape sequences or overprinting.
X */
int
esclength(string)
X    char *string;
X{
X    register char *p;
X    register int w = 0;
X    int m = 0; /* max -- widest of several overprintings */
X
X    if (!string || !*string) return 0; /* paranoia... */
X
X    for (p = string; *p; p++) {
X	switch (*p) {
X	case '\r':
X	    if (m < w) m = w;
X	    w = 0;
X	    break;
X	case '\b':
X	    if (m < w) m = w;
X	    if (p > string) w--;
X	    break;
X	case '\t':
X	    do {
X		++w;
X	    } while (w % tabsize);
X	    break;
X
X	/* special case: don't count ESCc as a character, in case
X	 * there are printer escape sequences like START BOLD.
X	 */
X
X	case '\033':
X	    if (m < w) m = w;
X	    if (*(p + 1)) --w; /* there's another character */
X	    break;
X	default:
X	    if (isprint(*p)) {
X		++w;
X	    }
X	    break;
X	}
X    }
X    return (m >= w) ? m : w;
X}
X
X/* $Log:	pfm.c,v $
X * Revision 1.6  89/12/14  16:05:25  lee
X * Fixed a minor bug in -p handling involving a core... er... dump.
X * 
X * Revision 1.5  89/12/11  14:05:30  lee
X * oops - previous revision calculated width of tabs incorrectly!
X * 
X * Revision 1.4  89/12/10  17:32:12  lee
X * Fixed some trickly little buglets...
X * Now prints the correct filename!  Still gets the line number out by
X * one sometimes, though.
X * A little faster now, too.
X * 
X * Revision 1.2  89/12/03  20:35:25  lee
X * Re-indented so I could understand it more easily, then made addch() a
X * macro and removed the calls to strcpy() in getline() to speed things up
X * after profiling...
X * 
X * Revision 1.1  89/12/03  14:43:13  lee
X * Initial revision
X * 
X *
X */
END_OF_FILE
if test 20126 -ne `wc -c <'pfm.c'`; then
    echo shar: \"'pfm.c'\" unpacked with wrong size!
fi
# end of 'pfm.c'
fi
echo shar: End of shell archive.
exit 0