Checkpoint: pull in mpm; merge pic from Taj's version of the world

This commit is contained in:
wkj 2004-05-16 07:56:41 +00:00
parent c5561c23cf
commit 5f1cf8e6fb
21 changed files with 3878 additions and 39 deletions

1
src/cmd/mpm/.cvsignore Normal file
View file

@ -0,0 +1 @@
pm

188
src/cmd/mpm/README Normal file
View file

@ -0,0 +1,188 @@
An experiment in page makeup for troff output...
-mpm is a version of standard -ms that causes extra
information for vertical justification and figure
placement to be included in troff output. Commands that
have been augmented to provide paddable space are
.SH and .NH
.PP and .LP no space if \n(PD is 0; normally .nr PD 0.3v; leave at least 1u
.IP and .QP also
.EQ and .EN
.TS and .TE no space if \n(TS is 0; normally .nr TS 0.5v
.PS and .PE
.P1 and .P2 display programs in CW font
.DS and .DE
.QS and .QE
Other commands, registers, strings, etc.:
.SP n explicit paddable space, just like .sp n.
generally you should ALWAYS use .SP instead of .sp.
if you need exactly a given vertical space, you can say
.SP 3i exactly
this space won't be padded.
.Tm words prints "words" and the output page number on stderr
sorry about the spelling; -ms pre-empted .TM
.NE n like .ne. note: does not cause a break
Others may be added as the need arises.
.nr FO n Set the page length. This value is the bottom of
the text on the page; a bottom title may lie below.
default is 10i (== 10 inches).
%o, %e are strings containing odd and even page titles
%# is the current page number (often useless)
.PT is a macro invoked at the top of each "page";
it will normally use %e, %o and %#. There is also
a .BT for page bottoms if desired.
.BP force a page break
.FL force all waiting figures out before any more running text
.1C, .2C multiple columns; number registers CW and GW set
the column and gutter width if you don't like the default.
absent a .FC command, all two-column contents collect
together on the page
.FC freeze current two-column contents and start afresh.
necessary if you want to switch between 1 and 2 column
text and keep the relative order among them.
Usage is some variant of
... | troff -mpm
/usr/lib/tmac/pm is the page-justifier itself; it is called automatically
by the -mpm macro package. If you are installing this yourself, you will
have to edit the 2nd line of tmac.pm to arrange that pm is called directly
from troff.
There are several lines in tmac.pm that say
.so /n/coma/usr/bwk/...
You should delete these; they are placeholders for some experiments.
If you use -mm, you are more or less out of luck, although we will be
happy to provide a crude and incomplete program that purports to convert
-mm to -ms. It may suggest what you need but it won't do the job.
To compile pm, you need a C++ compiler, preferably release 2.0 or later.
Put the .c and .h files in a directory, and type
make
This process may well fail. The usual cause is that different systems
put function declarations in different header files, and C++ insists that
all functions be properly declared. You can almost always get through this
part by adding function declarations. The most likely offender is malloc;
a line like
extern char *malloc(int);
near the top of slug.c will solve this one.
Bugs, etc.:
not all -ms commands have been decorated; in particular,
the rich variety of document types (TM, CSTR, etc.,) is not
really supported.
there are problems with funny first pages and troff input
that moves back up the page.
multiple columns: only .2C is available. The program does not check
whether something is wide or narrow: user has responsibility to mark
which with .1C or .2C.
headings are a bit tricky if you want things like
running titles that include the current section title.
normally a two-pass procedure using .Tm is needed.
It's a pain to force a blank vertical space of specified height.
Try this:
.de x
\v'\\$1'\0\h'-\w'\0'u'\c
..
.x 2.5i
If you want to roll your own, the following components are
included in pm's "command language". They are inserted in
the troff output in the form of "x X ..." commands, which
are created either by \X'...' or by the .X macro in -mpm.
Look at how they are used in /usr/lib/tmac/tmac.pm for examples.
BS n breakable stream n = min # lines that must appear on page
use: PP, LP, IP, ...
US unbreakable stream use: KS/KE, DS/DE, TS/TE, EQ/EN, PS/PE, etc.
BF v breakable float v = preferred vertical location of box center
use: FS/FE
use two successive BF's to give two preferences
UF v unbreakable float v = preferred vertical location of box center
use: KF/KE
use two successive UF's to give two preferences
PT page title use: user has absolute control between PT and END
no SP's or other pm commands inside are processed
BT bottom title use: user has absolute control between BT and END
END end end a US, BF, UF, PT, or BT
all constructs nest, but a float within another float
or a US block will not float within or outside the block
NE n need break page if a VBOX of height n would not fit on page
use: .NE n
SP n space paddable space of n
use: .SP n
PARM NP v top of pm text at v
new page
PARM FO v bottom of pm text at v
footer length of text on page = FO-NP
PARM PL v physical page ends at v
page length default = FO + NP
PARM MF x tolerance to prevent padding
minimum fullness default = 0.9
PARM CT x tolerance for two-column operation
column tolerance default = 0.5
PARM DBG x debugging flag
TM str message .Tm words prints <pageno> <tab> <words> on stderr
MC n o multiple column n columns, offset o.
Only 1 and 2 columns will work.
CMD BP break page force page break
CMD FL flush force all queued figures out before any more
stream material is output
CMD FC freeze columns force out current two-column contents;
start a fresh one
Something like this will probably have to be added:
NC new column HARD!
Known botches in the existing implementation of pm:
If a footnote is split across two pages, any associated separator line
will not be copied. If there are multiple footnotes on one page, there
will be multiple separators too. -mpm's .FS macro does not provide a
separator. If you want a separator line, put it in explicitly with
a call to the .FA macro.
There are not enough settable parameters; in particular, the
way to control the height is a botch.
Historical note: There is a simpler version of pm and -mpm
called pj and -mpj that only does vertical justification of
pages that have already been laid out by conventional means.
This simpler version may be adequate, but it is no longer
supported and memory of how it works is growing dim.

12
src/cmd/mpm/misc.cc Normal file
View file

@ -0,0 +1,12 @@
#include "misc.h"
char errbuf[200];
char *progname;
int wantwarn = 0;
int dbg = 0;
// dbg = 1 : dump slugs
// dbg = 2 : dump ranges
// dbg = 4 : report function entry
// dbg = 8 : follow queue progress
// dbg = 16: follow page fill progress

41
src/cmd/mpm/misc.h Normal file
View file

@ -0,0 +1,41 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <string.h>
// XXX: Apparently necessary for g++
#define typename tyname
extern char errbuf[];
extern char *progname;
extern int linenum;
extern int wantwarn;
// #define ERROR fflush(stdout), fprintf(stderr, "%s: ", progname), fprintf(stderr,
// #define FATAL ), exit(1)
// #define WARNING )
#define ERROR fprintf(stdout, "\n#MESSAGE TO USER: "), sprintf(errbuf,
#define FATAL ), fputs(errbuf, stdout), \
fprintf(stderr, "%s: ", progname), \
fputs(errbuf, stderr), \
fflush(stdout), \
exit(1)
#define WARNING ), fputs(errbuf, stdout), \
wantwarn ? \
fprintf(stderr, "%s: ", progname), \
fputs(errbuf, stderr) : 0, \
fflush(stdout)
#define eq(s,t) (strcmp(s,t) == 0)
inline int max(int x, int y) { return x > y ? x : y; }
inline int min(int x, int y) { return x > y ? y : x; }
inline int abs(int x) { return (x >= 0) ? x : -x; }
extern int dbg;
extern int pn, userpn; // actual and user-defined page numbers
extern int pagetop, pagebot; // printing margins
extern int physbot; // physical bottom of the page

24
src/cmd/mpm/mkfile Normal file
View file

@ -0,0 +1,24 @@
</$objtype/mkfile
TARG=aux/pm
OFILES=misc.$O\
slug.$O\
range.$O\
queue.$O\
page.$O\
HFILES=misc.h\
BIN=/$objtype/bin
</sys/src/cmd/mkone
CC=c++/$CC
LD=c++/$LD
CFLAGS=
slug.$O: slug.h
range.$O: range.h slug.h
queue.$O: page.h range.h slug.h
page.$O: page.h range.h slug.h
test:V: $O.out
tryout $O.out

612
src/cmd/mpm/page.cc Normal file
View file

@ -0,0 +1,612 @@
#include "misc.h"
#include "slug.h"
#include "range.h"
#include "page.h"
const int MAXRANGES = 1000;
static range *ptemp[MAXRANGES]; // for movefloats()
static void swapright(int n) // used by movefloats()
{
range *t = ptemp[n];
ptemp[n] = ptemp[n+1];
ptemp[n+1] = t;
ptemp[n]->setaccum( ptemp[n+1]->accum() -
ptemp[n+1]->rawht() + ptemp[n]->rawht() );
ptemp[n+1]->setaccum( ptemp[n]->accum() + ptemp[n+1]->rawht() );
}
// Figure out the goal position for each floating range on scratch,
// and move it past stream ranges until it's as close to its goal as possible.
static void movefloats(stream *scratch, double scale)
{
int nranges, i;
const int Huge = 100000;
for (nranges = 0; scratch->more(); scratch->advance())
ptemp[nranges++] = scratch->current();
scratch->freeall();
ufrange rtemp;
ptemp[nranges] = &rtemp;
rtemp.setgoal(Huge);
int accumV = 0; // compute accum values and
for (i = 0; i < nranges; i++) { // pick closest goal for floats
ptemp[i]->pickgoal(accumV, scale);
ptemp[i]->setaccum(accumV += ptemp[i]->rawht());
}
int j; // index for inner loop below:
for (i = nranges; --i >= 0; ) // stably sort floats to bottom
for (j = i; j < nranges; j++)
if (ptemp[j]->goal() > ptemp[j+1]->goal())
swapright(j);
else
break;
if (dbg & 16)
printf("#movefloats: before floating, from bottom:\n");
for (i = nranges; --i >= 0; ) { // find topmost float
if (ptemp[i]->goal() == NOGOAL)
break;
if (dbg & 16)
printf("# serialno %d goal %d height %d\n",
ptemp[i]->serialno(), ptemp[i]->goal(),
ptemp[i]->rawht());
} // i+1 is topmost float
for (i++ ; i < nranges; i++) // move each float up the page
for (j = i; j > 0; j--) // as long as closer to its goal
if (ptemp[j]->goal()
<= ptemp[j-1]->accum() + ptemp[j]->rawht()/2
&& ptemp[j-1]->goal() == NOGOAL)
swapright(j-1);
else
break;
if (ptemp[nranges] != &rtemp)
ERROR "goal sentinel has disappeared from movefloats" FATAL;
for (i = 0; i < nranges; i++) // copy sorted list back
scratch->append(ptemp[i]);
}
// Traverse the leaves of a tree of ranges, filtering out only SP and VBOX.
static range *filter(generator *g)
{
range *r;
while ((r = g->next()) != 0)
if (r->isvbox() || r->issp())
break;
return r;
}
// Zero out leading and trailing spaces; coalesce adjacent SP's.
static void trimspace(stream *scratch)
{
generator g;
range *r, *prevr = 0;
for (g = scratch; (r = filter(&g)) != 0 && r->issp(); prevr = r)
r->setheight(0); // zap leading SP
for ( ; (r = filter(&g)) != 0; prevr = r)
if (r->issp())
if (prevr && prevr->issp()) {
// coalesce adjacent SPs
r->setheight(max(r->rawht(), prevr->height()));
prevr->setheight(0);
} else // a VBOX intervened
r->setheight(r->rawht());
if (prevr && prevr->issp()) // zap *all* trailing space
prevr->setheight(0); // (since it all coalesced
// into the last one)
}
// Pad the non-zero SP's in scratch so the total height is wantht.
// Note that the SP values in scratch are not the raw values, and
// indeed may already have been padded.
static void justify(stream *scratch, int wantht)
{
range *r;
int nsp = 0, hsp = 0;
int adjht = scratch->height();
// Find all the spaces.
generator g;
for (g = scratch; (r = g.next()) != 0; )
if (r->issp() && r->height() > 0) {
nsp++;
hsp += r->height();
}
int excess = wantht - adjht;
if (excess < 0)
ERROR "something on page %d is oversize by %d\n",
userpn, -excess WARNING;
if (dbg & 16)
printf("# justify %d: excess %d nsp %d hsp %d adjht %d\n",
userpn, excess, nsp, hsp, adjht);
if (excess <= 0 || nsp == 0)
return;
// Redistribute the excess space.
for (g = scratch; (r = g.next()) != 0; )
if (r->issp() && r->height() > 0) {
int delta = (int) ((float)(r->height()*excess)/hsp + 0.5);
if (dbg & 16)
printf("# pad space %d by %d: hsp %d excess %d\n",
r->height(), delta, hsp, excess);
r->setheight(r->height() + delta);
}
}
// If r were added to s, would the height of the composed result be at most maxht?
int wouldfit(range *r, stream *s, int maxht)
{
if (r->rawht() + s->rawht() <= maxht)
return 1; // the conservative test succeeded
stream scratch; // local playground for costly test
for (stream cd = *s; cd.more(); cd.advance())
scratch.append(cd.current());
scratch.append(r);
movefloats(&scratch, ((double) scratch.rawht())/maxht);
trimspace(&scratch);
int retval = scratch.height() <= maxht;
scratch.freeall();
return retval;
}
// If s1 were added to s, would the height of the composed result be at most maxht?
// The computational structure is similar to that above.
int wouldfit(stream *s1, stream *s, int maxht)
{
if (s1->rawht() + s->rawht() <= maxht)
return 1;
stream scratch, cd;
for (cd = *s; cd.more(); cd.advance())
scratch.append(cd.current());
for (cd = *s1; cd.more(); cd.advance())
scratch.append(cd.current());
movefloats(&scratch, ((double) scratch.rawht())/maxht);
trimspace(&scratch);
int retval = scratch.height() <= maxht;
scratch.freeall();
return retval;
}
// All of stream *s is destined for one column or the other; which is it to be?
void multicol::choosecol(stream *s, int goalht)
{
stream *dest;
if (!leftblocked && wouldfit(s, &(column[0]), goalht))
dest = &(column[0]);
else {
dest = &(column[1]);
if (!s->current()->floatable())
// a stream item is going into the right
// column, so no more can go into the left.
leftblocked = 1;
}
for (stream cd = *s; cd.more(); cd.advance())
dest->append(cd.current());
}
double coltol = 0.5;
// Try, very hard, to put everything in the multicol into two columns
// so that the total height is at most htavail.
void multicol::compose(int defonly)
{
if (!nonempty()) {
setheight(0);
return;
}
scratch.freeall(); // fill scratch with everything destined
// for either column
stream cd;
for (cd = definite; cd.more(); cd.advance())
scratch.append(cd.current());
if (!defonly)
for (cd = *(currpage->stage); cd.more(); cd.advance())
if (cd.current()->numcol() == 2)
scratch.append(cd.current());
scratch.restoreall(); // in particular, floatables' goals
int i;
int rawht = scratch.rawht();
int halfheight = (int)(coltol*rawht);
// choose a goal height
int maxht = defonly ? halfheight : htavail;
secondtry:
for (i = 0; i < 2; i++)
column[i].freeall();
leftblocked = 0;
cd = scratch;
while (cd.more()) {
queue ministage; // for the minimally acceptable chunks
ministage.freeall(); // that are to be added to either column
while (cd.more() && !cd.current()->issentinel()) {
ministage.enqueue(cd.current());
cd.advance();
}
choosecol(&ministage, maxht);
if (cd.more() && cd.current()->issentinel())
cd.advance(); // past sentinel
}
if (height() > htavail && maxht != htavail) {
// We tried to balance the columns, but
// the result was too tall. Go back
// and try again with the less ambitious
// goal of fitting the space available.
maxht = htavail;
goto secondtry;
}
for (i = 0; i < 2; i++) {
movefloats(&(column[i]), ((double) column[i].rawht())/currpage->pagesize);
trimspace(&(column[i]));
}
if (dbg & 32) {
printf("#multicol::compose: htavail %d maxht %d dv %d\n",
htavail, maxht, height());
dump();
}
if (defonly)
stretch(height());
}
// A sequence of two-column ranges waits on the stage.
// So long as the page's skeleton hasn't changed--that is, the maximum height
// available to the two-column chunk is the same--we just use the columns that
// have been built up so far, and choose a column into which to put the stage.
// If the skeleton has changed, however, then we may need to make entirely
// new decisions about which column gets what, so we recompose the whole page.
void multicol::tryout()
{
if (htavail == prevhtavail)
choosecol(currpage->stage, htavail);
else
currpage->compose(DRAFT);
prevhtavail = htavail;
}
// Make both columns the same height.
// (Maybe this should also be governed by minfull,
// to prevent padding very underfull columns.)
void multicol::stretch(int wantht)
{
if (wantht < height())
ERROR "page %d: two-column chunk cannot shrink\n", userpn FATAL;
for (int i = 0; i < 2; i++)
justify(&(column[i]), wantht);
if (dbg & 16)
printf("#col hts: left %d right %d\n",
column[0].height(), column[1].height());
}
// Report an upper bound on how tall the current two-column object is.
// The (possibly composed) heights of the two columns give a crude upper
// bound on the total height. If the result is more than the height
// available for the two-column object, then the columns are each
// composed to give a better estimate of their heights.
int multicol::height()
{
int retval = max(column[0].height(), column[1].height());
if (retval < htavail)
return retval;
for (int i = 0; i < 2; i++) {
movefloats(&(column[i]), ((double) column[i].height())/currpage->pagesize);
trimspace(&(column[i]));
}
return max(column[0].height(), column[1].height());
}
void multicol::dump()
{
printf("####2COL dv %d\n", height());
printf("# left column:\n");
column[0].dump();
printf("# right column:\n");
column[1].dump();
}
// From the head of queue qp, peel off a piece whose raw height is at most space.
int peeloff(stream *qp, int space)
{
stream *s1 = qp->current()->children();
if (!(s1 && s1->more() && s1->current()->height() <= space))
// in other words, either qp's head is
// not nested, or its first subrange
return 0; // is also too big, so we give up
qp->split();
s1 = qp->current()->children();
stream *s2 = qp->next()->children();
while (s2->more() && s2->current()->rawht() <= space) {
s1->append(s2->current());
space -= s2->current()->rawht();
s2->advance();
}
return 1;
}
// There are four possibilities for consecutive calls to tryout().
// If we're processing a sequence of single-column ranges, tryout()
// uses the original algorithm: (1) conservative test; (2) costly test;
// (3) split a breakable item.
// If we're processing a sequence of double-column ranges, tryout()
// defers to twocol->tryout(), which gradually builds up the contents
// of the two columns until they're as tall as they can be without
// exceeding twocol->htavail.
// If we're processing a sequence of single-column ranges and we
// get a double-column range, then we use compose() to build a
// skeleton page and set twocol->htavail, the maximum height that
// should be occupied by twocol.
// If we're processing a sequence of double-column ranges and we
// get a single-column range, then we should go back and squish
// the double-column chunk as short as possible before we see if
// we can fit the single-column range.
void page::tryout()
{
if (!stage->more())
ERROR "empty stage in page::tryout()\n" FATAL;
int curnumcol = stage->current()->numcol();
if (dbg & 32) {
printf("#page::tryout(): ncol = %d, prevncol = %d; on stage:\n",
curnumcol, prevncol);
stage->dump();
printf("#END of stage contents\n");
}
switch(curnumcol) {
default:
ERROR "unexpected number of columns in tryout(): %d\n",
stage->current()->numcol() FATAL;
break;
case 1:
if (prevncol == 2)
compose(FINAL);
if (wouldfit(stage, &definite, pagesize - twocol->height()))
commit();
else if (stage->current()->breakable() || blank()
&& peeloff(stage,
pagesize - (definite.height() + twocol->height()))) {
// first add the peeled-off part that fits
adddef(stage->dequeue());
// then send the rest back for later
stage->current()->setbreaking();
welsh();
} else if (blank()) {
stage->current()->rdump();
ERROR "A %s is too big to continue.\n",
stage->current()->typename() FATAL;
} else
welsh();
break;
case 2:
if (prevncol == 1)
compose(DRAFT);
else
twocol->tryout();
if (scratch.height() <= pagesize)
commit();
else
welsh();
break;
}
prevncol = curnumcol;
}
// To compose the page, we (1) fill scratch with the stuff that's meant to
// go on the page; (2) compose scratch as best we can; (3) set the maximum
// height available to the two-column part of the page; (4) have the two-
// column part compose itself.
// In the computation of twocol->htavail, it does not matter that
// twocol->height() is merely an upper bound, because it is merely being
// subtracted out to give the exact total height of the single-column stuff.
void page::compose(int final)
{
makescratch(final);
int adjht = scratch.rawht();
if (dbg & 16)
printf("# page %d measure %d\n", userpn, adjht);
movefloats(&scratch, ((double) adjht)/pagesize);
trimspace(&scratch);
twocol->htavail = pagesize - (scratch.height() - twocol->height());
twocol->compose(final);
adjht = scratch.height();
if (dbg & 16)
printf("# page %d measure %d after trim\n", userpn, adjht);
}
// Fill the scratch area with ranges destined for the page.
// If defonly == 0, then add anything that's on stage--this is a trial run.
// If defonly != 0, use only what's definitely on the page.
void page::makescratch(int defonly)
{
scratch.freeall();
stream cd;
for (cd = definite; cd.more(); cd.advance())
scratch.append(cd.current());
if (!defonly)
for (cd = *stage; cd.more(); cd.advance())
if (cd.current()->numcol() == 1)
scratch.append(cd.current());
if (twocol->nonempty())
scratch.append(twocol);
}
// Accept the current contents of the stage.
// If the stage contains two-column ranges, add a sentinel to indicate the end
// of a chunk of stage contents.
void page::commit()
{
if (dbg & 4)
printf("#entering page::commit()\n");
int numcol = 0;
while (stage->more()) {
numcol = stage->current()->numcol();
adddef(stage->dequeue());
}
if (numcol == 2)
adddef(new sentrange);
}
// Send the current contents of the stage back to its source.
void page::welsh()
{
if (dbg & 4)
printf("#entering page::welsh()\n");
while (stage->more()) {
range *r = stage->dequeue();
r->enqueue(ANDBLOCK);
}
}
enum { USonly = 1 };
// So long as anything is eligible to go onto the page, keep trying.
// Once nothing is eligible, compose and justify the page.
void page::fill()
{
while (stage->prime())
stage->pend();
compose(FINAL);
if (dbg & 16)
scratch.dump();
if (anymore()) {
int adjht = scratch.height();
if (adjht > minfull*pagesize) {
justify(&scratch, pagesize);
adjht = scratch.height();
int stretchamt = max(pagesize - adjht, 0);
twocol->stretch(twocol->height() + stretchamt);
// in case the page's stretchability lies
// entirely in its two-column part
} else
ERROR "page %d only %.0f%% full; will not be adjusted\n",
userpn, 100*(double) adjht/pagesize WARNING;
}
}
void page::adddef(range *r)
{
if (dbg & 4)
printf("#entering page::adddef()\n");
switch (r->numcol()) {
case 1: definite.append(r);
break;
case 2: twocol->definite.append(r);
break;
default: ERROR "%d-column range unexpected\n", r->numcol() FATAL;
}
}
int multicol::print(int cv, int col)
{
if (col != 0)
ERROR "multicolumn output must start in left column\n" FATAL;
int curv = cv, maxv = cv; // print left column
for ( ; column[0].more(); column[0].advance()) {
curv = column[0].current()->print(curv, 0);
maxv = max(maxv, curv);
}
curv = cv; // print right column
for ( ; column[1].more(); column[1].advance()) {
curv = column[1].current()->print(curv, 1);
maxv = max(maxv, curv);
}
return maxv;
}
void page::print()
{
static int tops = 1, bots = 1;
if (!scratch.more()) {
ERROR "## Here's what's left on squeue:\n" WARNING;
squeue.dump();
ERROR "## Here's what's left on bfqueue:\n" WARNING;
bfqueue.dump();
ERROR "## Here's what's left on ufqueue:\n" WARNING;
ufqueue.dump();
ERROR "page %d appears to be empty\n", userpn WARNING;
fflush(stderr), fflush(stdout), exit(0);
// something is very wrong if this happens
}
printf("p%d\n", userpn); // print troff output page number
if (ptlist.more()) { // print page header
ptlist.current()->print(0, 0);
ptlist.advance();
} else if (tops) {
ERROR "ran out of page titles at %d\n", userpn WARNING;
tops = 0;
}
int curv = 0;
printf("V%d\n", curv = pagetop);// print page contents
for ( ; scratch.more(); scratch.advance()) {
curv = scratch.current()->print(curv, 0);
}
if (btlist.more()) { // print page footer
btlist.current()->print(0, 0);
btlist.advance();
} else if (bots) {
ERROR "ran out of page bottoms at %d\n", userpn WARNING;
bots = 0;
}
printf("V%d\n", physbot); // finish troff output page
}
int pagetop = 0; // top printing margin
int pagebot = 0; // bottom printing margin
int physbot = 0; // physical bottom of page
double minfull = 0.9; // minimum fullness before padding
int pn = 0; // cardinal page number
int userpn = 0; // page number derived from PT slugs
static void makepage()
{
page pg(pagebot - pagetop);
++pn;
userpn = ptlist.more() ? ptlist.current()->pn() : pn;
pg.fill();
pg.print();
}
static void conv(FILE *fp)
{
startup(fp); // read slugs, etc.
while (anymore())
makepage();
lastrange->print(0, 0); // trailer
checkout(); // check that everything was printed
}
int
main(int argc, char **argv)
{
static FILE *fp = stdin;
progname = argv[0];
while (argc > 1 && argv[1][0] == '-') {
switch (argv[1][1]) {
case 'd':
dbg = atoi(&argv[1][2]);
if (dbg == 0)
dbg = ~0;
break;
case 'm':
minfull = 0.01*atof(&argv[1][2]);
break;
case 'c':
coltol = 0.01*atof(&argv[1][2]);
break;
case 'w':
wantwarn = 1;
break;
}
argc--;
argv++;
}
if (argc <= 1)
conv(stdin);
else
while (--argc > 0) {
if (strcmp(*++argv, "-") == 0)
fp = stdin;
else if ((fp = fopen(*argv, "r")) == NULL)
ERROR "can't open %s\n", *argv FATAL;
conv(fp);
fclose(fp);
}
exit(0);
return 0; /* gcc */
}

119
src/cmd/mpm/page.h Normal file
View file

@ -0,0 +1,119 @@
extern queue squeue; // the three queues on which ranges reside
extern queue bfqueue;
extern queue ufqueue;
extern double minfull;
extern double coltol;
int anymore();
// The following is used in some calls to range::enqueue(int = 0).
#define ANDBLOCK 1
class page;
enum { DRAFT = 0, FINAL = 1 };
// The mergestream currpage->stage serves as a staging area for page makeup:
// when primed, it contains a minimal acceptable chunk of input ranges.
// The page must either take or leave everything that's on stage.
class mergestream : public queue {
page *currpage; // current page that's accepting stuff
public:
mergestream(page *cp) { currpage = cp; unblock(); }
void unblock();
int prime(); // stage next legal chunk
void pend(); // process pending chunk on stage
};
// The multicol currpage->twocol is the two-column piece of the page to which
// two-column ranges are currently being added.
// The page sets htavail to indicate how tall it is allowed to become.
// All ranges on definite must be placed when the multicol is printed.
// Each of these definite ranges also resides on one of column[0] and [1],
// which represent the current best guess about how to divide definite
// between the two columns.
class multicol : public range {
page *currpage; // current page that's accepting stuff
stream definite; // definitely on page
stream scratch; // for trial compositions
stream column[2]; // left (0) and right (1) columns
int leftblocked; // OK to add to left column?
int htavail; // max possible ht, set by page::tryout()
int prevhtavail; // max 2-colht last time we added something
friend class page;
public:
multicol(page *cp) { currpage = cp;
leftblocked = 0;
htavail = 0;
prevhtavail = -1;
setgoal(NOGOAL); }
// the two-column piece behaves as part
// of the stream of single-column input.
int numcol() { return 1; }
int nonempty() { return definite.more(); }
void choosecol(range *, int);// add first arg to one or other column
void choosecol(stream*, int);// add *all ranges on first arg*
// to one or other column
// NOT the same as a mapcar of the
// preceding function over the ranges
// on the first argument!
void compose(int); // divide into two columns
void tryout(); // decide which column gets stage contents
void stretch(int); // justify both columns to given height
int print(int curv, int col);
int height(); // an upper bound on actual height
int rawht() { return max(column[0].rawht(), column[1].rawht()); }
void reheight(int *cv, int *mv)
{ *cv += height(); *mv = max(*mv, *cv); }
void dump();
int isvbox() { return nonempty(); } // during trimspace()
};
// These sentinel ranges are used to separate the ranges on twocol::definite
// into the chunks in which they came from the staging area.
// Thus, they preserve the results of the computation that was done to prime
// page::stage.
class sentrange : public range {
public:
sentrange() { }
int numcol() { return 2; }
int issentinel() { return 1; }
};
class page {
int pagesize; // allowed maximum height
int prevncol; // was last item tried 1- or 2-column?
int vsince; // how many vboxes from "current" BS
// (to avoid putting a single line on
// a page with a very large floatable)
stream definite; // definitely on page, in input order
stream scratch; // playground in which to alter page
void cmdproc(); // process any of several commands
void parmproc(); // process any of several parameters
void tryout(); // see whether current stage contents fit
void compose(int); // float and trim current page contents
void makescratch(int); // fill scratch area
void commit(); // accept the items on stage
void welsh(); // reject the items on stage
void adddef(range *r); // add to one of the definite queues
// (definite or twocol->definite)
public:
mergestream *stage;
friend class mergestream;
multicol *twocol;
friend class multicol;
page(int p) { pagesize = p;
prevncol = 1;
vsince = 0;
stage = new mergestream(this);
twocol = new multicol(this); }
~page() { definite.freeall(); scratch.freeall(); }
void fill();
int blank() { return !definite.more() && !twocol->definite.more();}
void print();
};
// functions in page.c
int main(int, char **);

235
src/cmd/mpm/queue.cc Normal file
View file

@ -0,0 +1,235 @@
#include "misc.h"
#include "slug.h"
#include "range.h"
#include "page.h"
queue squeue;
queue bfqueue;
queue ufqueue;
// We use the stream function current() to access a queue's head.
// Thus, queue member curr should always point to its first range.
void queue::check(char *whence)
{
if (dbg & 8) {
char *p;
if (this == &squeue)
p = "squeue";
else if (this == &bfqueue)
p = "bfqueue";
else if (this == &ufqueue)
p = "ufqueue";
else
p = "weird queue";
printf("#checking %s\n", p);
}
if (first != curr)
ERROR "check(%s): first != curr, line %d\n", whence, curr->rp->lineno() FATAL;
}
// When ranges are told to enqueue themselves, they are being rejected from the
// stage back onto their original queues.
// They reset any parameters that may have been altered by staging or trial
// composition.
void range::enqueue(int block)
{
squeue.enqueue(this);
if (block)
squeue.block();
}
void ufrange::enqueue(int block)
{
restore(); // both goal positions
ufqueue.enqueue(this);
if (block)
ufqueue.block();
}
void bfrange::enqueue(int block)
{
restore(); // both goal positions
bfqueue.enqueue(this);
if (block)
bfqueue.block();
}
int anymore()
{
return !(squeue.empty() && ufqueue.empty() && bfqueue.empty());
}
void mergestream::unblock()
{
squeue.unblock();
bfqueue.unblock();
ufqueue.unblock();
}
// Fill the staging area with a minimal chunk of input ranges.
int mergestream::prime()
{
if (dbg & 4)
printf("#entering mergestream::prime()\n");
if (!empty())
return 1;
int brkok = 1; // is it OK to break after the last
// VBOX that was added to the stage?
int needheight = -1; // minimum acceptable height of the
// chunk being constructed on stage
// If the range at the head of any queue is breaking,
// deal with it first.
if (squeue.more() && squeue.current()->breaking())
enqueue(squeue.dequeue());
else if (bfqueue.more() && (bfqueue.current()->breaking() ||
(bfqueue.serialno() < squeue.serialno())))
enqueue(bfqueue.dequeue());
else if (ufqueue.more() && (ufqueue.current()->breaking() ||
(ufqueue.serialno() < squeue.serialno())))
enqueue(ufqueue.dequeue());
else while (squeue.more()) {
// Fill the stage with enough ranges to be a valid chunk.
range *r = squeue.dequeue();
if (r->isvbox()) { // VBOX
if (dbg & 16)
printf("#VBOX: !empty: %d; brkok: %d; vsince: %d\n",
!empty(), brkok, currpage->vsince);
if (!empty() // there's something there
&& brkok
// it's OK to break here
&& currpage->vsince >= 2
// enough stream has gone onto this page
&& rawht() >= needheight
// current need has been satisfied
) {
// the stage already contains enough
// ranges, so this one can wait
r->enqueue();
break;
} else {
if (r->rawht() > 0) {
++currpage->vsince;
brkok = r->brkafter();
}
enqueue(r);
}
} else if (r->isnested() || r->issp()) { // US, SP
if (!empty() && rawht() >= needheight) {
// enough already, wait
r->enqueue();
break;
}
currpage->vsince = 0;
enqueue(r);
if (height() >= needheight)
break;
} else if (r->isneed()) { // NE
if (!empty() && rawht() >= needheight) {
// not currently working on an unsatisfied NEed
r->enqueue();
break;
}
// deal with overlapping NEeds
needheight = rawht() + max(needheight - rawht(), r->needht());
enqueue(r);
} else if (r->forceflush() == NO) {
enqueue(r);
} else if (r->forceflush() == YES) {
currpage->vsince = 0;
if (!empty()) {
// ready or not, r must wait
r->enqueue();
break;
}
enqueue(r);
break;
} else
ERROR "unexpected %s[%s] in prime(), line %d\n",
r->typename(), r->headstr(), r->lineno() FATAL;
}
return more(); // 0 if nothing was staged
}
void page::cmdproc()
{
if (stage->next())
ERROR "more than a single command on bsqueue\n" FATAL;
switch (stage->current()->cmdtype()) {
case FC: // freeze the current 2-column range and start a new one
adddef(stage->dequeue());
twocol->compose(FINAL);
adddef(twocol);
twocol = new multicol(this);
break;
case BP: // force a page break
adddef(stage->dequeue());
squeue.block();
break;
case FL: // flush out all floatables that precede this range:
// no more stream input allowed until they're past
if (stage->serialno() > ufqueue.serialno() ||
stage->serialno() > bfqueue.serialno()) {
range *r = stage->dequeue();
r->enqueue(ANDBLOCK);
} else
adddef(stage->dequeue());
break;
default:
stage->current()->dump();
ERROR "unknown command\n" FATAL;
}
}
void page::parmproc()
{
if (stage->next())
ERROR "more than a single parameter on bsqueue\n" FATAL;
switch (stage->current()->parmtype()) {
case NP: // page top margin
if (blank())
pagetop = stage->current()->parm();
pagesize = pagebot - pagetop;
break;
case FO:
if (blank())
pagebot = stage->current()->parm();
pagesize = pagebot - pagetop;
break;
case PL:
if (blank())
physbot = stage->current()->parm();
break;
case MF:
minfull = 0.01*stage->current()->parm();
break;
case CT:
coltol = 0.01*stage->current()->parm();
break;
case WARN:
wantwarn = stage->current()->parm();
break;
case DBG:
dbg = stage->current()->parm();
break;
default:
stage->current()->dump();
ERROR "unknown parameter\n" FATAL;
}
adddef(stage->dequeue());
}
// Process the contents of the staging area; a relic that used to do more.
void mergestream::pend()
{
if (dbg & 4)
printf("#entering mergestream::pend()\n");
if (!more())
return;
if (current()->iscmd())
currpage->cmdproc();
else if (current()->isparm())
currpage->parmproc();
else
currpage->tryout();
}

613
src/cmd/mpm/range.cc Normal file
View file

@ -0,0 +1,613 @@
#include <math.h>
#include "misc.h"
#include "slug.h"
#include "range.h"
void sprange::reheight(int *cv, int *mv)
{
if (*cv != *mv)
ERROR "slug %d: an imbedded SP, line %d\n",
first->serialno(), first->lineno() WARNING;
*cv += dv;
*mv = max(*mv, *cv);
}
void sprange::rerawht(int *cv, int *mv)
{
*cv += rawht();
*mv = max(*mv, *cv);
}
void nestrange::restore()
{
subrange->restoreall();
}
void stream::freeall() // not a destructor; called explicitly
{
strblk *p, *q;
for (p = first; p; p = q) {
q = p->next;
delete p;
}
first = last = curr = 0;
}
void stream::dump()
{
for (stream s = *this; s.more(); s.advance())
s.current()->dump();
}
void stream::rdump()
{
for (stream s = *this; s.more(); s.advance())
s.current()->rdump();
}
int stream::restoreall()
{
for (stream s = *this; s.more(); s.advance())
s.current()->restore();
return measure(this);
}
range *stream::append(range *r)
{
if (last == 0)
curr = first = last = new strblk;
else {
last->next = new strblk;
last = last->next;
if (curr == 0)
curr = last;
}
last->next = 0;
return last->rp = r;
}
void stream::split() // duplicate current() range
{
strblk *s2 = new strblk;
range *r2 = curr->rp->clone();
s2->rp = r2;
s2->next = curr->next;
if (last == curr)
last = s2;
curr->next = s2;
curr->rp->killkids(); // children only in the 2nd one
// r2->crosslink(r1);
}
int stream::height()
{
int h;
stream s = *this;
for (h = 0; s.more(); s.advance())
h += s.current()->height();
return h;
}
int stream::rawht()
{
int h;
stream s = *this;
for (h = 0; s.more(); s.advance())
h += s.current()->rawht();
return h;
}
int measure(stream *sp) // record high-water mark of stream
{ // sets nested stream heights
stream s = *sp;
int curv, maxv;
for (maxv = curv = 0; s.more(); s.advance())
s.current()->reheight(&curv, &maxv);
return maxv;
}
int rawmeasure(stream *sp)
{
stream s = *sp;
int curv, maxv;
for (maxv = curv = 0; s.more(); s.advance())
s.current()->rerawht(&curv, &maxv);
return maxv;
}
void nestrange::rdump()
{
dump();
if (subrange)
subrange->rdump();
}
void nestrange::killkids()
{
subrange = new stream;
}
int nestrange::print(int curv, int col)
{
int ocurv = curv;
first->slugout(col);
for (stream s = *subrange; s.more(); s.advance())
curv = s.current()->print(curv, col);
return ocurv + height();
}
#define macroclone(rangetype) range *rangetype::clone() {\
rangetype *t = new rangetype;\
*t = *this;\
return t; }
macroclone(usrange);
macroclone(ufrange);
macroclone(bfrange);
#undef macroclone
#define macropickgoal(rangetype) void rangetype::pickgoal(int acv, double scale) {\
if (scale > 1) {\
goalV = (int)(scale*goalV);\
goal2 = (int)(scale*goal2);\
}\
if (abs(acv - goalV) > abs(acv-goal2))\
goalV = goal2; }
macropickgoal(ufrange)
macropickgoal(bfrange)
#undef macropickgoal
range *generator::next()
{
range *r;
if (child) {
if ((r = child->next()) != 0)
return r;
delete child;
child = 0;
}
if (!s.more())
return 0;
r = s.current();
if (r->isnested())
child = new generator(r->children());
s.advance();
return r;
}
range *queue::enqueue(range *r)
{
if (dbg & 8)
printf("#entering queue::enqueue()\n");
check("queue::enqueue");
if (!last || last->rp->serialno() < r->serialno()) // common case
return append(r);
if (dbg & 8)
printf("#queue::enqueue() pushing back\n");
newguy = new strblk;
newguy->rp = r;
if (r->serialno() < first->rp->serialno()) {
newguy->next = first;
curr = first = newguy;
return newguy->rp;
}
if (dbg & 8)
printf("#queue::enqueue() searching down queue\n");
for (curr = first;
next() && next()->serialno() < r->serialno();
curr = curr->next)
;
newguy->next = curr->next;
curr->next = newguy;
curr = first; // restore important queue condition
return newguy->rp;
}
range *queue::dequeue()
{
if (dbg & 8)
printf("#entering queue::dequeue()\n");
check("queue::dequeue");
curr = first->next;
range *retval = first->rp;
delete first;
first = curr;
if (!curr)
last = 0;
return retval;
}
// ================================================================================
// functions that munge the troff output stored in slugs[]
// ================================================================================
static void doprefix(FILE *fp) // copy 1st "x" commands to output
{
int c;
while ((c = getc(fp)) != EOF) {
if (c != 'x') {
ungetc(c, fp);
break;
}
putchar(c);
do {
putchar(c = getc(fp));
} while (c != '\n');
linenum++;
}
// printf("x font 1 R\n"); // horrible kludge: ensure a font for first f1 command
}
#define DELTASLUGS 15000
static slug *slugs = 0;
static int nslugs = 0; // slugs has nslugs slots
static slug *slugp = 0; // next free slug in slugs
static void readslugs(FILE *fp)
{
if ((slugs = (slug *) malloc((nslugs = DELTASLUGS)*sizeof(slug))) == NULL)
ERROR "no room for %d-slug array\n", nslugs FATAL;
slugp = slugs;
for (slugp = slugs; ; slugp++) {
if (slugp >= slugs+nslugs-2) {
int where = slugp - slugs;
if ((slugs = (slug *) realloc((char *) slugs, (nslugs += DELTASLUGS)*sizeof(slug))) == NULL)
ERROR "no room for %d slugs\n", nslugs FATAL;
ERROR "now slug array can hold %d slugs\n", nslugs WARNING;
slugp = slugs + where;
}
*slugp = getslug(fp);
if (slugp->type == EOF)
break;
}
*++slugp = eofslug();
printf("# %d slugs\n", slugp-slugs);
}
static slug *findend(slug *sp)
{
slug *p;
for (p = sp; p->type == sp->type; p++) // skip runs
; // espec UF UF UF
for ( ; p < slugp; p++)
switch (p->type) {
case US:
case UF:
case BF:
case PT:
case BT:
p = findend(p);
break;
case END:
return p;
}
ERROR "walked past EOF in findend looking for %d (%s), line %d\n",
sp->type, sp->typename(), sp->lineno() FATAL;
return sp;
}
static int markp(int i, int n, int parm)
{ // should VBOX i of n be marked to brevent breaking after it?
if (i >= n-1)
return 0;
return i <= parm-2 || i >= n-parm;
}
static void markbreak(slug *p)
{
// Mark impermissible breakpoints in BS's.
// The parm field of a VBOX is >0 if we shouldn't break after it.
int parm; // how many lines must stay on page
int goahead = 1; // true until we see the next BS
int nowmark = 0; // true when we should be marking
int n = 0;
while (p->type == BS)
parm = p++->parm; // latest BS parm applies
slug *op = p;
while (goahead) {
switch (p->type) {
case VBOX: // count VBOXes so second pass knows
if (p->dv > 0) // knows how far to end of BS
n++;
break;
case US: // mark around EQ/EN, etc.
nowmark = 1;
p = findend(p);
break;
case UF: // but not around floats, PTs, and BTs
case BF:
case PT:
case BT:
p = findend(p);
break;
case SP: // naked SP: probable macro botch
nowmark = 1; // mark around it anyhow
break;
case BS: // beginning of next paragraph
case END: // probable macro botch
case EOF:
goahead = 0; // stop work after marking
nowmark = 1;
default:
break;
}
p++;
if (nowmark) {
int i = 0; // VBOX counter for second pass
while (op < p) {
switch (op->type) {
case VBOX:
if (op->dv > 0)
op->parm = markp(i, n, parm);
i++;
break;
case US: // caused second pass to begin
case SP:
case BS:
case END:
case EOF:
op = p;
break;
case UF: // skip on this pass too
case BF:
case PT:
case BT:
op = findend(op);
break;
default:
break;
}
op++;
}
if (i != n)
ERROR "markbreak failed : i %d n %d\n",
i, n WARNING;
op = p;
nowmark = n = 0;
}
}
}
static void fixslugs() // adjust bases and dv's, set parameters, etc.
{
slug *p, *prevV = 0;
for (p = slugs; p < slugp; p++) {
if (p->type == VBOX) {
prevV = p;
continue;
}
if (p->base != 0) {
ERROR "%s slug (type %d) has base = %d, line %d\n",
p->typename(), p->type, p->base, p->lineno() WARNING;
}
if ((p->type == SP) || (p->type == NE))
continue;
if (p->type == PAGE)
prevV = 0;
if (p->dv != 0)
if (prevV) {
prevV->base = max(prevV->base, p->dv);
p->dv = 0;
} else {
ERROR "%s slug (type %d) has dv = %d, line %d\n",
p->typename(), p->type, p->dv, p->lineno() WARNING;
}
}
prevV = 0;
int firstNP = 0, firstFO = 0, firstPL = 0;
for (p = slugs; p < slugp; p++) {
switch (p->type) {
// adjust the dv in a sequence of VBOXes
// by subtracting from each the base of the preceding VBOX
case VBOX:
if (prevV)
p->dv -= prevV->base;
prevV = p;
break;
case SP:
p->dv = max(p->dv, 0);
break;
case PAGE:
p->neutralize();
prevV = 0;
break;
// record only first "declarations" of Page Top and bottom (FO);
case PARM:
switch (p->parm) {
case NP:
if (firstNP++ == 0)
pagetop = p->parm2;
p->neutralize();
break;
case FO:
if (firstFO++ == 0)
pagebot = p->parm2;
p->neutralize();
break;
case PL:
if (firstPL++ == 0)
physbot = p->parm2;
p->neutralize();
break;
}
break;
// things that begin groups; not US, which should nest properly
case UF:
case BF:
while ((p+1)->type == p->type) {
// join adjacent identical
(p+1)->parm2 = p->parm; // parm is latest
// parm2 is previous
p->neutralize(); // so it's not seen later
p++;
}
break;
// none of the above
case US:
case PT:
case BT:
case BS:
case END:
case TM:
case COORD:
case NE:
case MC:
case CMD:
case EOF:
break;
default:
ERROR "Unknown slug type %d in fixslugs, line %d\n",
p->type, p->lineno() WARNING;
break;
}
}
int pagesize = pagebot - pagetop;
if (pagesize == 0)
ERROR "Page dimensions not declared\n" FATAL;
if (physbot == 0)
physbot = pagebot + pagetop;
printf("# page top %d bot %d size %d physbot %d\n",
pagetop, pagebot, pagesize, physbot);
for (p = slugs; p < slugp; p++) {
switch (p->type) {
// normalize float parameters
case BF:
case UF:
// primary goal
p->parm = max(min(p->parm-pagetop, pagesize), 0);
// secondary goal
p->parm2 = max(min(p->parm2-pagetop, pagesize), 0);
break;
// normalize need parameters
case NE:
p->dv = max( min(p->dv, pagesize), 0);
break;
// mark permissible breaks
case BS:
markbreak(p);
break;
}
if (dbg & 1)
p->dump();
}
}
void checkout()
{
for (slug *p = slugs; p < slugp; p++)
switch (p->type) {
case PT:
case BT:
p = findend(p);
break;
case SP:
case VBOX:
if (p->seen != 1)
ERROR "%s slug %d seen %d times\n",
p->typename(), p->serialno(),
p->seen WARNING;
break;
}
}
eofrange *lastrange;
stream ptlist, btlist;
static slug *makeranges(slug *p, stream *s, int level)
{
stream *t;
for ( ; p < slugp; p++)
switch (p->type) {
case VBOX:
s->append(new vboxrange(p));
break;
case SP:
s->append(new sprange(p));
break;
case BS:
s->append(new bsrange(p));
break;
case US:
s->append(new usrange(p, t = new stream));
p = makeranges(p+1, t, level+1);
break;
case BF:
s->append(new bfrange(p, t = new stream));
p = makeranges(p+1, t, level+1);
break;
case UF:
s->append(new ufrange(p, t = new stream));
p = makeranges(p+1, t, level+1);
break;
case PT:
ptlist.append(new ptrange(p, t = new stream));
p = makeranges(p+1, t, level+1);
break;
case BT:
btlist.append(new btrange(p, t = new stream));
p = makeranges(p+1, t, level+1);
break;
case END:
s->append(new endrange(p));
return p;
case TM:
s->append(new tmrange(p));
break;
case COORD:
s->append(new coordrange(p));
break;
case NE:
if (level) {
ERROR "Nested NE commands are ignored, line %d\n",
p->lineno() WARNING;
p->dv = 0;
}
s->append(new nerange(p));
break;
case MC:
s->append(new mcrange(p));
break;
case CMD:
if (level)
ERROR "Nested command ignored, line %d\n",
p->lineno() WARNING;
s->append(new cmdrange(p));
break;
case PARM:
if (level)
ERROR "Nested parameter ignored, line %d\n",
p->lineno() WARNING;
s->append(new parmrange(p));
break;
case EOF:
lastrange = new eofrange(p);
return 0;
}
return p;
}
static queue text; // unexamined input ranges; the real data
void startup(FILE *fp)
{
doprefix(fp); // peel off 'x' commands
readslugs(fp); // read everything into slugs[]
fixslugs(); // measure parameters and clean up
makeranges(slugs, &text, 0); // add range superstructure
measure(&text); // heights of nested things
rawmeasure(&text);
while (text.more()) {
range *r = text.dequeue();
if (dbg & 2)
r->dump();
r->enqueue();
}
}

334
src/cmd/mpm/range.h Normal file
View file

@ -0,0 +1,334 @@
const int NOGOAL = -1;
class stream;
enum primeflush { NO, YES, EXPECTED, UNEXPECTED }; // mergestream::prime()
// Ranges do two things. They interpose a layer between slugs and the rest
// of the program; this is important because of the grossness of the slug
// data structure (made necessary by its origins in troff output). Ranges also
// group together other ranges into meaningful chunks like unbreakable stream
// objects, floatable objects, and page headers and footers.
// Member function height() returns a range's height as of the latest composition.
// Member function rawht() returns the range's original height in the input.
class range {
protected:
slug *first; // earliest slug in range
int accumV; // accumulated V to this point
public:
range() { first = 0; accumV = 0; }
range(slug *p) { first = p; accumV = 0; }
char *headstr() {
return first ? first->headstr() : (char*)""; }
char *typename() { return first->typename(); }
int serialno() { return first->serialno(); }
int lineno() { return first->lineno(); }
virtual void dump() { first->dump(); }
virtual void rdump() { dump(); }
virtual int print(int cv, int col) {
first->slugout(col); return cv; }
virtual int floatable() { return 0; }
virtual int brkafter() { return 1; }
virtual int isnested() { return 0; }
virtual int issp() { return 0; }
virtual int isvbox() { return 0; }
virtual int isneed() { return 0; }
virtual int iscmd() { return 0; }
virtual int cmdtype() { return -1; }
virtual int isparm() { return 0; }
virtual int parmtype() { return -1; }
virtual int parm() { return -1; }
virtual int breakable() { return 0; }
virtual int forceflush() { return UNEXPECTED; }
virtual int pn() { return 0; }
virtual stream *children() { return 0; } // see page::peeloff()
virtual void killkids() { }
virtual void enqueue(int = 0);
virtual int height() { return 0; }
virtual int rawht() { return 0; }
virtual int needht() { return 0; }
virtual void reheight(int *, int *) { }
virtual void rerawht(int *, int *) { }
virtual void setheight(int) { }
virtual void restore() { } // goals of floatables
virtual int goal() { return NOGOAL; }
int accum() { return accumV; }
void setaccum(int n) { accumV = n; }
virtual void setgoal(int) { }
virtual void pickgoal(int, double) { }
virtual int numcol() { return first->numcol(); }
virtual int issentinel() { return 0; }
virtual range *clone() { return 0; }
virtual int breaking() { return 0; }
virtual void setbreaking() { }
};
class vboxrange : public range {
int dv; // inherited from slug
int base; // inherited from slug
int brk; // 0 => ok to break after, 1 => no break
public:
vboxrange(slug *p) : range(p) { dv = p->dv; base = p->base; brk = p->parm; }
void dump() {
printf("#### VBOX brk? %d dv %d ht %d\n", brk, dv, dv+base); }
int print(int cv, int col) {
printf("V%d\n", cv += dv); first->slugout(col); return cv+base; }
int brkafter() { return !brk; }
int isvbox() { return 1; }
int forceflush() { return NO; }
int height() { return dv + base; }
int rawht() { return first->dv + first->base; }
void reheight(int *cv, int *mv) {
*cv += dv+base; *mv = max(*mv, *cv); }
void rerawht(int *cv, int *mv) {
*cv += rawht(); *mv = max(*mv, *cv); }
};
class sprange : public range {
int dv;
public:
sprange(slug *p) : range(p) { dv = first->dv; }
void dump() {
printf("#### SP dv %d (originally %d)\n", dv, first->dv); }
int print(int cv, int col) {
first->slugout(col); return cv + dv; }
int issp() { return 1; }
int forceflush() { return YES; }
int height() { return dv; }
int rawht() { return first->dv; }
void reheight(int *, int *);
void rerawht(int *, int *);
void setheight(int n) { dv = n; }
};
class tmrange : public range {
public:
tmrange(slug *p) : range(p) { }
int forceflush() { return NO; }
int print(int cv, int col) { first->slugout(col); return cv; }
};
class coordrange : public range {
public:
coordrange(slug *p) : range(p) { }
int forceflush() { return NO; }
int print(int cv, int col)
{ first->slugout(col); printf(" Y %d\n", cv); return cv; }
};
class nerange : public range {
public:
nerange(slug *p) : range(p) { }
int isneed() { return 1; }
int forceflush() { return YES; }
int needht() { return first->dv; }
};
class mcrange : public range {
public:
mcrange(slug *p) : range(p) { }
int forceflush() { return YES; }
};
class cmdrange : public range {
public:
cmdrange(slug *p) : range(p) { }
int iscmd() { return 1; }
int forceflush() { return YES; }
int cmdtype() { return first->parm; }
};
class parmrange : public range {
public:
parmrange(slug *p) : range(p) { }
int isparm() { return 1; }
int forceflush() { return YES; }
int parmtype() { return first->parm; }
int parm() { return first->parm2; }
};
class bsrange : public range {
public:
bsrange(slug *p) : range(p) { }
int forceflush() { return NO; }
int print(int cv, int col) { first->slugout(col); return cv; }
};
class endrange : public range {
public:
endrange(slug *p) : range(p) { }
int forceflush() { return UNEXPECTED; }
};
class eofrange : public range {
public:
eofrange(slug *p) : range(p) { }
int forceflush() { return UNEXPECTED; }
};
extern eofrange *lastrange; // the EOF block (trailer, etc.) goes here
int measure(stream *);
int rawmeasure(stream *);
// A nestrange packages together a sequence of ranges, its subrange.
// Other parts of the program reach in and alter the dimensions of
// some of these ranges, so when the height of a range is requested
// it is computed completely afresh.
// (Note: the alternative, of keeping around many copies of ranges
// with different dimensions, was abandoned because of the difficulty
// of ensuring that exactly one copy of each original range would be
// output.)
class nestrange : public range {
protected:
stream *subrange;
int isbreaking;
int rawdv;
public:
nestrange() : range() { subrange = 0; isbreaking = 0; rawdv = -1; }
nestrange(slug *p, stream *s) : range(p)
{ subrange = s; isbreaking = 0; rawdv = -1; }
void rdump();
virtual void restore();
stream *children() { return subrange; }
void killkids();
int height() { return measure(subrange); }
int rawht() { if (rawdv < 0 || isbreaking) rawdv = rawmeasure(subrange);
return rawdv; }
void reheight(int *cv, int *mv) {
*mv += measure(subrange); *cv = max(*mv, *cv); }
void rerawht(int *cv, int *mv) {
*mv += rawht(); *cv = max(*mv, *cv); }
int isnested() { return 1; }
int forceflush() { return EXPECTED; }
int print(int cv, int col);
int breaking() { return isbreaking; }
void setbreaking() { isbreaking++; }
};
class usrange : public nestrange {
public:
usrange() { }
usrange(slug *p, stream *s) : nestrange(p, s) {}
void dump() { printf("#### US dv %d\n", height()); }
range *clone();
};
class ufrange : public nestrange {
int goalV, goal2;
public:
ufrange() { }
ufrange(slug *p, stream *s) : nestrange(p, s) {
goalV = p->parm; goal2 = p->parm2; }
void dump() { printf("#### UF dv %d goal %d goal2 %d\n",
height(), goalV, goal2); }
int floatable() { return 1; }
void enqueue(int = 0);
range *clone();
int goal() { return goalV; }
void setgoal(int n) { goalV = goal2 = n; }
void pickgoal(int acv, double scale);
void restore() { goalV = first->parm; goal2 = first->ht; }
};
class bfrange : public nestrange {
int goalV, goal2;
public:
bfrange() { }
bfrange(slug *p, stream *s) : nestrange(p, s) {
goalV = p->parm; goal2 = p->parm2; }
void dump() { printf("#### BF dv %d goal %d goal2 %d\n",
height(), goalV, goal2); }
int floatable() { return 1; }
void enqueue(int = 0);
range *clone();
int goal() { return goalV; }
void setgoal(int n) { goalV = goal2 = n; }
void pickgoal(int acv, double scale);
void restore() { goalV = first->parm; goal2 = first->parm2; }
int breakable() { return 1; } // can be broken
};
class ptrange : public nestrange {
int pgno;
public:
int pn() { return pgno; }
ptrange(slug *p, stream *s) : nestrange(p, s) { pgno = p->parm; }
void dump() { printf("#### PT pgno %d dv %d\n", pgno, height()); }
};
class btrange : public nestrange {
int pgno;
public:
btrange(slug *p, stream *s) : nestrange(p, s) { pgno = p->parm; }
void dump() { printf("#### BT pgno %d dv %d\n", pgno, height()); }
};
// A stream is a sequence of ranges; we use this data structure a lot
// to traverse various sequences that crop up in page-making.
class stream {
protected:
public:
struct strblk { // ranges are linked by these blocks
strblk *next;
range *rp;
};
strblk *first;
strblk *last;
strblk *curr;
public:
stream() { curr = last = first = 0; }
stream(range *r) { curr = last = first = new strblk;
last->rp = r; last->next = 0; }
void freeall(); // note: not a destructor
void dump(); // top level
void rdump(); // recursive
int restoreall();
range *current() { return curr->rp; }
range *next() { return curr && curr->next ? curr->next->rp : 0; }
void advance() { curr = curr->next; }
range *append(range *r);
void split();
int more() { return curr && curr->rp; }
int height();
int rawht();
};
// A generator iterates through all the ranges of a stream
// (not just the root ranges of nestranges).
class generator {
stream s;
generator *child;
public:
generator() { child = 0; }
generator(stream *sp) { s = *sp; child = 0; }
range *next();
};
extern stream ptlist, btlist; // page titles
#define INFINITY 1000001
// A queue is a distinguished kind of stream.
// It keeps its contents in order by the serial numbers of the ranges.
// A queue can be blocked from dequeuing something to indicate
// that it's not worth considering the queue again on a given page.
class queue : public stream {
strblk *newguy;
protected:
int blocked;
void check(char *);
public:
queue() : blocked(0) { }
range *enqueue(range *r);
range *dequeue();
void block() { blocked = 1; }
void unblock() { blocked = 0; }
int more() { return !blocked && stream::more(); }
int empty() { return !stream::more(); }
int serialno() { return empty() ? INFINITY : current()->serialno(); }
};
// functions in range.c
void checkout();
void startup(FILE *);

603
src/cmd/mpm/slug.cc Normal file
View file

@ -0,0 +1,603 @@
#include "misc.h"
#include "slug.h"
//#include <libc.h>
#include <math.h>
static char *bufptr(int);
void slug::coalesce()
{
(this+1)->dp = dp; // pretty grimy, but meant to ensure
// that all output goes out.
// maybe it has to skip over PT's;
// some stuff is getting pushed inside PT..END
}
void slug::neutralize()
{
switch (type) {
case PAGE:
case UF:
case BF:
case PARM:
type = NEUTRAL;
coalesce();
break;
default:
ERROR "neutralized %d (%s) with %s\n",
type, typename(), headstr() WARNING;
break;
}
}
void slug::dump() // print contents of a slug
{
printf("# %d %-4.4s parm %d dv %d base %d s%d f%d H%d\n#\t\t%s\n",
serialno(), typename(), parm, dv, base,
size, font, hpos, headstr());
}
char *slug::headstr()
{
const int HEADLEN = 65;
static char buf[2*HEADLEN];
int j = 0;
char *s = bufptr(dp);
int n = (this+1)->dp - dp;
if (n >= HEADLEN)
n = HEADLEN;
for (int i = 0; i < n; i++)
switch (s[i]) {
case '\n':
case '\t':
case '\0':
case ' ':
break;
default:
buf[j++] = s[i];
break;
}
buf[j] = 0;
return buf;
}
static char *strindex(char s[], char t[]) // index of earliest t[] in s[]
{
for (int i = 0; s[i] != '\0'; i++) {
int j, k;
for (j = i, k = 0; t[k]!='\0' && s[j] == t[k]; j++, k++)
;
if (k > 0 && t[k] == '\0')
return s+i;
}
return 0;
}
void slug::slugout(int col)
{
static int numout = 0;
if (seen++)
ERROR "%s slug #%d seen %d times [%s]\n",
typename(), serialno(), seen, headstr() WARNING;
if (type == TM) {
char *p;
if ((p = strindex(bufptr(dp), "x X TM ")) != 0)
p += strlen("x X TM "); // skip junk
else
ERROR "strange TM [%s]\n", headstr() FATAL;
fprintf(stderr, "%d\t", userpn); // page # as prefix
for ( ; p < bufptr((this+1)->dp); p++)
putc(*p, stderr);
} else if (type == COORD) {
for (char *p = bufptr(dp); p < bufptr((this+1)->dp) && *p != '\n'; p++)
putc(*p, stdout);
printf(" # P %d X %d", userpn, hpos + col*offset);
return;
} else if (type == VBOX) {
if (numout++ > 0) // BUG??? might miss something
printf("s%d\nf%d\n", size, font);
printf("H%d\n", hpos + col*offset);
}
fwrite(bufptr(dp), sizeof(char), (this+1)->dp - dp, stdout);
}
char *slug::typename()
{
static char buf[50];
char *p = buf; // return value
switch(type) {
case EOF: p = "EOF"; break;
case VBOX: p = "VBOX"; break;
case SP: p = "SP"; break;
case BS: p = "BS"; break;
case US: p = "US"; break;
case BF: p = "BF"; break;
case UF: p = "UF"; break;
case PT: p = "PT"; break;
case BT: p = "BT"; break;
case END: p = "END"; break;
case NEUTRAL: p = "NEUT"; break;
case PAGE: p = "PAGE"; break;
case TM: p = "TM"; break;
case COORD: p = "COORD"; break;
case NE: p = "NE"; break;
case CMD: p = "CMD"; break;
case PARM: p = "PARM"; break;
default: sprintf(buf, "weird type %d", type);
}
return p;
}
// ================================================================================
// troff output-specific functions
// ================================================================================
const int DELTABUF = 500000; // grow the input buffer in chunks
static char *inbuf = 0; // raw text input collects here
static int ninbuf = 0; // byte count for inbuf
static char *inbp = 0; // next free slot in inbuf
int linenum = 0; // input line number
static inline void addc(int c) { *inbp++ = c; }
static void adds(char *s)
{
for (char *p = s; *p; p++)
addc(*p);
}
static char *getutf(FILE *fp) // get 1 utf-encoded char (might be multiple bytes)
{
static char buf[100];
char *p = buf;
for (*p = 0; (*p++ = getc(fp)) != EOF; ) {
*p = 0;
if (mblen(buf, sizeof buf) > 0) // found a valid character
break;
}
return buf;
}
static char *bufptr(int n) { return inbuf + n; } // scope of inbuf is too local
static inline int wherebuf() { return inbp - inbuf; }
static char *getstr(char *p, char *temp)
{ // copy next non-blank string from p to temp, update p
while (*p == ' ' || *p == '\t' || *p == '\n')
p++;
if (*p == '\0') {
temp[0] = 0;
return(NULL);
}
while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
*temp++ = *p++;
*temp = '\0';
return(p);
}
/***************************************************************************
bounding box of a circular arc Eric Grosse 24 May 84
Conceptually, this routine generates a list consisting of the start,
end, and whichever north, east, south, and west points lie on the arc.
The bounding box is then the range of this list.
list = {start,end}
j = quadrant(start)
k = quadrant(end)
if( j==k && long way 'round ) append north,west,south,east
else
while( j != k )
append center+radius*[j-th of north,west,south,east unit vectors]
j += 1 (mod 4)
return( bounding box of list )
The following code implements this, with simple optimizations.
***********************************************************************/
static int quadrant(double x, double y)
{
if ( x>=0.0 && y> 0.0) return(1);
else if( x< 0.0 && y>=0.0) return(2);
else if( x<=0.0 && y< 0.0) return(3);
else if( x> 0.0 && y<=0.0) return(4);
else return 0; /* shut up lint */
}
static double xmin, ymin, xmax, ymax; // used by getDy
static void arc_extreme(double x0, double y0, double x1, double y1, double xc, double yc)
/* start, end, center */
{ /* assumes center isn't too far out */
double r;
int j, k;
printf("#start %g,%g, end %g,%g, ctr %g,%g\n", x0,y0, x1,y1, xc,yc);
y0 = -y0; y1 = -y1; yc = -yc; // troff's up is eric's down
x0 -= xc; y0 -= yc; /* move to center */
x1 -= xc; y1 -= yc;
xmin = (x0<x1)?x0:x1; ymin = (y0<y1)?y0:y1;
xmax = (x0>x1)?x0:x1; ymax = (y0>y1)?y0:y1;
r = sqrt(x0*x0 + y0*y0);
if (r > 0.0) {
j = quadrant(x0,y0);
k = quadrant(x1,y1);
if (j == k && y1*x0 < x1*y0) {
/* viewed as complex numbers, if Im(z1/z0)<0, arc is big */
if( xmin > -r) xmin = -r; if( ymin > -r) ymin = -r;
if( xmax < r) xmax = r; if( ymax < r) ymax = r;
} else {
while (j != k) {
switch (j) {
case 1: if( ymax < r) ymax = r; break; /* north */
case 2: if( xmin > -r) xmin = -r; break; /* west */
case 3: if( ymin > -r) ymin = -r; break; /* south */
case 4: if( xmax < r) xmax = r; break; /* east */
}
j = j%4 + 1;
}
}
}
xmin += xc; ymin += yc; ymin = -ymin;
xmax += xc; ymax += yc; ymax = -ymax;
}
static int getDy(char *p, int *dx, int *maxv)
// figure out where we are after a D'...'
{
int x, y, x1, y1; // for input values
char temp[50];
p++; // get to command letter
switch (*p++) {
case 'l': // line
sscanf(p, "%d %d", dx, &y);
return *maxv = y;
case 'a': // arc
sscanf(p, "%d %d %d %d", &x, &y, &x1, &y1);
*dx = x1 - x;
arc_extreme(0, 0, x+x1, y+y1, x, y); // sets [xy][max|min]
printf("#arc bounds x %g, %g; y %g, %g\n",
xmin, xmax, ymin, ymax);
*maxv = (int) (ymin+0.5);
return y + y1;
case '~': // spline
for (*dx = *maxv = y = 0; (p=getstr(p, temp)) != NULL; ) {
// above getstr() gets x value
*dx += atoi(temp);
p = getstr(p, temp); // this one gets y value
y += atoi(temp);
*maxv = max(*maxv, y); // ok???
if (*p == '\n' || *p == 0) // input is a single line;
break; // don't walk off end if realloc
}
return y;
case 'c': // circle, ellipse
sscanf(p, "%d", dx);
*maxv = *dx/2; // high water mark is ht/2
return 0;
case 'e':
sscanf(p, "%d %d", dx, &y);
*maxv = y/2; // high water mark is ht/2
return 0;
default: // weird stuff
return 0;
}
}
static int serialnum = 0;
slug eofslug()
{
slug ret;
ret.serialnum = serialnum;
ret.type = EOF;
ret.dp = wherebuf();
return ret;
}
slug getslug(FILE *fp)
{
if (inbuf == NULL) {
if ((inbuf = (char *) malloc(ninbuf = DELTABUF)) == NULL)
ERROR "no room for %d character input buffer\n", ninbuf FATAL;
inbp = inbuf;
}
if (wherebuf() > ninbuf-5000) {
// this is still flaky -- lines can be very long
int where = wherebuf(); // where we were
if ((inbuf = (char *) realloc(inbuf, ninbuf += DELTABUF)) == NULL)
ERROR "no room for %d character input buffer\n", ninbuf FATAL;
ERROR "grew input buffer to %d characters\n", ninbuf WARNING;
inbp = inbuf + where; // same offset in new array
}
static int baseV = 0; // first V command of preceding slug
static int curV = 0, curH = 0;
static int font = 0, size = 0;
static int baseadj = 0;
static int ncol = 1, offset = 0; // multi-column stuff
char str[1000], str2[1000], buf[3000], *p;
int firstV = 0, firstH = 0;
int maxV = curV;
int ocurV = curV, mxv = 0, dx = 0;
int sawD = 0; // > 0 if have seen D...
slug ret;
ret.serialnum = serialnum++;
ret.type = VBOX; // use the same as last by default
ret.dv = curV - baseV;
ret.hpos = curH;
ret.base = ret.parm = ret.parm2 = ret.seen = 0;
ret.font = font;
ret.size = size;
ret.dp = wherebuf();
ret.ncol = ncol;
ret.offset = offset;
ret.linenum = linenum; // might be low
for (;;) {
int c, m, n; // for input values
int sign; // hoisted from case 'h' below
switch (c = getc(fp)) {
case EOF:
ret.type = EOF;
ret.dv = 0;
if (baseadj)
printf("# adjusted %d bases\n", baseadj);
printf("# %d characters, %d lines\n", wherebuf(), linenum);
return ret;
case 'V':
fscanf(fp, "%d", &n);
if (firstV++ == 0) {
ret.dv = n - baseV;
baseV = n;
} else {
sprintf(buf, "v%d", n - curV);
adds(buf);
}
curV = n;
maxV = max(maxV, curV);
break;
case 'H': // absolute H motion
fscanf(fp, "%d", &n);
if (firstH++ == 0) {
ret.hpos = n;
} else {
sprintf(buf, "h%d", n - curH);
adds(buf);
}
curH = n;
break;
case 'h': // relative H motion
addc(c);
sign = 1;
if ((c = getc(fp)) == '-') {
addc(c);
sign = -1;
c = getc(fp);
}
for (n = 0; isdigit(c); c = getc(fp)) {
addc(c);
n = 10 * n + c - '0';
}
curH += n * sign;
ungetc(c, fp);
break;
case 'x': // device control: x ...
addc(c);
fgets(buf, (int) sizeof(buf), fp);
linenum++;
adds(buf);
if (buf[0] == ' ' && buf[1] == 'X') { // x X ...
if (2 != sscanf(buf+2, "%s %d", str, &n))
n = 0;
if (eq(str, "SP")) { // X SP n
ret.type = SP; // paddable SPace
ret.dv = n; // of height n
} else if (eq(str, "BS")) {
ret.type = BS; // Breakable Stream
ret.parm = n; // >=n VBOXES on a page
} else if (eq(str, "BF")) {
ret.type = BF; // Breakable Float
ret.parm = ret.parm2 = n;
// n = pref center (as UF)
} else if (eq(str, "US")) {
ret.type = US; // Unbreakable Stream
ret.parm = n;
} else if (eq(str, "UF")) {
ret.type = UF; // Unbreakable Float
ret.parm = ret.parm2 = n;
// n = preferred center
// to select several,
// use several UF lines
} else if (eq(str, "PT")) {
ret.type = PT; // Page Title
ret.parm = n;
} else if (eq(str, "BT")) {
ret.type = BT; // Bottom Title
ret.parm = n;
} else if (eq(str, "END")) {
ret.type = END;
ret.parm = n;
} else if (eq(str, "TM")) {
ret.type = TM; // Terminal Message
ret.dv = 0;
} else if (eq(str, "COORD")) {
ret.type = COORD;// page COORDinates
ret.dv = 0;
} else if (eq(str, "NE")) {
ret.type = NE; // NEed to break page
ret.dv = n; // if <n units left
} else if (eq(str, "MC")) {
ret.type = MC; // Multiple Columns
sscanf(buf+2, "%s %d %d",
str, &ncol, &offset);
ret.ncol = ncol;
ret.offset = offset;
} else if (eq(str, "CMD")) {
ret.type = CMD; // CoMmaNd
sscanf(buf+2, "%s %s", str2, str);
if (eq(str, "FC")) // Freeze 2-Col
ret.parm = FC;
else if (eq(str, "FL")) // FLush
ret.parm = FL;
else if (eq(str, "BP")) // Break Page
ret.parm = BP;
else ERROR "unknown command %s\n",
str WARNING;
} else if (eq(str, "PARM")) {
ret.type = PARM;// PARaMeter
sscanf(buf+2, "%s %s %d", str2, str, &ret.parm2);
if (eq(str, "NP")) // New Page
ret.parm = NP;
else if (eq(str, "FO")) // FOoter
ret.parm = FO;
else if (eq(str, "PL")) // Page Length
ret.parm = PL;
else if (eq(str, "MF")) // MinFull
ret.parm = MF;
else if (eq(str, "CT")) // ColTol
ret.parm = CT;
else if (eq(str, "WARN")) //WARNings?
ret.parm = WARN;
else if (eq(str, "DBG"))// DeBuG
ret.parm = DBG;
else ERROR "unknown parameter %s\n",
str WARNING;
} else
break; // out of switch
if (firstV > 0)
ERROR "weird x X %s in mid-VBOX\n",
str WARNING;
return ret;
}
break;
case 'n': // end of line
fscanf(fp, "%d %d", &n, &m);
ret.ht = n;
ret.base = m;
getc(fp); // newline
linenum++;
sprintf(buf, "n%d %d\n", ret.ht, ret.base);
adds(buf);
if (!firstV++)
baseV = curV;
// older incarnations of this program used ret.base
// in complicated and unreliable ways;
// example: if ret.ht + ret.base < ret.dv, ret.base = 0
// this was meant to avoid double-counting the space
// around displayed equations; it didn't work
// Now, we believe ret.base = 0, otherwise we give it
// a value we have computed.
if (ret.base == 0 && sawD == 0)
return ret; // don't fiddle 0-bases
if (ret.base != maxV - baseV) {
ret.base = maxV - baseV;
baseadj++;
}
if (ret.type != VBOX)
ERROR "%s slug (type %d) has base = %d\n",
ret.typename(), ret.type, ret.base WARNING;
return ret;
case 'p': // new page
fscanf(fp, "%d", &n);
ret.type = PAGE;
curV = baseV = ret.dv = 0;
ret.parm = n; // just in case someone needs it
return ret;
case 's': // size change snnn
fscanf(fp, "%d", &size);
sprintf(buf, "s%d\n", size);
adds(buf);
break;
case 'f': // font fnnn
fscanf(fp, "%d", &font);
sprintf(buf, "f%d\n", font);
adds(buf);
break;
case '\n':
linenum++;
/* fall through */
case ' ':
addc(c);
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
// two motion digits plus a character
addc(c);
n = c - '0';
addc(c = getc(fp));
curH += 10 * n + c - '0';
adds(getutf(fp));
if (!firstV++)
baseV = curV;
break;
case 'c': // single ascii character
addc(c);
adds(getutf(fp));
if (!firstV++)
baseV = curV;
break;
case 'C': // Cxyz\n
case 'N': // Nnnn\n
addc(c);
while ((c = getc(fp)) != ' ' && c != '\n')
addc(c);
addc(c);
if (!firstV++)
baseV = curV;
linenum++;
break;
case 'D': // draw function: D.*\n
sawD++;
p = bufptr(wherebuf()); // where does the D start
addc(c);
while ((c = getc(fp)) != '\n')
addc(c);
addc(c);
if (!firstV++)
baseV = curV;
ocurV = curV, mxv = 0, dx = 0;
curV += getDy(p, &dx, &mxv); // figure out how big it is
maxV = max(max(maxV, curV), ocurV+mxv);
curH += dx;
linenum++;
break;
case 'v': // relative vertical vnnn
addc(c);
if (!firstV++)
baseV = curV;
sign = 1;
if ((c = getc(fp)) == '-') {
addc(c);
sign = -1;
c = getc(fp);
}
for (n = 0; isdigit(c); c = getc(fp)) {
addc(c);
n = 10 * n + c - '0';
}
ungetc(c, fp);
curV += n * sign;
maxV = max(maxV, curV);
addc('\n');
break;
case 'w': // word space
addc(c);
break;
case '#': // comment
addc(c);
while ((c = getc(fp)) != '\n')
addc(c);
addc('\n');
linenum++;
break;
default:
ERROR "unknown input character %o %c (%50.50s)\n",
c, c, bufptr(wherebuf()-50) WARNING;
abort();
break;
}
}
}

74
src/cmd/mpm/slug.h Normal file
View file

@ -0,0 +1,74 @@
enum slugtypes {
NONE, // can't happen
VBOX, // Vertical Box -- printable stuff
SP, // paddable SPace
BS, // start Breakable Stream
US, // start Unbreakable Stream
BF, // start Breakable Float
UF, // start Unbreakable Float
PT, // start Page Top material (header)
BT, // start page BoTtom material (footer)
END, // ENDs of groups
NEUTRAL, // NEUTRALized slugs can do no harm (cf. CIA)
PAGE, // beginning of PAGE in troff input
TM, // Terminal Message to appear during output
COORD, // output page COORDinates
NE, // NEed command
MC, // Multiple-Column command
CMD, // misc CoMmanDs: FC, FL, BP
PARM, // misc PARaMeters: NP, FO
LASTTYPE // can't happen either
};
enum cmdtypes {
FC, // Freeze 2-Column material
FL, // FLush all floats before reading more stream
BP // Break Page
};
enum parmtypes {
NP, // distance of top margin from page top (New Page)
FO, // distance of bottom margin from page top (FOoter)
PL, // distance of physical page bottom from page top (Page Length)
MF, // minimum fullness required for padding
CT, // tolerance for division into two columns
WARN, // warnings to stderr?
DBG // debugging flag
};
class slug {
int serialnum;
int dp; // offset of data for this slug in inbuf
int linenum; // input line number (approx) for this slug
short font; // font in effect at slug beginning
short size; // size in effect at slug beginning
short seen; // 0 until output
short ncol; // number of columns (1 or 2)
short offset; // horizontal offset for 2 columns
public:
short type; // VBOX, PP, etc.
short parm; // parameter
short base; // "depth" of this slug (from n command)
int hpos; // abs horizontal position
int dv; // height of this slug above its input Vpos
union {
int ht; // "height" of this slug (from n command)
int parm2; // second parameter, since only VBOXes have ht
};
friend slug getslug(FILE *);
friend void checkout();
friend slug eofslug();
void coalesce(); // with next slug in array slugs[]
void neutralize(); // render this one a no-op
void dump(); // dump its contents for debugging
char *headstr(); // string value of text
void slugout(int); // add the slug to the output
char *typename(); // printable slug type
int serialno() { return serialnum; }
int numcol() { return ncol; }
int lineno() { return linenum; }
};
// functions in slug.c
slug eofslug();
slug getslug(FILE *);

961
src/cmd/mpm/tmac.pm Normal file
View file

@ -0,0 +1,961 @@
.\" 10/22/92 activate next line before installing
.pi /$objtype/bin/aux/pm
.
. \" IZ - initialization
.de IZ
.fp 1 R \" force a font out into prefix
.nr PS 10 \" point size
.nr VS 12 \" line spacing
.ps \\n(PS
.ie \\n(VS>=41 .vs \\n(VSu
.el .vs \\n(VSp
.nr LL 6i \" line length
.ll \\n(LLu
.nr LT \\n(.l \" title length
.lt \\n(LTu
.if !\\n(HM .nr HM 1i \" top of page
.if !\\n(FM .nr FM 1i \" footer margin
.if !\\n(FO .nr FO \\n(.p-\\n(FM \" bottom of page
. \" to set text ht to N, set FO to N + \n(HM. default is 10i
.pl 32767u \" safety first: big pages for pm
.if !\\n(PO .nr PO \\n(.ou \" page offset
.nr PI 5n \" .PP paragraph indent
.nr QI 5n \" .QS indent
.nr DI 5n \" .DS indent
.nr PD 0.3v \" paragraph vertical separation
.nr TS 0.5v \" space around tables
.nr Kf 0.5v \" space around .KF/.KE
.nr Ks 0.5v \" space around .KS/.KE
.
.nr P1 .4i \" indent for .P1/.P2
.nr dP 1 \" delta point size for programs in .P1/.P2
.nr dV 2p \" delta vertical for programs
.nr dT 8 \" delta tab stop for programs
.nr DV .5v \" space before start of program
.nr IP 0 \" ?
.nr IR 0 \" ?
.nr I1 \\n(PIu
.ev 1
.if !\\n(FL .nr FL \\n(LLu \" footnote length
.ll \\n(FLu
.ps 8 \" text size & leading in footnote
.vs 10p
.ev
.if \\*(CH .ds CH "\(hy \\\\n(PN \(hy
.ds # #\\\\n(.c \\\\n(.F
.
.
.ME \" initialize date strings
.rm ME
. \" accents: \*'e \*`e \*:u \*^e \*~n \*va \*,c
.ds ' \h'\w'e'u*4/10'\z\(aa\h'-\w'e'u*4/10'
.ds ` \h'\w'e'u*4/10'\z\(ga\h'-\w'e'u*4/10'
.ds : \\v'-0.6m'\\h'(1u-(\\\\n(.fu%2u))*0.13m+0.00m'\\z.\\h'0.2m'\\z.\\h'-((1u-(\\\\n(.fu%2u))*0.13m+0.20m)'\\v'0.6m'
.ds ^ \\\\k:\\h'-\\\\n(.fu+1u/2u*2u+\\\\n(.fu-1u*0.13m+0.06m'\\z^\\h'|\\\\n:u'
.ds ~ \\\\k:\\h'-\\\\n(.fu+1u/2u*2u+\\\\n(.fu-1u*0.13m+0.06m'\\z~\\h'|\\\\n:u'
.ds v \\\\k:\\\\h'+\\\\w'e'u/4u'\\\\v'-0.6m'\\\\s6v\\\\s0\\\\v'0.6m'\\\\h'|\\\\n:u'
.ds , \\\\k:\\\\h'\\\\w'c'u*0.4u'\\\\z,\\\\h'|\\\\n:u'
..
.
.
. \" SP - generate paddable space
.de SP
.br
.nr X 1v
.if \\n(.$ .nr X \\$1v
.ie '\\$2'exactly' \{\
\v'\\nXu'\ \h'-\w'\ 'u'\c
.sp \\$1\}
.el .X "SP \\nX \\$2"
..
. \" NE - need space on this page
.de NE
.nr X 1v
.if \\n(.$ .nr X \\$1v
.X "NE \\nX \\$2"
..
. \" BP, FL, FC - begin page, flush figures, flush column
.de BP
.br
.X CMD BP
..
.de FL
.br
.X CMD FL
..
.de FC
.br
.X CMD FC
..
. \" X - generate an x X ... command in the output
.de X
....ie '\\n(.z'' \\!x X \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
....el \\!.X "\\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
...
.if !'\\n(.z'' .if \\n(.$=1 \\!.X "\\$1
.if !'\\n(.z'' .if \\n(.$=2 \\!.X "\\$1 \\$2
.if !'\\n(.z'' .if \\n(.$=3 \\!.X "\\$1 \\$2 \\$3
.if !'\\n(.z'' .if \\n(.$>3 \\!.X "\\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
.if '\\n(.z'' .if \\n(.$=1 \\!x X \\$1 \\*#
.if '\\n(.z'' .if \\n(.$=2 \\!x X \\$1 \\$2 \\*#
.if '\\n(.z'' .if \\n(.$=3 \\!x X \\$1 \\$2 \\$3 \\*#
.if '\\n(.z'' .if \\n(.$=4 \\!x X \\$1 \\$2 \\$3 \\$4 \\*#
.if '\\n(.z'' .if \\n(.$>4 \\!x X \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 \\*#
..
. \" DA - force date
.de DA
.if \\n(.$ .ds DY \\$1 \\$2 \\$3 \\$4
.ds CF \\*(DY
..
. \" ND - set new or no date
.de ND
.ds DY \\$1 \\$2 \\$3 \\$4
.rm CF
..
.de ME \" ME - set month strings
.if \\n(mo-0 .ds MO January
.if \\n(mo-1 .ds MO February
.if \\n(mo-2 .ds MO March
.if \\n(mo-3 .ds MO April
.if \\n(mo-4 .ds MO May
.if \\n(mo-5 .ds MO June
.if \\n(mo-6 .ds MO July
.if \\n(mo-7 .ds MO August
.if \\n(mo-8 .ds MO September
.if \\n(mo-9 .ds MO October
.if \\n(mo-10 .ds MO November
.if \\n(mo-11 .ds MO December
.if \\n(dw-0 .ds DW Sunday
.if \\n(dw-1 .ds DW Monday
.if \\n(dw-2 .ds DW Tuesday
.if \\n(dw-3 .ds DW Wednesday
.if \\n(dw-4 .ds DW Thursday
.if \\n(dw-5 .ds DW Friday
.if \\n(dw-6 .ds DW Saturday
.if "\\*(DY"" .ds DY \\*(MO \\n(dy, 19\\n(yr
..
. \" FP - font position for a family
.de FP
.if '\\$1'palatino'\{\
. fp 1 PA
. fp 2 PI
. fp 3 PB
. fp 4 PX\}
.if '\\$1'century'\{\
. ie '\\*(.T'202'\{\
. fp 1 NR Centsb
. fp 2 NI CentI
. fp 3 NB CentB
. fp 4 NX CentBI\}
. el \{\
. fp 1 NR
. fp 2 NI
. fp 3 NB
. fp 4 NX\}\}
.if '\\$1'helvetica'\{\
. fp 1 H
. fp 2 HI
. fp 3 HB
. fp 4 HX\}
.if '\\$1'bembo'\{\
. ie '\\*(.T'202'\{\
. fp 1 B1 Bembo
. fp 2 B2 BemboI
. fp 3 B3 BemboB
. fp 4 B4 BemboBI\}
. el \{\
. fp 1 B1
. fp 2 B2
. fp 3 B3
. fp 4 B4\}\}
.if '\\$1'optima'\{\
. fp 1 R Optima
. fp 2 I OptimaI
. fp 3 B OptimaB
. fp 4 BI OptimaBI\}
.if '\\$1'souvenir'\{\
. fp 1 R Souvenir
. fp 2 I SouvenirI
. fp 3 B SouvenirB
. fp 4 BI SouvenirBI\}
.if '\\$1'melior'\{\
. fp 1 R Melior
. fp 2 I MeliorI
. fp 3 B MeliorB
. fp 4 BI MeliorBI\}
.if '\\$1'times'\{\
. fp 1 R
. fp 2 I
. fp 3 B
. fp 4 BI\}
..
. \" TL - title
.de TL
.br
.if !\\n(1T .BG
....hy 0
.ft 3
.ps \\n(PS+2p
.vs \\n(VS+2p
.ll \\n(LLu
.ce 100 \" turned off in .RT
.sp .5i
..
. \" AU - remember author(s)
.de AU
.ft 1
.ps \\n(PS
.ie \\n(VS>=41 .vs \\n(VSu
.el .vs \\n(VSp
.SP .5
..
. \" AI - author's institution
.de AI
.SP .25
.ft 2
..
. \" AB - begin abstract
.de AB
.nr AB 1 \" we're in abstract
.if !\\n(1T .BG
.ft 1
.ps \\n(PS
.vs \\n(VSp
.ce
.in +\\n(.lu/12u
.ll -\\n(.lu/12u
.SP 1
.ie \\n(.$ \\$1
.el ABSTRACT
.SP .75
.RT
..
. \" AE - end of abstract
.de AE
.br
.nr AB 0
.in 0
.ll \\n(LLu
.ps \\n(PS
.ie \\n(VS>=41 .vs \\n(VSu
.el .vs \\n(VSp
.SP
..
. \" 2C - 2 columns
.de 2C
.MC 2
..
. \" 1C - 1 column
.de 1C
.MC 1
..
. \" MC - multiple columns
.de MC
.br
.if \\n(1T .RT
.if \\n(1T .NP
.if !\\n(OL .nr OL \\n(LL
.if \\n(CW=0 .nr CW \\n(LL*7/15
.if \\n(GW=0 .nr GW \\n(LL-(2*\\n(CW)
.nr x \\n(CW+\\n(GW
.if "\\$1"" .MC 2
.if \\$1=1 \{\
. X MC 1 0
. nr LL \\n(OLu\}
.if \\$1=2 \{\
. X MC 2 \\nx
. nr LL \\n(CWu\}
.ll \\n(LLu
.if \\$1>2 .tm -mpm can't handle more than two columns
.if \\n(1T .RT
..
. \" TS - table start, TE - table end; also TC, TQ, TH
.de TS
.br
.if !\\n(1T .RT
.SP \\n(TSu TS
.X "US TS
.if \\$1H .TQ
.nr IX 1
..
.de TC
.nr TZ \\n(.lu
.if \\n(.$ .nr TZ \\$1n
.ta \\n(TZuR
..
.de TD
.LP
.nr TZ 0
..
.de TQ
.di TT
.nr IT 1
..
.de TH
.if \\n(.d>0.5v \{\
. nr T. 0
. T# 0\}
.di
.nr TQ \\n(.i
.nr HT 1
.in 0
.mk #a
.mk #b
.mk #c
.mk #d
.mk #e
.mk #f
.TT
.in \\n(TQu
.mk #T
..
. \" TE - table end
.de TE
.nr IX 0
.if \\n(IT .if !\\n(HT \{\
. di
. nr EF \\n(.u
. nf
. TT
. if \\n(EF .fi\}
.nr IT 0
.nr HT 0
.rm a+ b+ c+ d+ e+ f+ g+ h+ i+ j+ k+ l+ n+ m+
.rr 32 33 34 35 36 37 38 40 79 80 81 82
.rr a| b| c| d| e| f| g| h| i| j| k| l| m|
.rr a- b- c- d- e- f- g- h- i- j- k- l- m-
.X "END US TE
.SP \\n(TSu TE
.bp
..
. \" EQ - equation, breakout and display
.de EQ
.nr EF \\n(.u
.rm EE
.nr LE 1 \" 1 is center
.ds EL \\$1
.if "\\$1"L" \{\
. ds EL \\$2
. nr LE 0\}
.if "\\$1"C" .ds EL \\$2
.if "\\$1"R" \{\
. ds EL \\$2 \" 2 is right adjust
. nr LE 2\}
.if "\\$1"I" \{\
. nr LE 0
. if "\\$3"" .ds EE \\h'|10n'
. el .ds EE \\h'\\$3'
. ds EL \\$2\}
.if \\n(YE .nf
.di EZ
..
. \" EN - end of equation
.de EN
.br
.di
.rm EZ
.nr ZN \\n(dn
.if \\n(ZN .if !\\n(YE .LP
.if !\\n(ZN .if !"\\*(EL"" .nr ZN 1
.if \\n(ZN \{\
. SP .5v EQ
. X "US EQ"\}
'pc
.if \\n(BD .nr LE 0 \" don't center if block display or mark/lineup
.if \\n(MK \{\
. if \\n(LE=1 .ds EE \\h'|10n'
. nr LE 0\}
'lt \\n(.lu
.if !\\n(EP .if \\n(ZN \{\
. if \\n(LE=1 .tl \(ts\(ts\\*(10\(ts\\*(EL\(ts
. if \\n(LE=2 .tl \(ts\(ts\(ts\\*(10\\*(EL\(ts
. if !\\n(LE \{\
. if !\\n(BD .tl \(ts\\*(EE\\*(10\(ts\(ts\\*(EL\(ts
. if \\n(BD .if \\n(BD<\\w\(ts\\*(10\(ts .nr BD \\w\(ts\\*(10\(ts
. if \\n(BD \!\\*(10\\t\\*(EL\}\}
.if \\n(EP .if \\n(ZN \{\
. if \\n(LE=1 .tl \(ts\\*(EL\(ts\\*(10\(ts\(ts
. if \\n(LE=2 .tl \(ts\\*(EL\(ts\(ts\\*(10\(ts
. if !\\n(LE \{\
. if !\\n(BD .tl \(ts\\*(EL\\*(EE\\*(10\(ts\(ts\(ts
. if \\n(BD .if \\n(BD<\\w\(ts\\*(10\(ts .nr BD \\w\(ts\\*(10\(ts
. if \\n(BD \!\\h'-\\\\n(.iu'\\*(EL\\h'|0'\\*(10\}\}
'lt \\n(LLu
'pc %
.if \\n(YE .if \\n(EF .fi
.if \\n(ZN .X "END US EQ"
.if \\n(ZN .SP .5v EN
.if \\n(ZN .bp
..
. \" PS - start picture
.de PS \" $1 is height, $2 is width, in inches
.br
.nr X 0.35v
.if \\$1>0 .X "SP \\nX PS"
.ie \\$1>0 .nr $1 \\$1
.el .nr $1 0
.X "US PS \\$1
.in (\\n(.lu-\\$2)/2u
..
. \" PE - end of picture
.de PE
.in
.X "END US PE
.nr X .65v
.if \\n($1>0 .X "SP \\nX PE"
.bp
..
.de IS \" for -mpm only
.KS
..
.de IE
.KE
.bp
..
. \" NP - new page
.de NP
.ev 2
.bp
.if \\n(KF=0 \{\
. nr PX \\n(.s
. nr PF \\n(.f
. nr PV \\n(.v
. lt \\n(LTu
. ps \\n(PS
. vs \\n(PS+2
. ft 1
. if \\n(PO .po \\n(POu \" why isn't this reset???
. PT \\$1
. bp
. rs
. BT
. bp
. nr %# +1
. ps \\n(PX
. vs \\n(PVu
. ft \\n(PF \}
.ev
..
.
.ds %e .tl '\\*(LH'\\*(CH'\\*(RH'
.ds %o .tl '\\*(LH'\\*(CH'\\*(RH'
.ds %E .tl '\\*(LF'\\*(CF'\\*(RF'
.ds %O .tl '\\*(LF'\\*(CF'\\*(RF'
.
. \" PT - page title
.de PT
.nr PN \\n(%#
.X "PT \\n(%#
.sp \\n(HMu/2u
.if \\n(OL .lt \\n(OLu \" why isn't this reset???
.if \\n(BT>0 .if \\n(%#%2 \\*(%o
.if \\n(BT>0 .if !\\n(%#%2 \\*(%e
.if \\n(BT=0 .tl '\0''' \" put out something or spacing is curdled
.X "END PT \\n(%#
..
. \" BT - bottom title
.de BT
.X "BT \\n(%#
.sp |\\n(FMu/2u+\\n(FOu-1v
.if \\n(%#%2 \\*(%O
.if !\\n(%#%2 \\*(%E
.nr BT \\n(BT+1
.X "END BT \\n(%#
..
. \" KS - non-floating keep
.de KS
.br
.if "\\n(.z"" .NP \" defends poorly against including ht of page stuff in diversion for .B1
.X "US KS 0
.nr KS +1
.SP \\n(Ksu
..
. \" KF - floating keep
.de KF
.ev 1
.br
.if \\n(KS>0 .tm KF won't work inside KS, line \\n(.c, file \\n(.F
.if \\n(KF>0 .tm KF won't work inside KF, line \\n(.c, file \\n(.F
.nr KF 1
.nr 10 0
. if !'\\$1'' .nr 10 \\$1u
. if '\\$1'bottom' .nr 10 \\n(FOu-1u
. if '\\$1'top' .nr 10 \\n(HM
. if \\n(10 .X "UF \\n(10 KF"
. if !\\n(10 .X "UF \\n(HM KF"
. nr X \\n(FOu-2u
. if \\n(10 .X "UF \\n(10 KF"
. if !\\n(10 .X "UF \\nX KF"
.nr SJ \\n(.u
.ps \\n(PS
.if \\n(VS>40 .vs \\n(VSu
.if \\n(VS<=39 .vs \\n(VSp
.ll \\n(LLu
.lt \\n(LTu
.SP \\n(Kfu
..
. \" KE - end of KS/KF
.de KE
.bp
.ie \\n(KS>0 \{\
. SP \\n(Ksu
. X "END US KS
. nr KS -1 \}
.el .ie \\n(KF>0 \{\
. SP \\n(Kfu
. nr KF 0
. X "END UF KF"
. if \\n(SJ .fi
. ev \}
.el .tm .KE without preceding .KS or .KF, line \\n(.c, file \\n(.F
..
.
. \" DS - display. .DS C center; L left-adjust; I indent (default)
.de DS \" $2 = amount of indent
.KS
.nf
.\\$1D \\$2 \\$1
.ft 1
.if !\\n(IF \{\
. ps \\n(PS
. if \\n(VS>40 .vs \\n(VSu
. if \\n(VS<=39 .vs \\n(VSp\}
..
.de D
.ID \\$1
..
.de CD
.XD
.ce 1000
..
.de ID
.XD
.if \\n(.$=0 .in +\\n(DIu
.if \\n(.$=1 .if "\\$1"I" .in +\\n(DIu
.if \\n(.$=1 .if !"\\$1"I" .in +\\$1n
.if \\n(.$>1 .in +\\$2n
.....in +0.5i
.....if \\n(.$ .if !"\\$1"I" .if !"\\$1"" .in \\n(DIu
.....if \\n(.$ .if !"\\$1"I" .if !"\\$1"" .in +\\$1n
..
.de LD
.XD
..
.de XD
.nf
.nr OI \\n(.i
.SP \\n(DVu
..
. \" BD - block display: save everything, then center it.
.de BD
.XD
.nr BD 1
.nf
.in \\n(OIu
.di DD
..
. \" DE - display end
.de DE
.ce 0
.if \\n(BD>0 .XF
.nr BD 0
.in \\n(OIu
.SP \\n(DVu
.KE
.fi
..
. \" XF - finish a block display to be recentered.
.de XF
.di
.if \\n(dl>\\n(BD .nr BD \\n(dl
.if \\n(BD<\\n(.l .in (\\n(.lu-\\n(BDu)/2u
.nr EI \\n(.l-\\n(.i
.ta \\n(EIuR
.nf
.DD
.in \\n(OIu
..
.
.
. \" SH - (unnumbered) section heading
.de SH
.RT
.nr X 1v
.nr Y 3v
.if \\n(1T .NP
.if \\n(1T .X "NE \\nY SH" \" should these be reversed, change Y to 4v
.if \\n(1T .X "SP \\nX SH
.ft 3
..
. \" NH - numbered heading
.de NH
.RT
.nr X 1v
.nr Y 3v
.if \\n(1T .NP
.if \\n(1T .X "NE \\nY NH" \" should these be reversed, change Y to 4v
.if \\n(1T .X "SP \\nX NH
.ft 3
.nr NS \\$1
.if !\\n(.$ .nr NS 1
.if !\\n(NS .nr NS 1
.nr H\\n(NS +1
.if !\\n(NS-4 .nr H5 0
.if !\\n(NS-3 .nr H4 0
.if !\\n(NS-2 .nr H3 0
.if !\\n(NS-1 .nr H2 0
.if !\\$1 .if \\n(.$ .nr H1 1
.ds SN \\n(H1.
.if \\n(NS-1 .as SN \\n(H2.
.if \\n(NS-2 .as SN \\n(H3.
.if \\n(NS-3 .as SN \\n(H4.
.if \\n(NS-4 .as SN \\n(H5.
\\*(SN
..
. \" RT - reset at beginning of each PP, LP, etc.
.de RT
.if !\\n(AB .if !\\n(1T .BG
.ce 0
.if !\\n(AB .if !\\n(KF .if !\\n(IF .if !\\n(IX .if !\\n(BE .di
.if \\n(QP \{\
. ll +\\n(QIu
. in -\\n(QIu
. nr QP -1\}
.if !\\n(AB \{\
. ll \\n(LLu\}
.if !\\n(IF .if !\\n(AB \{\
. ps \\n(PS
. ie \\n(VS>=41 .vs \\n(VSu
. el .vs \\n(VSp\}
.ie \\n(IP \{\
. in \\n(I\\n(IRu
. nr IP -1\}
.el .if !\\n(IR \{\
. nr I1 \\n(PIu
. nr I2 0
. nr I3 0
. nr I4 0
. nr I5 0\}
.if !\\n(AB .ft 1
.ta 5n 10n 15n 20n 25n 30n 35n 40n 45n 50n 55n 60n 65n 70n 75n 80n
.fi
..
. \" BG - begin, execute at first TL, AB, NH, SH, PP, etc.
.de BG \" IZ has been called, so registers have some value
.br
.if \\n(CW>0 .if \\n(LL=0 .nr LL \\n(CW+\\n(CW+\\n(GW
.ll \\n(LLu
.lt \\n(LLu
.po \\n(POu
.nr YE 1 \" ok to cause break in .EQ (earlier ones won't)
.ev 0
.hy 14
.ev
.ev 1
.hy 14
.ev
.ev 2
.hy 14
.ev
.nr 1T 1
.X "PARM NP \\n(HM
.X "PARM FO \\n(FO
.if !\\n(%# .nr %# 1
..
. \" PP - paragraph
.de PP
.RT
.if \\n(1T .NP
.if \\n(1T .X "SP \\n(PD PP"
.if \\n(1T .X "BS 2 PP"
.ti +\\n(PIu
..
. \" LP - left aligned paragraph
.de LP
.RT
.if \\n(1T .NP
.if \\n(1T .X "SP \\n(PD LP"
.if \\n(1T .X "BS 2 LP"
..
. \" IP - indented paragraph
.de IP
.RT
.if !\\n(IP .nr IP +1
.if \\n(1T .NP
.if \\n(1T .X "SP \\n(PD PP"
.if \\n(1T .X "BS 2 IP"
.nr IU \\n(IR+1
.if \\n(.$>1 .nr I\\n(IU \\$2n+\\n(I\\n(IRu
.if \\n(I\\n(IU=0 .nr I\\n(IU \\n(PIu+\\n(I\\n(IRu
.in \\n(I\\n(IUu
.nr TY \\n(TZ-\\n(.i
.nr JQ \\n(I\\n(IU-\\n(I\\n(IR
.ta \\n(JQu \\n(TYuR
.if \\n(.$ \{\
.ti \\n(I\\n(IRu
\&\\$1\t\c\}
..
. \" QP - quoted paragraph (within IP)
.de QP
.RT
.if \\n(1T .NP
.if \\n(1T .X "SP \\n(PD QP"
.if \\n(1T .X "BS 2 QP"
.nr QP 1
.in +\\n(QIu
.ll -\\n(QIu
.ti \\n(.iu
..
. \" RS - prepare for double indenting
.de RS
.nr IS \\n(IP
.RT
.nr IP \\n(IS
.nr IU \\n(IR
.nr IR +1
.if !\\n(I\\n(IR .nr I\\n(IR \\n(I\\n(IU+\\n(PIu
.in \\n(I\\n(IRu
.nr TY \\n(TZ-\\n(.i
.ta \\n(TYuR
..
. \" RE - retreat to the left
.de RE
.nr IS \\n(IP
.RT
.nr IP \\n(IS
.if \\n(IR>0 .nr IR -1
.in \\n(I\\n(IRu
..
. \" B - bold font
.de B
.nr PQ \\n(.f
.ft 3
.if \\n(.$ \&\\$1\\f\\n(PQ\\$2
..
. \" BI - bold italic
.de BI
.nr PQ \\n(.f
.ft 4
.if \\n(.$ \&\\$1\\f\\n(PQ\\$2
..
. \" R - Roman font
.de R
.nr PQ \\n(.f
.ft 1
.if \\n(.$ \&\\$1\f\\n(PQ\\$2
..
. \" I - italic font
.de I
.nr PQ \\n(.f
.ft 2
.if \\n(.$ \&\\$1\^\f\\n(PQ\\$2
..
. \" CW - constant width font from -ms
.de CW
.nr PQ \\n(.f
.if \\n(.$=0 .ft CW
.if \\n(.$>0 \%\&\\$3\f(CW\\$1\\f\\n(PQ\\$2
..
.de IT \" ditto to italicize argument
.nr Sf \\n(.f
\%\&\\$3\f2\\$1\f\\n(Sf\&\\$2
..
. \" TA - tabs set in ens or chars
.de TA
.ta \\$1n \\$2n \\$3n \\$4n \\$5n \\$6n \\$7n \\$8n \\$9n
..
. \" SM - make smaller size
.de SM
.ie \\n(.$ \&\\$3\s-2\\$1\s0\\$2
.el .ps -2
..
. \" LG - make larger size
.de LG
.ie \\n(.$ \&\\$3\s+2\\$1\s0\\$2
.el .ps +2
..
. \" NL - return to normal size
.de NL
.ps \\n(PS
..
. \" FS - begin footnote
.de FS
.if \\n(IF>0 .tm .FS within .FS/.FE, line \\n(.c, file \\n(.F
.if \\n(KF>0 .tm .FS won't work inside .KF, line \\n(.c, file \\n(.F
.if \\n(KS>0 .tm .FS won't work inside .KS, line \\n(.c, file \\n(.F
.nr IF 1
.ev 1
.ps \\n(PS-2
.ie \\n(VS>=41 .vs \\n(VSu-2p
.el .vs \\n(VSp-2p
.ll \\n(LLu
.br
.nr X \\n(FOu
.X "BF \\nX FS
.SP .3v
....FA \" deleted by authority of cvw, 10/17/88
..
. \" FE - end footnote
.de FE
.if !\\n(IF .tm .FE without .FS, line \\n(.c, file \\n(.F
.br
.X "END BF FE
.bp
.ev
.nr IF 0
..
. \" FA - the line for a footnote
.de FA
\l'1i'
.br
..
. \" Tm - message to be passed on
.de Tm
.ev 2
.if \\n(.$=1 .X "TM \\$1
.if \\n(.$=2 .X "TM \\$1 \\$2
.if \\n(.$=3 .X "TM \\$1 \\$2 \\$3
.if \\n(.$=4 .X "TM \\$1 \\$2 \\$3 \\$4
.if \\n(.$=5 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5
.if \\n(.$=6 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6
.if \\n(.$=7 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7
.if \\n(.$=8 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
.if \\n(.$=9 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
.br
.ev
..
.de MH
AT&T Bell Laboratories
Murray Hill, New Jersey 07974
..
.de HO
AT&T Bell Laboratories
Holmdel, New Jersey 07733
..
.de WH
AT&T Bell Laboratories
Whippany, New Jersey 07981
..
.de IH
AT&T Bell Laboratories
Naperville, Illinois 60540
..
. \" UL - underline argument, don't italicize
.de UL
\\$1\l'|0\(ul'\\$2
..
. \" UX - print $2 UNIX $1
.de UX
.ie \\n(UX \\$2\s-1UNIX\s0\\$1
.el \{\
\\$2\s-1UNIX\\s0\\$1\(rg
.nr UX 1\}
..
. \" QS - start quote
.de QS
.br
.LP
.in +\\n(QIu
.ll -\\n(QIu
..
. \" QE - end quote
.de QE
.br
.ll +\\n(QIu
.in -\\n(QIu
.LP
..
. \" B1 - begin boxed stuff
.de B1
.br
.di BB
.nr BC 0
.if "\\$1"C" .nr BC 1
.nr BE 1
..
. \" B2 - end boxed stuff
.de B2
.br
.nr BI 1n
.if \\n(.$>0 .nr BI \\$1n
.di
.nr BE 0
.nr BW \\n(dl
.nr BH \\n(dn
.ne \\n(BHu+\\n(.Vu
.nr BQ \\n(.j
.nf
.ti 0
.if \\n(BC>0 .in +(\\n(.lu-\\n(BWu)/2u
.in +\\n(BIu
.ls 1
.BB
.ls
.in -\\n(BIu
.nr BW +2*\\n(BI
.sp -1
\l'\\n(BWu\(ul'\L'-\\n(BHu'\l'|0\(ul'\h'|0'\L'\\n(BHu'
.if \\n(BC>0 .in -(\\n(.lu-\\n(BWu)/2u
.if \\n(BQ .fi
.br
..
. \" BX - boxed stuff
.de BX
\(br\|\\$1\|\(br\l'|0\(rn'\l'|0\(ul'
..
.
. \" macros for programs, etc.
.
.ig
programs are displayed between .P1/.P2 pairs
default is to indent by 1/2 inch, nofill, dP smaller
.P1 x causes an indent of x instead.
.P3 can be used to specify optional page-break points
inside .P1/.P2
..
.
. \" P1 - start of program
.de P1
.nr $1 \\n(P1
.if \\n(.$ .nr $1 \\$1n
.br
.X "SP \\n(DV P1"
.X "US P1"
.in \\n($1u
.nf
.nr v \\n(.v
.ps -\\n(dP
.vs -\\n(dVu
.ft CW
.nr t \\n(dT*\\w'x'u
.ta 1u*\\ntu 2u*\\ntu 3u*\\ntu 4u*\\ntu 5u*\\ntu 6u*\\ntu 7u*\\ntu 8u*\\ntu 9u*\\ntu 10u*\\ntu 11u*\\ntu 12u*\\ntu 13u*\\ntu 14u*\\ntu
..
. \" P2 - end of program
.de P2
.br
.ps \\n(PS
.vs \\nvu
.ft 1
.in
.X "END US P1
.X "SP \\n(DV P2"
.fi
..
. \" P3 - provides optional unpadded break in P1/P2
.de P3
.nr x \\n(DV
.nr DV 0
.P2
.P1 \\n($1u
.nr DV \\nx
..
.de [
[
..
.de ]
]
..
.IZ
.rm IZ
.so /sys/lib/tmac/tmac.srefs

View file

@ -11,17 +11,19 @@ obj *arcgen(int type) /* handles circular and (eventually) elliptical arcs */
static double prevw = HT10;
static double prevh = HT5;
static double prevrad = HT2;
static int dtox[2][4] ={ 1, -1, -1, 1, 1, 1, -1, -1 };
static int dtoy[2][4] ={ 1, 1, -1, -1, -1, 1, 1, -1 };
static int dctrx[2][4] ={ 0, -1, 0, 1, 0, 1, 0, -1 };
static int dctry[2][4] ={ 1, 0, -1, 0, -1, 0, 1, 0 };
static int nexthv[2][4] ={ U_DIR, L_DIR, D_DIR, R_DIR, D_DIR, R_DIR, U_DIR, L_DIR };
static int dtox[2][4] ={ { 1, -1, -1, 1}, {1, 1, -1, -1} };
static int dtoy[2][4] ={ {1, 1, -1, -1}, {-1, 1, 1, -1} };
static int dctrx[2][4] ={ {0, -1, 0, 1}, {0, 1, 0, -1} };
static int dctry[2][4] ={ {1, 0, -1, 0}, {-1, 0, 1, 0} };
static int nexthv[2][4] ={ {U_DIR, L_DIR, D_DIR, R_DIR}, {D_DIR, R_DIR, U_DIR, L_DIR} };
double dx2, dy2, ht, phi, r, d;
int i, head, to, at, cw, invis, ddtype, battr;
obj *p, *ppos;
double fromx, fromy, tox, toy, fillval = 0;
Attr *ap;
tox=toy=0.0; /* Botch? (gcc) */
prevrad = getfval("arcrad");
prevh = getfval("arrowht");
prevw = getfval("arrowwid");
@ -210,6 +212,7 @@ void arc_extreme(double x0, double y0, double x1, double y1, double xc, double y
extreme(xmax, ymax);
}
int
quadrant(double x, double y)
{
if ( x>=0.0 && y> 0.0) return(1);

View file

@ -12,6 +12,8 @@ obj *circgen(int type)
obj *p, *ppos;
Attr *ap;
r = r2 = 0.0; /* Botch? (gcc) */
battr = at = 0;
with = xwith = ywith = fillval = ddval = 0;
t = (type == CIRCLE) ? 0 : 1;

View file

@ -26,7 +26,7 @@ void pushsrc(int type, char *ptr) /* new input source */
srcp->type = type;
srcp->sp = ptr;
if (dbg > 1) {
printf("\n%3d ", srcp - src);
printf("\n%3d ", (int) (srcp - src));
switch (srcp->type) {
case File:
printf("push file %s\n", ((Infile *)ptr)->fname);
@ -57,7 +57,7 @@ void popsrc(void) /* restore an old one */
if (srcp <= src)
ERROR "too many inputs popped" FATAL;
if (dbg > 1) {
printf("%3d ", srcp - src);
printf("%3d ", (int) (srcp - src));
switch (srcp->type) {
case File:
printf("pop file\n");
@ -142,6 +142,7 @@ char *delimstr(char *s) /* get body of X ... X */
return tostring(buf);
}
int
baldelim(int c, char *s) /* replace c by balancing entry in s */
{
for ( ; *s; s += 2)
@ -187,11 +188,12 @@ void dodef(struct symtab *stp) /* collect args and switch input to defn */
ap->argstk[i] = "";
if (dbg)
for (i = 0; i < argcnt; i++)
printf("arg %d.%d = <%s>\n", ap-args, i+1, ap->argstk[i]);
printf("arg %d.%d = <%s>\n", (int) (ap-args), i+1, ap->argstk[i]);
argfp = ap;
pushsrc(Macro, stp->s_val.p);
}
int
getarg(char *p) /* pick up single argument, store in p, return length */
{
int n, c, npar;
@ -232,6 +234,7 @@ extern int thru;
extern struct symtab *thrudef;
extern char *untilstr;
int
input(void)
{
register int c;
@ -248,10 +251,13 @@ input(void)
return *ep++ = c;
}
int
nextchar(void)
{
register int c;
c = 0; /* Botch: gcc */
loop:
switch (srcp->type) {
case Free: /* free string */
@ -289,9 +295,9 @@ nextchar(void)
ERROR "argfp underflow" FATAL;
popsrc();
goto loop;
} else if (c == '$' && isdigit(*srcp->sp)) {
} else if (c == '$' && isdigit((unsigned char) *srcp->sp)) {
int n = 0;
while (isdigit(*srcp->sp))
while (isdigit((unsigned char) *srcp->sp))
n = 10 * n + *srcp->sp++ - '0';
if (n > 0 && n <= MAXARGS)
pushsrc(String, argfp->argstk[n-1]);
@ -380,7 +386,7 @@ void do_thru(void) /* read one line, make into a macro expansion */
ap->argstk[i] = "";
if (dbg)
for (i = 0; i < argcnt; i++)
printf("arg %d.%d = <%s>\n", ap-args, i+1, ap->argstk[i]);
printf("arg %d.%d = <%s>\n", (int) (ap-args), i+1, ap->argstk[i]);
if (strcmp(ap->argstk[0], ".PE") == 0) {
thru = 0;
thrudef = 0;
@ -400,6 +406,7 @@ void do_thru(void) /* read one line, make into a macro expansion */
pushsrc(Macro, thrudef->s_val.p);
}
int
unput(int c)
{
if (++pb >= pbuf + sizeof pbuf)
@ -580,7 +587,7 @@ void shell_init(void) /* set up to interpret a shell command */
void shell_text(char *s) /* add string to command being collected */
{
while (*shellp++ = *s++)
while ((*shellp++ = *s++))
;
shellp--;
}

View file

@ -47,6 +47,7 @@ void getdata(void), setdefaults(void);
void setfval(char *, double);
int getpid(void);
int
main(int argc, char *argv[])
{
char buf[20];
@ -120,27 +121,27 @@ static struct {
double val;
short scalable; /* 1 => adjust when "scale" changes */
} defaults[] ={
"scale", SCALE, 1,
"lineht", HT, 1,
"linewid", HT, 1,
"moveht", HT, 1,
"movewid", HT, 1,
"dashwid", HT10, 1,
"boxht", HT, 1,
"boxwid", WID, 1,
"circlerad", HT2, 1,
"arcrad", HT2, 1,
"ellipseht", HT, 1,
"ellipsewid", WID, 1,
"arrowht", HT5, 1,
"arrowwid", HT10, 1,
"arrowhead", 2, 0, /* arrowhead style */
"textht", 0.0, 1, /* 6 lines/inch is also a useful value */
"textwid", 0.0, 1,
"maxpsht", MAXHT, 0,
"maxpswid", MAXWID, 0,
"fillval", 0.7, 0, /* gray value for filling boxes */
NULL, 0, 0
{ "scale", SCALE, 1, },
{ "lineht", HT, 1, },
{ "linewid", HT, 1, },
{ "moveht", HT, 1, },
{ "movewid", HT, 1, },
{ "dashwid", HT10, 1, },
{ "boxht", HT, 1, },
{ "boxwid", WID, 1, },
{ "circlerad", HT2, 1, },
{ "arcrad", HT2, 1, },
{ "ellipseht", HT, 1, },
{ "ellipsewid", WID, 1, },
{ "arrowht", HT5, 1, },
{ "arrowwid", HT10, 1, },
{ "arrowhead", 2, 0, }, /* arrowhead style */
{ "textht", 0.0, 1, }, /* 6 lines/inch is also a useful value */
{ "textwid", 0.0, 1, },
{ "maxpsht", MAXHT, 0, },
{ "maxpswid", MAXWID, 0, },
{ "fillval", 0.7, 0, }, /* gray value for filling boxes */
{ NULL, 0, 0 }
};
void setdefaults(void) /* set default sizes for variables like boxht */

View file

@ -9,6 +9,7 @@ int whatpos(obj *p, int corner, double *px, double *py);
void makeattr(int type, int sub, YYSTYPE val);
YYSTYPE getblk(obj *, char *);
int
setdir(int n) /* set direction (hvmode) from LEFT, RIGHT, etc. */
{
switch (n) {
@ -20,6 +21,7 @@ setdir(int n) /* set direction (hvmode) from LEFT, RIGHT, etc. */
return(hvmode);
}
int
curdir(void) /* convert current dir (hvmode) to RIGHT, LEFT, etc. */
{
switch (hvmode) {
@ -32,7 +34,8 @@ curdir(void) /* convert current dir (hvmode) to RIGHT, LEFT, etc. */
return 0;
}
double getcomp(obj *p, int t) /* return component of a position */
double
getcomp(obj *p, int t) /* return component of a position */
{
switch (t) {
case DOTX:
@ -207,7 +210,9 @@ int whatpos(obj *p, int corner, double *px, double *py) /* what is the position
{
double x, y, x1, y1;
dprintf("whatpos %o %d %d\n", p, p->o_type, corner);
x1 = y1 = 0.0; /* Botch? (gcc) */
dprintf("whatpos %p %d %d\n", p, p->o_type, corner);
x = p->o_x;
y = p->o_y;
if (p->o_type != PLACE && p->o_type != MOVE) {
@ -320,7 +325,7 @@ obj *getlast(int n, int t) /* find n-th previous occurrence of type t */
dprintf("got a last of x,y= %g,%g\n", p->o_x, p->o_y);
return(p);
}
ERROR "there is no %dth last", n FATAL;
ERROR "there is no %dth last", n WARNING;
return(NULL);
}
@ -343,7 +348,7 @@ obj *getfirst(int n, int t) /* find n-th occurrence of type t */
dprintf("got a first of x,y= %g,%g\n", p->o_x, p->o_y);
return(p);
}
ERROR "there is no %dth ", n FATAL;
ERROR "there is no %dth ", n WARNING;
return(NULL);
}

View file

@ -133,7 +133,7 @@ WS [ \t]
<A>ccw { yylval.i = CCW; return(ATTR); }
<A>invis(ible)? { yylval.i = INVIS; return(ATTR); }
<A>noedge { yylval.i = INVIS; return ATTR; }
<A>fill return(yylval.i = FILL);
<A>fill { yylval.i = FILL; return ATTR; }
<A>solid ;
<A>dot(ted)? return(yylval.i = DOT);
<A>dash(ed)? return(yylval.i = DASH);

View file

@ -25,6 +25,8 @@ void print(void)
int fill, vis, invis;
double x0, y0, x1, y1, ox, oy, dx, dy, ndx, ndy;
x1 = y1 = 0.0; /* Botch? (gcc) */
for (i = 0; i < nobj; i++) {
p = objlist[i];
ox = p->o_x;
@ -180,6 +182,8 @@ void dotline(double x0, double y0, double x1, double y1, int ddtype, double ddva
int i, numdots;
double a, b, dx, dy;
b = 0.0; /* Botch? (gcc) */
if (ddval == 0)
ddval = prevval;
prevval = ddval;

View file

@ -12,7 +12,7 @@ YYSTYPE getvar(char *s) /* return value of variable s (usually pointer) */
p = lookup(s);
if (p == NULL) {
if (islower(s[0]))
if (islower((int) s[0]))
ERROR "no such variable as %s", s WARNING;
else
ERROR "no such place as %s", s WARNING;