[net.sources] Roff in C

colonel@gloria.UUCP (George Sicherman) (12/27/84)

[For all you do, this Bug's for you]

All right, here it is:  roff in C.  Also the manual page, in case
you cannot find your V7 copy.

For those of you who came in late, roff is nroff's predecessor.  It's
simple and fast -- about 4 or 5 times as fast as nroff.

--- CUT HERE ---
.TH ROFF 1 "19 May 1983"
.SH NAME
roff \- format text
.SH SYNOPSIS
.B roff
[ \fB+\fIn\fR ] [ \fB\-\fIn\fR ] [
.B \-s
] [
.B \-h
] file ...
.PP
.B nroff \-mr
[ option ] ... file ...
.br
.B troff \-mr
[ option ] ... file ...
.SH DESCRIPTION
.I Roff
formats text according to control lines embedded
in the text in the given files.
Encountering a nonexistent file terminates printing.
Incoming inter-terminal messages are turned off during printing.
The optional flag arguments mean:
.br
.ns
.TP 5
.BI + n
Start printing at the first page with number
.IR n .
.br
.ns
.TP 5
.BI \- n
Stop printing at the first page numbered higher
than
.IR n .
.br
.ns
.TP 5
.B  \-s
Stop before each page (including the first)
to allow paper manipulation;
resume on receipt of an interrupt signal.
.br
.ns
.TP 5
.B  \-h
Insert tabs in the output stream to replace
spaces whenever appropriate.
.PP
.DT
Input consists of intermixed
.I "text lines,"
which contain information to be formatted, and
.I "request lines,"
which contain instructions about how to format
it.
Request lines begin with a distinguished
.I "control character,"
normally a period.
.PP
Output lines may be
.I filled
as nearly as possible with words without regard to
input lineation.
Line
.I breaks
may be caused at specified places by
certain commands, or by the appearance of an
empty input line or an input line beginning with a space.
.PP
The capabilities of
.I roff
are specified in the attached Request Summary.
Numerical values are denoted there by n or +n,
titles by t, and single characters by c.
Numbers denoted +n may be signed + or \-,
in which case they signify relative changes to
a quantity, otherwise they signify
an absolute resetting.
Missing n fields are ordinarily taken to be 1,
missing t fields to be empty, and c fields to shut off
the appropriate special interpretation.
.PP
Running titles usually appear at top and bottom of every
page.
They are set by requests like
.PP
.in +10
.if t \&.he \(fmpart1\(fmpart2\(fmpart3\(fm
.if n \&.he 'part1'part2'part3'
.in -10
.PP
Part1 is left justified, part2 is centered,
and part3 is right justified on the page.
Any % sign in a title is replaced by the current
page number.
Any nonblank may serve as a quote.
.PP
ASCII tab characters are replaced in the input by a
.I "replacement character,"
normally a space,
according to the
column settings given by a .ta command.
(See .tr for how to convert this character on output.)
.PP
Automatic hyphenation of filled output is done
under control of .hy.
When a word contains a designated
.I "hyphenation character,"
that character disappears from the output and
hyphens can be introduced into
the word at the marked places only.
.PP
The
.B \-mr
option of
.I nroff
or 
.IR troff (1)
simulates
.I roff
to the greatest extent possible.
.SH FILES
/usr/lib/suftab	suffix hyphenation tables
.br
/tmp/rtm?	temporary
.br
.SH BUGS
.I Roff
is the simplest of the text formatting
programs, and is utterly frozen.
.br
This is a home-brewed C version of a V7 assembly-language program.
It comes with no guarantees.
In particular, ridiculously long source lines will crash it.
.bp
.tc |
.tr |
.in 0
.ce
REQUEST SUMMARY
.PP
.ul
.ta \w'.tr cdef.. 'u +\w'Break 'u +\w'Initial 'u
.di x
			\ka
.br
.di
.in \nau
.ti 0
Request	Break	Initial	Meaning
.na
.ti 0
.li
.ad	yes	yes	Begin adjusting right margins.
.ti 0
.li
.ar	no	arabic	Arabic page numbers.
.ti 0
.li
.br	yes	\-	Causes a line break \*- the filling of
the current line is stopped.
.ti 0
.li
.bl|n	yes	\-	Insert of n blank lines, on new page if necessary.
.ti 0
.li
.bp|+n	yes	n=1	Begin new page and number it n; no n means `+1'.
.ti 0
.li
.cc|c	no	c=.	Control character becomes `c'.
.ti 0
.li
.ce|n	yes	\-	Center the next n input lines,
without filling.
.ti 0
.li
.de|xx	no	\-	Define parameterless macro 
to be invoked by request `.xx'
(definition ends on line beginning `\fB..\fR').
.ti 0
.li
.ds	yes	no	Double space; same as `.ls 2'.
.ti 0
.li
.ef|t	no	t=\*a\*a\*a\*a	Even foot title becomes t.
.ti 0
.li
.eh|t	no	t=\*a\*a\*a\*a	Even head title becomes t.
.ti 0
.li
.fi	yes	yes	Begin filling output lines.
.ti 0
.li
.fo	no	t=\*a\*a\*a\*a	All foot titles are t.
.ti 0
.li
.hc|c	no	none	Hyphenation character becomes `c'.
.ti 0
.li
.he|t	no	t=\*a\*a\*a\*a	All head titles are t.
.ti 0
.li
.hx	no	\-	Title lines are suppressed.
.ti 0
.li
.hy|n	no	n=1	Hyphenation is done, if n=1;
and is not done, if n=0.
.ti 0
.li
.ig	no	\-	Ignore input lines through
a line beginning with `\fB..\fR'.
.ti 0
.li
.in|+n	yes	\-	Indent n spaces from left margin.
.ti 0
.li
.ix +n	no	\-	Same as `.in' but without break.
.ti 0
.li
.li|n	no	\-	Literal, treat next n lines as text.
.ti 0
.li
.ll|+n	no	n=65	Line length including indent is n characters.
.ti 0
.li
.ls|+n	yes	n=1	Line spacing set to n lines per output line.
.ti 0
.li
.m1|n	no	n=2	Put n blank lines between the top
of page and head title.
.ti 0
.li
.m2|n	no	n=2	n blank lines put between head title
and beginning of text on page.
.ti 0
.li
.m3|n	no	n=1	n blank lines put between end of
text and foot title.
.ti 0
.li
.m4|n	no	n=3	n blank lines put between the foot title
and the bottom of page.
.ti 0
.li
.na	yes	no	Stop adjusting the right margin.
.ti 0
.li
.ne|n	no	\-	Begin new page, if n output lines
cannot fit on present page.
.ti 0
.li
.nn|+n	no	\-	The next n output lines are not numbered.
.ti 0
.li
.n1	no	no	Add 5 to page offset;
number lines in margin from 1 on each page.
.ti 0
.li
.n2|n	no	no	Add 5 to page offset;
number lines from n;
stop if n=0.
.ti 0
.li
.ni|+n	no	n=0	Line numbers are indented n.
.ti 0
.li
.nf	yes	no	Stop filling output lines.
.ti 0
.li
.nx|file	\-	Switch input to `file'.
.ti 0
.li
.of|t	no	t=\*a\*a\*a\*a	Odd foot title becomes t.
.ti 0
.li
.oh|t	no	t=\*a\*a\*a\*a	Odd head title becomes t.
.ti 0
.li
.pa|+n	yes	n=1	Same as `.bp'.
.ti 0
.li
.pl|+n	no	n=66	Total paper length taken to be n lines.
.ti 0
.li
.po|+n	no	n=0	Page offset.
All lines are preceded by n spaces.
.ti 0
.li
.ro	no	arabic	Roman page numbers.
.ti 0
.li
.sk|n	no	\-	Produce n blank pages starting next page.
.ti 0
.li
.sp|n	yes	\-	Insert block of n blank lines,
except at top of page.
.ti 0
.li
.ss	yes	yes	Single space output lines,
equivalent to `.ls 1'.
.ti 0
.li
.ta|n|n..		\-	Pseudotab settings.
Initial tab settings are columns 9 17 25 ...
.ti 0
.li
.tc|c	no	space	Tab replacement character becomes `c'.
.ti 0
.li
.ti|+n	yes	\-	Temporarily indent next output
line n spaces.
.ti0
.li
.tr|cdef..	no	\-	Translate c into d, e into f, etc.
.ti0
.li
.ul|n	no	\-	Underline the letters and numbers
in the next n input lines.
.br
.tr ||
--- CUT HERE TOO ---
/*
 *	roff - C version.
 *	the Colonel (...sunybcs!gloria!colonel).  19 May 1983.
 */

#include <ctype.h>
#include <sgtty.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#define SUFTAB	"/usr/lib/suftab"
#define TXTLEN	(o_pl-o_m1-o_m2-o_m3-o_m4-2)
#define IDTLEN	(o_ti>=0?o_ti:o_in)
#define MAXMAC	64
#define MAXDEPTH 10
#define MAXLENGTH 255
#define UNDERL	0200

int sflag, hflag, startpage, stoppage;
char holdword[MAXLENGTH], *holdp;
char assyline[MAXLENGTH];
int assylen;
char ehead[100], efoot[100], ohead[100], ofoot[100];
struct macrotype {
	char mname[3];
	long int moff;
} macro[MAXMAC];
int n_macros;
int depth;
int adjtoggle;
int isrequest = 0;
char o_tr[40][2];	/* OUTPUT TRANSLATION TABLE */
int o_cc = '.';	/* CONTROL CHARACTER */
int o_hc = -1;	/* HYPHENATION CHARACTER */
int o_tc = ' ';	/* TABULATION CHARACTER */
int o_in = 0;	/* INDENT SIZE */
int o_ix = -1;	/* NEXT INDENT SIZE */
int o_ta[20] = {
	9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 
	113, 121, 129, 137, 145, 153, 161};
int n_ta = 20;	/* #TAB STOPS */
int o_ll = 65, o_ad = 1, o_po = 0, o_ls = 1, o_ig = 0, o_fi = 1;
int o_pl = 66, o_ro = 0, o_hx = 0, o_bl = 0, o_sp = 0, o_sk = 0;
int o_ce = 0, o_m1 = 2, o_m2 = 2, o_m3 = 1, o_m4 = 3, o_ul = 0;
int o_li = 0, o_n1 = 0, o_n2 = 0, o_bp = -1, o_hy = 1;
int o_ni = 1;	/* LINE-NUMBER INDENT */
int o_nn = 0;	/* #LINES TO SUPPRESS NUMBERING */
int o_ti = -1;	/* TEMPORARY INDENT */
int center = 0;
int page_no = -1;
int line_no = 9999;
int n_outwords;
FILE *File, *Macread, *Macwrite;
FILE *Save;
long int teller[MAXDEPTH], ftell();
char *strcat(), *strcpy();
char *sprintf();
char *request[] = {
	"ad","ar","bl","bp","br","cc","ce","de",
	"ds","ef","eh","fi","fo","hc","he","hx","hy","ig",
	"in","ix","li","ll","ls","m1","m2","m3","m4",
	"n1","n2","na","ne","nf","ni","nn","nx","of","oh",
	"pa","pl","po","ro","sk","sp","ss","ta","tc","ti",
	"tr","ul",0};
char *mktemp(), *mfilnam = "/tmp/rtmXXXXXX";
int c;		/* LAST CHAR READ */
struct sgttyb tty;

main(argc,argv)
int argc;
char **argv;
{
	while (--argc) switch (**++argv) {
	case '+':
		startpage=atoi(++*argv);
		break;
	case '-':
		++*argv;
		if (isdigit(**argv)) stoppage=atoi(*argv);
		else switch(**argv) {
		case 's':
			sflag++;
			break;
		case 'h':
			hflag++;
			break;
		default:
			bomb();
		}
		break;
	default:
		argc++;
		goto endargs;
	}
endargs:
	if (sflag) gtty(0,&tty);
	mesg(0);	/* BLOCK OUT MESSAGES */
	assylen=0;
	assyline[0]='\0';
	if (!argc) {
		File=stdin;
		readfile();
	}
	else while (--argc) {
		File=fopen(*argv,"r");
		if (NULL==File) {
			fprintf(stderr,"roff: cannot read %s\n",*argv);
			exit(1);
		}
		readfile();
		fclose(File);
		argv++;
	}
	writebreak();
	endpage();
	for (; o_sk; o_sk--) blankpage();
	mesg(1);	/* ALLOW MESSAGES */
}

mesg(f)
int f;
{
	static int mode;
	struct stat cbuf;
	char *ttyname();

	if (!isatty(1)) return;
	if (!f) {
		fstat(1,&cbuf);
		mode = cbuf.st_mode;
		chmod(ttyname(1),mode & ~022);
	}
	else chmod(ttyname(1),mode);
}

readfile()
{
	while (readline()) {
		if (isrequest) continue;
		if (center || !o_fi) {
			if (assylen) writeline(0,1);
			else blankline();
		}
	}
}

readline()
{
	int startline, doingword;
	isrequest = 0;
	startline = 1;
	doingword = 0;
	for (;;) {
		c=suck();
		if (c==EOF) {
			if (doingword) bumpword();
			break;
		}
		if (c!=o_cc && o_ig) {
			while (c!='\n' && c!=EOF) c=suck();
			break;
		}
		if (isspace(c) && !doingword) {
			startline=0;
			switch (c) {
			case ' ':
				assyline[assylen++]=' ';
				break;
			case '\t':
				tabulate();
				break;
			case '\n':
				goto out;
			}
			continue;
		}
		if (isspace(c) && doingword) {
			bumpword();
			if (c=='\t') tabulate();
			else if (assylen) assyline[assylen++]=' ';
			doingword=0;
			if (c=='\n') break;
		}
		if (!isspace(c)) {
			if (doingword) *holdp++ = o_ul? c|UNDERL: c;
			else if (startline && c==o_cc && !o_li) {
				isrequest=1;
				return readreq();
			}
			else {
				doingword=1;
				holdp=holdword;
				*holdp++ = o_ul? c|UNDERL: c;
			}
		}
		startline=0;
	}
out:
	if (o_ul) o_ul--;
	center=o_ce;
	if (o_ce) o_ce--;
	if (o_li) o_li--;
	return c!=EOF;
}

bumpword()
{
	*holdp = '\0';
	if (assylen + strlen(holdword) > o_ll - IDTLEN) writeline(o_ad,0);
	strcpy(&assyline[assylen],holdword);
	assylen+=strlen(holdword);
	holdp = holdword;
}

tabulate()
{
	int j;
	for (j=0; j<n_ta; j++) if (o_ta[j]-1>assylen+IDTLEN) {
		for (; assylen+IDTLEN<o_ta[j]-1; assylen++)
			assyline[assylen]=o_tc;
		return;
	}
	/* NO TAB STOPS REMAIN */
	assyline[assylen++]=o_tc;
}

int
readreq()
{
	char req[3];
	int r, s;
	if (skipsp()) return c!=EOF;
	c=suck();
	if (c==EOF || c=='\n') return c!=EOF;
	if (c=='.') {
		o_ig = 0;
		do (c=suck());
		while (c!=EOF && c!='\n');
		if (depth) endmac();
		return c!=EOF;
	}
	if (o_ig) {
		while (c!=EOF && c!='\n') c=suck();
		return c!=EOF;
	}
	req[0]=c;
	c=suck();
	if (c==EOF || c=='\n') req[1]='\0';
	else req[1]=c;
	req[2]='\0';
	for (r=0; r<n_macros; r++) if (!strcmp(macro[r].mname,req)) {
		submac(r);
		goto reqflsh;
	}
	for (r=0; request[r]; r++) if (!strcmp(request[r],req)) break;
	if (!request[r]) {
		do (c=suck());
		while (c!=EOF && c!='\n');
		return c!=EOF;
	}
	switch (r) {
	case 0: /* ad */
		o_ad=1;
		writebreak();
		break;
	case 1: /* ar */
		o_ro=0;
		break;
	case 2: /* bl */
		nread(&o_bl);
		writebreak();
		break;
	case 3: /* bp */
	case 37: /* pa */
		c=snread(&r,&s,1);
		if (s>0) o_bp=page_no-r;
		else if (s<0) o_bp=page_no+r;
		else o_bp=r;
		writebreak();
		if (line_no) {
			endpage();
			beginpage();
		}
		break;
	case 4: /* br */
		writebreak();
		break;
	case 5: /* cc */
		c=cread(&o_cc);
		break;
	case 6: /* ce */
		nread(&o_ce);
		writebreak();
		break;
	case 7: /* de */
		defmac();
		break;
	case 8: /* ds */
		o_ls=2;
		writebreak();
		break;
	case 9: /* ef */
		c=tread(efoot);
		break;
	case 10: /* eh */
		c=tread(ehead);
		break;
	case 11: /* fi */
		o_fi=1;
		writebreak();
		break;
	case 12: /* fo */
		c=tread(efoot);
		strcpy(ofoot,efoot);
		break;
	case 13: /* hc */
		c=cread(&o_hc);
		break;
	case 14: /* he */
		c=tread(ehead);
		strcpy(ohead,ehead);
		break;
	case 15: /* hx */
		o_hx=1;
		break;
	case 16: /* hy */
		nread(&o_hy);
		break;
	case 17: /* ig */
		o_ig=1;
		break;
	case 18: /* in */
		writebreak();
		snset(&o_in);
		o_ix = -1;
		break;
	case 19: /* ix */
		snset(&o_ix);
		if (!n_outwords) o_in=o_ix;
		break;
	case 20: /* li */
		nread(&o_li);
		break;
	case 21: /* ll */
		snset(&o_ll);
		break;
	case 22: /* ls */
		snset(&o_ls);
		break;
	case 23: /* m1 */
		nread(&o_m1);
		break;
	case 24: /* m2 */
		nread(&o_m2);
		break;
	case 25: /* m3 */
		nread(&o_m3);
		break;
	case 26: /* m4 */
		nread(&o_m4);
		break;
	case 27: /* n1 */
		o_n1=1;
		break;
	case 28: /* n2 */
		nread(&o_n2);
		break;
	case 29: /* na */
		o_ad=0;
		writebreak();
		break;
	case 30: /* ne */
		nread(&r);
		if (line_no+(r-1)*o_ls+1 > TXTLEN) {
			writebreak();
			endpage();
			beginpage();
		}
		break;
	case 31: /* nf */
		o_fi=0;
		writebreak();
		break;
	case 32: /* ni */
		snset(&o_ni);
		break;
	case 33: /* nn */
		snset(&o_nn);
		break;
	case 34: /* nx */
		do_nx();
		c='\n';	/* SO WE DON'T FLUSH THE FIRST LINE */
		break;
	case 35: /* of */
		c=tread(ofoot);
		break;
	case 36: /* oh */
		c=tread(ohead);
		break;
	case 38: /* pl */
		snset(&o_pl);
		break;
	case 39: /* po */
		snset(&o_po);
		break;
	case 40: /* ro */
		o_ro=1;
		break;
	case 41: /* sk */
		nread(&o_sk);
		break;
	case 42: /* sp */
		nread(&o_sp);
		writebreak();
		break;
	case 43: /* ss */
		writebreak();
		o_ls=1;
		break;
	case 44: /* ta */
		do_ta();
		break;
	case 45: /* tc */
		c=cread(&o_tc);
		break;
	case 46: /* ti */
		writebreak();
		c=snread(&r,&s,0);
		if (s>0) o_ti=o_in+r;
		else if (s<0) o_ti=o_in-r;
		else o_ti=r;
		break;
	case 47: /* tr */
		do_tr();
		break;
	case 48: /* ul */
		nread(&o_ul);
		break;
	}
reqflsh:
	while (c!=EOF && c!='\n') c=suck();
	return c!=EOF;
}

snset(par)
int *par;
{
	int r, s;
	c=snread(&r,&s,0);
	if (s>0) *par+=r;
	else if (s<0) *par-=r;
	else *par=r;
}

tread(s)
char *s;
{
	int leadbl;
	leadbl=0;
	for (;;) {
		c=suck();
		if (c==' ' && !leadbl) continue;
		if (c==EOF || c=='\n') {
			*s = '\0';
			return c;
		}
		*s++ = c;
		leadbl++;
	}
}

nread(i)
int *i;
{
	int f;
	f=0;
	*i=0;
	if (!skipsp()) for (;;) {
		c=suck();
		if (c==EOF || c=='\n') break;
		if (isspace(c)) break;
		if (isdigit(c)) {
			f++;
			*i = *i*10 + c - '0';
		}
		else break;
	}
	if (!f) *i=1;
}

int
snread(i,s,sdef)
int *i, *s, sdef;
{
	int f;
	f = *i = *s = 0;
	for (;;) {
		c=suck();
		if (c==EOF || c=='\n') break;
		if (isspace(c)) {
			if (f) break;
			else continue;
		}
		if (isdigit(c)) {
			f++;
			*i = *i*10 + c - '0';
		}
		else if ((c=='+' || c=='-') && !f) {
			f++;
			*s = c=='+' ? 1 : -1;
		}
		else break;
	}
	while (c!=EOF && c!='\n') c=suck();
	if (!f) {
		*i=1;
		*s=sdef;
	}
	return c;
}

int
cread(k)
int *k;
{
	int u;
	*k = -1;
	for (;;) {
		u=suck();
		if (u==EOF || u=='\n') return u;
		if (isspace(u)) continue;
		if (*k < 0) *k=u;
	}
}

defmac()
{
	int i;
	char newmac[3], *nm;
	if (skipsp()) return;
	nm=newmac;
	if (!Macwrite) openmac();
	*nm++ = suck();
	c=suck();
	if (c!=EOF && c!='\n' && c!=' ' && c!='\t') *nm++ = c;
	*nm = '\0';
		/* KILL OLD DEFINITION IF ANY */
	for (i=0; i<n_macros; i++) if (!strcmp(newmac,macro[i].mname)) {
		macro[i].mname[0]='\0';
		break;
	}
	macro[n_macros].moff=ftell(Macwrite);
	strcpy(macro[n_macros++].mname,newmac);
	while (c!=EOF && c!='\n') c=suck();	/* FLUSH HEADER LINE */
	while (copyline());
	fflush(Macwrite);
}

openmac()
{
	if (NULL==(Macwrite=fopen(mktemp(mfilnam),"w"))) {
		fprintf(stderr,"roff: cannot open temp file\n");
		exit(1);
	}
	Macread=fopen(mfilnam,"r");
	unlink(mfilnam);
}

int
copyline()
{
	int n, first, second;
	if (c==EOF) {
		fprintf(Macwrite,"..\n");
		return 0;
	}
	n=0;
	first=1;
	second=0;
	for (;;) {
		c=suck();
		if (c==EOF) {
			if (!first) putc('\n',Macwrite);
			return 0;
		}
		if (c=='\n') {
			putc('\n',Macwrite);
			return n!=2;
		}
		if (first && c=='.') n++;
		else if (second && n==1 && c=='.') n++;
		putc(c,Macwrite);
		second=first;
		first=0;
	}
}

submac(r)
int r;
{
	while (c!=EOF && c!='\n') c=suck();
	if (depth) teller[depth-1]=ftell(Macread);
	else {
		Save = File;
		File = Macread;
	}
	depth++;
	fseek(Macread,macro[r].moff,0);
}

endmac()
{
	depth--;
	if (depth) fseek(Macread,teller[depth-1],0);
	else File = Save;
	c='\n';
}

do_ta()
{
	int v;
	n_ta = 0;
	for (;;) {
		nread(&v);
		if (v==1) return;
		else o_ta[n_ta++]=v;
		if (c=='\n' || c==EOF) break;
	}
}

do_tr()
{
	char *t;
	t = &o_tr[0][0];
	*t='\0';
	if (skipsp()) return;
	for (;;) {
		c=suck();
		if (c==EOF || c=='\n') break;;
		*t++ = c;
	}
	*t = '\0';
}

do_nx()
{
	char fname[100], *f;
	f=fname;
	if (skipsp()) return;
	for (;;) switch(c=suck()) {
	case EOF:
	case '\n':
	case ' ':
	case '\t':
		if (f==fname) return;
		goto got_nx;
	default:
		*f++ = c;
	}
got_nx:
	fclose(File);
	*f = '\0';
	if (!(File=fopen(fname,"r"))) {
		fprintf(stderr,"roff: cannot read %s\n",fname);
		exit(1);
	}
}

int
skipsp()
{
	for (;;) switch(c=suck()) {
	case EOF:
	case '\n':
		return 1;
	case ' ':
	case '\t':
		continue;
	default:
		ungetc(c,File);
		return 0;
	}
}

writebreak()
{
	int q;
	if (assylen) writeline(0,1);
	q = TXTLEN;
	if (o_bl) {
		if (o_bl + line_no > q) {
			endpage();
			beginpage();
		}
		for (; o_bl; o_bl--) blankline();
	}
	else if (o_sp) {
		if (o_sp + line_no > q) newpage();
		else if (line_no) for (; o_sp; o_sp--) blankline();
	}
}

blankline()
{
	if (line_no >= TXTLEN) newpage();
	if (o_n2) o_n2++;
	spit('\n');
	line_no++;
}

writeline(adflag,flushflag)
int adflag, flushflag;
{
	int j, q;
	char lnstring[7];
	for (j=assylen-1; j; j--) {
		if (assyline[j]==' ') assylen--;
		else break;
	}
	q = TXTLEN;
	if (line_no >= q) newpage();
	for (j=0; j<o_po; j++) spit(' ');
	if (o_n1) {
		if (o_nn) for (j=0; j<o_ni+4; j++) spit(' ');
		else {
			for (j=0; j<o_ni; j++) spit(' ');
			sprintf(lnstring,"%3d ",line_no+1);
			spits(lnstring);
		}
	}
	if (o_n2) {
		if (o_nn) for (j=0; j<o_ni+4; j++) spit(' ');
		else {
			for (j=0; j<o_ni; j++) spit(' ');
			sprintf(lnstring,"%3d ",o_n2++);
			spits(lnstring);
		}
	}
	if (o_nn) o_nn--;
	if (center) for (j=0; j<(o_ll-assylen+1)/2; j++) spit(' ');
	else for (j=0; j<IDTLEN; j++) spit(' ');
	if (adflag && !flushflag) fillline();
	for (j=0; j<assylen; j++) spit(assyline[j]);
	spit('\n');
	assylen=0;
	assyline[0]='\0';
	line_no++;
	for (j=1; j<o_ls; j++) if (line_no <= q) blankline();
	if (!flushflag)  {
		strcpy(assyline,holdword);
		assylen=strlen(holdword);
		*holdword='\0';
		holdp=holdword;
	}
	if (o_ix>=0) o_in=o_ix;
	o_ix = o_ti = -1;
}

fillline()
{
	int excess, j, s, inc, spaces;
	adjtoggle^=1;
	if (!(excess = o_ll - IDTLEN - assylen)) return;
	if (excess < 0) {
		fprintf(stderr,"roff: internal error #2 [%d]\n",excess);
		exit(1);
	}
	for (j=2;; j++) {
		if (adjtoggle) {
			s=0;
			inc = 1;
		}
		else {
			s=assylen-1;
			inc = -1;
		}
		spaces=0;
		while (s>=0 && s<assylen) {
			if (assyline[s]==' ') spaces++;
			else {
				if (0<spaces && spaces<j) {
					insrt(s-inc);
					if (inc>0) s++;
					if (!--excess) return;
				}
				spaces=0;
			}
			s+=inc;
		}
	}
}

insrt(p)
int p;
{
	int i;
	for (i=assylen; i>p; i--) assyline[i]=assyline[i-1];
	assylen++;
}

newpage()
{
	if (page_no >= 0) endpage();
	else page_no=1;
	for (; o_sk; o_sk--) blankpage();
	beginpage();
}

beginpage()
{
	int i;
	if (sflag) waitawhile();
	for (i=0; i<o_m1; i++) spit('\n');
	writetitle(page_no&1? ohead: ehead);
	for (i=0; i<o_m2; i++) spit('\n');
	line_no=0;
}

endpage()
{
	int i;
	for (i=line_no; i<TXTLEN; i++) blankline();
	for (i=0; i<o_m3; i++) spit('\n');
	writetitle(page_no&1? ofoot: efoot);
	for (i=0; i<o_m4; i++) spit('\n');
	if (o_bp < 0) page_no++;
	else {
		page_no = o_bp;
		o_bp = -1;
	}
}

blankpage()
{
	int i;
	if (sflag) waitawhile();
	for (i=0; i<o_m1; i++) spit('\n');
	writetitle(page_no&1? ohead: ehead);
	for (i=0; i<o_m2; i++) spit('\n');
	for (i=0; i<TXTLEN; i++) spit('\n');
	for (i=0; i<o_m3; i++) spit('\n');
	writetitle(page_no&1? ofoot: efoot);
	page_no++;
	for (i=0; i<o_m4; i++) spit('\n');
	line_no=0;
}

waitawhile()
{
	int nix(), oldflags;
	if (isatty(0)) {
		oldflags=tty.sg_flags;
		tty.sg_flags &= ~ECHO;	/* DON'T ECHO THE RUBOUT */
		stty(0,&tty);
	}
	signal(SIGINT,nix);
	pause();
	if (isatty(0)) {
		tty.sg_flags = oldflags;
		stty(0,&tty);
	}
}

nix()
{}

writetitle(t)
char *t;
{
	char d, *pst, *pgform();
	int j, l, m, n;
	d = *t;
	if (o_hx || !d) {
		spit('\n');
		return;
	}
	pst=pgform();
	for (j=0; j<o_po; j++) spit(' ');
	l=titlen(++t,d,strlen(pst));
	while (*t && *t!=d) {
		if (*t=='%') spits(pst);
		else spit(*t);
		t++;
	}
	if (!*t) {
		spit('\n');
		return;
	}
	m=titlen(++t,d,strlen(pst));
	for (j=l; j<(o_ll-m)/2; j++) spit(' ');
	while (*t && *t!=d) {
		if (*t=='%') spits(pst);
		else spit(*t);
		t++;
	}
	if (!*t) {
		spit('\n');
		return;
	}
	if ((o_ll-m)/2 > l) m+=(o_ll-m)/2;
	else m+=l;
	n=titlen(++t,d,strlen(pst));
	for (j=m; j<o_ll-n; j++) spit(' ');
	while (*t && *t!=d) {
		if (*t=='%') spits(pst);
		else spit(*t);
		t++;
	}
	spit('\n');
}

char *
pgform()
{
	static char pst[11];
	int i;
	if (o_ro) {
		*pst='\0';
		i=page_no;
		if (i>=400) {
			strcat(pst,"cd");
			i-=400;
		}
		while (i>=100) {
			strcat(pst,"c");
			i-=100;
		}
		if (i>=90) {
			strcat(pst,"xc");
			i-=90;
		}
		if (i>=50) {
			strcat(pst,"l");
			i-=50;
		}
		if (i>=40) {
			strcat(pst,"xl");
			i-=40;
		}
		while (i>=10) {
			strcat(pst,"x");
			i-=10;
		}
		if (i>=9) {
			strcat(pst,"ix");
			i-=9;
		}
		if (i>=5) {
			strcat(pst,"v");
			i-=5;
		}
		if (i>=4) {
			strcat(pst,"iv");
			i-=4;
		}
		while (i--) strcat(pst,"i");
	}
	else sprintf(pst,"%d",page_no);
	return pst;
}

int
titlen(t,c,k)
char *t, c;
int k;
{
	int q;
	q=0;
	while (*t && *t!=c) q += *t++ == '%' ? k : 1;
	return q;
}

spits(s)
char *s;
{
	while (*s) spit(*s++);
}

spit(c)
char c;
{
	static int col_no, n_blanks;
	int ulflag;
	char *t;
	ulflag=c&UNDERL;
	c&=~UNDERL;
	for (t = (char *)o_tr; *t; t++) if (*t++==c) {
		c = *t;
		break;
	}
	if (page_no < startpage || (stoppage && page_no > stoppage)) return;
	if (c != ' ' && c != '\n' && n_blanks) {
		if (hflag && n_blanks>1)
		while (col_no/8 < (col_no+n_blanks)/8) {
			putc('\t',stdout);
			n_blanks-= 8 - (col_no & 07);
			col_no = 8 + col_no & ~07;
		}
		for (; n_blanks; n_blanks--) {
			putc(' ',stdout);
			col_no++;
		}
	}
	if (ulflag && isalnum(c)) fputs("_\b",stdout);
	if (c == ' ') n_blanks++;
	else {
		putc(c,stdout);
		col_no++;
	}
	if (c == '\n') {
		col_no=0;
		n_blanks=0;
	}
}

int
suck()
{
	for (;;) {
		c=getc(File);
		if (!iscntrl(c) && c!='\013' && c!='\f' && c!='\r') return c;
	}
}

bomb()
{
	fprintf(stderr,"usage: roff [+00] [-00] [-s] [-h] file ...\n");
	exit(1);
}
-- 
Col. G. L. Sicherman
...seismo!rochester!rocksanne!rocksvax!sunybcs!gloria!colonel

bph@ut-ngp.UUCP (hine, butler) (12/31/84)

[]
The posted version of this program does not work and was apparently
never tested.  As posted, any request at the beginning of a text file
causes the whole file to be skipped because of a logic error in the
basic input routine, called "suck()."  It is well named.  Leading
spaces do not cause a line break since the logic to do this is missing.
Blank lines disappear.  Doubtless there are other bugs as well.

Pity.  Would have been useful.

ken@rochester.UUCP (Ken Yap) (12/31/84)

A while back I translated the text formatter in the book "Software
Tools" into C and then added more functionality. As far as I can tell
it does all that roff used to do with the major exception of
hyphenation.  I have not worked on it for a while and it offered as
is.  If there is sufficient interest in this I will post the sources
and manual page to net.sources.  Mail me your votes. Do not follow-up
this article.
-- 
	Ken Yap

UUCP: (..!{allegra, decvax, seismo}!rochester!ken) ARPA: ken@rochester.arpa
USnail:	Dept. of Comp. Sci., U. of Rochester, NY 14627.

tim@cmu-cs-k.ARPA (Tim Maroney) (01/01/85)

The posting of the C version of roff created the following comment:

> The posted version of this program does not work and was apparently
> never tested.  As posted, any request at the beginning of a text file
> causes the whole file to be skipped because of a logic error in the
> basic input routine, called "suck()."  It is well named.  Leading
> spaces do not cause a line break since the logic to do this is missing.
> Blank lines disappear.  Doubtless there are other bugs as well.
> 
> Pity.  Would have been useful.

The statement about an initial request gobbling the whole file is just plain
false.  I have tested it with a few different requests as the first line of
the text file, without any problems.  It would have been nice if the person
had told us what request caused this error.

However, he was right that blank lines and lines with leading spaces do not
create the desired effect.  Fortunately, this is easy to fix.  Here is the
new version of the function readline() which provides the desired
functionality.  Just replace the old version of the function with this.

readline()
{
	int startline, doingword;
	isrequest = 0;
	startline = 1;
	doingword = 0;
	c=suck();
	if (c == '\n') {
		o_sp = 1;
		writebreak();
		goto out;
	}
	else if (isspace(c))
		writebreak();
	for (;;) {
		if (c==EOF) {
			if (doingword) bumpword();
			break;
		}
		if (c!=o_cc && o_ig) {
			while (c!='\n' && c!=EOF) c=suck();
			break;
		}
		if (isspace(c) && !doingword) {
			startline=0;
			switch (c) {
			case ' ':
				assyline[assylen++]=' ';
				break;
			case '\t':
				tabulate();
				break;
			case '\n':
				goto out;
			}
			c = suck();
			continue;
		}
		if (isspace(c) && doingword) {
			bumpword();
			if (c=='\t') tabulate();
			else if (assylen) assyline[assylen++]=' ';
			doingword=0;
			if (c=='\n') break;
		}
		if (!isspace(c)) {
			if (doingword) *holdp++ = o_ul? c|UNDERL: c;
			else if (startline && c==o_cc && !o_li) {
				isrequest=1;
				return readreq();
			}
			else {
				doingword=1;
				holdp=holdword;
				*holdp++ = o_ul? c|UNDERL: c;
			}
		}
		startline=0;
		c = suck();
	}
out:
	if (o_ul) o_ul--;
	center=o_ce;
	if (o_ce) o_ce--;
	if (o_li) o_li--;
	return c!=EOF;
}

Now what roff really needs is to separate the UNIX dependencies from the
rest of the code, and to add font support.  I intend to use it with the
smallc compiler on CP/M.  When I finish converting it, I will post the
smallc version here.
-=-
Tim Maroney, Carnegie-Mellon University Computation Center
ARPA:	Tim.Maroney@CMU-CS-K	uucp:	seismo!cmu-cs-k!tim
CompuServe:	74176,1360	audio:	shout "Hey, Tim!"

"Remember all ye that existence is pure joy; that all the sorrows are
but as shadows; they pass & are done; but there is that which remains."
Liber AL, II:9.

brian@ukma.UUCP (Brian Sturgill) (01/03/85)

Moving all further flames on this to net.sources.bugs.  Please comply.