page@swan.ulowell.edu (Bob Page) (11/10/88)
Submitted-by: paolucci@snll-arpagw.llnl.gov (Sam Paolucci) Posting-number: Volume 2, Issue 58 Archive-name: printers/lwf.1 Lwf converts ASCII text files to PostScript. Most of the processing is done on the host rather than on the printer. The name "lwf", perhaps poorly chosen, stands for "LaserWriter Filter". # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # Makefile # README # README2 # ehandler.ps # lwf.c # lwf.cfg # lwf.man # lwf.prologue # psclear.uu # range.c # This archive created: Wed Nov 9 21:07:28 1988 cat << \SHAR_EOF > Makefile # cc flags CFLAGS = +fi +L # Where to put the PostScript prologue file PROLOGUE = S:lwf.prologue # Environment options: # # -DAMIGA Since we don't have the passwd information # -DPROLOGUE="$(PROLOGUE)" # -DPR=\"where pr is, e.g. /bin/pr\" # -DSYSV For System V (assumes SYSV-style pr) # -DREVERSE=0 Set to non-zero if page reversal is to be the default # -DHOSTNAME=\"yourhostname\" If you don't have the hostname() system call ENV = -DREVERSE=1 -DAMIGA lwf: lwf.o range.o ln -o lwf lwf.o range.o -lsock32 -lma32 -lc32 lwf.o: lwf.c cc $(CFLAGS) $(ENV) lwf.c range.o: range.c cc $(CFLAGS) range.c clean: delete lwf.o range.o SHAR_EOF cat << \SHAR_EOF > README NOTES ----- Recently I ran into a posting of lwf in one of the UNIX groups on USENET. I ported it to the Amiga since I had a real need for something like this. It is relatively full featured, and I added a couple of additional options myself and fixed a few things. With appropriate mods to the makefile the code should compile and run fine on AmigaDOS as well as on UNIX machines (I used Manx). To recompile on the Amiga you will need Ameristar's socket library. On the Amiga it will look for environment variables USER and HOSTNAME which will be printed on the header page along with the date and file names. The environment variables are as used by Manx and ARP and can be set using the set command. If they are not set, the fields will be printed blank. The program generates a PostScript prologue file which is identical to lwf.prologue. If you want to use your own or a modified version of lwf.prologue, you need to use the -P command line option. If you have difficulty in printing a large file, it may be because the default is to print the pages in reverse order. To accomplish this feat, a temporary file is created in RAM: and deleted afterwards. For a large file this will require a corresponding amount of memory. If this is your problem, you can easily get around it by turning off the page reversal with the -r flag. I'm hoping to tie this code to a print spooler running over an Ethernet network. When that happens I'll post it. In the meantime the -p flag that appears in the manual page is not currently implemented on the Amiga. Defaults of all command line arguments can be specified in the configuration file lwf.cfg which should be placed in S:. An example file is enclosed. This file consists of lines in which the initial character in each line specifies the command. If the initial character is a space, an asterisk, a pound sign, a semicolon, or a line feed, the line is ignored. This makes it useful for commenting the file. White space following the command is optional. Comments can also be placed on the individual lines after the complete command is specified, as shown in the example. Also included in the directory are two useful files. If you are lucky enough to have a PostScript laser printer tied directly to your Amiga, you should download ehandler.ps into the printer once and only once, preferably from within your startup-sequence. In the case you should send a file to the printer which contains an error, this file, which is an official Adobe error handler, will generate an error description. The second file, psclear, should be downloaded into the laser printer when the printer seems to just be waiting for more information AND you have not used ehandler.ps. This file will send an end-of-file to the laser printer. Enjoy. Dr. Samuel Paolucci 1351 Roselli Dr. Livermore, CA 94550 (415)294-2018 ARPA: paolucci@snll-arpagw.llnl.gov SHAR_EOF cat << \SHAR_EOF > README2 Lwf converts ASCII text files to PostScript. It has been tested on the Apple LaserWriter/LaserWriter+ and the NEC Silentwriter LC-890. Most of the processing is done on the host rather than on the printer. The name "lwf", perhaps poorly chosen, stands for "LaserWriter Filter". There may be a couple of minor Berkeley-isms in the program (see the routine setup()) and it is somewhat Unix dependent (eg., popen(), /bin/pr are expected). The output conforms to the Adobe 2.0 file structuring conventions. The program is set up to use the (fixed-width) Courier font. If you use another font you'll have to modify the mapping table in lwf.c. I can't recommend a proportional font for this purpose. The distribution includes the following files: README2 This file Makefile lwf.c LaserWriter filter to convert ASCII text to PostScript range.c Routine to check if a number is in a given range lwf.1.man Manual page for lwf lwf.prologue Default PostScript support file Please check the Makefile and the configurable #defines in lwf.c. Check if the man page needs to be revised for your environment. You can then proceed with 'make install'. If you come across any bugs, including nonconformance to Adobe 2.0, or make any changes you'd like to share please send mail to me rather than posting to the net. Enjoy. ----- Barry Brachman | {ihnp4!alberta,uw-beaver,uunet}! Dept. of Computer Science| ubc-vision!ubc-csgrads!brachman Univ. of British Columbia| brachman@grads.cs.ubc.cdn Vancouver, B.C. V6T 1W5 | brachman%ubc.csnet@csnet-relay.arpa (604) 228-4327 | brachman@ubc.csnet SHAR_EOF cat << \SHAR_EOF > ehandler.ps %!PS-Adobe-2.0 % ehandler.ps -- Downloaded Error Break-page handler % Copyright (c) 1984, 1985, 1986 Adobe Systems Incorporated. % All Rights Reserved. 0000 % exitserver password /$brkpage where { %ifelse pop pop (Error Handler in place - not loaded again\n) print flush stop }{ %else dup serverdict begin statusdict begin checkpassword { %ifelse (Error Handler downloaded.\n)print flush exitserver }{ %else pop (Bad Password on loading error handler!!!\n) print flush stop } ifelse } ifelse /$brkpage 64 dict def $brkpage begin /prnt { %def dup type /stringtype ne { =string cvs } if dup length 6 mul /tx exch def /ty 10 def currentpoint /toy exch def /tox exch def 1 setgray newpath tox toy 2 sub moveto 0 ty rlineto tx 0 rlineto 0 ty neg rlineto closepath fill tox toy moveto 0 setgray show } bind def /nl { %def currentpoint exch pop lmargin exch moveto 0 -10 rmoveto } def /== { /cp 0 def typeprint nl } def /typeprint { dup type dup currentdict exch known {exec}{ unknowntype } ifelse } readonly def /lmargin 72 def /rmargin 72 def /tprint { %def dup length cp add rmargin gt { nl /cp 0 def } if dup length cp add /cp exch def prnt } readonly def /cvsprint { =string cvs tprint ( ) tprint } readonly def /unknowntype { %def exch pop cvlit (??) tprint cvsprint } readonly def /integertype { cvsprint } readonly def /realtype { cvsprint } readonly def /booleantype { cvsprint } readonly def /operatortype { (//) tprint cvsprint } readonly def /marktype { pop (-mark- ) tprint } readonly def /dicttype { pop (-dictionary- ) tprint } readonly def /nulltype { pop (-null- ) tprint } readonly def /filetype { pop (-filestream- ) tprint } readonly def /savetype { pop (-savelevel- ) tprint } readonly def /fonttype { pop (-fontid- ) tprint } readonly def /nametype { %def dup xcheck not { (/) tprint } if cvsprint } readonly def /stringtype { %def dup rcheck { %ifelse (\() tprint tprint (\)) tprint }{ %else pop (-string- ) tprint } ifelse }readonly def /arraytype { %def dup rcheck { %ifelse dup xcheck { %ifelse ({) tprint { typeprint } forall (}) tprint }{ %else ([) tprint { typeprint } forall (]) tprint } ifelse }{ %else pop (-array- ) tprint } ifelse } readonly def /packedarraytype { %def dup rcheck { %ifelse dup xcheck { %ifelse ({) tprint { typeprint } forall (}) tprint }{ %else ([) tprint { typeprint } forall (]) tprint } ifelse }{ %else pop (-packedarray- ) tprint } ifelse } readonly def /courier /Courier findfont 10 scalefont def /OLDhandleerror errordict /handleerror get def end %$brkpage /handleerror { %put systemdict begin $error begin $brkpage begin newerror { %ifelse /newerror false store vmstatus pop pop 0 ne { grestoreall } if initgraphics courier setfont lmargin 720 moveto (ERROR: ) prnt errorname prnt nl (OFFENDING COMMAND: ) prnt /command load prnt $error /ostack known { %if nl nl (STACK:) prnt nl nl $error /ostack get aload length { == } repeat } if systemdict /showpage get exec /newerror true store /OLDhandleerror load end end end exec }{ %else end end end } ifelse } dup 0 systemdict put % replace name by actual dict object dup 4 $brkpage put % replace name by dict object bind readonly errordict 3 1 roll put % put proc in errordict as /handleerror SHAR_EOF cat << \SHAR_EOF > lwf.c /* vi: set tabstop=4 : */ /* * lwf - Convert ASCII text to PostScript * * Usage: lwf [-c#] [-d] [-i#] [-l] [-m] [-n] [-olist] [-p[str]] [-P filename] * [-r] [-s#] [-t#] [-v] [-w] [-S] [file ...] * * * Options: * -c# Number of copies * -d Debug mode * -i# Indent each line # inches (so much for metric) * -l Landscape instead of Portrait * -m Use 3 hole punch margins * -n Leave top and bottom margin (66 lines/page at 10pt) * -olist Only print pages in the specified range * -p[str] Use pr to print, passing optional string * -P filename Copy prologue from filename instead of default * -r Toggle page reversal flag (see Makefile) * -s# Use point size # * -t# Spaces between tab stops is # characters * -v Verbose * -w Line wrap (not implemented yet) * -S Standalone mode (print header page, use EOF's) * * If no files are specified, stdin is used. * Form feeds handled. * Backspacing with underlining (or overprinting) works. * The output conforms to Adobe 2.0. * * Problems: * - assumes fixed-width (non-proportional) font in some places * - can't back up (using backspaces) over tabs * - assumes 8.5 x 11.0 paper * * BJB - Jun/87 * SP - May/88 * ======================================================================== * * Permission is given to freely copy and distribute this software provided: * * 1) You do not sell it, * 2) You do not use it for commercial advantage, and * 3) This notice accompanies the distribution * * Copyright (c) 1988 * Barry Brachman * Dept. of Computer Science * Univ. of British Columbia * Vancouver, B.C. V6T 1W5 * * .. {ihnp4!alberta, uw-beaver, uunet}!ubc-vision!ubc-csgrads!brachman * brachman@grads.cs.ubc.cdn * brachman%ubc.csnet@csnet-relay.arpa * brachman@ubc.csnet * ======================================================================== */ #ifndef AMIGA #include <pwd.h> #endif AMIGA #include <ctype.h> #include <stdio.h> #define min(a, b) ((a) < (b) ? (a) : (b)) /* * Configurable... * BUFOUT should be fairly large */ #define BUFIN 1024 /* maximum length of an input line */ #define BUFOUT (BUFIN * 5) #define MAXPAGES 10000 /* maximum number of pages per job */ #define DEFAULT_TAB_SIZE 8 #define DEFAULT_POINT_SIZE 10 #ifndef REVERSE #define REVERSE 0 #endif REVERSE #ifdef SYSV #define rindex strrchr #endif SYSV /* * As mentioned in the man page, /bin/pr doesn't handle formfeeds correctly * when doing multicolumn formatting. * Instead of starting a new column or page it passes a formfeed through, * causing pr and lwf to get out-of-synch with regard to the current * location on the page. * If your pr behaves this way (4.[23] does, SYSV doesn't), define PRBUG so * that fgetline() will filter out these bogus formfeeds while preserving * the column structuring. */ #ifndef SYSV #ifdef PR #define PRBUG 1 #endif PR #endif SYSV /* * PostScript command strings defined in the prologue file */ #define BACKSPACE "B" #define ENDPAGE "EP" #define LINETO "L" #define MOVETO "M" #define NEWPATH "NP" #define SHOW "S" #define STARTPAGE "SP" #define STARTHPAGE "SHP" #define STARTLPAGE "SLP" #define STROKE "ST" #define TAB "T" /* * Conformance requires that no PostScript line exceed 256 characters */ #define MAX_OUTPUT_LINE_LENGTH 256 #define TEXTFONT "Courier" #define HEADERFONT "Times-Roman" #define HEADERPS 18 /* header page point size */ #define PORTRAIT_START_Y 767 /* first row (Y coord) on each page */ #define LANDSCAPE_START_Y 587 /* first row (Y coord) on each page */ #define PORTRAIT_START_X 25 /* position of start of each line */ #define LANDSCAPE_START_X 25 /* position of start of each line */ #define START_X_HEADER 100 /* x start of each header line */ #define START_Y_HEADER 700 /* first row (Y coord) of header */ #define THREE_HOLE_X 1.0 /* portrait x offset (inches) 3 hole */ #define THREE_HOLE_Y 0.5 /* landscape y offset (inches) 3 hole */ #define MARGIN 41 /* top and bottom margin (points) */ #define MAX_X 612 #define MAX_Y 792 #define SEP_CHAR '\001' /* pr column separator character */ #define PS_EOF 04 #define NPSIZES 6 struct psize { int size; /* point size */ double charsperinch; /* approx. char width, for Courier */ int portrait_page_length; /* page length in lines */ int portrait_cols; /* maximum # of chars per line */ int landscape_page_length; int landscape_cols; } psize[NPSIZES] = { 7, 17.0, 106, 133, 80, 175, 8, 15.0, 93, 117, 70, 155, 9, 14.0, 82, 109, 62, 144, 10, 12.0, 74, 94, 56, 124, 11, 11.0, 67, 86, 51, 113, 12, 10.0, 62, 78, 47, 103 }; #ifdef PR #define USAGE \ "[-c#] [-d] [-i#] [-l] [-m] [-n] [-olist] [-p[str]] [-P filename] [-r] [-s#] [-S] [-t#] [-v] [file ...]" #else PR #define USAGE \ "[-c#] [-d] [-i#] [-l] [-m] [-n] [-olist] [-P filename] [-r] [-s#] [-S] [-t#] [-v] [file ...]" #endif PR struct psize *p, *get_psize(); long page_map[MAXPAGES]; /* offset of first byte of each page */ int page_count; int page; int lines_per_page; int columns; int point_size; int start_x, start_y; int ncopies; int margin; double offset; char bufin[BUFIN]; /* input buffer */ char bufout[BUFOUT]; /* used for page reversal and output buffering */ #ifdef AMIGA char username[32]; #else AMIGA char *username; #endif AMIGA char hostname[32]; char tmpname[32]; char *currentdate; int row; char *range; int tabstop; char *propts; int dflag, lflag, mflag, nflag, pflag, rflag, vflag, wflag, Sflag; FILE *freopen(); char *fgets(); char *strcpy(); char *fgetline(); char *prologue; char *progname; char *configfile = "s:lwf.cfg"; char *version = "lwf V2.0a 30-May-88"; main(argc, argv) int argc; char **argv; { register int i, j, first_file; char *pc; double offset, bsize, atof(); int margin; char *rindex(); FILE *infile, *popen(); double ceil(), floor(); if ((pc = rindex(argv[0], '/')) != (char *) NULL) progname = pc + 1; else progname = argv[0]; init(); getdefaults(); for (i = 1; i < argc && argv[i][0] == '-'; i++) { switch (argv[i][1]) { case 'c': ncopies = atoi(&argv[i][2]); if (ncopies <= 0) { fatal("number of copies must be > 0"); /*NOTREACHED*/ } break; case 'd': dflag = 1; break; case 'i': offset = atof(&argv[i][2]); if (offset < 0.0 || offset >= 8.5) { fatal("bad indent"); /*NOTREACHED*/ } break; case 'l': lflag = 1; break; case 'm': mflag = 1; break; case 'n': nflag = 1; break; case 'o': range = &argv[i][2]; if (checkrange(range)) { fatal("bad range specification"); /*NOTREACHED*/ } break; #ifdef PR case 'p': pflag = 1; propts = &argv[i][2]; break; #endif PR case 'P': if (++i == argc) { fatal("missing filename after -P"); /*NOTREACHED*/ } prologue = argv[i]; break; case 'r': rflag = !rflag; break; case 's': j = atoi(&argv[i][2]); if ((p = get_psize(j)) == (struct psize *) NULL) { fatal("bad point size"); /*NOTREACHED*/ } break; case 't': tabstop = atoi(&argv[i][2]); if (tabstop < 1) { fatal("bad tabstop"); /*NOTREACHED*/ } break; case 'v': vflag = 1; break; case 'w': wflag = 1; break; case 'S': Sflag = 1; break; default: (void) fprintf(stderr, "Usage: %s %s\n", progname, USAGE); exit(1); } } /* * Check that all files are readable. * This is so that no output at all is produced if any file is not * readable in case the output is being piped to a printer. */ for (j = i; j < argc; j++) { if (access(argv[j], 4) == -1) { fatal("cannot access %s", argv[j]); /*NOTREACHED*/ } } point_size = p->size; if (nflag) { margin = MARGIN; bsize = MAX_Y - 2 * (MAX_Y - PORTRAIT_START_Y + MARGIN); p->portrait_page_length = (int) floor(bsize / point_size); p->landscape_cols = (int) floor(bsize * p->charsperinch / 72.0); } if (lflag) { start_y = LANDSCAPE_START_Y; start_x = LANDSCAPE_START_X + margin + (int) (offset * 72.0); lines_per_page = p->landscape_page_length; columns = p->landscape_cols - (int) ceil(offset * p->charsperinch); if (mflag) { int nlines; nlines = (int) ceil((THREE_HOLE_Y * 72.0) / point_size); start_y -= (nlines * point_size); lines_per_page -= nlines; columns -= (int) ceil(THREE_HOLE_Y * p->charsperinch); } } else { start_y = PORTRAIT_START_Y - margin; lines_per_page = p->portrait_page_length; start_x = PORTRAIT_START_X; if (mflag) offset += THREE_HOLE_X; start_x += (int) (offset * 72.0); columns = p->portrait_cols - (int) ceil(offset * p->charsperinch); } if (vflag) { (void) fprintf(stderr, "%s\n\n", version); (void) fprintf(stderr, "Lines/page = %d\n", lines_per_page); (void) fprintf(stderr, "Columns = %d\n", columns); (void) fprintf(stderr, "X-offset = %5.2f inches\n", offset); } setup(); preamble(); first_file = i; if (!rflag && Sflag) header(argc - first_file, argv + first_file); if (i == argc) { /* no files on command line */ infile = stdin; #ifdef PR if (pflag) { build_prcmd(bufin, ""); if ((infile = popen(bufin, "r")) == (FILE *) NULL) { fatal("popen failed"); /*NOTREACHED*/ } } #endif PR if (vflag) (void) fprintf(stderr, "printing stdin\n"); print(infile); #ifdef PR if (pflag) (void) pclose(infile); #endif PR } /* * If page reversal is performed, process the file arguments right to left, * oth. left to right * If the correct flag is used for the printer the first file argument * will be on top in the printer's output tray when the paper is removed */ if (rflag) j = argc - 1; else j = i; while (i < argc) { infile = stdin; #ifdef PR if (pflag) { build_prcmd(bufin, argv[j]); if ((infile = popen(bufin, "r")) == (FILE *) NULL) { fatal("popen failed"); /*NOTREACHED*/ } } else { #endif PR if (freopen(argv[j], "r", stdin) == (FILE *) NULL) { fatal("can't open %s", argv[j]); /*NOTREACHED*/ } #ifdef PR } #endif PR if (vflag) (void) fprintf(stderr, "printing %s\n", argv[j]); print(infile); #ifdef PR if (pflag) (void) pclose(infile); #endif PR if (rflag) j--; else j++; i++; } if (rflag && Sflag) header(argc - first_file, argv + first_file); (void) printf("%%%%Trailer\n"); (void) printf("%%%%Pages: %d\n", page_count); (void) putc(PS_EOF, stdout); if (fflush(stdout) == EOF) { fatal("write error on stdout"); /*NOTREACHED*/ } exit(0); } /* initialize variables */ init() { dflag = 0; lflag = 0; mflag = 0; nflag = 0; vflag = 0; wflag = 0; Sflag = 0; rflag = REVERSE; #ifdef PR pflag = 0; propts = ""; #endif PR #ifdef PROLOGUE prologue = PROLOGUE; #else PROLOGUE prologue = ""; #endif PROLOGUE ncopies = 1; offset = 0.0; range = ":"; tabstop = DEFAULT_TAB_SIZE; p = get_psize(DEFAULT_POINT_SIZE); page_count = 0; margin = 0; } /* read the configuration file for defaults */ getdefaults() { FILE *deffile; char inline[100]; char *pp; int j; if ((deffile = fopen(configfile, "r")) == NULL) return; while (fgets(inline, 100, deffile) != NULL) { switch (inline[0]) { case ' ': case '*': case '#': case ';': case '\n': break; case 'c': (void) sscanf(inline+1, "%d", &ncopies); break; case 'd': dflag = 1; break ; case 'i': (void) sscanf(inline+1, "%lf", &offset); break; case 'l': lflag = 1; break ; case 'm': mflag = 1; break ; case 'n': nflag = 1; break ; case 'o': (void) sscanf(inline+1, "%s", pp); range = pp; while (*pp++); break; #ifdef PR case 'p': pflag = 1; (void) sscanf(inline+1, "%s", pp); propts = pp; while (*pp++); break ; #endif PR case 'P': (void) sscanf(inline+1, "%s", pp); prologue = pp; while (*pp++); break ; case 'r': rflag = !rflag; break; case 's': (void) sscanf(inline+1, "%d", &j); if ((p = get_psize(j)) == (struct psize *) NULL) { fatal("bad point size"); /*NOTREACHED*/ } break; case 't': (void) sscanf(inline+1, "%d", &tabstop); break; case 'v': vflag = 1; break ; case 'w': wflag = 1; break ; case 'S': Sflag = 1; break ; default: printf("Error in %s file: %s", configfile, inline) ; } } } /* * Return a pointer to the point size structure for the * specified point size */ struct psize * get_psize(size) int size; { register int i; for (i = 0; i < NPSIZES; i++) if (psize[i].size == size) break; if (i == NPSIZES) return((struct psize *) NULL); return(&psize[i]); } /* * Initial lines sent to the LaserWriter. * This stuff is sent to stdout since we don't want it to be reversed. * Generates the PostScript header and includes the prologue file. * There is limited checking for I/O errors here. * When the standard prologue is being used we probably should verify * that it is the correct version (via %%BeginProcSet). */ preamble() { FILE *fp; (void) printf("%%!PS-Adobe-2.0\n"); (void) printf("%%%%Creator: %s on %s\n", progname, hostname); (void) printf("%%%%CreationDate: %s\n", currentdate); (void) printf("%%%%For: %s\n", username); (void) printf("%%%%DocumentFonts: %s", TEXTFONT); if (Sflag) (void) printf(" %s\n", HEADERFONT); else (void) printf("\n"); (void) printf("%%%%Pages: (atend)\n"); if (strcmp(prologue, "")) { if ((fp = fopen(prologue, "r")) == (FILE *) NULL) { fatal("can't open prologue file `%s'", prologue); /*NOTREACHED*/ } while (fgets(bufin, sizeof(bufin), fp) != (char *) NULL) (void) fputs(bufin, stdout); (void) fclose(fp); } else { (void) printf("%%%%EndComments\n"); (void) printf("%% PostScript Prologue for lwf V2.0a\n"); (void) printf("/B {NW 0 rmoveto}bind def\n"); (void) printf("/EP {SV restore /#copies exch def showpage}bind def\n"); (void) printf("/L /lineto load def\n"); (void) printf("/M /moveto load def\n"); (void) printf("/NP /newpath load def\n"); (void) printf("/S /show load def\n"); (void) printf("/SHP {SP 2 setlinewidth}bind def\n"); (void) printf("/SLP {SP 612 0 translate 90 rotate}bind def\n"); (void) printf("/SP {/SV save def findfont exch scalefont setfont ( )\n"); (void) printf(" stringwidth pop dup /W exch def neg /NW exch def}bind def\n"); (void) printf("/ST /stroke load def\n"); (void) printf("/T {W mul 0 rmoveto}bind def\n"); (void) printf("%%%%EndProlog\n"); } if (ferror(stdout) || fflush(stdout) == EOF) { fatal("write error on stdout"); /*NOTREACHED*/ } } #ifdef PR /* * Generate a command, in the specified buffer, to print the given file * according to the options in effect. */ build_prcmd(buf, file) char *buf, *file; { #ifdef SYSV (void) sprintf(buf, "%s -e%d -w%d -l%d -s%c %s %s", PR, tabstop, columns, lines_per_page, SEP_CHAR, propts, file); #else SYSV (void) sprintf(buf, "%s -w%d -l%d -s%c %s %s", PR, columns, lines_per_page, SEP_CHAR, propts, file); #endif SYSV if (vflag) (void) fprintf(stderr, "pr cmd: %s\n", buf); } #endif PR /* * Print a file * * The input stream may be stdin, a file, or a pipe. * If page reversal is being performed, the output goes to a temporary file and * then reverse() is called to do the page reversal to stdout. */ print(infile) FILE *infile; { register int eof, pagenum, r; register char *p; FILE *outfile; char *mktemp(); if (rflag) { static char bigbuf[BUFSIZ]; page = 0; page_map[page] = 0L; #ifdef AMIGA (void) sprintf(bufin, "ram:%sXXXXXX", progname); #else AMIGA (void) sprintf(bufin, "/tmp/%sXXXXXX", progname); #endif AMIGA if (vflag) (void) fprintf(stderr, "temp will be: %s ... ", bufin); p = mktemp(bufin); if (vflag) (void) fprintf(stderr, "%s\n", p); if ((outfile = fopen(p, "w+")) == (FILE *) NULL) { (void) fprintf(stderr, "%s: can't create %s\n", progname, p); cleanup(); /*NOTREACHED*/ } setbuf(outfile, bigbuf); #ifdef AMIGA (void) strcpy(tmpname, p); #else AMIGA if (!dflag) (void) unlink(p); else (void) fprintf(stderr, "will not unlink %s\n", p); #endif AMIGA } else outfile = stdout; pagenum = 1; eof = 0; while (!eof) { row = start_y; if ((r = inrange(pagenum, range)) == -1) { cleanup(); /*NOTREACHED*/ } else if (r == 1) eof = printpage(infile, outfile); else if (r == 0) eof = flushpage(infile); else { fatal("bad inrange result"); /*NOTREACHED*/ } pagenum++; } if (row != start_y) endpage(outfile); if (vflag) (void) fprintf(stderr, "\n"); if (fflush(outfile) == EOF) { fatal("write error while flushing output"); /*NOTREACHED*/ } if (rflag) { reverse(outfile); (void) fclose(outfile); #ifdef AMIGA (void) unlink(tmpname); #endif AMIGA } } /* * Process the next page. * Return 1 on EOF, 0 otherwise. */ printpage(infile, outfile) FILE *infile, *outfile; { register int lineno; if (ungetc(getc(infile), infile) == EOF) return(1); startpage(page_count + 1, outfile); for (lineno = 0; lineno < lines_per_page; lineno++) { if (fgetline(bufin, sizeof(bufin), infile) == (char *) NULL) return(1); if (bufin[0] == '\f') break; if (bufin[0] != '\0') { (void) fprintf(outfile, "%d %d %s\n", start_x, row, MOVETO); proc(bufin, outfile); } row -= point_size; } endpage(outfile); return(0); } /* * The next page will not be printed; just consume the input and discard. * Don't change xrow since we don't want an endpage(). */ flushpage(infile) FILE *infile; { register int lineno, xrow; xrow = row; for (lineno = 0; lineno < lines_per_page; lineno++) { if (fgetline(bufin, sizeof(bufin), infile) == (char *) NULL) return(1); if (bufin[0] == '\f') break; xrow -= point_size; } return(0); } /* * Start a new page. */ startpage(n, outfile) int n; FILE *outfile; { (void) fprintf(outfile, "%%%%Page: %d %d\n", n, n); (void) fprintf(outfile, "%d /%s %s\n", point_size, TEXTFONT, lflag ? STARTLPAGE : STARTPAGE); } /* * A page has been written to the temp file. * Record the start of the next page. * Terminate the page and indicate the start of the next. */ endpage(outfile) FILE *outfile; { long ftell(); if (page_count == MAXPAGES) { fatal("pagelimit (%d) reached", MAXPAGES); /*NOTREACHED*/ } (void) fprintf(outfile, "%d %s\n", ncopies, ENDPAGE); if (rflag) { if (fflush(outfile) == EOF) { fatal("write error while flushing page"); /*NOTREACHED*/ } page_map[++page] = ftell(outfile); } page_count++; if (vflag) (void) fprintf(stderr, "x"); } /* * Print the pages to stdout in reverse order. * Assumes that the number of characters per page can be contained in an int. */ reverse(outfile) FILE *outfile; { register int i; int bytecount, nbytes; int fseek(); if (vflag) (void) fprintf(stderr, "\nreversing %d page%s\n", page, page > 1 ? "s" : ""); if (dflag) { for (i = 0; i <= page; i++) (void) fprintf(stderr, "[%ld]\n", page_map[i]); } for (i = page - 1; i >= 0; i--) { if (fseek(outfile, page_map[i], 0) == -1L) { fatal("seek error"); /*NOTREACHED*/ } nbytes = (int) (page_map[i + 1] - page_map[i]); while (nbytes > 0) { bytecount = min(nbytes, sizeof(bufout)); if (fread(bufout, 1, bytecount, outfile) != bytecount) { fatal("read error while reversing pages"); /*NOTREACHED*/ } if (fwrite(bufout, 1, bytecount, stdout) != bytecount) { fatal("write error while reversing pages"); /*NOTREACHED*/ } nbytes -= bytecount; } } } /* * Process a line of input, escaping characters when necessary and handling * tabs. * * The output is improved somewhat by coalescing consecutive tabs and * backspaces and eliminating tabs at the end of a line. * * Overprinting (presumably most often used in underlining) can be far from * optimal; in particular the way nroff underlines by sequences like * "_\ba_\bb_\bc" creates a large volume of PostScript. This isn't too * serious since a lot of nroff underlining is unlikely. * * Since a newline is generated for each call there will be more * newlines in the output than is necessary. */ proc(in, outfile) char *in; FILE *outfile; { register int i; register char *last, *p, *q; int currentp, instr, tabc, tabto; char *savep; static int colskip, ncols; static int seen_sep = 0; currentp = 0; instr = 0; tabto = 0; last = bufout + MAX_OUTPUT_LINE_LENGTH - 20; /* subtract slop factor */ q = bufout; *q = '\0'; for (p = in; *p != '\0'; p++) { switch (*p) { case SEP_CHAR: /* * This assumes that the input buffer contains the entire line * otherwise the column count will be off. * Also, the input stream must be formatted into a constant number * of columns oth. it would be necessary to scan each line to * count SEP_CHARs (which is not hard but could be slow). */ if (!seen_sep) { /* discern number of columns */ seen_sep = 1; ncols = 2; /* there are at least two columns... */ savep = p++; while (*p != '\0') { if (*p++ == SEP_CHAR) ncols++; } p = savep; colskip = columns / ncols; if (vflag) (void) fprintf(stderr, "Using %d columns\n", ncols); } if (instr) { (void) sprintf(q, ")%s ", SHOW); q += strlen(q); instr = 0; } tabto += (colskip - currentp); currentp = 0; break; case '\t': /* * Count the number of tabs that immediately follow the one we're * looking at */ tabc = 0; while (*(p + 1) == '\t') { p++; tabc++; } if (currentp > 0) { /* not beginning of line */ i = tabstop - (currentp % tabstop) + tabc * tabstop; if (instr) { (void) sprintf(q, ")%s ", SHOW); q += strlen(q); instr = 0; } } else i = (tabc + 1) * tabstop; tabto += i; currentp += i; break; case '\b': *q = '\0'; (void) fprintf(outfile, "%s)%s\n", bufout, SHOW); /* backspacing over tabs doesn't work... */ if (tabto != 0) { fatal("attempt to backspace over a tab"); /*NOTREACHED*/ } p++; for (i = 1; *p == '\b'; p++) i++; if (currentp - i < 0) { fatal("too many backspaces"); /*NOTREACHED*/ } if (!instr) { fatal("bad backspacing"); /*NOTREACHED*/ } if (i == 1) /* frequent case gets special attention */ (void) sprintf(bufout, "%s (", BACKSPACE); else (void) sprintf(bufout, "-%d %s (", i, TAB); currentp -= i; q = bufout + strlen(bufout); p--; break; case '\f': tabto = 0; /* optimizes */ *q = '\0'; if (instr) (void) fprintf(outfile, "%s)%s\n", bufout, SHOW); else (void) fprintf(outfile, "%s\n", bufout); endpage(outfile); startpage(page_count + 1, outfile); row = start_y; (void) fprintf(outfile, "%d %d %s\n", start_x, row, MOVETO); q = bufout; currentp = 0; instr = 0; break; case '\r': tabto = 0; /* optimizes */ if (instr) { *q = '\0'; (void) fprintf(outfile, "%s)%s\n", bufout, SHOW); instr = 0; q = bufout; } (void) fprintf(outfile, "%d %d %s\n", start_x, row, MOVETO); currentp = 0; break; case '\\': case '(': case ')': if (!instr) { if (tabto) { (void) sprintf(q, "%d %s ", tabto, TAB); q += strlen(q); tabto = 0; } *q++ = '('; instr = 1; } *q++ = '\\'; *q++ = *p; currentp++; break; default: /* * According to the PostScript Language Manual, PostScript files * can contain only "the printable subset of the ASCII character * set (plus the newline marker)". */ if (!isascii(*p) || !isprint(*p)) { fatal("bad character in input"); /*NOTREACHED*/ } if (!instr) { if (tabto) { (void) sprintf(q, "%d %s ", tabto, TAB); q += strlen(q); tabto = 0; } *q++ = '('; instr = 1; } *q++ = *p; currentp++; break; } if (q >= last) { *q = '\0'; if (instr) (void) fprintf(outfile, "%s)%s\n", bufout, SHOW); else (void) fprintf(outfile, "%s\n", bufout); q = bufout; instr = 0; } } if (instr) { (void) sprintf(q, ")%s", SHOW); q += strlen(q); } else *q = '\0'; if (q >= last) { fatal("bufout overflow"); /*NOTREACHED*/ } if (bufout[0] != '\0') (void) fprintf(outfile, "%s\n", bufout); } /* * Find out who the user is, etc. * Possible system dependencies here... */ setup() { int len; char *p; long t, time(); int getusername(), gethostname(); char *ctime(), *getlogin(), *malloc(); struct passwd *pw, *getpwuid(); #ifdef AMIGA (void) getusername(username, sizeof(username)); #else AMIGA if ((p = getlogin()) == (char *) NULL) { if ((pw = getpwuid(getuid())) == (struct passwd *) NULL) p = "Whoknows"; else p = pw->pw_name; endpwent(); } username = (char *) malloc((unsigned) (strlen(p) + 1)); (void) strcpy(username, p); #endif AMIGA #ifdef HOSTNAME (void) strncpy(hostname, HOSTNAME, sizeof(hostname)); hostname[sizeof(hostname) - 1] = '\0'; #else HOSTNAME (void) gethostname(hostname, sizeof(hostname)); #endif HOSTNAME t = time((long *) 0); p = ctime(&t); len = strlen(p); *(p + len - 1) = '\0'; /* zap the newline character */ currentdate = (char *) malloc((unsigned) len); (void) strcpy(currentdate, p); } /* * Print a header page. * Assumes setup() has already been called to fill in the user, host, etc. * Uses HEADERFONT in HEADERPS point. */ header(nfiles, files) int nfiles; char **files; { register int i; register char *p; if (vflag) { (void) fprintf(stderr, "printing header\n"); (void) fprintf(stderr, "%d file%s are:\n", nfiles, nfiles > 1 ? "s" : ""); if (nfiles == 0) (void) fprintf(stderr, "\tstdin\n"); for (i = 0; i < nfiles; i++) (void) fprintf(stderr, "\t%s\n", files[i]); } ++page_count; (void) fprintf(stdout, "\n%%%%Page: %d %d\n", page_count, page_count); (void) fprintf(stdout, "%d /%s %s\n", HEADERPS, HEADERFONT, STARTHPAGE); /* * The header sheet looks like: * * ---------------------------- * ---------------------------- * * User: * Host: * Date: * Files: * * ---------------------------- * ---------------------------- */ row = START_Y_HEADER; (void) printf("%s %d %d %s\n", NEWPATH, START_X_HEADER, row, MOVETO); (void) printf("%d %d %s\n", START_X_HEADER + 400, row, LINETO); row -= 6; (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) printf("%d %d %s\n", START_X_HEADER + 400, row, LINETO); row -= 24; (void) printf("%s\n", STROKE); (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) sprintf(bufin, "User: %s", username); proc(bufin, stdout); row -= 24; (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) sprintf(bufin, "Host: %s", hostname); proc(bufin, stdout); row -= 24; (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) sprintf(bufin, "Date: %s", currentdate); proc(bufin, stdout); row -= 24; if (nfiles == 0) { (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) sprintf(bufin, "File: <stdin>"); proc(bufin, stdout); } else { register int len, max, sum; /* * If the list of files is "too long" we'll only print as many as * possible * Arbitrary chop off point is 50 characters * (assume bufin is bigger than this) */ (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) sprintf(bufin, "File%s: ", nfiles > 1 ? "s" : ""); p = bufin + (sum = strlen(bufin)); max = 50; for (i = 0; i < nfiles - 1; i++) { sum += (len = strlen(files[i]) + 1); if (sum >= max) break; (void) sprintf(p, "%s,", files[i]); p += len; } sum += (len = strlen(files[i]) + 1); if (sum < max) (void) sprintf(p, "%s", files[i]); else (void) strcpy(p, "..."); proc(bufin, stdout); } row -= 12; (void) printf("%s %d %d %s\n", NEWPATH, START_X_HEADER, row, MOVETO); (void) printf("%d %d %s\n", START_X_HEADER + 400, row, LINETO); row -= 6; (void) printf("%d %d %s\n", START_X_HEADER, row, MOVETO); (void) printf("%d %d %s\n", START_X_HEADER + 400, row, LINETO); (void) printf("%s\n", STROKE); (void) printf("1 %s\n", ENDPAGE); if (fflush(stdout) == EOF) { fatal("write error on stdout"); /*NOTREACHED*/ } } /* * Special version of fgets. * Read until a formfeed, newline, or overflow. * If a formfeed is the first character, return it immediately. * If a formfeed is found after the first character, replace it by a newline * and push the formfeed back onto the input stream. * A special case is a formfeed followed by a newline in which case the * newline is ignored . * The input buffer will be null-terminated and will *not* end with a newline. * The buffer size n includes the null. */ char * fgetline(s, n, iop) char *s; int n; register FILE *iop; { register int ch; register char *cs; if (n < 2) { fatal("fgetline called with bad buffer size!?"); /*NOTREACHED*/ } cs = s; n--; /* the null */ /* * Check out the special cases */ if ((ch = getc(iop)) == EOF) return((char *) NULL); if (ch == '\f') { #ifdef PR #ifdef PRBUG if (pflag) { /* * Filter out the formfeeds */ do { if (ch == '\f') continue; if (ch == '\n') break; *cs++ = ch; n--; } while (n > 0 && (ch = getc(iop)) != EOF); if (ch == EOF) { if (ungetc(ch, iop) == EOF && !feof(iop)) { /* Shouldn't happen since a getc() was just done */ fatal("fgetline - ungetc failed"); /*NOTREACHED*/ } } else if (ch != '\n') { fatal("fgetline - input line too long"); /*NOTREACHED*/ } *cs = '\0'; return(s); } #endif PRBUG #endif PR if ((ch = getc(iop)) != '\n') { /* * If EOF was just read it will be noticed next time through */ if (ungetc(ch, iop) == EOF && !feof(iop)) { /* Shouldn't happen since a getc() was just done */ fatal("fgetline - ungetc failed"); /*NOTREACHED*/ } } *cs++ = '\f'; *cs = '\0'; return(s); } /* * Check for "weird" input characters is made in proc() */ while (n-- > 0) { if (ch == '\f' || ch == '\n') break; *cs++ = ch; if ((ch = getc(iop)) == EOF) break; } if (ch == EOF && cs == s) /* Nothing was read */ return((char *) NULL); if (ch == '\f') { if (ungetc(ch, iop) == EOF) (void) fprintf(stderr, "fgetline - can't ungetc??\n"); } else if (ch != '\n' && ch != EOF) { fatal("fgetline - input line too long"); /*NOTREACHED*/ } *cs = '\0'; return(s); } /*VARARGS*/ fatal(s, a, b, c, d, e, f, g, h, i, j) char *s; { (void) fprintf(stderr, "%s: ", progname); (void) fprintf(stderr, s, a, b, c, d, e, f, g, h, i, j); (void) fprintf(stderr, "\n"); cleanup(); /*NOTREACHED*/ } /* * Clean up and exit after an error */ cleanup() { exit(1); } SHAR_EOF cat << \SHAR_EOF > lwf.cfg # # lwf configuration file # m ; use 3 hole punch margins n ; leave top and bottom margins S ; print header page SHAR_EOF cat << \SHAR_EOF > lwf.man .TH LWF 1-LOCAL "21 February 1988" .UC .SH NAME lwf \- ASCII to PostScript filter .SH SYNOPSIS .B lwf [-c#] [-d] [-i#] [-l] [-m] [-n] [-olist] [-p[str]] .br .ti +4 [-P filename] [-r] [-s#] [-S] [-t#] [-v] [file ...] .SH DESCRIPTION .I Lwf takes one or more ASCII text files as input and produces PostScript instructions that may be sent (see \fBlpr(1)\fR) to a PostScript printer (e.g., an Apple LaserWriter) for printing. If no files are given on the command line, the standard input is read. The program correctly handles the form feed character and tabs and understands backspacing; underscores followed by backspaces may be used to underline. Courier font is used. The output conforms to the Adobe 2.0 file structuring conventions. .PP Note that flag arguments apply to all of the files in the argument list. For example, using ``-s8'' prints each of the files in 8 point type. .PP Multiple copies of each page can be printed by immediately following the .B -c flag with the number of copies to make. The pages are not collated. .PP Lines may be indented (shifted to the right) using the \fB-i\fR flag. This flag is immediately followed by the distance in inches to shift all text from the left edge of the paper instead of the default amount (about 1/3 of an inch); the resolution is approximately 1/72 of an inch. .PP The .B -l flag indicates that landscape format is to be used instead of the default portrait format. .PP Margins suitable for use with a three hole punch can be obtained using the .B -m flag. This flag may be used with either portrait or landscape mode. In portrait mode the .B -m flag and any indentation specified by a .B -i flag are additive. .PP To get top and bottom margins suitable for printing 66 lines per page (with the default 10 point size), use the .B -n flag. This option works good when printing manual pages. .PP The .B -o flag is immediately followed by a range specification that indicates which pages are to be printed. A range specification is a comma-separated list of numbers and ranges. A number N selects the Nth page; a range N:M selects the Nth through Mth pages, inclusive; an initial :N selects from the beginning up to and including the Nth page; and a final N: selects from the Nth page to the end. The default, ``:'', is to print all pages. .PP The .B -p flag indicates that .B pr(1) is to be used to perform pagination and print page headers. An argument string to be passed on to \fBpr(1)\fR can immediately follow the flag. The usual way of producing multicolumn output is to pass a .B -n flag to \fBpr(1)\fR, where .B n is the number of columns to generate. Note that this string must be properly quoted if it contains whitespace, metacharacters, backslashes, etc. \fILwf\fR passes on the number of lines and columns to \fBpr(1)\fR so that multicolumn output is handled correctly. When specifying multicolumns you are responsible for selecting an appropriate point size and/or landscape format. The formfeed character is handled incorrectly by some versions of \fBpr(1)\fR when multiple columns have been specified. Instead of starting a new column or page it simply passes formfeeds through. .B Lwf filters out these formfeeds. .PP .I Lwf normally includes a standard internal PostScript prologue in its output. Optionally, a compile time option is not to include it in .I lwf. In this case .I lwf will look for the file lwf.prologue in a standard place (usually /usr/local/lib/lwf.prologue). The argument following a .B -P flag specifies a prologue file to be substituted for the standard system file. .PP The default is to perform page reversal, which is correct for printers like the Apple LaserWriter. The .B -r flag disables page reversal so that the pages appear in the correct sequence in the output tray of printers like the NEC Silentwriter LC-890. .PP The .B -s flag, immediately followed by a 7, 8, 9, 10, 11, or 12 causes the corresponding point size to be used. The default point size is 10. .PP By default, the output of .I lwf is intended to go through a spooler that monitors the status of the printer and separates jobs. The .B -S flag indicates that such a monitor is not being used and that a header page should be printed to separate jobs and end-of-file characters should be inserted in the output. .PP The .B -t flag is immediately followed by a number indicating the distance between tab stops. The default value is 8 characters. .PP The .B -d and .B -v flags are used for debugging. .SH EXAMPLES The command .sp 2 .ti +5 lwf -p-2 -i0.5 -s12 file1.c file2.c | lpr -Plw .sp 2 would print the two files on printer 'lw' in portrait format with page headings, indenting 0.5 inches from the left edge, using 12 point type. Each file would be printed in two columns. .sp 1 The command .sp 2 .ti +5 lwf -l -s7 -p'-h foo' | lpr -Plw .sp 2 would print the standard input with page headings in landscape format using 7 point type. The filename for the header line would be 'foo'. .sp 1 The command .sp 2 .ti +5 lwf -p'-h foo .login -h bar .cshrc' | lpr -Plw .sp 2 would print the file ``.login'' with a header title ``foo'' and ``.cshrc'' with the header title ``bar''. .sp 1 A useful csh alias is: .sp 2 .ti +5 alias prlw 'lwf -s7 -t4 -l -p-2 \\!* | lpr -Plw' .sp 2 which, when invoked as .sp 2 .ti +5 prlw foo1 foo2 .sp 2 prints foo1 and foo2, two columns per page, on printer 'lw'. .SH FILES /tmp/lwfXXXXXX \- temporary file used for page reversal .SH SEE ALSO pr(1), lpr(1) .SH LIMITATIONS The maximum input line length is 1024 characters. This should not present a problem since the corresponding output line length would be too long to be printed. The program quits and prints a message if an input line is too long. Output lines that are too long are normally silently truncated by the printer. .sp 2 .I Lwf can be compiled such that there is a limit on the number of pages it can produce. .SH BUGS It might be argued that flags should be allowed to be interspersed between file arguments. .sp 2 The character '\\001' (SOH) should not appear in the input as it is used within the program to delimit columns. The first time the program reads this character it determines the number of columns being printed; all subsequent lines are expected to have this number of columns or be ``single column''. .sp 2 A \fB-s\fR flag should not be passed on to \fBpr(1)\fR since .I lwf uses it. .sp 2 The program can only handle the 6 different point sizes and the single font. .sp 2 So much for metric. .SH AUTHOR Barry Brachman .br Dept. of Computer Science .br University of British Columbia With additions/revisions and Amiga port by: Dr. Samuel Paolucci .br 1351 Roselli Dr. .br Livermore, CA 94550 .br (415)294-2018 .br ARPA: paolucci@snll-arpagw.llnl.gov SHAR_EOF cat << \SHAR_EOF > lwf.prologue %%EndComments % PostScript Prologue for lwf V2.0a /B {NW 0 rmoveto}bind def /EP {SV restore /#copies exch def showpage}bind def /L /lineto load def /M /moveto load def /NP /newpath load def /S /show load def /SHP {SP 2 setlinewidth}bind def /SLP {SP 612 0 translate 90 rotate}bind def /SP {/SV save def findfont exch scalefont setfont ( ) stringwidth pop dup /W exch def neg /NW exch def}bind def /ST /stroke load def /T {W mul 0 rmoveto}bind def %%EndProlog SHAR_EOF cat << \SHAR_EOF > psclear.uu begin 644 psclear !!```$ `` end size 1 SHAR_EOF cat << \SHAR_EOF > range.c /* vi: set tabstop=4 : */ /* * Return 1 if the given number is in the specified range, * -1 if there is an error in the range specification, * 0 otherwise * * Ranges have a similar (we use a colon instead of a dash) form to that * used by [nt]roff; i.e., a comma separated list of specifiers of the * form: * 1) n means x such that x = n * 2) :n means all x such that x <= n * 3) n: means all x such that x >= n * 4) n:m means all x such that n <= x <= m * 5) : means all x * n is an int * * Problems: * The routine prints an error message if the range is strange - this * might not always be desirable. * * Jul/86 BJB */ /* * =================================================================== * * Permission is given to freely copy and distribute this software * providing: * * 1) You do not sell it, * 2) You do not use it for commercial advantage, and * 3) This notice accompanies the distribution * * Copyright (c) 1988 * Barry Brachman * Dept. of Computer Science * Univ. of British Columbia * Vancouver, B.C. V6T 1W5 * * .. {ihnp4!alberta, uw-beaver, uunet}!ubc-vision!ubc-csgrads!brachman * brachman@grads.cs.ubc.cdn * brachman%ubc.csnet@csnet-relay.arpa * brachman@ubc.csnet * ==================================================================== */ #include <ctype.h> #include <stdio.h> #define streq(a, b) (!strcmp((a), (b))) #define smalloc(a, t, s) ((a = (t) malloc((unsigned) (s))) == NULL) #define srealloc(a, t, b, s) ((a = (t) realloc(b, (unsigned) (s))) == NULL) #define max(a, b) ((a) >= (b) ? (a) : (b)) #define SEP_CHAR ':' #define SEP_T 0 /* separator token */ #define NUM_T 1 /* number token */ #define BAD_T 2 /* token for bad character */ #define STR_ALLOC 80 struct range_header { char *range_str; /* range character string */ int range_str_alloc; /* length in bytes */ int nranges; /* number of range entries */ struct range *range; } range_header = { NULL, 0, NULL }; /* * If hflag (lflag) is non-zero then the high (low) value is present */ struct range { char hflag; /* high value present */ char lflag; /* low value present */ int high; /* high part of range */ int low; /* low part of range */ }; #ifdef RANGE_DEBUG /* * This is a program for demonstrating and debugging the range checking * code * Enter a range when prompted * (If there is a previous range shown you may enter <return> to * reselect it) * Enter a value * The program will indicate whether the value is in the given range */ char buf[BUFSIZ], range[BUFSIZ]; main(argc, argv) int argc; char **argv; { register int i; char *p; struct range_header *rh; struct range *r; FILE *fp; char *gets(), *index(), *strcpy(); buf[0] = range[0] = '\0'; if (argc == 2) { if ((fp = fopen(argv[1], "r")) == NULL) { (void) fprintf(stderr, "Can't open %s\n", argv[1]); exit(1); /*NOTREACHED*/ } } else fp = stdin; if (fp == stdin) (void) printf("Range? "); while (fgets(buf, sizeof(buf), fp) != NULL) { if ((p = index(buf, '\n')) != NULL) *p = '\0'; if (buf[0] != '\0') { (void) strcpy(range, buf); if (checkrange(range)) { if (fp == stdin) (void) printf("Range? "); continue; } rh = &range_header; (void) printf("%s (%d alloc) (%d ranges):\n", rh->range_str, rh->range_str_alloc, rh->nranges); for (r = rh->range, i = 0; i < rh->nranges; i++, r++) (void) printf("hflag=%d lflag=%d high=%d low=%d\n", r->hflag, r->lflag, r->high, r->low); } if (fp != stdin) continue; (void) printf("Value? "); if (gets(buf) == NULL) break; i = inrange(atoi(buf), range); if (i == 0) (void) printf("\tno\n"); else if (i == 1) (void) printf("\tyes\n"); else if (i == -1) (void) printf("\terror\n"); else (void) printf("\tbad result\n"); (void) printf("Range ['%s']? ", range); } (void) printf("\n"); } #endif RANGE_DEBUG /* * Check and compile the given range specification and then determine if * the number is in the range * Return -1 if there is a compilation error, 1 if the number is in the * range, or 0 if the number isn't in the range */ inrange(num, range_spec) int num; char *range_spec; { register int i, rc; register struct range_header *rh; register struct range *r; if (checkrange(range_spec)) return(-1); rh = &range_header; rc = 0; for (r = rh->range, i = 0; rc == 0 && i < rh->nranges; i++, r++) { if (r->hflag) { if (num > r->high) continue; if (r->lflag && num < r->low) continue; rc = 1; } else if (r->lflag) { if (num >= r->low) rc = 1; } else /* both unset -> ":" */ rc = 1; } return(rc); } /* * Check and compile a range specification * Print a message and return -1 on error; return 0 oth. * * Could be more efficient by allocating more structures at a time... SMOP */ checkrange(range_spec) char *range_spec; { register struct range_header *rh; register struct range *r; int len; int ltype, lval, rtype, rval; char *p; char *malloc(), *realloc(), *strcpy(); rh = &range_header; /* * Check if the previous range is being used */ if (rh->range_str != NULL && streq(range_spec, rh->range_str)) return(0); /* * New range spec * If there is enough space, reuse it; oth. allocate enough * (amount allocated never shrinks) */ len = max(strlen(range_spec) + 1, STR_ALLOC); if (rh->range_str != NULL && len > rh->range_str_alloc) { free(rh->range_str); rh->range_str = (char *) malloc((unsigned) len); rh->range_str_alloc = len; } else if (rh->range_str == NULL) { rh->range_str = (char *) malloc((unsigned) len); rh->range_str_alloc = len; } (void) strcpy(rh->range_str, range_spec); if (rh->range != NULL) free((char *) rh->range); rh->range = NULL; p = range_spec; while (1) { lval = getnum(&p, <ype); if (ltype == BAD_T) { (void) fprintf(stderr, "range: bad first number\n"); *rh->range_str = '\0'; /* invalidate */ return(-1); } if (rh->range == NULL) { smalloc(r, struct range *, sizeof(struct range)); rh->nranges = 1; } else { len = sizeof(struct range) * ++(rh->nranges); srealloc(r, struct range *, (char *) rh->range, len); } rh->range = r; r += rh->nranges - 1; /* point to new one */ r->hflag = r->lflag = 0; r->high = r->low = 0; /* * If ltype != NUM_T there is no lval */ if (ltype == NUM_T) { r->lflag = 1; r->low = lval; } switch (*p) { case ',': /* single number */ r->hflag = 1; r->high = lval; p++; continue; case '\0': /* single number at end */ r->hflag = 1; r->high = lval; return(0); case ':': p++; if (*p == '\0') /* no rval */ return(0); if (*p == ',') { /* no rval */ p++; break; } rval = getnum(&p, &rtype); if (rtype == BAD_T) { (void) fprintf(stderr, "range: bad second number\n"); *rh->range_str = '\0'; return(-1); } if (lval > rval) { (void) fprintf(stderr, "range: values reversed\n"); *rh->range_str = '\0'; /* invalidate */ return(-1); } r->hflag = 1; r->high = rval; if (*p == '\0') return(0); if (*p == ',') p++; break; default: (void) fprintf(stderr, "range: bad character\n"); *rh->range_str = '\0'; /* invalidate */ return(-1); } } } static getnum(pp, type) char **pp; int *type; { register int sign, val; register char *p; p = *pp; if (!isdigit(*p) && *p != '-') { if (*p == SEP_CHAR) *type = SEP_T; else *type = BAD_T; return(0); } sign = 1; if (*p == '-') { sign = -1; p++; } if (!isdigit(*p)) { *type = BAD_T; return(0); } for (val = 0; isdigit(*p) && *p != '\0'; p++) val = val * 10 + *p - '0'; if (*p != '\0' && *p != ',' && *p != SEP_CHAR) { *type = BAD_T; return(0); } *pp = p; *type = NUM_T; return(sign * val); } SHAR_EOF # End of shell archive exit 0 -- Bob Page, U of Lowell CS Dept. page@swan.ulowell.edu ulowell!page Have five nice days.