/* * Copyright (c) 1987 University of Maryland Department of Computer Science. * All rights reserved. Permission to copy for any purpose is hereby granted * so long as this copyright notice remains intact. */ #ifndef lint static char rcsid[] = "$Header: verser2.c,v 2.5 87/06/16 17:15:19 chris Exp $"; #endif /* * Verser2 -- Second half of DVI to Versatec driver * * Reads pre-sorted pages as put out by verser1, and shovels bitmaps * out to the Versatec as fast as possible. Warning: there is some * inline assembly code used in inner loops, where the C compiler * produced particuarly poor code. * * We use a technique known as a `band buffer', where we keep track * of what has yet to be written to the Versatec in a buffer that * represents a `band' across the current page, analagous to a magnifying * bar across the page. Only the region in the band can be written, * and the band moves only downward; this is why verser1 must sort * each page, at least by y coordinate. This also implies that the * `tallest' object we can write is the same height as the band. This * is a problem for large characters. For these there is some (as yet * unimplemented) code that will ask for a `part' of each character * to be drawn into the band. The character would then be repeated * with a request for the next part when the band has moved down to * just below the bottom of the previous part. Rules are also broken * up as appropriate (and that code *is* implemented). * * Another important point is that the band buffer is treated as a * `cylinder' rather than a `strip': we write bits onto the cylinder, * then roll it forward over the page, moving the bits off the cylinder * and onto the paper, leaving that part of the cylinder clean, ready * for more bits. The variable `CurRow' points at the current row * in the buffer/on the cylinder, and `FirstRow' and `LastRow' bound * the `dirty' part of the cylinder. Modular arithmetic suffices to * change linear to cylindrical. * * Whenever CurRow is more than MIN_OUT rows ahead of FirstRow, we * write out that much of the cylinder, cleaning it. This keeps the * paper moving, lest the developer soak one spot and produce `streaky' * output. * * Yet another point of note is that because the band always moves * `down' on the page, we need only a positive offset from the current * row to move to a new row. This means (among other things) that we * can use negative offsets for special purposes. */ #include #include #include #include #ifdef ACCOUNT_FILE #include #endif ACCOUNT_FILE #include "types.h" #include "conv.h" #include "fio.h" #include "font.h" #include "verser.h" #define SPEED_HACK char *ProgName; extern int errno; extern char *optarg; extern int optind; /* Globals */ jmp_buf failbuf; /* in case of Versatec write() problems */ struct font *Fonts[NFONTS]; /* the fonts */ char TeXFontDesc[256]; /* getenv("TEXFONTDESC") from verser1 */ int RasterOrientation; /* ROT_NORM or ROT_RIGHT, based on HFlag */ int DFlag; /* -d => output discarded */ int HFlag; /* -h => horizontal (rotated bitmaps) */ int SFlag; /* -s => silent processing */ int TFlag; /* -t => output to tape */ int Debug; /* -D => debug flag */ char VBuffer[ROWS][COLUMNS]; /* Versatec band buffer */ int CurRow; /* current row in buffer */ int CurCol; /* current column in buffer */ int FirstRow; /* the first row used */ int LastRow; /* the last row used */ int NLines; /* counts lines; used for pagefeeds */ int Pages; /* counts pages; for accounting */ int vp; /* Versatec file descriptor */ int pltmd[] = {VPLOT, 0, 0};/* print & plot mode, for ioctl */ int prtmd[] = {VPRINT, 0, 0}; #ifdef ACCOUNT_FILE struct passwd *getpwuid(); #endif ACCOUNT_FILE /* * RowsBetween tells how many rows (in cylindrical arithmetic) there * are between the first position and the second. If the second value * is less than the first value, add ROWS to do the appropriate modular * arithmetic. We cannot use `%' as C `%' is machine-dependent with * respect to negative values. */ #define RowsBetween(f, n) ((n) >= (f) ? (n) - (f) : (n) - (f) + ROWS) /* * This is it... on your marks ... get set ... main! */ main(argc, argv) int argc; register char **argv; { register int c; register char *s; int dpi, usermag, num, denom, dvimag; int VFlag = 0; #ifdef ACCOUNT_FILE int acct_fd; acct_fd = open(ACCOUNT_FILE, 1); (void) setuid(getuid()); #endif ACCOUNT_FILE ProgName = *argv; if (setjmp(failbuf)) { /* still have to do accounting */ #ifdef ACCOUNT_FILE /* * Kind of strange to charge for a printout that failed * because we ran out of paper, but that was the way they * wanted it.... */ if (NLines) Pages++;/* count the partial page */ DoAccount(acct_fd); #endif ACCOUNT_FILE exit(1); /* NOTREACHED */ } while ((c = getopt(argc, argv, "dstv:D")) != EOF) { switch (c) { case 'd': /* output to /dev/null */ DFlag++; break; case 's': /* silent processing except for errors */ SFlag++; break; case 't': /* output to tape (not implemented) */ TFlag++; error(0, 0, "tape option not yet implemented"); break; case 'v': /* Versatec already open as fd */ VFlag++; vp = atoi(optarg); break; case 'D': Debug++; break; case '?': fprintf(stderr, "Usage: %s [-d] [-s] [-t] [file]\n", ProgName); exit(1); } } if (optind < argc) if (freopen(argv[optind], "r", stdin) == NULL) error(1, 0, "can't open %s", argv[optind]); HFlag = getchar(); if ((HFlag >> 1) != VERSION) error(1, 0, "input file is not version %d", VERSION); HFlag &= 1; RasterOrientation = HFlag ? ROT_RIGHT : ROT_NORM; s = TeXFontDesc; c = GetLong(stdin); while (--c >= 0) *s++ = getchar(); if (feof(stdin)) (void) GetByte(stdin); /* let GetByte do error */ *s = 0; dpi = GetLong(stdin); usermag = GetLong(stdin); num = GetLong(stdin); denom = GetLong(stdin); dvimag = GetLong(stdin); SetConversion(dpi, usermag, num, denom, dvimag); fontinit(*TeXFontDesc ? TeXFontDesc : (char *) NULL); ReadFonts(); if (DFlag) { (void) fprintf(stderr, "Output will be discarded\n"); (void) fflush(stderr); vp = open("/dev/null", 1); } else { if (!VFlag) { vp = open(VERSATEC_FILE, 1); if (vp < 0) { if (errno == ENXIO) error(1, 0, "\ can't open versatec---already in use"); if (errno == EIO) error(1, 0, "\ can't open versatec---device offline"); error(1, errno, "can't open %s", VERSATEC_FILE); } } ioctl(vp, VSETSTATE, pltmd); } if (!HFlag) CutMarks(); /* initial cut marks */ ReadInput(); FormFeed(0); /* end up in print mode */ if (!SFlag) (void) putc('\n', stderr); #ifdef ACCOUNT_FILE DoAccounting(acct_fd); #endif ACCOUNT_FILE exit(0); } #ifdef ACCOUNT_FILE /* * Accounting is done by writing the program name ("tex"), the user name, * and the number of pages at the end of the file. (The program name is * for statistics.) */ DoAccounting(fd) int fd; { register struct passwd *p; char buf[128]; if (fd < 0 || (p = getpwuid(getuid())) == 0) return; /* * The '+ 2' is because there is an extra page at the end, and * because the Versatec does pagefeeds when it is opened. */ (void) sprintf(buf, "tex %s %d\n", p->pw_name, Pages + 2); (void) lseek(fd, 0L, 2); (void) write(fd, buf, strlen(buf)); } #endif ACCOUNT_FILE /* * Read the font definitions. * * Anti-streak hack: get the rasters ahead of time, #ifdef SPEED_HACK. */ ReadFonts() { register struct font *f, **fp; register int c; register char *s; #ifdef SPEED_HACK register struct glyph *g; #endif i32 mag, dsize; char *fname; char nm[512]; if (!SFlag) (void) fprintf(stderr, "[fonts:\n"); fp = Fonts; while (GetByte(stdin) == 1) { (void) GetLong(stdin); /* checksum */ mag = GetLong(stdin); /* magfactor */ dsize = GetLong(stdin); /* design size */ c = GetLong(stdin); s = nm; while (--c >= 0) *s++ = getchar(); if (feof(stdin)) (void) GetByte(stdin); /* let GetByte do error */ *s = 0; f = GetFont(nm, mag, dsize, "versatec", &fname); if (f == NULL) { GripeCannotGetFont(nm, mag, dsize, "versatec", fname); exit(1); /* NOTREACHED */ } if (Debug) { (void) fprintf(stderr, "[%s -> %s]\n", Font_TeXName(f), fname); (void) fflush(stderr); } if (!SFlag) { register char *t = fname; s = fname; while (*s) if (*s++ == '/' && *s) t = s; (void) fprintf(stderr, " %s\n", t); } #ifdef SPEED_HACK for (c = 0; c < 128; c++) { g = GLYPH(f, c); if (GVALID(g)) (void) RASTER(g, f, RasterOrientation); } #endif *fp++ = f; } if (!SFlag) (void) fprintf(stderr, "]\n"); } /* * Read the input stream, decode it, and put character rasters or rules at * the positions given. */ ReadInput() { register int yx, fcp, height; /* * Loop forever. I had a `for (;;)' but everything crept off the * right side of the screen. */ next: fGetLong(stdin, yx); /* position */ fGetLong(stdin, fcp); /* character, most likely */ if (feof(stdin)) return; /* done */ /* * A `position' of -1 indicates either a rule or an end of page. * Anything else is a character. */ if (yx != -1) { /* place character */ register struct glyph *g; register struct font *f; register int fnum; /* * Any delta-y required is stored in the upper 16 bits of yx. */ if ((height = yx >> 16) != 0) MoveDown(height); /* * Extract the x, font, char, and part info into CurCol, * fnum, yx, and fcp. */ CurCol = yx & 0xffff; fnum = fcp >> FONTSHIFT; yx = (fcp >> CHARSHIFT) & CHARMASK; fcp = fcp & PARTMASK; f = Fonts[fnum]; /* trusting */ g = GLYPH(f, yx); /* * In case this character does not fit, write * out the used part of the band. It had better * fit afterward.... */ height = g->g_height; if (height >= ROWS - RowsBetween(FirstRow, CurRow)) DumpTopOfBand(); if (fcp) /* cannot handle these yet */ error(0, 0, "\ part code not implemented; skipping char %d in %s", yx, f->f_path); else if (HASRASTER(g)) { #ifdef SPEED_HACK /* XXX, but saves time */ VWriteChar(g->g_raster, height, g->g_width); #else VWriteChar(RASTER(g, f, RasterOrientation), height, g->g_width); #endif } /* dump if we can do at least MIN_OUT rows */ if (RowsBetween(FirstRow, CurRow) > MIN_OUT) DumpTopOfBand(); goto next; /* done with character */ } /* * If the `character' is negative, we need to move down first, * possibly because this is an end-of-page. If this is not the * end of the page, it must be a rule. */ if (fcp < 0) { /* move down */ yx = -fcp; fGetLong(stdin, fcp); /* junk */ fGetLong(stdin, fcp); if (fcp == 0) { /* end page */ /* dump entire band */ WriteBuf(&VBuffer[0][0], FirstRow, LastRow, 1); CurRow = LastRow = FirstRow; if (!HFlag) { WriteBlanks(yx - NLines); CutMarks(); } else FormFeed(1); if (!SFlag) (void) fprintf(stderr, "."); NLines = 0; Pages++; goto next; /* all done */ } MoveDown(yx); /* must be a rule; move down by yx rows */ } /* * At this point we have a rule to put at the current * position, CurRow. */ height = (fcp & 0xff00) >> 8; /* make sure it fits */ if (height >= ROWS - RowsBetween(FirstRow, CurRow)) DumpTopOfBand(); VWriteRule(fcp); goto next; /* done with rule */ } /* * Write the given raster for the given character. * * Basically, the task is to move bits from the raster to the Versatec * buffer. However, because the character being plotted can be on an * arbitrary bit boundary, things are not as simple as we might like. * The solution used here is to shift each raster value right, OR it * into the buffer, then (at the next location) OR in the bits that * `fell off the right edge'. */ VWriteChar(rastp, height, width) char *rastp; /* raster pointer */ int height, width; /* height & width of char */ { register char *bp; /* Versatec buffer pointer [r11] */ register char *rp; /* raster pointer [r10] */ register int rshift; /* right shift index [r9] */ register int lshift; /* left shift index [r8] */ register int j; /* width loop downcounter */ register int o; /* offset to next row in buffer */ int row; /* current row in buffer */ int col; /* column in buffer of left edge */ int i; /* height loop downcounter */ int w; /* raster width (bytes) */ if ((rp = rastp) == NULL) return; /* an all-white character (`cannot happen') */ row = CurRow; col = CurCol >> 3; i = height; w = (width + 7) >> 3; o = COLUMNS - w; #if defined(lint) || !defined(vax) rshift = CurCol & 7; lshift = 8 - rshift; #else lint || !vax rshift = -(CurCol & 7); /* Vax does '>>' as negative '<<' */ lshift = 8 + rshift; #endif lint || !vax bp = &VBuffer[row][col]; #define avoiding_shifts_is_faster /* but is it??? */ #ifdef avoiding_shifts_is_faster /* * One out of eight or so times, the shift values will be * zero. This makes the code run faster. */ if (rshift == 0) { while (--i >= 0) { j = w; while (--j >= 0) *bp++ |= *rp++; if (++row >= ROWS) { row = 0; bp = &VBuffer[0][col]; } else bp += o; } } else #endif { while (--i >= 0) { j = w; while (--j >= 0) { #if defined(lint) || !defined(vax) *bp++ |= (*rp & 255) >> rshift; *bp |= (*rp++ & 255) << lshift; #else lint || !vax /* * THE FOLLOWING ASSEMBLY CODE IS INSERTED * BECAUSE THE COMPILER CAN'T OPTIMIZE THE * C CODE WORTH A DARN */ asm(" movzbl (r10)+,r1 # *rp++ & 255"); asm(" ashl r9,r1,r0 # >> rshift"); asm(" bisb2 r0,(r11)+ # *bp++ |="); asm(" ashl r8,r1,r0 # << lshift"); asm(" bisb2 r0,(r11) # *bp |="); #endif lint || !vax } if (++row >= ROWS) { row = 0; bp = &VBuffer[0][col]; } else bp += o; } } j = height + CurRow - 1;/* have now set bits this far */ if (j >= ROWS) j -= ROWS; /* keep it modular */ /* * There are two cases. Either the buffer is not currently wrapped, * in which case the regions past LastRow or before FirstRow extend * it; or it is wrapped, in which case the region between LastRow * and FirstRow extends it: * * case 1 case 2 * -------- -------- * | | last ->| XXXX | * first ->| XXXX | | | * | XXXX | | | * last ->| XXXX | first ->| XXXX | * | | | XXXX | * -------- -------- * * The `X's mark the region that is in use; the blank spaces * mark the region that causes the `last' value to change. */ if (FirstRow <= LastRow) { /* first case: not wrapped */ if (j < FirstRow || j > LastRow) LastRow = j; } else { /* second case: wrapped */ if (j > LastRow && j < FirstRow) LastRow = j; } } /* * Write a rule at the current row according to the (packed) information in * 'info'. This includes the x position and the height and width of the * rule. */ VWriteRule(info) int info; { register char *bp; /* buffer pointer */ register int j; register int lbits; /* bits along left */ register int rbits; /* bits along right */ register int o; /* offset to next row */ register int i; register int full; /* number of 8 bit words to set */ register int height; /* rule height */ register int width; /* rule width */ register int row; register int col; i = info; CurCol = (i & 0x7fff0000) >> 16; height = (i & 0xff00) >> 8; width = i & 0xff; col = CurCol >> 3; row = CurRow; j = CurCol & 7; /* bit # of start position */ lbits = 0xff >> j; /* bits to set along left edge */ /* there are 8-j bits set in lbits */ o = 8 - j - width; if (o > 0) { /* then lbits has o too many bits set */ lbits >>= o; lbits <<= o; /* puts zeros into o righthand bits */ rbits = 0; full = 0; } else { i = (CurCol + width) & 7; /* bit # of ending position */ rbits = 0xff00 >> i; /* bits to set along right edge */ /* there are i bits set in rbits (well, in the low byte) */ full = (width - i - (8 - j)) >> 3; } bp = &VBuffer[row][col]; i = height; /* Often "full" is zero, which makes things faster */ if (full) { /* oh well */ o = COLUMNS - full - 1; while (--i >= 0) { *bp++ |= lbits; for (j = full; --j >= 0;) *bp++ |= 0xff; *bp |= rbits; if (++row >= ROWS) { row = 0; bp = &VBuffer[0][col]; } else bp += o; } } else { o = COLUMNS - 1; while (--i >= 0) { *bp++ |= lbits; *bp |= rbits; if (++row >= ROWS) { row = 0; bp = &VBuffer[0][col]; } else bp += o; } } i = CurRow + height - 1; if (i >= ROWS) i -= ROWS; /* * This is another way of expressing both cases 1 and 2 in * VWriteChar(). I think the other way is likely to be * faster, and characters occur far more frequently; but this * is the more readable by far. */ if (RowsBetween(FirstRow, LastRow) < RowsBetween(FirstRow, i)) LastRow = i; } /* * Dump out the top portion of the band (rows [Firstrow, CurRow)). */ DumpTopOfBand() { /* * To exclude CurRow, subtract one, but modularly, modularly! */ WriteBuf(&VBuffer[0][0], FirstRow, CurRow ? CurRow - 1 : ROWS - 1, 1); FirstRow = CurRow; } /* * Move the current row in the band buffer down by delta rows, by, * if necessary, writing out the currently-used portion of the buffer. */ MoveDown(delta) register int delta; { if (delta >= ROWS - RowsBetween(FirstRow, CurRow)) { /* * Need to roll the cylinder forward. Write out the used * part, and then write as many blank lines as necessary. */ WriteBuf(&VBuffer[0][0], FirstRow, LastRow, 1); WriteBlanks(delta - RowsBetween(CurRow, LastRow) - 1); CurRow = LastRow = FirstRow; /* band is now empty */ } else { /* * Because RowsBetween returns nonnegative integers, we * know delta <= ROWS, so can do mod more quickly thus: */ CurRow += delta; /* result < 2*ROWS */ if (CurRow >= ROWS) CurRow -= ROWS; /* now result < ROWS */ } } /* * Write the lines between the first and last inclusive from the given * buffer. If 'cl', clear after writing. */ WriteBuf(buf, first, last, cl) register char *buf; register int first, last; { if (first > last) { /* recursively do wrapped part first */ WriteBuf(buf, first, ROWS - 1, cl); first = 0; } buf = &buf[first * COLUMNS]; last = COLUMNS * (first = last - first + 1); /* * If the write fails, the Versatec is probably out of paper, and in * any case, things are probably in bad shape. */ if (write(vp, buf, last) != last) { error(0, errno, "Versatec write error"); longjmp(failbuf, 1); } if (cl) bzero(buf, (unsigned) last); NLines += first; } /* * Write 'n' blank lines. */ WriteBlanks(n) register int n; { register int k; static char nullbuf[MIN_OUT][COLUMNS]; while (n > 0) { k = n > MIN_OUT ? MIN_OUT : n; WriteBuf(&nullbuf[0][0], 0, k - 1, 0); n -= k; } } /* * Write cut marks. We borrow row 0 of VBuffer for this. */ CutMarks() { register short *bp = (short *) &VBuffer[0][0]; *bp = 0xffff; bp[(COLUMNS / 2) - 1] = 0xffff; WriteBuf(&VBuffer[0][0], 0, 0, 1); } /* * Perform a page feed. Restore plot mode if `setplot'. */ FormFeed(setplot) int setplot; { ioctl(vp, VSETSTATE, prtmd); (void) write(vp, "\f", 2); /* \0 really IS required occasionally */ if (setplot) ioctl(vp, VSETSTATE, pltmd); }